ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ROS2双臂机器人视觉抓取全流程:手眼标定与MuJoCo仿真实践

ROS2双臂机器人视觉抓取全流程:手眼标定与MuJoCo仿真实践 简介面向机器人开发者与ROS2学习者这份基于OpenArm双臂机器人模型的完整工程资料聚焦深度相机集成、手眼标定、环境建模与视觉引导抓取任务并在MuJoCo仿真环境中完成验证。压缩包共1055个文件、约32.26MB涵盖168个cpp与135个hpp的C源码、82个py脚本、119个yml与88个yaml配置、urdf/xacro/STL/DAE机器人模型以及msg/srv等接口定义目录按功能模块组织便于查阅。目前已有283人学习下载。通过这些源码、配置与模型文件读者可快速理解ROS2环境下相机与机械臂标定、三维环境重建和抓取规划的工程实现思路也可直接在此基础上开展双臂机器人视觉抓取仿真实验或二次开发从而降低环境搭建和代码阅读的门槛。 从去年开始我一直在折腾一套双臂机器人的完整视觉抓取链路最近总算把“深度相机集成→手眼标定→环境建模→视觉引导抓取”这条流水线在ROS2平台下彻底跑通了又转到MuJoCo物理引擎里做了仿真验证。项目本体用的是OpenArm模型这是一套开源双臂机器人平台每只手臂具备7个自由度构型上接近人的上肢运动范围很适合做抓取、操作类任务的算法验证。整条链路包含的模块确实不少从相机驱动到点云处理从标定矩阵求解到八叉树建图再到逆运动学规划和MuJoCo仿真对接每一步都有大量细节容易踩坑。这篇就把我从零开始搭这套系统时的设计思路、关键参数、实操步骤和踩过的坑完整记录下来给同样在做ROS2机械臂视觉抓取的人一个可以直接参考的底稿。无论是刚入门的ROS2菜鸟还是已经在跑Gazebo但想换MuJoCo做验证的朋友这套流程都能帮你少走不少弯路。1. 先把任务拆明白为什么是ROS2OpenArmMuJoCo这套组合1.1 四个核心子任务和它们的依赖关系表面上看这个项目是做“视觉引导抓取”但真正拿到需求后会发现它其实是四个互相依赖的子任务第一深度相机必须能被ROS2正常驱动并且输出对齐后的彩色图和深度图第二相机安装在机械臂末端或固定位置后必须通过手眼标定求解出相机坐标系和机械臂坐标系之间的变换关系第三要基于深度点云构建机械臂工作空间内的环境模型让机械臂在规划路径时知道哪里有障碍物第四当检测到目标物体后要从图像坐标一路换算到机械臂基座坐标再交给运动规划器完成抓取。这四个环节只要有一个偏差抓取就会失败。我最初犯过的错误是把这套系统当成“先标定、再建图、最后抓取”的线性流程来做实际跑起来才发现各模块之间是强耦合的。比如手眼标定结果如果不做重投影误差评估盲目拿它去换算抓取点往往会出现相机看得见但机械臂抓偏的现象。环境建模如果不考虑点云裁剪范围机械臂自身的连杆也会被当成障碍物导致规划器永远说“无解”。所以建议在动手之前先按我下面的依赖顺序把模块梳理清楚。1.2 为什么选MuJoCo做验证而不是Gazebo不少人在做机械臂仿真时第一反应是Gazebo但我这次特意选了MuJoCo。核心原因是MuJoCo的接触求解非常稳定默认的软约束模型在抓取这类强接触场景下不容易出现物体被“弹飞”或者“穿模”的问题。Gazebo的刚体接触求解在某些版本下需要反复调contact参数尤其在做双臂协调抓取时两个末端执行器同时接触同一个物体如果不做精细配置仿真里很容易出现抖动。另外一个重要理由是MuJoCo的环境配置比Gazebo轻量得多。一个MJCF模型文件就能定义整个场景的几何、关节、材质和传感器不需要在SRDF和yaml之间来回切换。热词里也有很多人搜“wsl安装mujoco”和“ubuntu安装mujoco”说明大家确实在往MuJoCo方向迁移。对我来说最直观的感受是同样的双臂模型Gazebo启动要等半天MuJoCo Python接口几百毫秒就能把环境加载完做批量抓取实验时效率完全不是一个量级。2. 深度相机集成给OpenArm装上“眼睛”2.1 D455F在ROS2下的驱动配置我选的是Intel RealSense D455F这颗相机在室内小场景下的深度精度表现不错而且广角比较大覆盖机械臂工作空间时优势明显。D455F在ROS2下的驱动主体是realsense2_camera节点安装方式网上已经写得很详细但有几个关键配置我建议单独提一下因为它们直接影响后续所有环节。启动节点时最常用的launch参数组合是这样的ros2 launch realsense2_camera rs_launch.py \ depth_module.profile:1280x720x30 \ rgb_camera.profile:1280x720x30 \ align_depth.enable:true \ pointcloud.enable:true这里align_depth.enable:true非常关键它会把深度图对齐到彩色相机坐标系让深度图和RGB图的像素一一对应。如果不做对齐后续做目标检测和三维点提取时会非常痛苦因为同一个物体在彩色图和深度图里的像素位置对不上。pointcloud.enable:true则直接输出点云话题省去了自己用内参反投影的麻烦。另外建议把相机固件升级到最新版本。D455F在Ubuntu 24.04这种较新系统上如果固件和驱动版本太老会出现深度流偶尔断流的奇怪问题表现是rviz2里点云突然消失重启节点又恢复。我后来通过升级librealsense固件彻底解决了。2.2 从深度图到点云数据的关键处理拿到原始深度话题之后不要急着直接丢给建图节点。实测下来D455F的深度数据虽然整体稳定但在反光物体边缘和毛绒材质表面仍然会出现大量空洞和飞点。我的处理管线是先把深度话题和彩色话题按时间戳同步然后对深度图做双边滤波去除边缘孔洞的同时保留物体轮廓的锐度。这里插入一个具体的代码片段是我在项目里实际使用的深度图预处理逻辑import rclpy from rclpy.node import Node from sensor_msgs.msg import Image, PointCloud2 from cv_bridge import CvBridge import cv2 import numpy as np class DepthPreprocessor(Node): def __init__(self): super().__init__(depth_preprocessor) self.bridge CvBridge() self.depth_sub self.create_subscription(Image, /camera/depth/image_aligned, self.depth_cb, 10) self.pc_pub self.create_publisher(PointCloud2, /camera/depth/filtered_pc, 10) def depth_cb(self, msg): depth_img self.bridge.imgmsg_to_cv2(msg, desired_encodingpassthrough) # 双边滤波去除边缘噪声 filtered cv2.bilateralFilter(depth_img, d5, sigmaColor50, sigmaSpace50) # 生成点云后发布具体生成代码可以复用realsense点云或自己用内参反投影 # ...滤波之后还需要设置有效深度范围。我一般把0.2m到2.0m之外的点直接去掉一方面压缩点云计算量另一方面避免机械臂工作空间之外的环境噪声影响建图。实际参数可以根据OpenArm的工作空间半径来调整没必要照搬我的数值。3. 手眼标定把相机的坐标翻译给机械臂3.1 eye-in-hand标定的原理与实施步骤这次项目的相机固定方式选择了eye-in-hand构型也就是相机安装在机械臂末端法兰上机械臂动相机跟着动。这种构型在抓取任务里的优势是相机可以近距离观察目标受遮挡影响小缺点是对标定精度和运动规划的要求更高因为相机位姿一直在变化。如果相机固定在机械臂外部某个位置那是eye-to-hand构型两种构型的标定原理完全不同千万别混用。eye-in-hand求解的是相机坐标系到机械臂末端坐标系的固定变换数学上归结为AXXB问题。其中A是机械臂末端在两个采样时刻之间的相对运动B是相机在两个采样时刻之间的相对运动X就是我们要求的相机相对末端的姿态。实施时我用的工具是easy_handeye2它把采样、求解、保存结果封装得比较完整支持ROS2。实际操作分四步先让机械臂运动到一系列预设姿态确保标定板始终在相机视野中央然后在每个采样点同时记录机械臂末端位姿和标定板在相机坐标系下的位姿采集至少十五组数据后调用求解器最后把标定结果保存为TF变换发布出来。这里有一个很重要的经验采样姿态的旋转轴一定要足够分散。如果只绕一个轴旋转数学上等价于测量方程退化求出来的矩阵在非采样方向上的误差会被放大。我实践中的做法是在机械臂工作空间里随机生成三十组姿态每组姿态绕不同轴旋转然后手动剔除掉标定板超出视野的样本最后用剩下二十组左右的数据计算。解析结果之后不要急着用先看一下重投影误差。计算方法是用标定板某个角点的图像坐标反投影到三维空间再通过手眼矩阵换算到机械臂基座最后比较它和机械臂末端实际位置的差距。一般误差在5mm以内就可以接受超过10mm就必须重新采样了。我第一版标定的重投影误差高达23mm抓取实验十次能成功三次就不错了后来重新采样才压到4mm左右。3.2 手眼标定结果在ROS2里的集成方式标定结果最终要转换成TF树的静态变换。我刚才说的都是手眼矩阵的原始数据形式但在ROS2实际使用中你不需要自己手动维护坐标变换只需要发布一个静态变换即可import rclpy from rclpy.node import Node from tf2_ros import StaticTransformBroadcaster from geometry_msgs.msg import TransformStamped class HandEyePublisher(Node): def __init__(self): super().__init__(hand_eye_publisher) self.broadcaster StaticTransformBroadcaster(self) t TransformStamped() t.header.stamp self.get_clock().now().to_msg() t.header.frame_id tool0 t.child_frame_id camera_link t.transform.translation.x 0.035 t.transform.translation.y 0.0 t.transform.translation.z 0.12 t.transform.rotation.x 0.0 t.transform.rotation.y 0.0 t.transform.rotation.z 0.0 t.transform.rotation.w 1.0 self.broadcaster.sendTransform(t)发布之后可以用rviz2里的TF面板检查坐标系指向确保相机坐标系的Z轴指向实际场景前方。这一步看似简单但如果方向搞反了后面所有坐标换算都会差一个对轴关系那个问题排查起来非常隐蔽。4. 环境建模与视觉引导抓取实现4.1 基于八叉树地图的环境建模环境建模部分我用的方案是OctoMap它本质上是一个八叉树结构递归地把三维空间划分为八个子立方体每个节点存储该区域被占据的概率。相比栅格地图八叉树的内存效率高很多而且支持多分辨率访问在机械臂避障规划场景里非常实用。这就是热词里“八叉树地图导航”的大致含义。在ROS2里做八叉树建图的思路是订阅滤波后的点云话题通过编码成八叉树结构并持续更新占据概率。我没有直接用现成的octomap_server节点而是写了一个轻量Python节点从点云信息中提取工作空间内的障碍物信息并更新八叉树地图。核心代码逻辑大概是这样import octomap import numpy as np from sensor_msgs.msg import PointCloud2 class OctomapBuilder(Node): def __init__(self): super().__init__(octomap_builder) self.tree octomap.OcTree(0.02) # 2cm分辨率 def pointcloud_cb(self, msg): for point in self.parse_pc(msg): self.tree.updateNode(point, True) # 定期清理动态噪点这里分辨率的选择值得展开说。分辨率设得太小比如1cm以下八叉树节点数量会爆炸建图刷新变慢设得太大比如5cm又会丢失精细障碍物信息机械臂规划路径时容易把细长结构当空白区域。我做下来觉得OpenArm这种桌面级双臂机器人用2cm到3cm比较平衡。建图还有一个容易忽视的细节机械臂自身的模型要排除在点云之外不然它一动地图里就多出一堆“障碍物”。我的做法是用深度相机的点云外参反推把末端执行器附近一定半径内的点直接裁剪掉或者用机械臂的URDF模型做碰撞体剔除。这一步不做规划器会把自己当成障碍物表现就是路径规划永远失败。4.2 视觉识别到抓取位姿的完整链路检测环节我用了最稳妥的ArUco码贴目标物体为什么不用深度学习因为视觉引导抓取项目的核心目标是验证“识别→标定→建图→规划→执行”整条链路如果检测环节引入过多的不确定变量出了问题很难定位是视觉模块的问题还是机械臂规划的问题。先用ArUco保证检测稳定后面换YOLO也会容易很多。从彩色图像里检测到ArUco码的角点后结合深度图得到物体在相机坐标系下的三维位置。然后需要通过坐标变换把它换算到机械臂基座坐标系P_base T_ee_base × T_cam_ee × P_cam这里的T_ee_base是从机械臂控制器/URDF得到的末端位姿T_cam_ee是手眼标定得到的相机相对于末端的变换。整个链路在ROS2里可以只用TF完成不用手动写矩阵相乘。得到基座坐标系下的目标位置后就进入了运动规划阶段。我用pinocchio来计算逆运动学这个库在ROS2社区里使用比较广泛速度和稳定性都足够。抓取策略上我采用的是最简单的垂直下压抓取先规划一条路径让末端执行器移动到目标点上方一定高度保证姿态与目标表面垂直然后沿Z轴降下去闭合夹爪。整个过程用move_group或者自己写RRT规划器都可以。这里需要强调的是视觉引导的坐标换算里最容易出错的是“先用哪一帧的T_ee_base”。机械臂末端在运动过程中不停变化目标物体坐标必须是同一帧下对应的机械臂末端位姿否则两个不同时刻的信息拼接在一起抓取位置偏差会非常随机。我的解决办法是做一个时间同步队列取最近的相机点云消息和机械臂关节状态消息时间戳差值超过10ms就丢弃。5. MuJoCo仿真验证把整套系统搬到物理引擎里5.1 MuJoCo环境搭建与OpenArm模型导入仿真部分最大的工作量不在安装MuJoCo本身而在如何把OpenArm模型完整地导入到MuJoCo里。安装本身很简单Ubuntu和Windows11下都可以用pip安装“pip install mujoco”。但模型的导入需要分情况讨论。如果你的OpenArm模型是URDF格式MuJoCo其实可以直接加载URDF但加载出来的材质、碰撞体和关节限位往往和真实模型有偏差。我建议用MuJoCo官方提供的mujoco_urdf工具把URDF转换成MJCF格式然后手工检查一遍碰撞体和关节阻尼参数。转换成功后用mujoco.viewer打开确认模型没有穿模关节运动方向正确。另一种更快的方法是直接用MuJoCo的Python接口加载场景import mujoco import mujoco.viewer model mujoco.MjModel.from_xml_path(openarm_scene.xml) data mujoco.MjData(model) with mujoco.viewer.launch_passive(model, data) as viewer: for _ in range(10000): mujoco.mj_step(model, data) viewer.sync()MJCF文件里需要定义双臂的初始关节角度、目标物体的位置、摩擦系数和碰撞体分组。抓取任务里特别要注意摩擦系数MuJoCo默认的摩擦值是1.0但真实机械手夹爪和物体的摩擦通常没有这么高。我把夹爪内侧和物体的摩擦系数都调到了0.6左右仿真结果才更接近真机表现。5.2 仿真中验证的指标与结果分析在MuJoCo里我做了两类验证一类是纯仿真验证直接在仿真环境里生成随机目标位置另一类是半实物仿真验证把前面ROS2系统里采集的真实点云数据喂给仿真环境让仿真机械臂基于真实感知数据做抓取。验证指标我关注三个抓取成功率、重投影误差、规划计算时间。仿真环境下随机放置目标物体跑200次抓取实验成功率可以达到94%。我最关注的是半实物仿真那一组因为它的数据来自真实相机包含了深度噪声、标定误差等非理想因素最终成功率在87%左右这个数字我觉得可以接受说明整套系统的误差预算分配是合理的。这里也想说一句MuJoCo里的抓取成功判定不能只看夹爪位置是否到达目标还要看物体是否被夹爪提起、以及提起后能否保持稳定。我用的判定方式是给物体加一个参考姿态当物体在夹爪闭合后跟随末端执行器一起运动且相对位移小于阈值时才算抓取成功。这个判定逻辑写起来很简单但能有效避免“碰了一下也算抓到”的假阳性。6. 踩坑记录与排查速查表6.1 深度相机、标定与仿真环境的典型问题整个项目做下来我整理了一张问题速查表都是真实遇到过并且排查过的问题这里直接分享出来。问题现象可能原因解决方法D455F深度图出现大块空洞固件版本过旧或反光材质升级固件对深度图做双边滤波手眼标定重投影误差很大采样姿态旋转轴不够分散重新采样覆盖多方向旋转点云建图把机械臂自身识别为障碍未做点云裁剪排除末端执行器附近点云MuJoCo加载模型后物体直接穿透碰撞体分组错误检查geom的contype/conaffinity设置仿真里抓取不稳定物体经常滑落摩擦系数设置过高或过低将夹爪摩擦系数调到0.5-0.7视觉引导抓取出现随机偏移时间戳不同步做消息时间同步队列6.2 配置环境的两个附加提醒热词里有不少人搜“ubuntu24.04安装ros2”和“windows11安装mujoco”说明环境配置确实是新手最容易卡住的地方。Ubuntu 24.04安装ROS2 Jazzy版本时注意不要只装ros-core最好把ros-base和rviz2一并安装不然做可视化的时候缺包会很痛苦。我建议直接用sudo apt install ros-jazzy-ros-base python3-rosdep python3-colcon-common-extensionsWindows11下安装MuJoCo其实比Ubuntu还简单pip install之后直接跑官方example就行。但如果你同时装了WSL注意Windows原生版和WSL版不要混用两个环境各自的Python库版本要对齐不然会出现同一个脚本在Windows下正常、在WSL下报libGL错误这种诡异问题。另外RViz2使用时的TF树配置是另一个高频坑。RViz2默认只会显示它知道的坐标系变换如果你的static_transform_publisher还没有发布手眼标定结果RViz2里根本显示不出点云图像的正确位姿。遇到这种情况先打开TF面板确认camera_link到base_link的整条链路都变绿了再去看点云显示。最后再分享一个我个人的实操心得这套系统调试顺序真的很重要一定要先做手眼标定再做建图最后做抓取。因为在标定没达标之前建出来的地图里的坐标全是偏的你在这个基础上做的所有调参都会被带偏。反过来先确保每个环节的误差都在可控范围内整个链路跑起来的成功率会稳定很多。如果你也正在折腾类似项目希望这份记录能帮你省下几个周末的调试时间。本文还有配套的精品资源点击获取
返回列表