地瓜机器人RDK X5智能车竞赛实战:从视觉识别到模型部署全解析

发布时间:2026/9/28 12:24:33
地瓜机器人RDK X5智能车竞赛实战:从视觉识别到模型部署全解析 1. 先讲清楚智能车竞赛里地瓜机器人到底能干些什么连续带了三届智能车竞赛我最大的感受是现在的赛题早就不是早年那种“单片机灰度传感器循迹跑圈”的玩法了。尤其近两年赛道里频繁出现“智慧医疗”“智慧物流”“智慧交通”这一类的场景题核心考核点也变成了——车能不能看得懂场景、做得出决策、完得成任务。单纯靠51或者STM32硬算图像基本不现实。我今年带学生用的主控是地瓜机器人也就是地平线旗下的RDK系列机器人开发者套件我们用的是RDK X5。这套东西最大的价值在于板子上直接集成了BPU脑处理单元也就是地平线的神经网络加速单元可以本地跑深度学习模型做目标检测、分类、关键点识别不需要把图像传回电脑做处理。对于竞赛场景来说这一条就是压倒性的优势——它把“车端识别”这件事从“不可能”变成了“半小时就能跑通demo”。这篇文章我会完整还原我们队伍从零开始做“智慧医疗赛季”的过程。赛题大概背景是模拟医院院内物资配送场景小车要从药房出发识别指定药品抓取或者模拟抓取后送到对应病房中途还要避让行人、绕过障碍、识别楼层标识。听起来花样不少但拆开看真正核心的就三件事视觉感知、路径规划、车辆控制。地瓜机器人刚好覆盖了前两件事的计算需求车体控制我们用STM32做底层执行。如果你也是打竞赛的或者想在机器人方向入门嵌入式AI这篇文章几乎可以当一份手把手的项目笔记来用。后面我会把硬件选型思路、软件架构、每一步的代码、踩过的坑全部写出来代码是完整的可以直接抄。2. 整体设计思路与硬件选型2.1 为什么选地瓜机器人而不是树莓派先给还在纠结选型的朋友算一笔账。树莓派4B或者5也不是不能跑AI板子上一块VideoCore GPU虽然支持OpenCL但真正跑YOLO系列模型时帧率很难看日常跑个轻量分类还凑合一旦上检测模型算力直接吃满CPU温度飙升车内供电还得重新设计。英伟达的Jetson Nano性能倒是好一些但价格常年不稳定而且晶圆供应紧张的时候买一套下来比地瓜机器人贵不少。地瓜机器人这边以我们用过的RDK X5为例8核Arm CPU、BPU算力能到10 TOPS级别官方适配了地平线的工具链常用的YOLO系列、EfficientNet、PicoDet这类模型都能直接量化部署。更关键的是机器人开发套件是“一整包”的思路板子、摄像头、扩展板、散热壳、例程镜像全部配套开箱即用。对一个竞赛队伍来说时间比什么都贵选一套生态完整、例程齐全的平台能少踩一半的坑。还有一个非常实际的原因功耗。整车用12V锂电池供电经过降压模块给工控板和单片机供电。 RDK X5的典型功耗在5W到8W左右树莓派4B在满负载下经常冲到10W以上Nano更是动不动十几瓦。功耗低意味着电池容量可以不用堆太大车体重量下降转向和刹车都更好调。2.2 整车完整硬件清单单说主控没什么意义我把我们整车用到的东西列出来你们参考时可以按自己的预算和赛题要求增减。部件型号用途主控地瓜机器人 RDK X5带散热壳图像采集、AI推理、决策规划下层控制板STM32F103RCT6电机驱动、编码器读取、舵机转向摄像头USB摄像头1080P广角采集赛道、障碍、药品图像显示器5寸HDMI触摸屏调试时显示画面和UI比赛时可拆底盘四轮差速底盘带编码器电机整车移动平台电机驱动BTN7971双路驱动模块驱动直流减速电机供电12V 5200mAh锂电池 降压模块整车电源传感器超声波测距模块 HC-SR04近距离防撞辅助显示/交互7段数码管 LED灯带模拟病房楼层指示这套方案里STM32只干实时性要求高的事情比如PID调速、编码器反馈、超声波避障的急停逻辑。决策层的代码全部放在地瓜机器人上两边通过串口通信只传指令和数据不互相拖累。这种“高算力做主脑、单片机做小脑”的方案是当前机器人竞赛里最稳的组合。2.3 软件架构一句话说清楚代码怎么分层软件部分我强烈建议按模块分开写不要全部堆在一个main文件里。我们最终的代码结构是这样的smart_medical_robot/ ├── main.py # 主程序入口 ├── config.py # 全局配置串口、模型路径、阈值 ├── camera/ │ └── camera_node.py # 摄像头采集与预处理 ├── vision/ │ ├── detector.py # 目标检测推理BPU加速 │ └── classifier.py # 药品分类后处理 ├── plan/ │ └── decision.py # 状态机决策待命→取药→送药→返回 ├── control/ │ └── serial_cmd.py # 与STM32通信的串口协议 ├── ui/ │ └── display.py # 屏幕显示与语音提示 ├── models/ │ ├── medicine_det.bin # 药品检测模型地平线bin格式 │ └── labels.txt # 类别标签 └── resources/ └── sounds/ # 语音提示音频每个模块一个文件权限划分明确。摄像头只管采集检测器只管推理决策层拿到检测结果再决定下一步动作。这样写的最大好处是调试方便比如比赛现场发现检测不准只需要改vision包里的代码不会把控制逻辑牵连进去。3. 环境搭建与核心开发要点3.1 系统镜像与开发机连接地瓜机器人官方提供了Ubuntu镜像系统装好后板子自带Python 3.8环境而且预装好了地平线的运行时库包括hobot_dnn、hobot_vio等。我用的是官方提供的“Desktop”版本镜像自带图形界面比赛调试时直接接显示器看画面非常方便。开发时我习惯把板子和电脑连同一个WiFi通过SSH操作。板子的IP地址可以在接显示器的时候用ifconfig查到也可以直接在路由器后台找主机名。用SSH工具有很多Windows下我推荐MobaXterm因为它自带文件管理器往板子上传模型和代码直接拖拽就行省去记scp命令的时间。提示RDK X5默认不开SSH需要先接显示器进入系统在“设置”里打开SSH服务或者执行sudo apt install openssh-server安装。这个步骤很多新手会卡住记得提前配好。3.2 摄像头与图像采集摄像头这块我们测试过USB免驱摄像头和MIPI摄像头。MIPI摄像头的帧率和延迟表现更好但是安装位置受限不能随意拉长线USB摄像头胜在灵活结构上随便固定对焦也方便。我们最终选的是1080P的USB摄像头采集图像后先缩放到640x480再送进模型识别速度能稳定跑到30帧以上。代码上直接用OpenCV的VideoCapture就能读取import cv2 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) cap.set(cv2.CAP_PROP_FPS, 30) while True: ret, frame cap.read() if not ret: continue # 这里是后续检测和决策的入口 cv2.imshow(frame, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()有个细节要提醒比赛场地灯光复杂尤其是顶灯直射时镜头容易过曝。摄像头开启自动白平衡和自动曝光是对的但最好在程序启动时手动设置一次曝光值避免画面亮度来回跳导致检测不稳。这个值每个场地不一样比赛前一天到场地实测调一次就行。3.3 深度学习模型的适配与部署这是地瓜机器人最有技术含量的地方也是一开始最容易劝退新手的地方。官方工具链虽然把模型转换流程简化了但还是需要经过“原始模型→ONNX→地平线模型.bin”这一条路。我们检测药品用的模型是YOLOv8n原始权重是PyTorch格式。转换过程大致如下导出ONNX在电脑上装好ultralytics库执行yolo export modelyolov8n.pt formatonnx opset11。注意要带opset11太高的opset地平线工具链可能不支持。转换为地平线模型在板子上用hb_mapper工具准备好YAML配置文件指定模型输入尺寸、量化方式默认int8量化、输入节点和输出节点。配置文件里最重要的几个参数model: onnx_model: yolov8n.onnx input_shape: [1, 3, 640, 640] # 量化数据需要准备一批代表环境真实分布的图片 calibrate_data: ./calib_data/ preprocess: data_type: rgb mean: [0, 0, 0] scale: 0.003921568627451 quant: calibration_type: max看到这个配置很多人会问为什么要量化直接用FP16不行吗答案是BPU对int8支持最好速度最快内存占用最低。量化后的模型推理延迟能压到20毫秒以内对跑在车上的实时视觉来说是质的区别。当然量化会带来几个点的精度损失但只要校准集选得贴近比赛现场的光线、角度损失可接受。转换完成以后会得到一个 .bin 文件这就是可以在BPU上直接跑的模型。运行时调用官方提供的hobot_dnn接口写起来并不复杂from hobot_dnn import pyeasy_dnn model pyeasy_dnn.load(../models/medicine_det.bin) # 输入输出tensor已经自动绑定直接调用 outputs model[0].forward(input_tensor)这里的input_tensor不是简单的numpy数组需要用hobot_dnn内建的数据类型包一层而且要注意输入数据的格式是NHWC还是NCHW在配置yaml里已经定了。具体细节官方例程里都有直接抄例程再改自己的模型名是最快的方式。3.4 串口通信主控与STM32怎么聊串口通信是整套系统里最容易被低估的部分。刚开始我们直接用的就是最低级的串口发字符串把控制指令像“MOVE 100 200”这样发下去看起来没问题但碰上电机干扰大的路段偶尔会丢包车就原地发呆。后来我们改成了带帧头、帧尾、校验和的结构化协议稳了很多。协议结构设计帧头0xAA 0x55 数据长度1字节 指令1字节 数据N字节 校验和1字节 帧尾0x0D 0x0A举个例子要让STM32以30%功率前进2秒我们组装的协议是AA 55 04 01 1E 02 25 0D 0A这里指令01表示“速度控制”数据1E30、02时长2秒校验和是前面所有字节不含帧头帧尾的和取低8位。STM32端解析也不复杂收满一帧校验通过后执行对应动作并回传一个ACK确认帧。地瓜机器人端只要收到ACK就认为指令送达可以进入下一步状态。这套“发出指令—等待确认—再发下一条”的机制看着多了一次握手却让整个流程的排错变得异常清晰。哪一步出了问题看日志就能定位到是“没发出去”还是“没收到”。4. 智慧医疗场景的核心功能实现4.1 状态机设计从取药到送药全流程整个过程我们拆成了六个状态INIT、IDLE待命、TO_MEDICINE去药房、PICKUP取药、TO_WARD送药到病房、RETURN返回起点。主程序就是一个状态机循环每一帧图像进来检测器输出结果决策层依据当前状态和检测结果进行状态转移。这个设计是在我们试过一段“把所有逻辑平铺写在while循环里”之后彻底重构成的。平铺逻辑在简单场景下还能工作但一旦赛题要求加一个“暂停避让行人”的新规则平铺代码几乎要从头改。状态机的好处是每个状态只处理自己的事边界清晰加状态不影响老状态。简要的伪代码while True: frame camera.read() detections detector.infer(frame) state decision.get_state() if state TO_MEDICINE: # 识别药房标志向药房方向行进 decision.drive_to_medicine(detections) elif state PICKUP: # 检测到药品目标夹持闭合语音提示 decision.pickup_medicine(detections) elif state TO_WARD: # 根据病房编号选择对应路径 decision.drive_to_ward(detections, patient_room) ...比赛现场最考验这部分的不是“算法多高级”而是状态转移的鲁棒性。比如TO_MEDICINE状态下如果连续5帧都没识别到药房标志该怎么处理。我们用的策略是“连续丢帧N次后转入低速寻路模式”而不是直接停摆或者盲目前冲。4.2 药品识别与药品分类药品包装小、颜色相近、反光严重是视觉识别里比较难搞的目标。我们最开始直接用YOLOv8n检测效果一般主要是因为训练数据来自网上找的药品图片和现场实际药品拍照角度、光线差异太大。后来我们改成赛前到场拍照把现场药品自己采集了200多张标注后做训练集识别率直接从63%提升到了95%以上。这里要特别强调采集数据时的注意事项请务必将药品放在不同距离、不同角度、不同光线下拍摄尽量覆盖比赛时可能出现的情况。反光是最大的干扰源拍摄时用亚光纸垫底能有效减少镜面反射。检测框拿到以后如果是多种药还需要根据赛题要求做“按编号分类”。我们在检测的ROI里再做一个细分类用的是轻量分类模型输入尺寸96x96分类数量就是药品的种类数。这个模型跑在BPU上几乎不占算力一举两得。4.3 路径规划与避障路径规划有两种做法一种是完全依赖视觉循线另一种是“先定位再走点”。我们一开始想用地平线官方提供的视觉巡线例程——在场地贴一圈色带摄像头识别色带偏差PID纠偏前进。这个办法简单直接而且地瓜机器人官方仓库里自带巡线的Python示例代码改改参数就能用。但问题来了赛题要求的“智慧医疗”场景场地上不仅有路线色带还有模拟行人小纸板或者人形牌、障碍物、病房门口标识。纯巡线只能保证“在路上”不能保证“安全到达”。所以我们做了一个简单的融合策略正常情况下视觉循线作为主控制目标位置由色带路径和转向标志决定。超声波模块检测到0.3米内有障碍物时进入减速避让状态先停车等待2秒若障碍物消失则继续巡线若依然存在则切换绕行路径。病房门口用Apriltag码标志摄像头识别到对应编号的Apriltag后执行定点停车。Apriltag识别用OpenCV的apriltag库官方在ROS2里也有对应的功能包但如果不想引入ROS直接用Python的dt_apriltags库就能做识别速度极快from dt_apriltags import Detector import cv2 import numpy as np at_detector Detector(searchpath[apriltags], familiestag36h11) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) detections at_detector.detect(gray) for tag in detections: if tag.tag_id target_room_id: # 找到目标病房停车并执行送药动作 send_command(STOP) synthesize_speech(f已到达{tag.tag_id}号病房)4.4 UI与语音交互竞赛中有一项评分是“交互体验”也就是机器人是否有人机交互的能力。大多数队伍会忽略这个点但这其实是加分项里性价比最高的部分。我们用5寸触摸屏做了一个简单的患者呼叫列表界面当小车到达病房时屏幕显示“药品已送达3号病房”同时用本地语音合成播报中文内容。RDK X5上做中文语音合成很简单装一个pynput加edge-tts或者用离线方案pyttsx3加中文字库。我们选用的是edge-tts声音自然基本不延迟。为了比赛现场不依赖外网提前把常用提示语生成成了mp3用pygame播放即可。语音模块常驻一个后台线程def play_audio(text, langzh-CN): from edge_tts import Communicate import asyncio async def _run(): com Communicate(text, lang) await com.save(tmp.mp3) pygame.mixer.music.load(tmp.mp3) pygame.mixer.music.play() asyncio.run(_run())这是最简单还能保证语音自然度的方案现场效果在竞赛里帮我们拿了不少交互分。5. 完整可运行代码主循环与关键模块下面我们直接从完整运行的角度把最核心的代码文件贴出来讲。限于篇幅我这里会给出主程序、检测模块、串口通信模块的核心代码全部可以直接丢进你的工程里改着用。5.1 主程序入口 main.py这里的核心是“循环、实时性、状态机的粘合”。每一帧从摄像头取回图像做一次推理把结果送入状态机状态机再决定要做什么。所有这些必须在100毫秒内跑完否则车的反应会迟钝。import cv2 import time from camera.camera_node import CameraNode from vision.detector import MedicineDetector from plan.decision import DecisionNode from control.serial_cmd import SerialCmd from ui.display import DisplayNode CONFIG { model_path: models/medicine_det.bin, labels_path: models/labels.txt, serial_port: /dev/ttyS0, baud_rate: 115200, camera_index: 0, target_room: 3, max_missing_frames: 10, } class SmartMedicalRobot: def __init__(self, cfg): self.cfg cfg self.camera CameraNode(cfg[camera_index]) self.detector MedicineDetector(cfg[model_path], cfg[labels_path]) self.serial SerialCmd(cfg[serial_port], cfg[baud_rate]) self.display DisplayNode() self.decision DecisionNode(cfg, self.serial) self.missing_frames 0 self.state IDLE def run(self): self.serial.open() self.display.show_status(系统启动完成) self.decision.init() while True: t0 time.time() frame self.camera.read() if frame is None: continue detections self.detector.predict(frame) self.display.update(frame, detections, self.state) if len(detections) 0: self.missing_frames 1 if self.missing_frames self.cfg[max_missing_frames]: self.decision.on_no_target() else: self.missing_frames 0 self.state self.decision.update(detections) # 控制帧率避免推理线程过载 elapsed time.time() - t0 if elapsed 0.05: time.sleep(0.05 - elapsed) if __name__ __main__: robot SmartMedicalRobot(CONFIG) robot.run()5.2 摄像头模块 camera_node.py这个模块做三件事打开摄像头、设置参数、返回一帧BGR图像。比赛现场光线变化剧烈轻微过曝时我一般不开自动曝光而是固定曝光值。import cv2 class CameraNode: def __init__(self, index0, width1280, height720, fps30): self.cap cv2.VideoCapture(index) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) self.cap.set(cv2.CAP_PROP_FPS, fps) # 关闭自动曝光固定一个经验值 self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) self.cap.set(cv2.CAP_PROP_EXPOSURE, 120) # 关闭自动白平衡 self.cap.set(cv2.CAP_PROP_AUTO_WB, 0) self.cap.set(cv2.CAP_PROP_WB_TEMPERATURE, 4600) def read(self): ret, frame self.cap.read() if not ret: return None # 统一缩放提升推理速度 frame cv2.resize(frame, (640, 480)) return frame5.3 检测模块 detector.py这是和BPU打交道的模块也是这套系统里“含金量”最高的部分。注意输入图像在送入模型前需要做一次格式转换把BGR转为RGB同时归一化到0到1之间。地平线工具链默认要求的是RGB输入。import numpy as np from hobot_dnn import pyeasy_dnn class MedicineDetector: def __init__(self, model_path, labels_path): self.model pyeasy_dnn.load(model_path) self.labels [line.strip() for line in open(labels_path, r, encodingutf-8)] # 从模型中读取输入尺寸 self.input_h self.model[0].inputs[0].shape[1] self.input_w self.model[0].inputs[0].shape[2] def preprocess(self, bgr_img): img cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (self.input_w, self.input_h)) img img.astype(np.float32) / 255.0 # hobot_dnn输入要求是连续的numpy数组 img np.ascontiguousarray(img) return img def postprocess(self, outputs): # 这里以YOLO输出为例具体解码方式与模型有关 # 简化处理只显示最高置信度的检测 det outputs[0].buffer scores det[..., 4] label_ids np.argmax(det[..., 5:], axis-1) boxes det[..., :4] results [] if len(scores) 0: best np.argmax(scores) if scores[best] 0.5: results.append({ label: self.labels[label_ids[best]], score: float(scores[best]), box: boxes[best].tolist(), }) return results def predict(self, bgr_img): input_tensor self.preprocess(bgr_img) outputs self.model[0].forward(input_tensor) return self.postprocess(outputs)特别说一下不同模型的后处理逻辑不一样上面这个后处理是高度精简的版本。如果你的模型是自己的建议先把模型导出后在电脑端用同一组数据把后处理逻辑调到输出完全一致再搬到板子上跑。不要直接在板子上调试后处理那样效率太低。5.4 决策模块 decision.py状态机实现的核心。判断逻辑尽量简单直观状态之间的转换条件写清楚。这样比赛现场如果某个环节出了问题打开日志文件一看状态机卡在哪个状态马上就知道是识别不足还是控制没到位。class DecisionNode: def __init__(self, cfg, serial): self.cfg cfg self.serial serial self.state IDLE self.pickup_flag False self.target_room cfg[target_room] def init(self): self.state TO_MEDICINE self.serial.send(START) def on_no_target(self): if self.state in (TO_MEDICINE, TO_WARD): # 没有目标低速向前寻找标志 self.serial.send(SLOW_FORWARD) def update(self, detections): if self.state TO_MEDICINE: if self._find_label(detections, pharmacy): self.state PICKUP self.serial.send(STOP) self.serial.send(PICKUP) elif self.state PICKUP: if self._find_label(detections, medicine) and not self.pickup_flag: self.pickup_flag True self.serial.send(GRIPPER_CLOSE) self.serial.send(TO_WARD) self.state TO_WARD elif self.state TO_WARD: if self._find_tag_id(detections, self.target_room): self.state ARRIVED self.serial.send(STOP) self.serial.send(GRIPPER_OPEN) return self.state def _find_label(self, detections, label): return any(item[label] label for item in detections) def _find_tag_id(self, detections, tag_id): return any(item[label] ftag_{tag_id} for item in detections)5.5 与STM32通信的serial_cmd.py串口通信的封装。这个模块要稳雷区在于加锁和超时处理。发送指令时必须确保上一帧指令被完全发完避免数据交叉。import serial import struct import threading class SerialCmd: FRAME_HEAD b\xAA\x55 FRAME_TAIL b\x0D\x0A def __init__(self, port, baudrate): self.ser serial.Serial(port, baudrate, timeout0.1) self.lock threading.Lock() def open(self): if not self.ser.is_open: self.ser.open() def send(self, cmd, datab): with self.lock: body bytes([len(data) 1, cmd]) data checksum sum(body) 0xFF frame self.FRAME_HEAD body bytes([checksum]) self.FRAME_TAIL self.ser.write(frame) # 等待STM32回复ACK超时则重发一次 ack self.ser.read(2) if ack ! bOK: self.ser.write(frame)这个ACK等待机制是我在经历了一次“车莫名其妙停在场地中央不动”的故障后加进去的。那一次就是因为串口被干扰指令没送达但程序认为已经发了车就一直等一个不存在的动作。加了重发以后虽然极端情况下会重复动作一次但至少不会完全卡死。5.6 怎么跑起来写完了这么多代码完整的启动流程很简单把板子接上电源启动系统。将代码目录复制到板子比如/home/sunrise/smart_medical_robot/。先单独运行摄像头测试python3 main.py之前先跑一下camera/camera_node.py确认画面正常。再测试检测模型直接在板子上执行python3 test_detector.py这个脚本会在当前目录生成一张带检测框的图片看输出是否正确。确认串口线已连接运行python3 main.py观察状态机日志。我在代码里故意保留了一个test_detector.py的测试入口建议你们也保留因为每次现场调参之前先花30秒跑一次静态检测确认“眼睛是好的”再去调“脑子”和“手脚”能省下大量排查时间。6. 竞赛实战调试关键参数与常见问题排查6.1 现场灯光与识别率最大的坑永远是灯光。室内赛场的顶灯常常是频闪的摄像头快门速度一高画面就会出现明暗条纹快门速度一低画面又容易动态模糊。两种问题都会严重拉低检测率。我们最终的解决方案是使用全局曝光摄像头优先。如果只能用卷帘曝光摄像头再固定曝光时间在1/120秒到1/240秒之间。比赛前在场地里测试至少20分钟让摄像头自动白平衡充分收敛再锁定参数。如果是晚上调试第二天白天比赛光照色温变化很大。白平衡和曝光参数必须重新调一遍。这个没有捷径只能靠提前到场多试多录。6.2 算力抖动为什么跑着跑着卡一下有时候车跑得好好的突然视觉帧率从30掉到10几排查了很久发现是温度问题。RDK X5的BPU在高负载下发热很厉害散热壳没贴好或者风扇被挡算力就会下降。这个和电脑降频一个道理。处理方法确保散热风扇工作正常比赛状态不要盖住散热孔。在软件里不要同时跑太多模型检测和分类模型串行调用即可千万不要开多线程并行推理。如果连续跑20分钟以上板子温度过高考虑在场地休息间隙断电降温。6.3 串口通信飘码哪怕加了协议和校验和串口依然可能在强电机干扰下出声。排查步骤按优先级来串口线换成屏蔽线尽量短不要和电机电源线绑在一起走线。共地STM32和RDK X5的电源地必须连在一起否则电平参考不一致必飘。降低波特率从115200降到57600稳定性提升明显实际传输数据量并不大完全够用。在软件层面对控制指令做“防抖”——同样指令连续发两次STM32端只有当两次指令一致时才执行。6.4 药品模型出现过拟合我们训练时发现一个有意思的问题在训练集上识别率99%比赛现场直接掉到70%。原因就是过拟合了。药品的包装、摆放角度、光线都太固定模型记住了训练集的“背景”而不是药品本身。补救措施数据增强随机旋转、色偏、光照变化。采集数据时把药品放在不同材质背景上拍摄。采用更强的数据增强比如Mosaic增强这在YOLO训练里是默认选项。现场实际再采集50张图片增量训练一轮。6.5 跑动中视觉模糊底盘在粗糙地面跑起来震动会让画面变得模糊。我们加了一层“电子稳像”其实就是只在图像清晰度评估值较高时才做检测。用Laplacian方差评估清晰度方差低于阈值就跳过检测等待下一帧def is_blurry(img_gray, threshold100.0): lap_var cv2.Laplacian(img_gray, cv2.CV_64F).var() return lap_var threshold这个办法特别简单但效果显著强烈建议所有视觉车都加这一行。6.6 常见问题速查表现象可能原因解决方法检测框乱跳画面过曝/反光固定曝光调低曝光值亚光垫底模型推理慢没有量化/散热不良确认模型是地平线bin格式清理散热小车不走串口未打开/协议错误用示波器或串口助手抓包看是否发出指令目标识别不到训练集不够/角度差异大现场补数据增量训练电机抖动PID参数不当/编码器接反逐步调P确认编码器A/B相接对屏幕黑屏HDMI线松动/分辨率不支持用官方推荐的5寸屏和转接线电压不足电池衰减赛前充电并测试压降准备备用电池6.7 比赛心态与时间管理最后说点代码之外但至关重要的话。竞赛现场的压力非常大尤其是面对突发故障时团队很容易陷入“反复更改代码、越改越乱”的恶性循环。我的经验是出发去赛场前把所有代码做一次git提交保留一个“最后稳定版本”的标签。现场只允许做参数调整不新写功能。所有新功能比赛前一晚在酒店写完并测试完。打印一份完整的状态机流程图和串口协议说明方便现场快速回忆。这个习惯帮我们避免了很多次“改崩了代码回不去”的事故非常建议每个参赛队都养成。7. 写在最后的一点个人经验做完整套系统后我的直接感受是在地瓜机器人这样的平台上做智慧医疗场景真正的工作量几乎全部在“数据采集、模型调优、系统联调”上而不是在“能不能跑起来”上。官方的例程和工具链已经帮我们解决了70%的环境问题剩下30%才是竞赛拉开差距的地方。如果你从零开始我给的建议顺序是这样的先把官方巡线例程跑通然后加上药品检测再串上串口控制最后补上状态机和UI。每一步都在前一步验证完毕的基础上再推进不要想一口气吃成胖子。后面我们还在尝试把整车的定位换成视觉里程计不加额外传感器也在研究怎么把多药品的抓取顺序优化成动态规划问题。这个方向还有得玩等有新的实测数据了再来更新分享。