简介:这份资源面向计算机、人工智能、自动化、电子信息等专业的高校学生与科研开发者,提供一套完整的垃圾分类机器人实现方案,将YOLOv5视觉识别与MoveIt机械臂运动规划相结合,可用于毕业设计、课程设计或项目立项演示。压缩包共356个文件,约188.62MB,涵盖50个Python脚本、23个C++源文件、50个launch启动文件、57个yaml配置、24个xml与17个msg通信定义,以及14个pt权重、12个stl模型、39个wav语音文件和urdf/srdf机器人描述,构成从感知、决策到执行的完整工程链路。目前已有98人学习关注。资源内含设计文档与可运行源码,读者可借此理解YOLO检测节点与机械臂抓取节点的协同逻辑、ROS话题与消息传递机制,并参考语音控制、反馈节点等模块进行二次开发,适合在现有代码基础上修改扩展功能或直接用于课题实践。
1. 从零搭一套垃圾分类机器人:YOLOv5 识别 + MoveIt 抓取到底难在哪
很多人第一次听到「垃圾分类机器人」,脑子里浮现的是流水线上一个六轴机械臂飞速分拣的画面,但真正动手做的时候,卡住你的往往不是算法本身,而是视觉和机械臂之间那条看不见的链路。YOLOv5 负责告诉你「这是什么垃圾、在画面哪个位置」,MoveIt 负责让机械臂「怎么过去、怎么抓、怎么放」,中间还夹着相机标定、坐标变换、抓取姿态生成、碰撞检测这一堆事。这套方案适合有 ROS 基础、想做一个完整感知-决策-执行闭环的本科生或研究生,也适合想从纯视觉转具身智能方向的工程师。它不需要你有多贵的硬件,一台带 GPU 的电脑加一个桌面级机械臂就能跑通全流程,但每一步都有坑,下面把我踩过的和见过的都讲清楚。
2. YOLOv5 垃圾分类检测:从数据集到可用的推理节点
2.1 为什么垃圾分类场景选 YOLOv5 而不是更新的模型
垃圾分类这个任务有几个特点:类别数少(通常四类或六类)、目标尺寸中等偏大、对实时性要求高、部署环境算力有限。YOLOv5 在这个场景下依然是性价比最高的选择,原因很实际。第一,它的工程化程度高,训练脚本、导出 ONNX、TensorRT 加速的链路非常成熟,你不需要自己去写数据加载和推理后处理。第二,社区里垃圾分类的预训练权重和数据集最多,迁移学习起点高。第三,YOLOv5s 在 RTX 3060 上跑 640 分辨率能到 100 FPS 以上,留给机械臂规划的时间很充裕。如果你用 YOLOv8 或 RT-DETR,精度可能高一点,但部署复杂度和显存占用会上去,对于桌面级垃圾分类机器人来说不划算。
数据集方面,常见做法是自己拍。把纸杯、塑料瓶、电池、果皮分别放在不同光照和角度下拍,每类至少 300 张,用 LabelImg 标注成 YOLO 格式。注意标注框要贴紧目标边缘,垃圾分类里透明塑料瓶和白色纸杯容易混淆,标注时要把瓶盖和瓶身都框进去,否则模型学到的特征不完整。
2.2 训练垃圾分类模型的完整命令与超参数设置
下面是我在 Ubuntu 20.04 + PyTorch 1.13 + CUDA 11.6 环境下跑通的训练流程。先准备数据目录结构:
# 数据集目录结构 dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 对应YOLO格式标签 │ └── val/ └── data.yaml # 数据集配置文件data.yaml内容如下,注意nc是类别数,names顺序要和标注时一致:
# data.yaml path: /home/user/dataset train: images/train val: images/val nc: 4 names: ['paper', 'plastic', 'battery', 'kitchen']然后拉取 YOLOv5 代码并开始训练:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # 开始训练,使用yolov5s预训练权重 python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data /home/user/dataset/data.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name trash_v1参数说明:--img 640是输入分辨率,垃圾分类目标通常占画面 1/4 以上,640 够用;--batch 16在 8GB 显存下比较稳,如果爆显存降到 8;--epochs 100配合预训练权重通常能收敛,看mAP@0.5到 0.9 以上就可以停;--hyp用 low 增强配置,因为垃圾分类场景光照变化不会太剧烈,过度增强反而让模型学偏。训练过程中重点看val/box_loss和metrics/mAP_0.5,如果 box_loss 震荡不降,检查标注框有没有越界或宽高为 0 的脏数据。
2.3 把训练好的模型封装成 ROS 推理节点
训练完得到best.pt,接下来要让它和 ROS 通信。我一般用cv_bridge把 ROS 图像话题转成 OpenCV 格式,推理完把检测框和类别通过自定义消息发出去。核心代码如下:
#!/usr/bin/env python3 import rospy import cv2 import torch import numpy as np from sensor_msgs.msg import Image from cv_bridge import CvBridge from trash_robot.msg import Detection, DetectionArray class TrashDetector: def __init__(self): # 加载本地训练好的YOLOv5模型 self.model = torch.hub.load('/home/user/yolov5', 'custom', path='/home/user/yolov5/runs/train/trash_v1/weights/best.pt', source='local') self.model.conf = 0.5 # 置信度阈值 self.model.iou = 0.45 # NMS IoU阈值 self.bridge = CvBridge() self.pub = rospy.Publisher('/trash/detections', DetectionArray, queue_size=10) rospy.Subscriber('/camera/color/image_raw', Image, self.callback) def callback(self, msg): frame = self.bridge.imgmsg_to_cv2(msg, 'bgr8') results = self.model(frame) detections = DetectionArray() for *xyxy, conf, cls in results.xyxy[0]: det = Detection() det.class_name = self.model.names[int(cls)] det.confidence = float(conf) det.x1, det.y1, det.x2, det.y2 = map(int, xyxy) # 计算目标中心点像素坐标,后续用于深度对齐 det.cx = (det.x1 + det.x2) // 2 det.cy = (det.y1 + det.y2) // 2 detections.detections.append(det) self.pub.publish(detections) if __name__ == '__main__': rospy.init_node('trash_detector') TrashDetector() rospy.spin()这段代码的关键点:torch.hub.load的source='local'避免每次联网下载;conf=0.5是垃圾分类的常用阈值,太低会把背景误检成垃圾,太高会漏掉遮挡目标;发布的消息里带了像素中心点cx, cy,后面要和深度图对齐才能算出三维坐标。注意推理时把模型设为eval()模式并加torch.no_grad(),否则显存会慢慢涨上去。
3. MoveIt 机械臂抓取:从坐标变换到抓取姿态生成
3.1 手眼标定:把像素坐标变成机械臂能懂的三维点
YOLOv5 给你的是像素坐标,MoveIt 需要的是机械臂基坐标系下的三维坐标,中间差一个手眼标定。桌面级垃圾分类机器人常用 eye-to-hand 构型,相机固定在支架上俯视工作台。标定用easy_handeye或aruco_ros都行,我习惯用 ArUco 码标定,流程是:打印一个已知尺寸的 ArUco 码固定在机械臂末端,控制机械臂走十几个不同位姿,每次记录末端位姿和相机看到的码位姿,求解 AX=XB。
标定完得到相机到基座的变换矩阵T_base_cam,然后结合深度图把像素点投影到三维空间:
# 像素坐标 + 深度 -> 相机坐标系三维点 def pixel_to_3d(cx, cy, depth, fx, fy, ppx, ppy): # depth是深度图上对应像素的深度值,单位米 z = depth x = (cx - ppx) * z / fx y = (cy - ppy) * z / fy return np.array([x, y, z]) # 再通过标定矩阵转到基坐标系 point_cam = pixel_to_3d(det.cx, det.cy, depth_value, fx, fy, ppx, ppy) point_base = T_base_cam @ np.append(point_cam, 1.0) point_base = point_base[:3]参数说明:fx, fy, ppx, ppy是相机内参,用camera_calibration包标定一次即可;depth_value取检测框中心区域的中值深度,不要取单点,避免深度图噪声导致抓空。这里最常见的翻车是深度图和 RGB 图没对齐,cx, cy直接拿去查深度会偏几十个像素,抓取位置就飞到桌子外面去了。
3.2 用 MoveIt 的 Python 接口做抓取规划
拿到基坐标系下的三维点后,下一步是让机械臂末端移动到目标上方再下降抓取。我用moveit_commander写一个简单的抓取流程:
#!/usr/bin/env python3 import rospy import moveit_commander import geometry_msgs.msg from tf.transformations import quaternion_from_euler rospy.init_node('trash_grasp') moveit_commander.roscpp_initialize([]) arm = moveit_commander.MoveGroupCommander('arm_group') arm.set_planning_time(5.0) # 规划超时5秒 arm.set_num_planning_attempts(10) # 尝试10次 arm.set_max_velocity_scaling_factor(0.3) # 速度降到30%,安全 def grasp_at(x, y, z): # 先到目标上方10cm的预抓取位姿 pre = geometry_msgs.msg.Pose() pre.position.x, pre.position.y, pre.position.z = x, y, z + 0.10 # 末端垂直向下,四元数从欧拉角转 q = quaternion_from_euler(3.14159, 0, 0) pre.orientation.x, pre.orientation.y, pre.orientation.z, pre.orientation.w = q arm.set_pose_target(pre) plan = arm.go(wait=True) arm.stop() arm.clear_pose_targets() if not plan: rospy.logwarn('预抓取规划失败') return False # 再直线下降到抓取位姿 waypoints = [] target = pre target.position.z = z waypoints.append(target) (plan, fraction) = arm.compute_cartesian_path(waypoints, 0.01, 0.0) if fraction < 0.9: rospy.logwarn('笛卡尔路径完成度不足: %.2f' % fraction) return False arm.execute(plan, wait=True) return True逻辑说明:先到上方 10cm 是为了避免末端从侧面撞到物体或桌面;compute_cartesian_path走直线下降,比set_pose_target更可控,0.01是路径分辨率,fraction低于 0.9 说明有碰撞或超出工作空间,直接放弃这次抓取。速度缩放到 0.3 是血泪经验,全速跑一旦规划出错机械臂会猛冲,桌面上的东西全给你扫飞。
3.3 抓取姿态和夹爪控制的关键参数
抓取姿态不是随便给个垂直向下就完事。对于塑料瓶这种圆柱体,夹爪要垂直于瓶身轴线;对于纸杯,从上方抓比从侧面抓稳。我一般根据 YOLO 检测框的宽高比判断物体朝向:宽高比接近 1 的当圆形处理,垂直向下抓;宽高比大于 2 的当长条形,夹爪旋转 90 度从侧面夹。
夹爪控制用GripperCommand动作接口,参数就两个:位置和力度。位置 0 是闭合,0.04 是张开(具体看夹爪型号),力度设 20N 左右,太大捏扁纸杯,太小夹不住。发送前确认夹爪控制器已经加载,rostopic list里能看到/gripper_controller/command再往下走。
4. 避坑与排查:垃圾分类机器人最容易翻车的五个地方
4.1 检测框中心点深度值为 0 或异常
现象:YOLO 检测到了垃圾,但拿中心点去查深度图得到 0 或几米开外的值,机械臂抓空气。原因通常是深度相机对透明塑料瓶和反光表面测距失效,或者 RGB 和深度图时间戳不同步。解决办法:取检测框内深度有效值的中位数而不是单点,对透明物体在下方垫一张白纸增加反射,用message_filters做时间同步。
4.2 MoveIt 规划一直失败提示碰撞
现象:arm.go()返回 False,RViz 里看到机械臂周围有红色碰撞体。原因多半是场景里没加桌面碰撞体,或者加了但尺寸不对,机械臂把自己和桌面判成碰撞。解决办法:用planning_scene接口添加桌面和相机的碰撞体,尺寸比实际大 1-2cm 留余量,然后调set_planning_time到 5 秒以上给规划器更多搜索时间。
4.3 手眼标定矩阵用错方向
现象:标定完抓取位置整体偏移一个固定量,或者机械臂往反方向跑。原因是T_base_cam和T_cam_base搞反了,或者标定时的坐标系定义和实际不一致。解决办法:标定完先拿一个已知位置的物体验证,把标定矩阵求逆再试一次,哪个对得上用哪个。标定板不要放在工作台边缘,放中间减少畸变影响。
4.4 YOLOv5 推理节点和 MoveIt 抢 GPU 或 CPU
现象:检测帧率突然掉到个位数,机械臂规划也变卡。原因是两个节点在同一台机器上跑,YOLO 推理占满 GPU,MoveIt 的规划器是 CPU 密集型的,互相抢资源。解决办法:给 YOLO 节点设CUDA_VISIBLE_DEVICES限定 GPU,MoveIt 节点用taskset绑到特定 CPU 核,或者把检测频率降到 5Hz,机械臂抓取不需要 30Hz 的检测。
4.5 夹爪抓取后物体滑落
现象:明明夹住了,抬起来就掉。原因是夹爪力度不够、夹取点太靠边缘、或者物体表面太滑。解决办法:夹取点选检测框中心而不是边缘,力度从 20N 往上调但别超过 40N,透明瓶子可以贴一小块砂纸或硅胶垫增加摩擦。另外抓取后先抬升 2cm 停顿一下再移动,给夹爪稳定时间。
5. 进阶技巧:用仿真先跑通再上真机,省下反复调试的时间
真机调试最耗时的不是算法,是每次改代码都要担心机械臂撞坏。我的习惯是先在 Gazebo + MoveIt 里把整条链路跑通,再迁移到真机。具体做法:用ros2_control或ros_control加载机械臂的 URDF,在 Gazebo 里放一个虚拟相机和几个垃圾模型,YOLO 节点订阅 Gazebo 相机话题,MoveIt 控制仿真机械臂。仿真里跑通后,真机只需要改相机话题名和标定矩阵,其余代码不动。
验证方法上,我一般分三步:第一步只跑检测,在 RViz 里看检测框和类别对不对;第二步只跑规划,手动给几个目标点看机械臂能不能安全到达;第三步全链路跑,记录每次抓取的成功率和失败原因。成功率低于 80% 就先别演示,回去查是检测漏了还是抓取姿态不对。
| 调试阶段 | 验证内容 | 通过标准 |
|---|---|---|
| 检测单独跑 | 四类垃圾分类准确率 | mAP@0.5 > 0.9 |
| 规划单独跑 | 十个随机点到达 | 成功率 100% |
| 全链路 | 连续抓取 20 次 | 成功率 > 80% |
还有一个技巧是给 YOLO 加一个「二次确认」:检测到目标后,机械臂先移动到目标上方,用腕部相机或同一相机再拍一张,确认目标还在且位置没变再下降抓取。这能过滤掉传送带或人为移动导致的误抓,代价是多花 1-2 秒,但成功率提升明显。
最后说个我自己的教训:别一上来就追求六轴全自由度抓取,先用三轴或四轴把「看到-过去-夹住-放回」跑通,再逐步加自由度。我第一版就是六轴全开,结果光调抓取姿态就花了两周,后来退回三轴一天就跑通了。希望帮到你。
本文还有配套的精品资源,点击获取