
1. 为什么我决定从零手写一套机械臂yolov5抓取仿真如果你不是科班学机器人出身只是被机械臂抓取这个方向的成果视频吸引过来估计和我当初一样一上来就被一堆概念砸晕目标检测、坐标变换、逆运动学、ROS 2、Gazebo、MoveIt……网上资料确实多但大多只讲某一块比如教你怎么训练yolov5或者教你怎么让机械臂在Gazebo里动起来很少有人把识别到抓取这条完整链路串起来。更别说不同版本的ROS、不同型号的机械臂、不同版本的Gazebo互相组合还各有各的坑。所以我把自己的实践过程拆成一个系列博客第一篇先解决地基问题环境怎么搭、机械臂怎么在Gazebo里跑起来、相机怎么出图、yolov5怎么训练自己的数据集。整套流程跑通之后下一篇再讲坐标变换和抓取闭环。这篇内容适配三类人第一正在做毕业设计或者准备竞赛需要快速搭出一个完整的视觉抓取Demo第二对机器视觉和机器人控制都有兴趣但一直不知道如何把它们串起来的新手第三准备上真机但不想一上来就用几万块的机械臂试错的老哥——先在仿真里把逻辑调通能省太多钱。先说清楚一件事我不会写那种一行一行贴代码、你照着敲就行的保姆级教程因为设备和环境不同照抄容易翻车。我会把每一步背后的为什么讲清楚你理解了之后换一个型号的机械臂或者换一个目标物也能自己改。1.1 这个项目的核心价值在哪里机械臂抓取是一个典型的感知-决策-控制闭环问题。感知看的是图像里的物体在哪里决策决定机械臂应该怎么移动控制让机械臂实际执行。市面上很多开源项目只做了其中一部分比如有人只训练了yolov5识别苹果但识别完之后怎么让机械臂过去拿没有下文。有人只做了机械臂的正逆运动学仿真但目标物体的位置是手动输入的根本没有视觉参与。你要真正学会抓取就必须把感知和运动控制打通而这正是这个系列要解决的。在Gazebo里做仿真还有一个好处能够无限重复实验。你在仿真中把物体位置随机摆放几千次用算法去测试抓取成功率这在真机上基本不可能实现。光照条件可以任意调节相机参数也可以改成本几乎为零。更重要的是仿真里出了问题最多就是机械臂穿模不会撞坏设备对新手极其友好。1.2 动手之前你必须懂的几个基础点说实话如果完全零基础直接把下面这些环境全装好会有点痛苦。但我建议你至少先掌握三件事Linux命令行基本操作。因为ROS 2和Gazebo主要跑在Ubuntu上你需要熟悉cd、ls、source、export这些命令以及了解环境变量是什么。Python基础语法。yolov5是用Python写的后续写ROS节点、做推理也都要用Python不需要很深入能看懂类、函数、字典就够了。ROS 2的核心概念不用全背下来但要理解节点node、话题topic、消息message三者之间的关系节点之间靠话题通信一个发布者往话题上发数据订阅者从话题上收数据。如果这些基础还没有建议先花一周补一补否则后面踩坑排查时会非常吃力。心里有底之后我们开始搭架构。2. 整个抓取系统的架构拆解从相机像素到机械臂末端很多新手一上来就急着装环境、跑代码结果装了三天什么都没跑通因为根本不知道每一步是在干什么。所以我先把这个系统的全局逻辑讲透你有了地图再进去探索就不容易迷路。2.1 一条完整的数据流长什么样假设我们的任务是用一个安装在机械臂末端的相机拍摄桌面上一个方块然后控制机械臂去抓起这个方块。整个系统大概分成五个环节相机拍摄场景输出一张RGB图像。YOLOv5模型对图像做目标检测输出方块的包围框bounding box我们取包围框中心的像素坐标(u, v)。结合相机内参和深度信息或者先假设物体在某个平面上把像素坐标转换成相机坐标系下的三维坐标(x, y, z)。通过TF2坐标变换把相机坐标系下的坐标转换到机械臂基座坐标系下得到机械臂需要到达的目标位置。运动规划器求解逆运动学生成一组关节角度轨迹发送给Gazebo中的控制器机械臂执行抓取。这个过程可以类比成你伸手拿水杯眼睛看到杯子这是相机目标检测大脑估算杯子的位置这是坐标变换大脑指挥手臂伸过去这是运动规划手臂肌肉驱动关节动起来这是控制器。2.2 机械臂运动规划与逆解在仿真里的角色机械臂的正运动学是给定六个关节的角度求末端执行器在三维空间的位置和姿态。逆运动学反过来给定末端执行器的目标位姿求六个关节的角度。Gazebo本身不负责求逆解它只负责物理仿真。我们用MoveIt2来做运动规划它内部调用运动学插件比如KDL来求解逆运动学并做避障规划。规划出的一条合法轨迹会通过ros2_control框架发给Gazebo里的JointTrajectoryController控制器再把位置指令转成力矩驱动模型转动。所以如果你看到机械臂在Gazebo里飘来飘去或者乱抖大概率是控制器的PID参数没调好或者是关节的阻尼系数太小导致机械臂在重力作用下产生了振荡。2.3 Gazebo适合做什么、不适合做什么Gazebo是一个开源的多机器人仿真环境内置物理引擎ODE/Bullet等可以模拟重力、摩擦、碰撞、传感器噪声。它最大的优势是跟ROS生态无缝衔接传感器数据可以以ROS消息的形式直接发布出来运动控制也可以直接通过ros2_control接口操作。但Gazebo并不是万能的。它的物理引擎对柔性物体、流体、复杂接触的模拟精度远不如专业商业软件。研究机械臂柔性关节、模仿学习这类方向Gazebo可能不够精确但做视觉抓取、路径规划、多传感器融合它足够用了。还要提一个词仿真发散。这是新手很容易遇到的——仿真跑着跑着机器人零件突然飞出去或者画面剧烈抖动。原因往往是关节力矩过大、更新频率过低、碰撞几何设置不合理。后面我会专门讲怎么排查。3. 环境搭建踩坑记录Ubuntu ROS 2 Gazebo yolov5这一章是整个系列里最劝退的一章也是最值得记录的一章。我前后折腾了两周才把环境完全理顺很多问题网上搜不到直接的答案只能自己看日志排查。这里把关键步骤和踩过的坑写清楚你照着做能少走一半弯路。3.1 版本组合怎么选先说结论用Ubuntu 22.04 ROS 2 Humble Gazebo 11这是当前最成熟稳定的组合。虽然现在有Ubuntu 24.04 ROS 2 Jazzy Gazebo Harmonic这种新组合很多AI生成的教程也会写但实际用下来新组合的软件包还不是特别齐全遇到问题能搜到的解决方案也少。新手不要追新稳定压倒一切。安装ROS 2 Humble时我建议直接装完整桌面版省得之后缺这个缺那个sudo apt update sudo apt install ros-humble-desktop python3-argcomplete装完后记得source环境echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc接着装Gazebo。Ubuntu 22.04的软件源里自带Gazebo 11直接sudo apt install gazebo11但要注意ROS 2和Gazebo之间通讯需要的桥梁包名字是gazebo_ros_pkgs不带它是连不上的sudo apt install ros-humble-gazebo-ros-pkgs这里有个容易忽略的点ros-humble-gazebo-ros-pkgs会同时装上gazebo_ros、gazebo_msgs、gazebo_ros2_control等一批依赖包后面做控制器都要用。如果你想加载机械臂模型通常还需要对应的描述包比如sudo apt install ros-humble-ur-description ros-humble-ur-gazebo或者Panda机械臂相关的franka_description包。不同机械臂的依赖不一样记得按需安装。3.2 Gazebo界面一直在闪的排查过程这个坑必须单独拉出来讲因为太常见了。现象就是gazebo启动之后主窗口的渲染画面不停闪烁、黑屏、或者卡成PPT。网上有人让重装驱动、有人让换显卡但很多情况下问题没这么复杂。我当时用的是一台配置还不错的笔记本Ubuntu装在虚拟机里启动gazebo就闪。排查过程如下第一步看日志有没有报错。在终端启动gazebo时留意有没有OpenGL相关的警告。如果出现类似Failed to create OpenGL context之类基本就是渲染环境的问题。第二步确认是不是虚拟机的3D加速没开。VMware里检查虚拟机设置-显示器-加速3D图形是否勾选VirtualBox里同样要开启3D加速。我当时就是没开开启了之后闪烁明显减少但偶尔还是会闪。第三步如果还是闪可以尝试强制使用软件渲染export LIBGL_ALWAYS_SOFTWARE1 gazebo这个办法能强制不依赖显卡用CPU渲染画面稳定但帧率会很低拖动视角时明显卡顿。适合临时验证不适合长时间仿真。第四步如果你的机器是双显卡比如笔记本有核显和独显可以试试在启动前指定用独显运行命令里加上export __GLX_VENDOR_LIBRARY_NAMEnvidia export __NV_PRIME_RENDER_OFFLOAD1这个方法在支持NVIDIA的驱动下有效但不一定通用。说到底这类问题真的没什么万能药我的建议是从最简单的检查3D加速开始一步步排除不要一上来就重装驱动。3.3 yolov5的Python环境配置yolov5对Python和PyTorch有一套依赖要求。我强烈建议不要直接用系统的Python而是用conda建一个独立环境这样以后装其他深度学习库时不会互相污染。conda create -n grasp python3.9 -y conda activate grasp然后安装PyTorch。如果你没有NVIDIA GPU或者只是想先跑通流程直接用CPU版最省事pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu如果有GPU去PyTorch官网选择对应CUDA版本的安装命令。克隆yolov5仓库并安装依赖git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里再提醒一个坑yolov5的requirements.txt会安装一堆依赖包括opencv-python、pandas、matplotlib这些。如果你已经在系统里装过ROS相关的cv_bridge它带的是OpenCV的版本可能与pip安装的OpenCV冲突。解决办法是先激活你的conda环境在conda环境里跑yolov5不要混用系统Python。装好之后从官网下载yolov5s.pt这个预训练权重先跑一个官方的推理demo验证环境python detect.py --weights yolov5s.pt --source data/images/bus.jpg如果能在runs/detect/exp下看到标了检测框的图片说明yolov5环境OK。4. 让机械臂在Gazebo里动起来模型导入与控制器配置环境搭好之后最激动人心的一步就是看到机械臂在仿真环境里真正动起来。这一步涉及的东西挺多URDF模型、ros2_control控制器、Gazebo插件。我们一步步来。4.1 选择哪款机械臂热词里经常能看到panda机械臂gazebo仿真和ur10机械臂可以通过ros控制吗这类搜索。市面上流行的机械臂模型很多但对新手来说我建议从UR5e或者Franka Panda里选一个。UR5e优傲的资料非常丰富ur_description和ur_gazebo是官方维护的ROS 2包安装很简单Gazebo模型和控制器配置都是现成的特别适合第一次接触机械臂仿真的新手。Franka Panda机械臂精度高MoveIt2社区也提供了panda_moveit_config但它的控制模式更复杂在ROS 2环境下的配置比UR5e稍微麻烦一点。如果追求开箱即用UR5e是更好的起点。我自己用的是UR5e全程没怎么改模型直接加载官方xacro文件就能用。下面就以UR5e为例讲怎么让它在Gazebo里动起来。4.2 在Gazebo中加载机械臂模型如果把ur_description和ur_gazebo装好了启动其实很简单ros2 launch ur_gazebo ur5e_bringup.launch.py这个launch文件会做几件事启动Gazebo空世界、把UR5e的xacro模型加载到机器人状态发布器robot_state_publisher、加载ros2_control的ControllerManager。如果你用的是自定义机械臂那就要自己写URDF包含每个link的惯性参数、碰撞属性、关节的transmission以及gazebo插件。这一步很容易漏的是惯性参数——如果质量或惯量不对机械臂一启动就会被重力拽塌。在加载模型前需要创建控制器配置文件。常见路径是config/controllers.yamlcontroller_manager: ros__parameters: update_rate: 100 joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcaster joint_trajectory_controller: type: joint_trajectory_controller/JointTrajectoryController joint_trajectory_controller: ros__parameters: joints: - shoulder_pan_joint - shoulder_lift_joint - elbow_joint - wrist_1_joint - wrist_2_joint - wrist_3_joint command_interfaces: - position state_interfaces: - position这里定义了两个控制器joint_state_broadcaster负责发布每个关节的状态joint_trajectory_controller可以接收目标轨迹。注意joints列表要跟URDF里关节的名字完全一致否则控制器加载时会提示找不到关节。然后在launch文件里加载config文件并启动controller_spawner。这些细节在官方包里都已经写好了你只需要确保自己的机械臂URDF里也定义了对应的关节名即可。4.3 启动控制器并手动测试关节运动如果你用的是官方UR5e包启动后控制器通常会自动加载。可以用下面的命令查看控制器列表ros2 control list_controllers如果状态显示inactive需要手动激活ros2 control set_controller_state active joint_trajectory_controller然后你可以通过rqt的插件直观地控制机械臂关节ros2 run rqt_joint_trajectory_controller rqt_joint_trajectory_controller点开插件拖动滑块会看到一个关节一个关节地转起来。我最开始试的时候机械臂的肘关节动了一下就开始剧烈抖动后来检查发现是因为控制器的update_rate设置太低导致关节跟踪跟不上把update_rate调到200之后就好了。还有一个比较实用的测试方式用命令行直接发一个目标关节位置ros2 topic pub /joint_trajectory_controller/joint_trajectory trajectory_msgs/msg/JointTrajectory {joint_names: [shoulder_lift_joint], points: [{positions: [-0.5], time_from_start: {sec: 2}}]} --once这会让shoulder_lift_joint在2秒内转到-0.5弧度。如果机械臂顺利执行说明你的控制链路已经通了。5. 相机仿真与图像获取为yolov5准备眼睛机械臂能动之后接下来就需要让机器人看见世界。这一步是把视觉和运动控制连接到一起的关键但又往往容易被忽视。很多人训练好了yolov5模型却不知道仿真相机怎么把图片送进模型推理结果卡在中间接口上。5.1 在URDF里添加相机传感器我采用的是让相机固定在机械臂末端的方式这样相机视角会随着机械臂运动后续做手眼标定时也更灵活。在UR5e的URDF里加一个自定义link和sensorlink namecamera_link inertial mass value0.1/ inertia ixx1e-5 iyy1e-5 izz1e-5 ixy0 ixz0 iyz0/ /inertial visual geometrybox size0.03 0.03 0.03//geometry /visual collision geometrybox size0.03 0.03 0.03//geometry /collision /link joint namecamera_joint typefixed parent linkwrist_3_link/ child linkcamera_link/ origin xyz0 0 0.1 rpy0 0 0/ /joint然后在URDF的尾部添加Gazebo相机插件gazebo referencecamera_link sensor namecamera typecamera update_rate30/update_rate camera image_width640/image_width image_height480/image_height horizontal_fov1.047/horizontal_fov clip near0.05/near far100/far /clip /camera plugin namecamera_driver filenamelibgazebo_ros_camera.so ros namespace/camera/namespace /ros image_topicimage_raw/image_topic camera_info_topiccamera_info/camera_info_topic /plugin /sensor /gazebo这里面有几个参数要注意update_rate决定相机帧率不是越高越好30就够image_width和image_height决定图像分辨率640x480对yolov5来说足够了更大会更慢。horizontal_fov是水平视场角单位是弧度约60度就是1.047。5.2 通过ROS话题获取实时图像启动机械臂和相机之后可以先用ros2 topic list看看多了哪些话题正常情况下应该能看到/camera/image_raw/camera/camera_info用rqt_image_view可以可视化图像ros2 run rqt_image_view rqt_image_view在弹出的窗口里选择/camera/image_raw话题就能看到相机视角下的画面。如果你把物体放在机械臂前面画面里应该能看到物体。从ROS消息中取图像需要在Python节点里做转换。推荐用cv_bridgefrom sensor_msgs.msg import Image from cv_bridge import CvBridge bridge CvBridge() def image_callback(msg): cv_image bridge.imgmsg_to_cv2(msg, desired_encodingbgr8) # cv_image就是numpy数组可以直接输入yolov5做推理这里有个小坑cv_bridge在ROS 2 Humble下默认输出的图像编码是rgb8但opencv用的是BGR。所以desired_encoding要指定bgr8否则颜色会错乱导致检测效果变差。5.3 仿真相机参数与真实相机的一致性很多新手低估了相机内参对齐的重要性。你用仿真图像训练了一个yolov5模型结果放到真机上检测率大跌原因之一可能是仿真相机的视场角、分辨率、畸变参数跟你的真实相机差别太大。在Gazebo里你可以通过设置 标签里的参数来控制内参比如设置horizontal_fov就相当于确定了水平焦距与图像宽度的关系。如果要更精确可以在graphic里指定distortion。仿真里有一个好处你能拿到确切的内参矩阵写代码时不需要额外做标定。但我建议你从一开始就把仿真相机的分辨率、FOV设置成和实际用的相机一致尽量减小仿真到真机的迁移成本。如果后续要加深度相机Gazebo还支持gazebo_ros_depth_camera插件可以直接发布深度图点云用RGB-D相机做抓取会更鲁棒。6. yolov5训练自己的数据集从采集到部署yolov5的预训练权重是在COCO数据集上训练的里面没有你场景里的目标。要识别你桌面上的方块、螺丝刀或者苹果必须自己准备数据集训练一个检测模型。6.1 从Gazebo批量采集图像刚才相机已经出图了现在就差拍照片。自己在Gazebo里控制机械臂移动视角、移动物体然后用Python脚本订阅/image_raw话题保存图片这个过程可以自动化。我写了一个最简单的采集节点代码如下import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from cv_bridge import CvBridge import cv2 class ImageCollector(Node): def __init__(self): super().__init__(image_collector) self.sub self.create_subscription(Image, /camera/image_raw, self.callback, 10) self.bridge CvBridge() self.count 0 def callback(self, msg): img self.bridge.imgmsg_to_cv2(msg, desired_encodingbgr8) cv2.imwrite(fframes/frame_{self.count:04d}.png, img) self.count 1 self.get_logger().info(fsaved {self.count}) def main(): rclpy.init() node ImageCollector() rclpy.spin(node)运行前先在目录下创建frames文件夹然后在Gazebo里变换物体位置或者启动脚本后缓慢移动机械臂末端让相机从不同角度拍摄目标。对于一份合格的检测数据集一个很重要的原则是多样性。千万不要只在固定位置、固定光照下拍几十张。我在仿真里调节了太阳光角度、物体的随机位置和摆放姿态一共采了1000张图像其中800张用于训练200张用于验证。6.2 标注与数据集格式标注工具我推荐labelImg虽然界面朴素但生成的是YOLO格式的txt文件可以直接用。注意标注时把类别名统一比如我想识别一个红色立方体就标记为cube。最终数据集目录结构如下datasets/ images/ train/ frame_0000.jpg ... val/ frame_0800.jpg ... labels/ train/ frame_0000.txt ... val/ frame_0800.txt ...每个txt文件的内容格式是class_id x_center y_center width height其中坐标都是归一化到0~1的。比如一个cube的标注可能是0 0.5321 0.4875 0.1023 0.0881用labelImg打开图像画好框保存时它自动生成对应txt。记得在标注前先设置输出格式为YOLO。6.3 训练参数与超参数选择进入yolov5目录创建data.yamlpath: /path/to/datasets train: images/train val: images/val nc: 1 names: [cube]然后执行训练命令python train.py --img 640 --batch 16 --epochs 100 --data data.yaml --weights yolov5s.pt这里--img是输入分辨率--batch是批大小--epochs是训练轮数。对于自定义小数据集100轮足够。yolov5有一个专门保存超参数的文件hyp.scratch.yaml里面包括学习率lr0、动量momentum、权重衰减weight_decay等一系列参数。新手直接改lr0的后果往往是训飞——损失变成NaN。我的建议是第一次训练不动超参数只调epochs和batch。等训练结束后看mAP指标如果收敛得慢再尝试把lr0从0.01调到0.005。训练过程中yolov5会在runs/train/exp目录下输出结果包括PR曲线、混淆矩阵、各个类别的mAP。如果val集的mAP达到0.9以上说明模型已经能很稳定地识别你的目标了。6.4 把训练好的模型接到ROS节点里训练完成后在runs/train/exp/weights下会有best.pt和last.pt。best.pt就是验证集上表现最好的模型权重。在ROS节点中加载yolov5模型使用官方torch.hub加载方式比较方便import torch model torch.hub.load(/path/to/yolov5, custom, pathruns/train/exp/weights/best.pt, sourcelocal)然后对每一帧图像推理results model(cv_image) boxes results.xyxy[0].cpu().numpy()xyxy是一个Nx6的数组每行是[x1, y1, x2, y2, conf, cls]。我们计算中心点像素坐标u int((x1 x2) / 2) v int((y1 y2) / 2)这里的(u, v)就是物体中心在图像中的像素坐标下一步就需要结合相机内参和坐标变换去求解三维位置。这是下一篇的重头戏。7. 第一篇展望下一步的坐标变换与抓取闭环很多东西在单看每一步时都有教程但真正难的是把它们串成一个连续的系统。这一篇我们能明显感受到串起来的雏形环境跑通了机械臂可以动相机有图像yolov5也能识别到目标。但离看到就去抓还差关键的几步。7.1 当前完成的进度环到目前为止我们已经搭建了基于Ubuntu 22.04 ROS 2 Humble Gazebo 11的完整开发环境UR5e机械臂的Gazebo仿真与ros2_control控制器安装在机械臂末端的仿真RGB相机以及图像话题的订阅yolov5训练自己的数据集并能在Python脚本中完成目标检测简单来说感知和控制这两头都已经跑起来了但它们之间还缺一座桥——坐标变换。7.2 下一篇要解决的核心问题我们必须把yolov5输出框的像素坐标(u, v)转换为机械臂基座坐标系下的三维坐标(x, y, z)。这里涉及相机内参矩阵将像素坐标转换为相机归一化平面坐标深度信息要么假定物体在桌面上要么使用深度相机TF2坐标系变换监听camera_optical_frame到base_link的坐标变换变换完成后才可以给MoveIt2下发一个目标位姿让它规划出一条无碰撞的运动轨迹。最终实现的效果是相机看到物体机械臂自动移动到物体上方重力抓取或夹爪闭合。下一篇我会专门写坐标变换的推导与代码实现以及如何用MoveIt2在Gazebo里完成一次完整的抓取动作。7.3 给新手几个救命建议我是真的踩过不少坑才把这一套跑通有几个建议写在这里第一个建议不要一上来就把整个系统想得特别复杂先让机械臂在Gazebo里动起来再逐步加相机加检测加控制。每一步都验证成功后再进入下一步否则出了问题根本不知道错在哪一环。第二个建议注意你的launch文件里controller_manager的update_rate。这个值太低会导致机械臂跟踪轨迹时滞后出现抖动乃至发散。普通关节型机械臂建议设在100到200之间。第三个建议如果你的仿真画面在加入相机传感器后开始掉帧闪烁可以考虑把相机的update_rate降到15分辨率从640x480降到320x240。yolov5在低分辨率下也能检测不影响先跑通流程。第四个建议永远优先考虑在仿真中试错。我见过不少朋友买了入门级机械臂直接上来调视觉抓取结果机械臂快速砸向桌面直接把末端撞歪了。在Gazebo里这种事故最多损失一个模型修一下就能再来。这个系列的第一篇到这里就收尾了。环境、机械臂、相机、检测这些零部件都已备齐。下一篇我会把坐标变换和抓取闭环补全到时候整个抓取流程就能自动化跑起来。希望这篇内容能让你少踩一半的坑。