安徽省冠战队技术复盘:机器人视觉识别、运动控制与状态机全解析

发布时间:2026/8/30 17:48:26
安徽省冠战队技术复盘:机器人视觉识别、运动控制与状态机全解析 安徽省冠再见安大。这篇博客不想写成感言而是一次正经的技术复盘。标题里写的“安徽省冠”是过去一年我们在安徽大学实验室里从零开始做的竞赛项目拿到的成绩。项目本身不是开源框架而是一套完整的机器人竞赛解决方案视觉识别、底盘运动控制、机械臂抓取、任务状态调度全部自己写。从第一版“能跑就不错”到赛场上稳定输出中间踩的坑基本可以写成一本小册子。如果你准备参加机器人类竞赛或者正在做自动化、嵌入式视觉相关的课程设计这篇文章可以直接收藏。下面会把系统架构、环境准备、代码部署、各模块测试方法、性能优化和故障排查完整过一遍重点讲清楚一件事怎样让一个多模块联动的机器人系统在赛场上稳定完赛而不是在实验室里“偶尔能跑通”。1. 核心能力速览能力项说明项目类型机器人竞赛整体技术方案覆盖感知、决策、执行三层主要功能自动循迹、视觉识别物料、机械臂抓取与定点投放、任务流程调度软件语言Python、C可组合使用视觉方案OpenCV 颜色/轮廓识别ArUco 定位可扩展轻量分类网络决策框架任务状态机 行为列表可编排多阶段任务通信方式上位机与下位机之间使用串口/ROS Topic 通信推荐硬件Jetson Nano / 树莓派 4B 作为上位机STM32 作为下位机支持平台Ubuntu 20.04/22.04Windows 可通过 WSL 或远程调试是否支持批量任务支持任务列表可预先编排并自动执行是否支持接口 API模块可被命令行调用也可封装为本地 HTTP/ROS 服务适合场景机器人竞赛、课程设计、产学研项目原型、自动化产线 Demo需要说明本文是参赛项目的技术复盘不提供完整成品代码包。但每个模块的设计思路和测试方法足够通用可以按自己的硬件参数复刻。2. 赛题分析与系统方案设计比赛类项目最容易犯的错是一上来就写代码。真正该做的第一件事是把赛题规则拆成技术需求。2.1 赛题关键信息拆解以我们参赛的赛题为例核心动作包括小车从起点出发沿场地白线或色带自动循迹。经过物料区通过视觉识别指定颜色的物料。机械臂抓取物料移动到目标投放区。投放完成后返回继续下一个任务点。全程不允许人为遥控必须自主完成。把规则翻译成技术语言就是赛题要求技术需求自动循迹底盘运动控制 线检测/路点导航识别物料颜色视觉识别模块输出目标类别和坐标机械臂抓取坐标变换 机械臂逆解 抓取控制定点投放位置标定 状态确认多任务连续执行任务状态机保证状态流转和异常恢复2.2 系统分层设计我们最终把系统分成了三层感知层负责图像采集、目标识别、位置估计。决策层负责任务编排、状态切换、异常处理。执行层负责底盘运动、机械臂动作、传感器读取。三层之间的数据流是摄像头 - 感知层(识别结果) - 决策层(任务状态) - 执行层(速度指令/舵机指令)这里最重要的设计原则是层与层之间通过结构化消息通信不允许直接调用内部变量。比如感知层输出的是统一的识别结果结构体决策层不关心它是用颜色阈值还是YOLO实现的。这样做的好处是后期调试方便。某个模块出问题可以直接单独测试不用把整个机器人跑起来。3. 环境准备与硬件清单竞赛项目必须在真实硬件上跑所以环境准备比普通软件项目更复杂。3.1 硬件清单部件作用备注上位机主控跑视觉、决策算法Jetson Nano 或树莓派 4B下位机主控电机控制、传感器采集STM32 或 Arduino摄像头图像采集USB 摄像头即可注意帧率和分辨率直流电机 编码器底盘驱动编码器用于里程计舵机/机械臂物料抓取与投放建议使用单独供电电源模块供电上位机与电机必须分开供电电源分开供电这条非常关键。电机启动瞬间电流很大如果和主控共用电源很容易导致主控重启。3.2 软件依赖上位机建议使用 Ubuntu 20.04 或 22.04并安装以下依赖# 基础工具 sudo apt update sudo apt install -y python3-pip git cmake v4l-utils # Python 视觉与计算依赖 pip install numpy opencv-python pyserial # 如果需要 ROS 方案按 ROS 版本安装对应环境 # 这里以 ROS Noetic 为例 sudo apt install -y ros-noetic-ros-base下位机开发推荐 STM32CubeIDE 或 PlatformIO。如果没有硬件开发条件也可以先用串口助手模拟下位机指令提前联调上位机逻辑。3.3 目录规划建议项目目录按以下结构组织robot_project/ ├── config/ # 所有参数配置 │ ├── camera.yaml │ ├── pid.yaml │ └── task_list.yaml ├── perception/ # 感知模块 │ ├── camera.py │ └── detect.py ├── decision/ # 决策模块 │ └── state_machine.py ├── control/ # 执行模块 │ ├── serial_comm.py │ └── motion.py ├── tests/ # 各模块独立测试脚本 ├── logs/ # 运行日志 └── main.py # 主入口把参数放到配置文件里而不是写死在代码中是这次项目复盘里最值得强调的一点。比赛现场调整参数非常多如果每次调参都要改代码很容易引入新的 bug。4. 软件部署与启动流程4.1 代码启动流程主入口启动流程设计为先加载配置再初始化各模块最后进入任务循环。# main.py 简化示例 import yaml from perception.camera import Camera from perception.detect import Detector from decision.state_machine import StateMachine from control.serial_comm import SerialComm def main(): # 1. 加载配置 with open(config/camera.yaml, r) as f: camera_config yaml.safe_load(f) with open(config/task_list.yaml, r) as f: task_config yaml.safe_load(f) # 2. 初始化模块 camera Camera(camera_config) detector Detector() comm SerialComm(port/dev/ttyUSB0, baudrate115200) state_machine StateMachine(task_config) # 3. 启动任务循环 while True: image camera.read() detections detector.run(image) state state_machine.update(detections) cmd state_machine.get_command() comm.send(cmd) if __name__ __main__: main()4.2 启动检查清单首次启动不要直接跑完整车测试按顺序做以下检查摄像头能否正常打开帧率是否达标。串口设备节点是否存在权限是否正确。电机驱动板是否能响应速度指令。视觉识别模块能否识别测试物料。状态机能否按预设流程正常切换。每一步都有独立的测试脚本确认通过后再进入下一步。5. 核心模块设计与功能测试5.1 视觉识别模块视觉识别在比赛场景中承担两个任务一是识别目标物料的颜色和位置二是辅助定位。5.1.1 方案选型没有直接上 YOLO 这类目标检测网络原因很简单比赛场地环境相对固定目标物料的颜色和形状是提前知道的用传统视觉方案已经能满足需求而且 CPU 推理速度更快实时性更稳定。最终使用的方案是HSV 颜色阈值过滤目标颜色。轮廓提取获取物料候选区域。面积和形状过滤排除误检。计算目标中心点作为抓取坐标。5.1.2 核心代码示例import cv2 import numpy as np class ColorDetector: def __init__(self, hsv_lower, hsv_upper, min_area500): self.hsv_lower np.array(hsv_lower) self.hsv_upper np.array(hsv_upper) self.min_area min_area def detect(self, frame): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, self.hsv_lower, self.hsv_upper) mask cv2.erode(mask, None, iterations2) mask cv2.dilate(mask, None, iterations2) contours, _ cv2.findContours( mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) results [] for cnt in contours: area cv2.contourArea(cnt) if area self.min_area: continue x, y, w, h cv2.boundingRect(cnt) cx, cy x w // 2, y h // 2 results.append({center: (cx, cy), area: area}) return results5.1.3 测试方法与预期结果测试目的验证不同光线条件下颜色识别稳定性和目标中心点输出准确性。操作步骤准备红、蓝、绿三种颜色的物料各一个。分别在不同角度、不同距离下采集图像并运行识别。检查输出中心点与人工标注中心点之间的偏差。预期结果目标颜色识别准确率在 95% 以上。中心点像素误差在 10 像素以内。误检率低于 5%。如果识别不稳定优先调整 HSV 阈值而不是直接改算法。前期花时间采集场地光照下的样本把范围调准比后期加后处理更有价值。5.2 运动控制模块底盘控制使用差速模型。上位机下发线速度和角速度STM32 接收后换算成左右电机 PWM 输出。5.2.1 串口通信协议设计通信协议要足够简单且抗干扰。我们使用的是以下格式帧头(0xA5) 数据类型(0x01) 线速度(int16) 角速度(int16) 校验和(1字节)对应 Python 发送代码import struct import serial def send_velocity(ser: serial.Serial, linear: float, angular: float): linear_int int(linear * 1000) angular_int int(angular * 1000) data struct.pack(BBhhB, 0xA5, 0x01, linear_int, angular_int, 0) # 实际校验和需要按协议计算 ser.write(data)5.2.2 测试方法与预期结果测试目的验证小车能否按指令直线行驶、原地旋转和闭环巡线。操作步骤先下发固定线速度观察小车走 1 米后的横向偏移。再下发固定角速度观察小车旋转 90 度的误差。最后开启视觉巡线测试场地白线跟踪。预期结果直线 1 米横向偏移小于 5 厘米。旋转 90 度误差小于 3 度。巡线速度在 0.3 m/s 时能稳定过弯。常见失败原因电机左右不对称、编码器安装松动、PID 参数未调好。5.3 任务调度与状态机比赛中最容易出问题的是任务流程混乱。比如物料抓取没成功但程序直接进入投放状态最后整场比赛丢分。我们用状态机来解决这个问题。核心思想是每个任务步骤都是独立状态进入下一个状态前必须满足退出条件。5.3.1 状态定义状态说明进入条件退出条件IDLE初始状态系统启动收到开始信号SEARCH搜索物料收到开始信号识别到目标GRASP抓取物料目标确认机械臂闭合抓取成功CARRY携带物料移动抓取成功到达投放点RELEASE投放物料到达投放点机械臂张开确认投放RETURN返回起点投放成功回到起点FINISH完成全部任务所有任务完成无5.3.2 状态机伪代码class StateMachine: def __init__(self, tasks): self.tasks tasks self.current_state IDLE def update(self, detection_result): if self.current_state SEARCH: if detection_result: self.current_state GRASP elif self.current_state GRASP: if self.check_grasp_success(): self.current_state CARRY # 其余状态逻辑省略 return self.current_state5.3.3 测试方法与预期结果测试目的验证多任务连续执行时状态切换是否准确异常条件下能否恢复。操作步骤先跑单次抓取投放全流程。再跑 5 次连续任务统计成功率。人为制造失败场景比如抓取时物料滑落观察状态机是否进入重试逻辑。预期结果单次任务成功率 100%。连续任务成功率 80% 以上。失败场景下 3 秒内恢复或进入安全状态。状态机是这次项目里收益最高的模块。它让整个系统的逻辑变得非常清楚也让多人协作开发变成了可能。每个人只需要负责自己那个状态节点。6. 比赛现场的稳定性优化实验室跑得好赛场崩掉是竞赛项目最常见的结局。这里的差距不在算法本身而在工程细节。6.1 光线变化问题比赛场地的灯光和实验室不一样摄像头曝光和色温会有明显偏差。我们做了三件事采集多个时间点的场地图像建立阈值参数集。启动时自动读取当前帧的平均亮度选择匹配的参数组。视觉处理前先做白平衡补偿。6.2 通信稳定性USB 摄像头和串口在机器人上同时工作容易出现通信延迟或丢包。排查后发现是电磁干扰和带宽冲突导致的。解决方案给串口线加磁环减少干扰。摄像头降低分辨率到 640x480帧率保持 30。所有指令增加超时重发机制。6.3 任务状态卡死问题最严重的一次事故是机械臂抓取动作完成后传感器信号没有及时返回状态机卡在“GRASP”状态整台车停在原地。改进方案除了传感器信号确认增加时间超时判断。如果机械臂动作超过 2 秒即使没有收到传感器信号也认为是动作完成进入下一个状态。这是典型的工程容错思路。7. 性能观察与资源占用竞赛小车算力有限必须把性能开销控制在合理范围。7.1 观察方法上位机使用htop观察 CPU 使用率使用jtopJetson 平台观察 GPU 使用率。# CPU 内存观察 htop # Jetson 平台查看 GPU 占用 jtop7.2 性能瓶颈分析这次项目中最耗性能的是图像处理环节。直接对整个画面做 HSV 过滤和轮廓提取CPU 占用会明显偏高。针对这个问题做了两个优化将图像处理区域裁剪到 ROI只处理画面中央的物料区。降低处理分辨率从 1280x720 降到 640x480识别精度几乎没有损失但 CPU 占用大幅下降。7.3 延迟分配参考环节耗时说明图像采集约 15ms640x480 30fps颜色识别约 20ms取决于 ROI 大小状态机决策小于 1ms状态判断非常快串口指令发送约 5ms115200 波特率总循环周期约 40ms满足 25Hz 控制频率这里的数值是我们项目实测量级不同硬件和分辨率会有差异实际以本机测试为准。但优化思路通用先定位瓶颈再压缩耗时最长的环节。8. 常见问题与排查方法问题现象可能原因排查方式解决方案摄像头打不开设备节点冲突或权限不足检查/dev/video*列表添加用户到 video 组或指定正确设备节点串口指令无响应串口号错误或波特率不一致用串口助手发送测试帧确认设备路径与波特率重新打开串口视觉识别抖动RGB 阈值不稳或曝光剧烈变化打印实时 HSV 值采用 ROI 约束启用自动曝光补偿机械臂抓不到物料目标中心坐标标定偏差检查像素坐标到物理坐标的映射重新标定相机与机械臂坐标系状态机卡死缺少超时机制查看日志中状态切换记录为每个状态增加超时退出逻辑车轮左右偏电机 PWM 基准不同测量空载转速在控制层增加左右电机速度补偿比赛中途重启电源供电不足测量电机启动时电压跌落上位机与电机分开供电选用大电流电源投放位置偏移里程计累计误差观察里程计读数与真实位置偏差增加视觉辅助定位或加终点传感器校正9. 工程化与团队协作建议竞赛项目做了半年最大的收获不是奖项本身而是学会了一套协作开发方式。这里整理几条对团队最有用的经验。9.1 配置与代码分离所有可调参数都放到配置文件中。比赛现场调参时只需要修改 yaml 文件不需要重新部署代码。这个习惯大幅降低了现场操作的出错率。9.2 日志必须覆盖关键节点每个状态切换、每次识别结果、每条下发的控制指令都要记录到日志。线上跑崩了第一件事不是猜问题而是看日志定位是哪个环节出了问题。日志示例2025-05-10 14:23:05 [STATE] SEARCH - GRASP 2025-05-10 14:23:05 [DETECT] colorred, center(320, 180), area2345 2025-05-10 14:23:07 [ACT] gripper close, successTrue 2025-05-10 14:23:07 [STATE] GRASP - CARRY9.3 测试要分等级单元测试单独验证视觉函数、串口编码函数。模块联调视觉 机械臂一起测试。整车测试完整流程跑一遍。模拟赛连续跑 10 次完整任务统计稳定率。不要跳过单元测试直接整车联调。省下的时间最终会在赛场上以更高倍数还回来。9.4 版本管理Git 分支结构main分支只放稳定可发布版本。dev分支是日常开发。每个模块拆成独立目录禁止互相调用内部实现。比赛前一周冻结代码只允许修改配置参数。这个约束虽然严格但保证了赛前系统稳定性。10. 从比赛到工程项目的经验沉淀比赛结束后再看这套系统有些模块可以直接沉淀成通用组件颜色识别模块可以抽成独立的视觉工具库配合不同场景的阈值配置即可复用。串口通信协议可以固化成通用的上位机与下位机通信框架。状态机决策框架可以复用大部分代码只需要替换具体状态节点。这其实是最有价值的部分。竞赛项目的时间窗口很短很多代码为了赶进度牺牲了可维护性。但凡是认真做了接口设计的模块赛后都能继续使用。如果你们也在备赛建议从一开始就按“可复用组件”的标准来写代码。宁愿前期多花两天设计接口也不要到最后靠熬夜改状态逻辑续命。安大的三年最后换来的不只是省冠的名字而是一套可以继续打磨的技术底座。暂时画一个句号但技术这条路还长。