简介:一套结合YOLOv5垃圾识别与MoveIt机械臂分类的完整源码及设计文档,面向人工智能、自动化、电子信息等专业的高校学生、教师和毕业设计开发者,解决从视觉检测到机械臂抓取分类的工程落地问题,也适合具备Python/C++基础的学习者进阶。压缩包共356个文件、约188.62MB,核心代码以Python、C++脚本和launch/yaml配置为主,包含14个模型权重、50个启动文件、57个YAML配置、12个STL模型以及URDF/SRDF机械臂描述,另附MSG消息定义、WAV语音提示和RViz/Notebook演示文档,便于按模块检索和二次开发,整体结构清晰。内含设计文档、操作录屏与运行日志,可辅助理解环境配置、节点通信与排错思路。已有97人学习/下载,适合作为毕业设计、课程设计或项目初期演示的完整参考,也可在其基础上替换识别类别或扩展机械臂动作。
1. YOLOv5+MoveIt垃圾分类机器人:一套能直接跑的毕设级方案拆解
做机械臂毕设最怕什么?不是算法难,而是视觉和机械臂两摊事凑不到一个系统里——OpenCV能识别但给不了三维位姿,机械臂能规划但喂进去的目标点老偏,最后答辩卡在“联动”上。这套基于YOLOv5垃圾分类识别与MoveIt机械臂控制的源码包,解决的就是“识别之后怎么把坐标交给机械臂”这条完整链路:检测模型用YOLOv5训练和部署,机械臂用MoveIt做逆解与轨迹规划,中间靠手眼标定和TF坐标变换衔接,还配了设计文档方便照着重现。适合毕业设计、课程设计,以及想把一个桌面级机械臂从“只会动”调到“能干活”的从业者。
2. 系统组成与选型:为什么是YOLOv5和MoveIt这个组合
分类机器人不是“能识别”就够了,关键是识别结果要能变成机械臂能执行的动作。选型时把视觉和运动层拆开看,最容易出问题的地方是两个子系统之间的数据接口——这一章先把选型理由和系统边界讲清楚,后面配置起来才有方向。
2.1 识别层选型:为什么是YOLOv5而不是传统OpenCV方案
垃圾分类场景里,物体类别多、外形差异大,瓶子和纸盒的形状会随摆放姿势变形。传统OpenCV方案靠颜色阈值和轮廓匹配,对光照极其敏感,换个环境就要重新调参数,垃圾类别一多就撑不住。YOLOv5的优势体现在三方面:一是基于锚框的网格化预测,对小目标比较友好;二是模型轻量,YOLOv5s在桌面级GPU上能做到实时推理;三是迁移学习方便,基于COCO预训练权重微调几轮就能收敛,适合毕设这种数据量不大的场景。
实际选型时要注意YOLOv5是“识别”不是“定位”——它输出的是2D边界框和类别,不会直接告诉你物体在机械臂坐标系下的三维坐标。所以架构上要在识别后面接一层坐标变换:深度学习负责粗定位,深度相机或固定平面假设负责深度信息,TF树负责把所有坐标统一到机械臂基座坐标系。这是整套系统最核心的设计,别指望YOLOv5直接给你一个可抓取的xyz值。
2.2 运动层选型:MoveIt对机械臂分类任务的价值
机械臂控制有两条路:自己写逆解公式,或者用MoveIt这类运动规划框架。自写逆解适合固定自由度的教学演示,一旦出现奇异点或障碍物就麻烦;MoveIt把逆解、避障、轨迹规划、碰撞检测都封装好了,规划的是一整条关节轨迹而不是单点。这对垃圾分类很有用——因为从相机位置到垃圾桶上方,机械臂要经过一个中间过渡点,用MoveIt的规划接口可以指定笛卡尔路径和多个waypoint。
MoveIt的另一个价值是仿真和真机共用同一套接口。在Gazebo里验证过的规划代码,换到真机上只需要改一下控制器配置和Move Group的连接方式。源码包里MoveIt相关配置文件,本质是把URDF描述、SRDF定义、控制器yaml这三层关系整理清楚了,拿到后直接看这份关系,比自己在ROS Wiki里翻更高效。
2.3 硬件与通信配置:从机械臂自由度到相机位姿
机械臂分类方案的落地,硬件选型决定一半成败。自由度上5到6轴比较常见,5轴能覆盖水平面上的抓取,6轴多一个末端姿态自由度,抓取时可以从容调节俯仰角。舵机选型用总线舵机(比如串行总线控制的LX-16A类)比PWM舵机好,原因有两个:一是总线舵机可以回读角度,机械臂重复定位精度有保障;二是MoveIt规划的关节轨迹可以逐点映射到舵机角度指令,不需要额外编码器反馈。
相机安装方式直接影响后续标定复杂度。固定Eye-to-Hand(相机装在机械臂外部)适合桌面级垃圾分类,因为视野覆盖整个工作台,不用每次抓取前都把机械臂移开“让视线”;Eye-in-Hand(相机装在机械臂末端)适合目标位置不固定、需要近距离检视的场景,但标定要跟着臂动变换,实现成本高。桌面分类场景我更推荐Eye-to-Hand,标定一次只求一个固定的变换矩阵,稳。
下面给一张我拆这套源码时整理的系统选型与对应配置文件表,拿到手可以按表核对:
| 组件 | 推荐选型 | 作用 | 源码中对应文件 |
|---|---|---|---|
| 主控/系统 | Ubuntu 18.04/20.04 + ROS Melodic/Noetic | 运行MoveIt、控制机械臂 | package.xml、launch目录 |
| 视觉推理 | YOLOv5s/YOLOv5m + PyTorch | 检测垃圾类别与边界框 | detect/video/文件、权重文件 |
| 深度/坐标获取 | RGB-D相机(如低成本深度相机) | 提供深度、计算物体三维坐标 | 视觉节点、相机驱动launch |
| 机械臂 | 5/6自由度桌面机械臂(总线舵机) | 执行抓取与投放 | URDF/SRDF、controller.yaml |
| 运动规划 | MoveIt + OMPL | 逆解、避障、轨迹规划 | moveit_config包 |
这里要插一个重要提醒:很多人把YOLOv5装在Windows上训练,然后把模型丢到ROS机器上推理,结果发现PyTorch版本不一致,模型加载就报错。常见做法是训练和推理保持同一套环境,或者导出ONNX在推理端加载。这条我在后面踩坑章还会专门展开。
3. 环境搭建与YOLOv5识别:从依赖安装到训练自己数据集的完整步骤
这一章的落地目标是让YOLOv5在当前机器上真正跑起来,并用垃圾分类数据集完成一次训练和推理。整个过程分四段:环境、数据、训练、推理输出。源码包里YOLOv5训练相关的代码和文档是现成的,但环境依赖和超参这东西机器之间差异大,还是得自己过一遍。
3.1 环境搭建:conda + PyTorch + YOLOv5依赖的版本组合
先建一个独立的conda环境,不要直接装在系统Python里,否则后面装ROS、装别的视觉库会把依赖搞乱。我一般按下面的步骤来:
conda create -n yolo python=3.8 -y conda activate yolo pip install torch==1.8.0 torchvision==0.9.0 --extra-index-url https://download.pytorch.org/whl/cu111 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这段里Python 3.8是为了兼容ROS Melodic/Noetic自带的Python 2/3版本,避免后面视觉节点和ROS节点分属两套Python。torch版本选1.8.0是考虑到和CUDA 11.1配对比较稳定,如果你机器CUDA版本不是11.1,根据nvidia-smi显示的CUDA版本去PyTorch官网选对应轮子就行。requirements.txt里实际会装numpy、opencv、matplotlib这些视觉基础库,也会装pyyaml用于读配置文件。
装完跑一句python -c "import torch; print(torch.cuda.is_available())",返回True就说明GPU可用。不要在这步省时间——后面训练一个垃圾分类数据集,CPU跑几百轮是煎熬,GPU能把时间缩短十几倍。
3.2 准备垃圾分类数据集:目录结构、标注与YOLO格式转换
垃圾分类数据集不像COCO那种公开集规整,很多是自己拍的、网上下载的,还有直接从开源数据集转过来的。YOLOv5要求的数据格式是:每张图片对应一个同名txt,txt里每行是“类别id x_center y_center width height”,坐标都是相对图片宽高的归一化值。目录结构一般是这样:
datasets/ garbage/ images/ train/ # 训练图片 val/ # 验证图片 labels/ train/ # 对应标注txt val/如果你手里拿到的是VOC格式的XML标注,需要转换一下。我习惯写个小脚本:
import os, xml.etree.ElementTree as ET # 类别映射,按自己的分类修改 classes = ['bottle', 'paper', 'metal_can', 'plastic_bag'] def convert_voc(xml_file, out_dir): tree = ET.parse(xml_file) root = tree.getroot() w = int(root.find('size/width').text) h = int(root.find('size/height').text) out = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in classes: continue cls_id = classes.index(cls) box = obj.find('bndbox') x1 = float(box.find('xmin').text) y1 = float(box.find('ymin').text) x2 = float(box.find('xmax').text) y2 = float(box.find('ymax').text) # 转YOLO归一化格式 cx = ((x1 + x2) / 2) / w cy = ((y1 + y2) / 2) / h bw = (x2 - x1) / w bh = (y2 - y1) / h out.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") base = os.path.basename(xml_file).replace('.xml', '') with open(os.path.join(out_dir, base + '.txt'), 'w') as f: f.write('\n'.join(out)) # 遍历所有xml执行转换,out_dir换成你的labels/train目录这段脚本的核心逻辑是从XML里读原始框坐标,换算成中心点加宽高的归一化形式。注意类别顺序一旦定了就别改,训练、推理、后续分类投放逻辑都要用同一份映射。我在项目文档里看到数据集大概分四到五类,你把classes按自己的实际类别改就行,脚本不需要动。
3.3 训练命令与超参数:从预训练权重到自定义类别
一切就绪后开始训练。分类任务用已经有COCO预训练权重的YOLOv5s起步,比从零训练收敛快得多,而且对小数据集友好:
python train.py \ --data garbage.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640 \ --device 0 \ --workers 4garbage.yaml里面写了训练和验证图片的路径、类别数和类别名。--epochs 100在B最多时我会先设50看趋势,Loss曲线还抖动就加到100或150;--batch-size视显存而定,8GB显存跑16是可以接受的,6GB就降到8;--img 640保持默认,垃圾分类目标不算特别小,640的输入尺寸在速度和精度之间比较均衡。
YOLOv5的超参数写在data/hyps/hyp.scratch-low.yaml里,对自定义数据集影响较大的几个:lr0: 0.01是初始学习率,数据量小时如果损失震荡可以降到0.005;mosaic: 1.0开启马赛克数据增强,对小物体有效,但如果你的标注框特别密集,mosaic容易把样本裁剪得碎片化,建议设为0.5试点;mixup: 0.0默认关闭,角度分类这类任务开mixup反而会让模型学到错误的纹理混合。这种超参调完一轮记一下,别靠记忆,后面你会反复回到同一个问题。
3.4 识别结果输出:单帧推理与连续视频流的过滤
训练完看runs/train/exp*/weights/best.pt,这是验证集上表现最好的权重。拿它跑单帧的推理:
import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/exp/weights/best.pt') results = model('test.jpg') # 输出检测结果和坐标 results.show() print(results.pandas().xyxy[0])如果你是要接机械臂的实时识别,不能直接对每一帧的results做抓取——单帧识别框抖动会直接导致机械臂位置偏差。常见做法是在连续帧里做一个置信度筛选,比如要求同一类别连续5帧都被检出且中心点偏移小于阈值,才认为这是一个稳定目标。这部分在源码的视觉节点里应该有体现,但你要学会看这层逻辑,不要直接裸用单帧结果驱动机械臂。
4. MoveIt机械臂配置与抓取规划:从URDF到可执行Trajectory
识别层能稳定输出类别和像素坐标后,任务就进入机械臂侧:给定一个目标点(比如“桌面上坐标x y z处的塑料瓶”),MoveIt负责算出到达该点的关节角度序列并生成安全轨迹。这一章教你怎么读懂源码里的MoveIt配置,并在Gazebo仿真里跑通。
4.1 从URDF到MoveIt配置:Setup Assistant是关键步骤
MoveIt一切配置的基础是URDF,即机械臂的模型描述文件,里面定义了连杆长度、关节类型和限位等。源码包里会提供一个URDF文件,对应某款常见的桌面机械臂(比如5自由度的舵机机械臂)。如果用户要把源码里的机械臂换成自己的型号,就得重新走一遍MoveIt Setup Assistant,把原始URDF导入,生成一个moveit_config包。
Setup Assistant里几个关键步骤:一是定义机械臂的规划组(planning group),一般把从基座到末端的连续关节设为一个group,比如叫“arm”,夹爪单独设一个group叫“gripper”;二是设置自碰撞矩阵,自动生成的碰撞矩阵默认保守,真实机械臂如果碰撞模型和URDF几何轮廓差异大,规划时会莫名失败;三是设置末端执行器,把它关联到arm组上。这套生成的config包里,我们需要关注的是config/目录下的joint_limits.yaml和srdf内容,它们直接决定MoveIt规划器对关节空间的理解。
4.2 规划组与运动规划:OMPL与IKFast对抓取任务的选择
MoveIt里的运动规划器插件一般默认用OMPL(开放式运动规划库),它里面又分包络多种算法。对桌面级机械臂,我更常用OMPL的RRTConnect(快速扩展随机树的一种改进版),因为它对高维空间和窄通道场景更友好,规划速度快。如果目标点位姿要求非常精确,可以考虑用IKFast——它需要预先用机械臂的URDF生成逆解求解器,然后MoveIt直接调用解析逆解,速度远快于数值迭代。代价是生成IKFast的配置工程,对新手有点玄学,容易在编译环境上卡住;但它在重复规划时的稳定性值得投入。
规划组的参数决定了MoveIt搜索的空间维度。比如只把肩关节、肘关节、腕关节这些旋转轴纳入arm组,那些固定不动的被动关节不要加进去,否则规划器的搜索空间瞬间膨胀,轨迹计算时间成倍增长,甚至出现规划失败。你会在源码的MoveIt配置里看到“planning_group”和“joint_model_group”名的设置,动手改自己机械臂时,这两个术语要对得上。
4.3 仿真联调:Rviz+Gazebo怎么配合
MoveIt配置完成后,通常有两个启动入口:一个是demo.launch,只在Rviz里显示机械臂模型,可以手动拖拽目标位姿来调试规划,但机械臂不会真动;另一个是gazebo.launch,加载Gazebo物理仿真环境,MoveIt规划出的轨迹会通过ros_control控制器驱动仿真机械臂摆动。调试的路径是先demo.launch里检查预期运动轨迹,确认无误再放到Gazebo里跑整体流程。
这里有一个仿真联调时让我反复折腾的点:MoveIt发布的/move_group/goal是一条关节角度轨迹,但Gazebo的控制器接收的是关节速度/位置指令,两者之间需要bridge。源码里应该有controller.yaml和ros_control.launch,对照看一下controller配置的joints是否和URDF里的joint名称完全一致——差一个字母,机械臂就像一具僵尸,Gazebo里完全没反应。
我调试Gazebo时习惯先启动机械臂的关节状态发布器,再启动MoveIt节点,最后让机械臂回零位。如果手臂启动后抽动或漂移,多半是初始关节角度和URDF默认值对不上,在launch里加一个初始化机器人状态起止位置设置的参数就行。
4.4 MoveIt Commander抓取:从目标位姿到夹爪动作
真正控制机械臂执行抓取,我一般用MoveIt Commander的Python接口,代码量和理解成本都最低。下面这段是抓取流程的骨架:
from moveit_commander import RobotCommander, PlanningSceneInterface, MoveGroupCommander # 初始化 robot = RobotCommander() scene = PlanningSceneInterface() arm = MoveGroupCommander("arm") # 对应Setup Assistant里配置的规划组名 gripper = MoveGroupCommander("gripper") # 目标位姿:位置由视觉识别+坐标变换得到 target_pose = { 'position': (0.3, 0.1, 0.15), 'orientation': (0.0, 0.0, 0.0, 1.0) # 四元数,保持末端水平 } # 先运动到目标点上方安全高度 arm.set_pose_target(target_pose) plan_success, plan, t, err = arm.plan() if plan_success: arm.execute(plan, wait=True) # 下降抓取 target_grasp = target_pose.copy() target_grasp['position'] = (0.3, 0.1, 0.05) arm.set_pose_target(target_grasp) arm.go(wait=True) # 闭合夹爪 gripper.set_named_target("close") gripper.go(wait=True)这里几个参数容易踩坑:MoveGroupCommander("arm")的字符串要和Setup Assistant里的Planning Group名字一致,不一致直接报找不到组。set_pose_target给的位姿是相对于机械臂基座坐标系的,所以视觉识别的像素坐标必须先经过坐标变换,直接填像素坐标等于乱指。execute之后建议加一个arm.stop()并清除目标位姿,防止下一次规划时目标被上次残留位姿影响。
5. 避坑:视觉-机械臂联调最常见的五个翻车点
把识别和运动两层接通,是从“都能跑”到“系统能用”最难的一段路。很多现象看起来是算法问题,实际是坐标变换或配置问题。以下是我拆这类毕设源码时最常遇到的五个坑,每一条都是“现象→原因→解决”的逻辑。
5.1 现象:MoveIt规划一直失败,提示“NO IK solution”或“Trajectory not valid”
最常见的原因是机械臂可达到的工作空间和目标点不匹配——视觉算出来的目标位置在机械臂物理够不到的范围之外,比如目标点太靠近机械臂基座,或者太高超出关节限位。另一个常见原因是末端姿态约束得太死,目标点的姿态如果让末端执行器的朝向和几何环境冲突,也容易找不到解。解决方法是先检查目标点在TF上的位置是否正确,再看URDF定义的关节限位是否被误填了太小,最后把set_pose_target改成set_joint_value_target,用关节空间直接指定目标姿态,绕开IK求解的约束。
5.2 现象:YOLOv5识别结果时有时无,边界框位置抖动剧烈,机械臂抓取时物体抓偏
单帧检测器天然不稳定,哪怕置信度调到0.45,连续帧里同一物体的中心点也可能有几十像素的偏移。尤其是桌面反光强烈的场景,塑料瓶高光区会让边界框左右摇摆。解决办法分两层:软件层加一个低通滤波,对连续N帧的检测中心做EMA平滑,再判断目标是否稳定;硬件层改用更稳定的光源,或者在相机前加偏振片去掉高光。源码里如果直接拿识别框底边中心当抓取点,那大概率会在反光场景抓偏。
5.3 现象:机械臂能规划到位,但抓下去离物体实际位置偏了几厘米
这一步十有八九来自深度不准或手眼标定矩阵不准。如果用的是单目相机加固定平面假设,那么物体高度一有变化,Z方向的误差就按比例放大;RGB-D相机则会有边缘深度孔洞,物体表面反光区域深度缺失。解决:深度值不要取边界框中心单像素的深度,而应该取中心周围一块区域(比如5×5像素窗口)的均值深度,同时剔掉深度为0或远超阈值的数据。手眼标定的问题放在下一条,它的错误同样会导致这种偏差。
5.4 现象:坐标变换代入正确,TF树也通了,但机械臂指向仍然“左右镜像”
这是手眼标定矩阵符号、坐标轴方向和相机安装朝向三者组合出错。最常见的错位发生在Eye-to-Hand标定时,标定得到的4×4变换矩阵是“相机坐标系到机械臂基座坐标系”的,而编码时把它用反了。没有标定板的时候,有些人会拿机械臂末端的已知点去凑标定矩阵,这时很容易把x/y方向弄反。排查手段很直观:在Rviz里加载TF树,把相机原点显示出来,如果相机原点出现在机械臂基座坐标系里的错误位置,那说明标定矩阵需要转置或补一个旋转角。对着Rviz调比对着坐标纸调快得多。
5.5 现象:仿真一切正常,接真机机械臂就“抽筋”或狂抖
Gazebo仿真里的控制器是理想化的,真机舵机有响应延迟和回读误差。MoveIt规划的轨迹默认是按平滑速度插值的,但一本期刊的舵机角度速度响应可能跟不上。解决思路:给MoveIt规划的轨迹加上时间缩放——把max_velocity_scaling_factor设到0.5以下,“max_acceleration_scaling_factor”同理。真机上总线舵机每帧更新的频率如果不高于MoveIt发布轨迹的频率,就会产生指令堆积,表现为关节不连贯抖动。那之后我调真机强制把速度和加速度缩放设到0.3~0.5,再一级级往上加,不再直接跑满。
6. 进阶:从单类抓取到多类分拣的验证技巧
源码里如果只做了一个简单目标的抓取,那还只是“演示”,到系统的边界还有很多空间可以延展。这里给出两个可以直接上手的验证和优化方向。
6.1 自动化验证:先在仿真里统计成功率再上真机
在Gazebo仿真里跑100次抓取,统计成功率,用数据说话。自动化验证的脚本思路是:在场景中随机生成若干垃圾模型(瓶、罐、盒)的位置,调用相同的视觉识别加MoveIt规划链路去抓取,每次记录“识别成功/规划成功/抓取成功”三个布尔值,最后输出统计表。100次试验跑完,能直观暴露故障瓶颈:如果规划成功率不到六成,问题大概率在目标点位姿生成;如果规划全成、抓取成功率低,那就是夹爪设计和物体形状的物理问题,而不是代码问题。这套脚本在Gazebo里跑,机械臂不会坏,哪怕机械臂把物体碰飞或卡住,重置一下仿真场景即可重来。
6.2 多类分拣的优先级队列与动态避障
从单类到多类分类,关键不是模型多加几个类别,而是任务调度。多类垃圾出现时,机械臂不能见到哪个抓哪个——如果抓了占用机械臂移动路径的物体,可能造成碰撞。我一般会在上游加一个“优先级队列”:按“瓶子>金属罐>纸盒”这类顺序调度,每次只识别视野内优先级最高的一类去抓取,其余类别等待。MoveIt侧利用PlanningSceneInterface把已经抓取的垃圾在场景中标记为移除,避免规划时把它当作障碍物;同时把垃圾桶的位置作为固定的“投放目标”,用set_pose_target去投放位置时把垃圾桶外形加入碰撞场景,机械臂规划时会自动绕开。
上面这个做法,本质是“视觉识别→二维坐标+深度→三维位姿→MoveIt规划避障→执行抓取”的完整闭环。需要时刻记住,MoveIt对目标点的理解是刚性的,任何坐标误差都会被直接放大到抓手位置偏差,因此所有测试都要建立在一套可以反复执行的验证流程上。从那以后我每次拿到任何一个机械臂毕业设计源码,都强制自己先走一遍“识别稳定性测试→坐标变换验证→仿真抓取统计→真机小步调参”这条流水线,不跳过任何一步。希望帮到你。
本文还有配套的精品资源,点击获取