简介:本资源是第二十五届中国机器人及人工智能大赛智慧药房组参赛项目DrugDeliverer的完整设计源码,面向高校机器人/人工智能方向学生、ROS开发者及医疗自动化系统学习者,聚焦药房场景下的药品智能识别、路径规划与自主递送问题。压缩包共163个文件,总计55.36MB,涵盖23个核心Python脚本(实现业务逻辑与算法)、39个YAML配置文件(定义机器人参数与任务流程)、11个launch与10个srv文件(支撑ROS通信与服务调用)、19张JPG图片(含现场部署与界面参考)、4个Shell脚本(用于环境启动与监控)以及PDF/MD文档、C++节点代码等,结构完整、模块清晰,具备可复现性与工程参考价值。已有803人学习下载,提供从感知(YOLOX模型bin)、决策(goal发送节点)、执行(send_goals_node.cpp)到交互(playsound、reminder_basic.py)的全链路实现,是理解智慧药房系统集成与多模态协同的优质实践样本。
1. 项目概述:从“智慧药房”到“DrugDeliverer”的实战思考
最近刚带队打完第二十五届中国机器人及人工智能大赛,我们做的项目叫“DrugDeliverer”,一个基于Python的智慧药房机器人系统。这个项目听起来挺高大上,但说白了,核心就一件事:怎么让一个机器人,在模拟的药房环境里,又快又准又稳地把药送到指定位置。这背后涉及的东西可不少,从机器人的感知、决策、规划,到药房业务逻辑的数字化建模,再到整个系统的稳定性和容错性,每一步都是坑。我干了十几年机器人开发,这次比赛算是把工业级应用里的一些思路,在竞赛场景下做了一次极限压榨和验证。如果你也在做类似的机器人项目,或者对ROS、Python、机器视觉和路径规划感兴趣,那这篇复盘应该能给你不少直接的启发和能“抄作业”的代码思路。
2. 核心需求与系统架构设计
2.1 赛题场景与核心痛点拆解
大赛给的场景是一个模拟智慧药房仓库。你需要设计一个机器人(通常是轮式移动机器人),它需要完成几个核心任务:首先,在未知或部分已知的仓库地图中自主导航;其次,准确识别货架上的药品(通过视觉或RFID等);然后,接收来自上位机系统的订单(比如“去A03货架取3盒阿莫西林”);最后,规划最优路径前往目标货架,完成取药或送药操作,并返回充电桩或分拣台。
这里面的核心痛点非常明确:
- 环境不确定性:比赛场地每次可能略有不同,障碍物位置会变。你的机器人不能依赖一份固定死的地图,必须有一定的实时感知和重规划能力。
- 任务并发与调度:订单可能连续下发,机器人需要判断是“一单一送”还是“多单合并配送”更高效。这涉及到简单的任务队列管理和调度算法。
- 精度与可靠性要求极高:药房场景下,拿错药是绝对不允许的。这就要求机器人的定位精度、导航稳定性以及末端执行器(如机械臂或抓取机构)的操作精度必须很高。
- 全流程自动化:从接收订单到最终完成,尽可能减少人工干预。这要求各模块(感知、决策、控制、通信)必须无缝衔接,形成一个健壮的整体。
2.2 DrugDeliverer 系统架构选型
基于以上痛点,我们设计了“DrugDeliverer”的系统架构。核心思想是分层解耦和模块化通信。
[用户/订单系统] | | (订单数据, JSON格式) V [任务调度与管理中心] (Python Core) | | (任务指令) V [机器人决策层] (ROS Node: brain_node) | | (导航目标、动作指令) (感知数据、状态反馈) V ^ [功能执行层] (ROS Nodes) ----------------------+ | (速度指令、控制命令) V [机器人硬件] (底盘、激光雷达、摄像头、机械臂)为什么这么选?
- ROS (Robot Operating System) 作为中间件:这是机器人领域的“事实标准”。它提供了节点间通信(Topic/Service/Action)、坐标变换(TF)、可视化(Rviz)等基础设施,让我们能专注于算法开发,而不是底层通信。我们用的是ROS Noetic(对应Ubuntu 20.04),生态成熟稳定。
- Python 作为核心语言:任务调度、业务逻辑、视觉处理(OpenCV)、与上位机通信(WebSocket/HTTP)等高层模块全部用Python编写。Python开发效率高,库丰富,非常适合快速原型和算法验证。我们的“大脑”节点(
brain_node)就是一个Python写的ROS节点。 - 感知-决策-控制分离:这是经典的控制架构。感知层(激光SLAM、视觉识别)只管“看到什么”;决策层(
brain_node)根据看到的信息和任务要求,决定“做什么”;控制层(底盘驱动、机械臂控制器)只管“怎么做”。这样模块独立性好,一个模块出问题不影响整体。
注意:在资源受限的竞赛机器上(比如Jetson Nano),要特别注意Python和ROS节点的资源消耗。我们通过将视觉识别这类重计算模块单独放在一个节点,并采用异步回调、降低图像处理分辨率等方式来优化。
3. 核心模块实现与关键技术点
3.1 高精度建图与定位(SLAM)
这是移动机器人一切行动的基础。我们采用了Cartographer算法进行激光SLAM建图。
为什么选Cartographer?Cartographer是Google开源的算法,它在处理回环检测和生成一致性地图方面非常出色,特别适合结构化的室内环境(如药房仓库)。相比Gmapping,它更稳定,生成的地图质量更高,虽然计算量稍大,但在我们的i5工控机上跑得绰绰有余。
实操步骤与关键配置:
- 数据采集:手动遥控机器人,在比赛场地内缓慢、均匀地走“∞”字形和绕边,确保激光雷达扫描到所有角落和回环。
- 配置
.lua文件:这是Cartographer的参数文件,调参是关键。-- 关键参数调整(片段) MAP_BUILDER.num_subdivisions_per_laser_scan = 1 -- 对于高速旋转雷达(如10Hz),设为1保证实时性 POSE_GRAPH.optimize_every_n_nodes = 90 -- 优化频率,值越小地图一致性越好,但计算越频繁 TRAJECTORY_BUILDER_2D.submaps.num_range_data = 90 -- 每个子图包含的激光帧数,影响地图细节 - 建图启动命令:
建图完成后,使用roslaunch cartographer_ros demo_revo_lds.launchcartographer_pbstream_to_ros_map工具将.pbstream格式的地图转换为ROS标准的.pgm和.yaml地图文件。
避坑心得:
- 雷达安装高度和角度:确保雷达平面与地面平行,且安装高度能扫到地面(用于检测地面小障碍)和一定高度的货架腿。我们最初装高了,导致地图上货架底部是“空心”的,机器人规划路径时会穿过去。
- IMU(惯性测量单元)不是必须,但很有用:如果机器人有IMU,在Cartographer配置中启用它,可以极大地改善机器人在快速旋转或直线运动时的位姿估计,减少地图的“拉花”现象。
- 保存原始数据包(bag):建图时用
rosbag record命令保存所有传感器数据。这样后期可以反复回放调试参数,不用每次都实地跑一遍。
3.2 动态路径规划与导航
有了地图,就要让机器人在里面动起来。我们使用ROS的Navigation Stack作为基础框架,但做了大量定制。
全局规划器(Global Planner):默认的navfn或global_planner在结构化环境中够用,但我们发现它对“死胡同”的规划有时不够平滑。我们换成了**teb_local_planner的作者开发的global_planner的改进版**,或者直接使用move_base_flex框架,它更灵活。
局部规划器(Local Planner):teb_local_planner(Timed Elastic Band)是我们的不二之选。它不同于传统的动态窗口法(DWA),其优化的是整条轨迹在时间和空间上的形状,对于需要精确通过狭窄通道(如货架间走廊)的场景,控制更精准,轨迹更平滑。
关键参数调优(teb_local_planner):
TebLocalPlannerROS: max_vel_x: 0.4 # 最大前进速度,室内环境0.3-0.5较安全 max_vel_theta: 0.5 # 最大旋转速度 acc_lim_x: 0.5 # 加速度限制,太大容易打滑,太小启动慢 min_obstacle_dist: 0.25 # 与障碍物的最小距离,根据机器人半径设置 footprint_model: # 机器人轮廓模型,必须准确!我们用的是多边形顶点。 vertices: [[-0.25, -0.15], [-0.25, 0.15], [0.25, 0.15], [0.25, -0.15]] oscillation_recovery: true # 启用振荡恢复,防止在障碍物前“抽搐”动态障碍物处理: 比赛环境中可能有其他移动的机器人或工作人员。我们扩展了costmap_2d的配置,将激光雷达的实时数据加入到obstacle_layer中,并适当调小inflation_radius(膨胀半径),让机器人敢于靠近静态障碍物行驶,同时对突然出现的动态障碍物又能及时避让。
提示:
teb_local_planner计算量较大。在树莓派或Jetson上,如果发现控制频率跟不上,可以尝试减少no_inner_iterations和no_outer_iterations参数,牺牲一点轨迹最优性换取实时性。
3.3 药品识别与抓取策略
这是“智慧药房”的核心业务环节。我们采用了多传感器融合的方案。
粗定位:AprilTag视觉标签在每个货架的特定位置粘贴AprilTag二维码。机器人导航到货架前后,通过摄像头识别AprilTag,可以快速、高精度地获得自身相对于货架的位置和姿态修正。这解决了激光定位在相似货架环境中可能产生的累积误差问题。
# 使用 apriltag_ros 包 # 在launch文件中定义tag家族和大小 <param name="tag_family" value="tag36h11"/> <param name="tag_size" value="0.05"/> # 标签边长,单位米识别到Tag后,会发布一个包含位姿的TF帧(如
tag_0),我们的程序只需监听这个TF变换,就能知道药盒相对于机器人坐标系的位置。精识别:基于深度学习的药品包装识别仅仅知道货架位置还不够,需要确认具体药品。我们训练了一个轻量级的卷积神经网络(CNN),用于识别药品包装盒上的文字和图案。
- 数据集:我们自己拍摄了数百张不同光照、角度下的目标药品包装图片,并使用LabelImg进行标注。
- 模型选型:考虑到部署在Jetson Nano上,我们选择了MobileNetV2作为主干网络,后面接一个简单的全连接层进行分类。使用TensorFlow Lite进行量化后部署,推理速度在Nano上能达到~30ms/帧。
- 集成:当机器人通过AprilTag定位到药盒大致区域后,控制摄像头对准该区域拍照,送入CNN模型进行识别,确认药品名称与订单匹配。
抓取执行我们使用了一个二自由度的简易舵机抓取器。抓取逻辑很简单:
- 通过AprilTag位姿,结合已知的药盒尺寸,计算机械爪需要到达的三维坐标。
- 控制机器人底盘进行最后的微调,使抓取器正对药盒中心。
- 发送指令给舵机控制器,执行抓取动作。关键点:在抓取前,加入一个“预抓取视觉校验”步骤,用CNN再次确认眼前的药盒是否正确,并微调抓取中心点。这个双重校验机制保证了几乎100%的抓取准确率。
3.4 任务调度与状态管理(Python核心)
这是整个系统的“指挥官”,一个独立的Python ROS节点(brain_node)。它负责:
- 通信:通过WebSocket或ROS Service与模拟的上位机订单系统连接,接收订单。
- 解析与排队:将订单解析为一系列原子任务(如
导航到A03,识别药品,抓取,返回)。 - 调度:实现一个简单的状态机。我们用了
transitions这个轻量级Python状态机库,让代码逻辑非常清晰。from transitions import Machine class RobotBrain: states = ['idle', 'navigating', 'identifying', 'grabbing', 'returning', 'error'] def __init__(self): self.machine = Machine(model=self, states=RobotBrain.states, initial='idle') # 定义状态转移 self.machine.add_transition('new_order', 'idle', 'navigating') self.machine.add_transition('arrived_at_shelf', 'navigating', 'identifying') self.machine.add_transition('identification_ok', 'identifying', 'grabbing') # ... 更多转移 self.machine.add_transition('mission_complete', 'returning', 'idle') self.machine.add_transition('error_occurred', '*', 'error') - 监控与恢复:监听各个功能模块(导航、识别、抓取)的反馈状态。如果某个任务超时或失败,触发错误处理流程,例如重试、绕行或上报错误。
为什么不用ROS Action?ROS Action本身适合可中断、有反馈的长时任务(如导航)。但我们的业务逻辑涉及多个Action的顺序执行、条件判断和错误处理,用一个中心化的状态机来管理所有流程,逻辑更集中,调试更方便。brain_node通过调用各个模块的Action或Service来驱动它们。
4. 系统集成、调试与实战优化
4.1 集成与联调:让模块“对话”起来
模块单独测试都没问题,但集成起来就是各种“惊喜”。最大的挑战是坐标系(TF)的统一和时序问题。
TF树管理: 机器人有底盘坐标系(base_link)、激光雷达坐标系(laser)、摄像头坐标系(camera)、机械爪坐标系(gripper)。它们之间的静态变换在URDF文件中定义。而地图坐标系(map)、里程计坐标系(odom)和base_link之间的动态变换由SLAM和里程计提供。必须确保TF树是完整且连续的。我们经常用rosrun tf view_frames生成TF树图来检查,或者用Rviz的TF显示功能,看各个坐标系是否如预期般连接。
时序与延迟处理: 视觉识别需要时间(~100ms),机械爪动作需要时间(~500ms)。如果brain_node发送“识别”指令后立刻查询结果,肯定会拿到空值。我们的做法是异步回调和带超时的等待。
# brain_node 中发送识别请求的伪代码 def identify_medicine(self, image_topic): from threading import Event identification_done = Event() result = None def callback(msg): nonlocal result result = msg.data # 假设识别结果在msg.data中 identification_done.set() # 订阅一次性的识别结果话题 sub = rospy.Subscriber('/identification_result', String, callback, queue_size=1) # 发布图像到识别节点 pub.publish(image_msg) # 等待结果,设置超时(如2秒) if identification_done.wait(timeout=2.0): sub.unregister() # 重要!用完取消订阅,避免回调函数堆积 return result else: rospy.logwarn("药品识别超时!") sub.unregister() return None4.2 性能优化与稳定性提升
降低CPU占用:
- 将Cartographer的
POSE_GRAPH.optimize_every_n_nodes参数调大,减少回环检测优化频率。 - 视觉识别节点,仅在收到指令时才采集一帧图像进行处理,而不是持续运行。
- 使用
py-spy工具分析Python节点的CPU热点,对关键循环进行优化(如用NumPy向量化操作)。
- 将Cartographer的
通信优化:
- 图像传输是带宽大户。我们使用
compressed_image_transport将摄像头图像压缩后再发布,订阅端解压,网络负载大幅下降。 - 对于不要求高实时性的状态信息(如电池电量),降低发布频率(如从10Hz降到1Hz)。
- 图像传输是带宽大户。我们使用
增加“心跳”与“看门狗”: 为每个关键ROS节点(导航、识别、抓取控制)编写一个简单的“看门狗”脚本。该脚本定期检查对应节点是否存活(例如,通过
rosnode ping或检查其发布的话题是否更新)。如果节点僵死,看门狗脚本会尝试重启它,并通过ROS Service通知brain_node进入“错误恢复”状态。
4.3 比赛现场应对策略
比赛环境和实验室完全不同。灯光、地面反光、无线网络干扰都是变量。
- 灯光:我们提前训练视觉模型时,就使用了数据增强(随机调整亮度、对比度、饱和度),提高了模型的鲁棒性。现场如果光线过暗或过亮,我们准备了一个USB补光灯,可以临时夹在机器人上。
- 地面:光滑瓷砖地面可能导致轮子打滑,影响里程计精度。我们除了依赖激光SLAM外,还融合了IMU数据。同时,将
teb_local_planner的acc_lim_x和acc_lim_theta参数调小,让机器人加减速更柔和。 - 网络:与上位机的通信(WebSocket)准备了备用方案。我们让机器人本地缓存最后接收到的几个订单,万一网络短暂中断,可以继续执行缓存任务,同时尝试重连。
5. 常见问题排查与解决实录
在实际开发和比赛过程中,我们遇到了无数问题。下面这个表格总结了一些最典型的问题和我们的解决方案,希望能帮你快速排雷。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 机器人建图时原地旋转或地图严重扭曲 | 1. 激光雷达数据有问题。 2. IMU数据未正确配置或噪声大。 3. 机器人底盘里程计数据异常。 | 1. 用rostopic echo /scan查看激光数据是否正常(范围值是否合理)。2. 用 rviz显示激光扫描,看是否与物理环境匹配。3. 检查IMU话题是否发布,在Cartographer配置中是否正确启用和配置了IMU参数。 4. 检查 /odom话题数据,手动推动机器人,看位姿变化是否平滑。 |
| 导航时,机器人规划出路径但不动,或者一直在原地小幅调整(振荡) | 1. 代价地图(costmap)设置不当,机器人认为前方有障碍。 2. 局部规划器参数过于保守。 3. 机器人轮廓(footprint)设置错误。 | 1. 在rviz中同时显示global_costmap和local_costmap,观察目标点附近是否有膨胀的障碍物区域(红色)。2. 检查 local_costmap的obstacle_layer和inflation_layer参数,适当减小inflation_radius。3. 调整 teb_local_planner的min_obstacle_dist和penalty_epsilon参数,让机器人更“敢于”靠近障碍物行驶。4.仔细核对 footprint参数,确保其形状和尺寸与实物完全一致。 |
| 视觉识别准确率在实验室很高,现场骤降 | 1. 现场光照条件与训练数据差异大。 2. 摄像头对焦或曝光问题。 3. 拍摄距离、角度变化大。 | 1.数据增强是关键:重新训练模型时,必须加入大量光照变化的数据增强。 2. 现场调试时,可以尝试自动白平衡或手动设置摄像头参数。 3. 增加一个图像预处理环节:自动对比度拉伸(CLAHE)或灰度归一化,减少光照影响。 4. 使用AprilTag先进行粗定位,确保每次识别时摄像头与药盒的距离和角度相对固定。 |
| 机械爪抓取位置总是有偏差 | 1. 手眼标定不准确。 2. 机器人底盘最终停止位置有误差。 3. 舵机控制存在回差。 | 1.重新进行精确的手眼标定。我们使用多个已知位置的AprilTag,采集多组机械爪坐标和相机识别坐标,用最小二乘法求解变换矩阵。 2. 在导航到位后,加入一个基于视觉的微调步骤:让机器人根据识别到的药盒中心像素坐标,计算一个小的平移量,再移动底盘进行补偿。 3. 对舵机进行校准,记录其开合角度与实际位置的关系,并在控制代码中做反向补偿。 |
| 整个系统运行一段时间后变卡,甚至节点失联 | 1. 内存泄漏(特别是Python节点)。 2. CPU过热降频。 3. ROS通信缓冲区堆积。 | 1. 使用htop或rosnode info查看节点内存和CPU占用。重点检查Python节点中是否有大型对象未及时释放、列表无限增长等问题。2. 给工控机或Jetson加装散热风扇。 3. 检查所有话题的 queue_size设置是否合理,对于实时性高的话题,队列不宜过大。对于不重要的日志话题,可以降低发布频率或直接关掉。 |
最后一点个人体会:机器人项目,尤其是这种集成度高的比赛项目,稳定性压倒一切。一个跑得慢但从不卡死的系统,远比一个跑得快但偶尔崩溃的系统得分高。我们的策略是,在保证核心功能(导航、识别、抓取)100%可靠的基础上,再去优化速度。每次修改代码或参数后,都要进行长时间的“压力测试”,模拟连续执行数十个订单,观察系统状态。日志记录要详尽,rospy.loginfo和rospy.logwarn是你的好朋友,它们能帮你快速定位问题发生的上下文。
本文还有配套的精品资源,点击获取