
刚开始和UR机械臂加Realsense这套组合打交道时我犯过一个特别典型的错相机和机械臂都通了MoveIt也规划得丝滑但让机械臂去抓桌上的物体抓爪总是往左边偏几个厘米。一开始我以为是DH参数没配好反复检查UR的模型和装配关系折腾了两三天最后才意识到问题根本不是机械臂本身而是“相机看到的世界”和“机械臂理解的世界”之间缺少一张翻译表。这张翻译表就是手眼标定矩阵。这篇文章把我在ROS Noetic环境下给UR机械臂和Intel Realsense D435i做手眼标定的完整过程整理出来包括标定原理、软硬件准备、基于easy_handeye的实际操作步骤、以及我踩过的各种坐标系混用和参数配置坑。如果你是第一次做机械臂视觉引导看完应该能少走不少弯路。1. 为什么手眼标定是机械臂抓取的“翻译官”坐标关系与常见误区1.1 三个坐标系之间的换算关系要理解手眼标定先得搞清楚机器人工作场景里的几个坐标系。机械臂基座有一个世界坐标系通常叫base_link机械臂末端法兰上有一个末端坐标系UR机器人里一般叫wrist_3_link或tool0相机自身有一个光学坐标系比如Realsense D435i的是camera_color_optical_frame。相机在做识别时给出来的物体位姿是相对于相机光学坐标系的。比如检测到杯子的中心是(0.05, -0.02, 0.3)单位是米这个坐标的原点在相机镜头的光心。但机械臂执行运动时它需要的是物体在base_link坐标系下的坐标。这两个坐标系之间怎么换算机械臂基座到末端的变换T_base_to_ee可以从UR控制器的运动学正解实时读到末端到相机的变换T_ee_to_cam就是未知的。那么物体在基座坐标系下的位姿P_base可以写成P_base T_base_to_ee * T_ee_to_cam * P_cam这个T_ee_to_cam就是手眼矩阵。它包含一个3x3的旋转矩阵和一个3x1的平移向量一共6个自由度。在eye-in-hand的结构下相机固定在机械臂末端这个矩阵是固定的不随机械臂的姿态变化而变化。所以只要标定出这个矩阵就能把视觉识别结果稳定地转换到机械臂的坐标系里。很多初学者容易把相机内参标定和手眼标定混在一起。简单区分内参标定解决的是“像素坐标到相机坐标”的问题告诉你图像上的一个点对应相机前方什么方向、多远距离手眼标定解决的是“相机坐标系到机器人坐标系”的问题告诉你相机在机器人空间中的位置和姿态。两个都重要缺哪个都不行。1.2 常见的错误认知第一个认知误区是“相机只要装上了就能用”。Realsense这类深度相机确实自带出厂内参RGB图像和深度图像也能对齐但它的坐标系是相对于它自己的和机械臂的基座没有任何关系。如果不做手眼标定哪怕点云看起来再漂亮机械臂抓取时一样会飘。第二个误区是“手动测量一下相机安装位置就行了”。有人会用尺子量相机到法兰的距离填成一个平移量就完事。手动测量只能得到近似值而且旋转部分的误差根本无法通过尺子量出来。相机只要歪个两三度远距离物体在抓取时的位置偏差就会被放大到几厘米甚至更多。第三个误区是“只采集一个姿态就能标定”。至少需要两个不同姿态下的观测才能构建出约束方程实际上因为噪声的存在单一或少量姿态的解非常不稳定通常建议采集10到15个姿态差异明显的样本。在动手做之前我还想提醒一个容易被忽略的问题如果机械臂末端或者相机被拆装过、相机镜头焦距调过比如手动旋转了D435i的调焦环、机械臂发生过碰撞导致结构变形原来的手眼标定结果就失效了需要重新标定。这不是“标定一次用一辈子”的事情。2. 开工前的软硬件准备UR驱动、Realsense与标定板的连接和验证2.1 硬件连接与系统环境我用的硬件是UR5e机械臂CB3控制柜和e系列的操作逻辑略有不同但驱动原理类似和Intel Realsense D435i运行环境是Ubuntu 20.04 ROS Noetic。工控机用的是普通台式机配置不需要太高但建议至少16GB内存因为同时跑Realsense驱动、MoveIt、rviz和控制器通信时内存占用还是不小。Realsense D435i强烈建议直接连接主板的USB 3.0接口不要通过USB Hub转接。USB供电不稳定会导致相机频繁掉线或深度图像出现花屏。如果用的是笔记本也尽量插在原生USB口而不是扩展坞上。我早期用扩展坞跑过半小时内相机掉了两三次排查起来折磨人。机械臂和工控机之间的通信方式有两种一种是直接用网线连接UR控制柜的LAN口设置好静态IP另一种是走现场局域网。建议开发阶段直接用网线直连IP设置成192.168.1.x网段避免现场网络风暴影响实时通信。UR控制柜端的IP一般是192.168.1.50工控机网卡设置成192.168.1.100子网掩码255.255.255.0即可。2.2 软件环境搭建软件部分需要安装UR机械臂驱动、Realsense驱动、MoveIt以及后续标定要用的工具包。如果是从零开始装ROS环境社区里常见的“一键安装”脚本可以帮你跳过很多编译坑省出时间直接做正事。下面的命令是安装核心依赖# UR机械臂官方工业驱动 sudo apt install ros-noetic-ur-robot-driver # Realsense相机驱动 sudo apt install ros-noetic-realsense2-camera ros-noetic-realsense2-description # MoveIt以及机械臂相关配置 sudo apt install ros-noetic-moveit # 手眼标定工具 sudo apt install ros-noetic-easy-handeye # 标定板识别aruco sudo apt install ros-noetic-aruco-rosUR驱动这里有一个特别重要的坑UR官方推荐在控制柜的示教器上安装一个叫externalcontrol-3.x.urcap的URCap插件这个插件负责让外部控制器工控机里的ur_robot_driver通过以太网接管机械臂的控制。URCap的版本必须和ur_robot_driver软件包版本对应否则会出现连接不稳定、断开后无法重连的问题。在UR5e上建议使用3.x版本的URCap并且从UR官网的“Support”页面下载对应控制柜固件版本的安装包而不是随意找一个版本装上。装完URCap后在示教器上启动External Control程序确认屏幕上的状态变为“Connected”再在工控机上启动驱动roslaunch ur_robot_driver ur5_bringup.launch robot_ip:192.168.1.50启动后通过下面的命令确认机械臂状态正常rostopic echo /joint_states如果能看到六个关节的角速度、位置信息正常发布说明驱动已经工作。Realsense驱动的启动方式相对简单roslaunch realsense2_camera rs_camera.launch align_depth:truealign_depth:true参数很重要它会把深度图重投影到RGB相机坐标系上这样RGB图像中某个像素就能直接拿到对应的深度值。视觉识别和抓取定位时基本都依赖对齐后的数据。启动后用rviz加载Realsense插件或者直接查看话题列表确认/camera/color/image_raw和/camera/depth/image_rect_raw都在正常发布。2.3 标定板准备与检测验证标定板我用的是Aruco marker而不是传统棋盘格。原因很实际Aruco板在较远距离、部分遮挡情况下依然可以被稳健检测到而且aruco_ros包和easy_handeye的配合很成熟配置简单。生成Aruco标定板可以打印一张标准Aruco词典比如DICT_4X4_50或DICT_ARUCO_ORIGINAL的单个Marker尺寸建议打印成边长为12cm到15cm左右贴在硬质平板或泡沫板上确保表面平整不反光。图片打印出来后用直尺量一下实际打印尺寸不要直接相信打印软件里填的数值。Aruco检测脚本里填写的markerSize必须是实际测量值单位是米差一厘米都会导致标定结果出现系统性偏差。启动Aruco检测的示例roslaunch aruco_ros single.launch markerId:582 markerSize:0.12 ref_frame:camera_color_optical_frame这里的ref_frame填的是相机光学坐标系这个地方非常关键。D435i的相机驱动发布了好几个坐标系最常见的是camera_link和camera_color_optical_frame。camera_link是相机外壳的物理坐标系而真正的成像坐标系是camera_color_optical_frame它的轴方向和camera_link不一样——光轴方向沿Z轴Y轴朝下。如果ref_frame填错后面所有位姿转换都会引入一个固定角度的旋转误差标定解出来必然是错的。检测启动后在rviz里添加TF显示和Image显示把Aruco marker放在相机前面观察是否出现aruco_marker_frame这个TF并且能看到一个带坐标轴的检测框。看到这个坐标系后标定前的准备工作就完成了。3. 手眼标定的数学本质AXXB与eye-in-hand/eye-to-hand选型3.1 AXXB到底在解什么手眼标定算法的核心是求解一个形如AX XB的矩阵方程。这里A、B、X都是4x4的齐次变换矩阵X是要标定的手眼矩阵。举个例子。相机固定在机械臂末端eye-in-hand标定板固定在工作空间中。机械臂运动到第i个姿态时我们能通过两类信息得到两个变换一类是机械臂末端到基座的变换A_i T_base_to_ee_i这从UR控制器实时可以读到。另一类是标定板到相机坐标系的变换B_i T_cam_to_marker_i这通过Aruco检测可以得到。对象是同一个标定板它相对于基座坐标系的位置始终不变。于是存在一个固定不变的变换T_base_to_marker满足A_i * X * B_i T_base_to_marker取第i个和第j个姿态做差就能消掉那个固定的T_base_to_marker整理后恰好变成A_j^{-1} * A_i * X X * B_j * B_i^{-1}这就是经典的AX XB方程。X无论放在等式哪边本质上都是那个固定的相机到末端的变换T_ee_to_cam。方程组是已知的机械臂运动学观测值和视觉观测值联立出来的所以只需要多组姿态数据就能用算法求最小二乘解。常见求解算法有Tsai-Lenz、Park-Martin、Daniilidis等它们的差异在于对角速度和旋转矩阵参数化的方式不同。easy_handeye里通常默认用Tsai-Lenz适用于大多数固定安装场景精度足够。3.2 eye-in-hand与eye-to-hand的区别很多人选方案时卡在eye-in-hand还是eye-to-hand上简单说一下区别。eye-in-hand是相机装在机械臂末端上相机跟着末端一起运动。它跟踪目标时视野会随机械臂移动灵活度高近距离识别时容易调整角度适合抓取、分拣这类目标在工作区内移动的任务。缺点是相机连线和固装在机械臂运动时承受振动和弯折长期使用可能导致相机松动需要不定期重新标定。eye-to-hand是相机固定在工作区域外部比如装在支架上俯视工作台。它的视野稳定不会因为机械臂运动产生运动模糊适合视觉定位精度要求高、目标不移动的固定工作站。缺点是安装位置受限而且目标被机械臂遮挡时看不到。这套项目里D435i是装在UR机械臂法兰上的采用的自然是eye-in-hand方案。easy_handeye里对应的参数是eye_on_hand:true。如果以后有人做眼在外的项目记得把参数改成false同时公式里X的含义也会变成相机到基座的固定变换不要照抄本文配置。3.3 为什么需要10到15个姿态样本理论上AXXB只需要两次姿态就可以求解实际标定却需要采集大量样本原因很简单机械臂运动学读数和视觉检测都有噪声。样本少的时候噪声直接进入方程解出来的X可能离真实值很远样本多了以后噪声会在最小二乘意义下被“平均”掉解就更稳定。但并不是随便采集10个姿态就够了。姿态之间的差异要大要能覆盖到几个不同方向的俯仰、偏航和距离变化。最忌讳的姿态采集方式是只在一个固定高度上绕着一个轴转圈这样旋转部分的约束严重不足解出来的旋转矩阵可能完全错误。我常用的采集策略是让机械臂末端大致扫过一个“半球面”有近的、有远的、有俯视的、有侧视的保证标定板在图像中出现的位置、大小和角度都有明显变化。如果你用easy_handeye的标定界面每次采集后它会显示当前已采集样本数还会在计算时给出重投影误差。重投影误差超过0.5像素以1080p图像为基准时重新补采几个姿态通常会比直接接受结果更稳妥。4. 基于easy_handeye的完整标定流程从数据采集到生成变换矩阵4.1 构建机械臂和相机的TF连接标定开始前首先要保证TF树里能从机械臂基座一路连到相机光学坐标系。也就是说要有base_link → wrist_3_link → camera_mount_link → camera_link → camera_color_optical_frame这条完整的链路。UR机械臂自身的驱动会发布base_link到wrist_3_link的TF。Realsense驱动会发布camera_link到camera_color_optical_frame的TF。中间缺少的是wrist_3_link到camera_link这一段的连接这个一般是用户根据实际安装关系配置的。最简单的做法是在计算机器人模型URDF时加一个固定关节把Realsense的少量描述文件包含进来link namecamera_mount_link / joint namecamera_mount_joint typefixed parent linkwrist_3_link / child linkcamera_mount_link / origin xyz0.0 0.0 0.05 rpy0.0 0.0 0.0 / /joint joint namecamera_link_joint typefixed parent linkcamera_mount_link / child linkcamera_link / origin xyz0.0 0.0 0.0 rpy0.0 0.0 0.0 / /joint这个xyz里的距离需要用尺子量一下相机和法兰之间的实际安装偏移拧紧螺丝后量出来的值与标定结果会更快收敛。不需要量得非常精确最终标定算法会修正这个初始近似值的残差但不能差太离谱比如相机装在前侧却填成后侧会让非线性优化掉进错误局部解。这里还有个容易出错的概念UR机械臂的wrist_3_link是法兰末端坐标系tool0通常是工具法兰的理论坐标系。在MoveIt的UR模型里wrist_3_link更常用easy_handeye配置robot_effector_frame时用wrist_3_link不要写成tool0否则标定出来的矩阵会自带一个固定偏移。4.2 配置easy_handeye的launch文件easy_handeye提供了一个标定流程启动机械臂驱动、相机、标定板检测后它会开启一个用于采集样本和求解的图形界面。为了让它能配套UR和D435i工作需要写一个launch文件。参考配置如下launch !-- 机械臂基座坐标系 -- arg namerobot_base_frame defaultbase_link / !-- 机械臂末端法兰坐标系 -- arg namerobot_effector_frame defaultwrist_3_link / !-- 相机光学坐标系 -- arg nametracking_base_frame defaultcamera_color_optical_frame / !-- 标定板的TF名aruco_ros检测出来的frame -- arg nametracking_marker_frame defaultaruco_marker_frame / include file$(find easy_handeye)/launch/calibrate.launch arg nameeye_on_hand valuetrue / arg namerobot_base_frame value$(arg robot_base_frame) / arg namerobot_effector_frame value$(arg robot_effector_frame) / arg nametracking_base_frame value$(arg tracking_base_frame) / arg nametracking_marker_frame value$(arg tracking_marker_frame) / arg namecamera_topic value/camera/color/image_raw / arg namemarker_size value0.12 / /include /launch这里eye_on_hand必须为true因为相机是固定在机械臂末端跟着一起运动的。camera_topic指定RGB彩色图像话题Aruco检测会基于这个图像计算标定板位姿。marker_size要和实际打印标定板尺寸一致前面已经强调过。4.3 手动采集标定样本配置好launch之后依次按顺序启动roslaunch ur_robot_driver ur5_bringup.launch robot_ip:192.168.1.50 roslaunch realsense2_camera rs_camera.launch align_depth:true roslaunch aruco_ros single.launch markerId:582 markerSize:0.12 ref_frame:camera_color_optical_frame roslaunch easy_handeye ur5_realsense_calibration.launch启动后easy_handeye的界面会弹出通常是一个基于rqt的标定面板。在面板中需要关注几个功能区当前TF状态显示确认aruco_marker_frame存在于相机坐标系下“采集样本”按钮“计算”按钮计算完成后显示的重投影误差和生成矩阵的预览区域。实际操作时我习惯用UR示教器的手动模式缓慢移动机械臂而不是用MoveIt规划运动因为手动模式可以精细调整姿态确保标定板始终完整出现在图像中同时能更方便地让标定板靠近或远离相机。每调整到一个新姿态先在rviz里确认标定板在图像中的位置合适——最好位于画面中心区域且不要太小建议标定板的在图像中占比不低于10%。然后点击“采集样本”。重复这个过程直到采集10到15个样本。样本采集过程中的注意事项不要连续采集差异太小的姿态。起码让机械臂的末端姿态变动角度超过15度或位置移动超过10厘米这样每次样本对解的约束才有明显贡献。让标定板在图像中的位置“动起来”。不要始终放在画面正中可以让它出现在画面左上、右下等不同位置这样能增强对不同视角约束的覆盖。采集过程中标定板本身不能移动。固定在工作台上或者用夹子夹住确保它相对于机械臂基座是静止的。4.4 求解与结果发布采集完样本后点击“计算”按钮。easy_handeye会用算法求解AXXB给出一个候选的变换矩阵并输出重投影误差。正常情况下重投影误差应该小于0.5像素如果误差偏大先回到上一步补采几个姿态再重新计算。计算完成后标定结果会保存成一个YAML文件默认路径在~/.ros/easy_handeye/目录下。文件内容大致如下transformation: translation: x: 0.051 y: -0.012 z: 0.088 rotation: x: 0.006 y: 0.029 z: -0.683 w: 0.730这个变换就是从机械臂末端到相机光学坐标系的齐次变换矩阵的四元数和平移向量表示。要让后续所有程序都能使用这个标定结果有两个常见做法。做法一把结果写进URDF中固定wrist_3_link到camera_mount_link的origin里用四元数替换之前手动估算的近似值。好处是MoveIt、TF树在每次启动后自动加载不需要额外启动静态变换发布节点。做法二用easy_handeye自带的发布功能在标定完成后直接把结果以静态变换的形式发布到TFrosrun tf2_ros static_transform_publisher 0.051 -0.012 0.088 0.006 0.029 -0.683 0.730 wrist_3_link camera_color_optical_frame这个方法适合快速验证如果做了标定板测试不想重新生成URDF模型直接用这条命令最快。5. 标定结果的验证与常见坑误差排查、优化与实战经验5.1 标定结果对不对的验证方法标定完不能直接上去抓东西必须做验证。我有两个常用的验证步骤。第一个是视觉验证法。保持标定板固定把机械臂末端移动到标定板前方某个位置然后在rviz里同时显示Aruco marker实际检测的TF和机械臂基座到标定板的TF。理论上Aruco检测出来的aruco_marker_frame位置应该和由T_base_to_ee * T_ee_to_cam * T_cam_to_marker推算出来的位置高度一致。如果偏差在1厘米以内基本合格如果差很多说明标定矩阵有问题或者某条TF链路中间断了。第二个是实际操作验证法。放一个已知大小、已知位置的目标物在相机视野里视觉识别后把识别到的位姿发给机械臂做抓取。这是最接近真实使用场景的验证方式。如果抓取偏差超过1到2厘米再结合误差方向排查。下面总结一些我遇到过的典型问题和对应排查方向。现象可能原因排查方向固定朝某个方向偏几厘米坐标系弄混比如camera_link和camera_color_optical_frame混用检查URDF里的TF链路确认用的是光学坐标系250px重投影误差但抓取偏差随时间增大机械臂运动学精度有问题用UR示教器手动示教几个点对比没问题再考虑外力误差随机械臂姿态变化变化标定样本姿态太少或姿态退化重新采集更多差异化姿态Aruco检测不稳定偶尔漏检标定板太小、光照不足或反光换大标定板给工作区加漫射照明只在特定距离下准镜头畸变或内参不准对Realsense单独做一次相机内参标定5.2 坐标系混用是头号坑在前面的每一个环节里我都在强调坐标系问题。为什么这么重要因为我踩过最大的坑就是它。D435i驱动发布的所有图像和TF里camera_color_optical_frame和camera_link是两个完全不同的坐标系。camera_link的X轴朝前相对相机外壳而言camera_color_optical_frame的Z轴沿光轴方向。如果你在Aruco检测、TF连接、easy_handeye配置里混用了这两个坐标系标定出来的矩阵要么无法收敛要么收敛到错误结果。排查方法很直接启动Realsense驱动后在rviz里看TF树。如果看到一个叫camera_color_optical_frame的坐标轴和camera_link坐标轴方向明显不同这是正常的但你要确保所有视觉处理的坐标系都用camera_color_optical_frame而不是camera_link。同理机械臂末端坐标系也要保持一致。UR的tool0和wrist_3_link虽然物理上几乎在同一个位置但它们的坐标系方向定义有差异有的模型里它们有固定的微小偏移。用手眼标定时用wrist_3_link抓取规划时也用wrist_3_link不要一会用这个一会用那个。5.3 样本采集质量的实操技巧easy_handeye虽然步骤简单但真正决定标定精度的是采集阶段的数据质量。我总结出几个很直接的技巧。第一机械臂移动要“慢而稳”。机械臂停下来后至少等一两秒再采集因为惯性停止后控制系统会有一个微小的稳定过程立刻采集可能还在抖动图像被运动模糊影响Aruco角点检测精度会下降。第二覆盖近距离和远距离两个范围。D435i的最佳深度测量范围在30厘米到1米之间。标定时让标定板在相机正前方0.3到0.6米范围内移动不要再远。太远时RGB图像中标定板太小角点检测的亚像素精度下降直接影响结果。第三姿态变化要“立体”。我一般会让机械臂在横滚、俯仰、偏航三个方向上都做变化。比如先平视采集几个点再仰视、俯视、侧视各采集几个点不要始终用同一个观察视角。第四如果使用棋盘格标定板要避免使用反光纸张和塑料覆膜反光会让角点检测产生系统性偏移。A4纸打印后用双面胶贴到硬纸板或铝板上就行不要用玻璃夹着。5.4 关于重投影误差的合理预期easy_handeye计算完成后显示的重投影误差是标定板在图像中的检测角点位置和通过标定矩阵重投影回去的角点位置的误差单位是像素。这个数值以1080p分辨率图像为基准小于0.5像素基本合格小于0.2像素属于非常好的结果。如果你的重投影误差偏高但又不确定是哪个环节出问题我的排查顺序是这样的检查TF链路里相机坐标系有没有用错检查手眼标定的初始配置robot_effector_frame是不是wrist_3_link检查标定板marker_size和实际尺寸是否一致检查Aruco检测是否稳定是不是有反光或运动模糊增加样本数重新计算看误差是否下降。大多数情况下误差偏高的原因集中在第1步和第3步一次跑通容易出问题的都在这两个地方。5.5 长期使用中的保养建议手眼标定做完之后最好把测量结果和采集时的机械臂姿态记录到一个固定文档中方便日后排查。要注意的是只要相机没有拆装、镜头没有调焦、机械臂没有发生碰撞或更换关键零件标定矩阵可以长期使用。但如果出现下面这些情况建议重新标定相机固定螺丝松动过重新拧紧后即使位置看起来一样也要重新标定机械臂发生过碰撞尤其是碰撞点靠近法兰位置的时候给机械臂加装了夹爪或末端执行器改变了末端重心或机械结构更换过另一台同型号相机即使安装位置一样也可能因为个体差异导致标定失效。我在实际项目中还发现一个容易被忽略的细节Realsense的RGB相机在出厂时有自己的内参但如果在rs_camera.launch中设置了rgb_camera.profile或手动调过白平衡、曝光等参数图像分辨率或畸变模型会变化最稳妥的做法是标定前把相机参数恢复默认并在后续使用中固定这些参数不要频繁改动。这套标定流程做完视觉引导的基础工作就算扎实了。后面再接目标检测、姿态估计、轨迹规划都在这个坐标系框架下工作不再需要关心相机和机械臂的空间关系。做机器人的核心就是坐标系之间的转换它虽然枯燥但每一步绕不过去。