
做视觉引导机械臂的项目最绕不开的一步就是手眼标定。UR 机械臂配上 Intel RealSense D435i 这种组合在高校实验室、创业公司和很多制造现场的视觉抓取场景里都极其常见可标定这一步我见过太多人卡在数据采完、矩阵算出来不对或者干脆不知道从哪开始。这篇就把整个流程从头到尾串一遍从 AXXB 的原理讲到工具链搭建再到数据采集、求解、验证和踩坑记录适合正在搭视觉抓取环境、或者在给毕设和项目做机器人视觉的同学直接参考。我不会只丢给你几条命令而是会讲清楚每一步在干什么、为什么这么干以及哪些地方最容易出问题。标定这事原理学一遍可能只需要半小时但真正在实机上跑通、拿到一个能用的变换矩阵往往要折腾一两天。这篇文章的目的就是帮你把这一两天压缩到一两个小时。1. 手眼标定的核心原理与方案选型1.1 手眼标定到底在解什么AXXB 的直觉理解先说一个特别朴素的类比。你坐桌子前眼睛看到桌上一杯水然后伸手去拿。你的视觉系统能告诉你水杯在“眼睛坐标系”下的位置你的运动系统能控制“手坐标系”运动但眼睛坐标和手的坐标之间到底是什么关系大脑需要有一个固定的映射。手眼标定就是求这个映射。机械臂场景里我们把相机坐标系当作“眼睛”把机械臂末端tool0当作“手”。对眼在手上eye-in-hand的布置相机固定在机械臂末端相机跟着末端一起动。当机械臂从一个位姿运动到另一个位姿时机械臂末端相对基座坐标系的变换是已知的从控制器读出来相机相对标定板的变换也能通过识别标定板算出来。问题在于相机相对机械臂末端的变换是一个固定值不随运动改变这个固定变换就是我们要求的 X。数学化一点在第一个位姿下末端到基座的变换是 A₁相机到标定板的变换是 B₁第二个位姿下末端到基座是 A₂相机到标定板是 B₂。因为 X 固定所以有 A₁X B₁ A₂X B₂整理一下就是 (A₂⁻¹A₁) X X (B₂⁻¹B₁)简写为 AX XB。A 是相邻两次末端运动的相对变换B 是相邻两次相机运动的相对变换。X 有 6 个自由度3 个旋转、3 个平移解这个方程需要多组不同姿态下的样本。这就是为什么手眼标定对“机械臂姿态变化是否丰富”特别敏感。如果每次机械臂都只是平移旋转分量很小那么 A 和 B 的旋转部分趋近单位阵方程组病态解出来的 X 旋转部分会被噪声主导。后面 3.2 节我会专门讲数据采集怎么规划位姿。1.2 两种安装方式怎么选eye-in-hand 与 eye-to-hand手眼标定按相机安装位置分成两类这个选择直接影响你要求解的矩阵。安装方式相机位置求解目标常见应用优点缺点eye-in-hand眼在手上固定在机械臂末端相机→末端cam to tool0抓取、装配、近距离检测视野随机械臂移动可以靠近工件遮挡少标定板无需固定在工作区相机质量增加末端负载视野范围有限eye-to-hand眼在手外固定在工作区上方/侧方相机→基座cam to base全局定位、上料下料、安全监控不影响机械臂负载视野覆盖整个工作区存在遮挡可能标定板需要固定在机械臂末端本文针对的是最常用的场景Realsense D435i 装在 UR 机械臂末端法兰上做 eye-in-hand 手眼标定。如果你的项目是眼在手外原理和工具链类似只需要把 easy_handeye 的 eye_on_hand 参数设为 false然后让机械臂夹着标定板在不同位姿下给相机“看”即可。1.3 为什么 UR RealSense 是研究场景的黄金搭档URUniversal Robots机械臂之所以被选做这场景的主力一是它的 ROS 驱动生态非常成熟URScript、RTDE 接口开放得彻底能直接读末端实时位姿二是它的控制精度在协作臂里属于第一梯队重复定位精度 ±0.03mm 左右。手眼标定本质上依赖机械臂自身位姿读数作为真值机械臂精度越好标定结果的天然上限就越高。如果换一些精度很差的机械臂标定出的手眼矩阵旋转部分可能还行平移部分往往会明显偏。D435i 被广泛使用则是因为它在几千元价位上同时给了你彩色图、深度图、IMU、全局快门传感器。对抓取项目来说彩色图用来识别物体、深度图用来估计位姿、IMU 以后还能做视觉惯性里程计。尺寸 90mm × 25mm × 25mm、重量约 130g 的体量对 UR5 这类负载 5kg 的臂来说完全不是负担。唯一要注意的是D435i 是消费级相机没有工业级防护粉尘油污环境得加保护外壳。2. 准备工作系统环境与工具链搭建2.1 安装 ROS鱼香ROS一键安装流程与版本选择手眼标定整套工具链我推荐直接用 Ubuntu 20.04 ROS Noetic。不是 ROS 2 Humble 不行而是 easy_handeye、aruco_ros 这些标定工具的发展重心还在 ROS 1UR 官方 ROS 驱动对 Noetic 的验证也最充分你能搜到的资料九成都是 Noetic 的遇到问题好查。等你把标定流程跑通了再考虑把视觉识别部分迁到 ROS 2 也不迟。ROS 1 的安装方式手动按官方 wiki 来要配软件源、加密钥、装几百个包很多新手会卡在密钥添加那一步。现在最省事的做法是直接用鱼香ROS一键安装wget http://fishros.com/install -O fishros . fishros运行后根据提示选择安装 ROS 1 Noetic 桌面完整版即可脚本会自动配源、装依赖、初始化 rosdep。整个过程大约 20 分钟比手动操作少踩很多坑。装完之后执行source /opt/ros/noetic/setup.bash which roscore能看到 roscore 路径就说明安装成功。之后记得把 source 命令写进 ~/.bashrc不然每次开终端都要手动 source。如果后面想学 ROS 2鱼香ROS一键安装也支持 Humble不过本文的驱动和标定流程还是在 Noetic 下跑。我自己的建议是机器人控制涉及大量底层驱动、TF、消息接口选择生态最稳的一套不要追新。2.2 RealSense 驱动部署SDK 与 ROS 包D435i 在 ROS 下使用需要装 Intel 官方驱动。最省心的办法是直接用 ROS 仓库里编译好的二进制包sudo apt-get install ros-noetic-realsense2-camera但如果你希望用上相机最新的固件和完整 SDK 功能推荐从源码编译git clone https://github.com/IntelRealSense/librealsense.git cd librealsense sudo apt-get install libglfw3-dev libssl-dev libusb-1.0-0-dev ./scripts/setup_udev_rules.sh mkdir build cd build cmake ../ -DCMAKE_BUILD_TYPERelease -DBUILD_PYTHON_BINDINGSON make -j$(nproc) sudo make install编译完之后把realsense-ros源码放进工作空间mkdir -p ~/ur_ws/src cd ~/ur_ws/src git clone https://github.com/IntelRealSense/realsense-ros.git -b ros1-legacy cd ~/ur_ws catkin_make跑起来验证一下roslaunch realsense2_camera rs_camera.launch rostopic list | grep image_raw看到/camera/color/image_raw和/camera/depth/image_rect_raw就正常了。这里我建议把彩色图分辨率设为 640x48030fps深度图选对齐到彩色图的 stream这样后面视觉识别时不用自己做对齐roslaunch realsense2_camera rs_camera.launch align_depth:true color_width:640 color_height:480 color_fps:302.3 标定方案选型easy_handeye aruco_ros市面上做手眼标定的方案有三类第一类是 OpenCV 里自己写用cv2.calibrateHandEye这种函数核心代码也就几十行但标定板角点检测、位姿求解、数据同步全都要自己处理适合学习原理不适合工程效率。第二类是visp_hand2eye_calibration这是一套基于 ViSP 视觉库的标定工具标定板用棋盘格命令行交互功能全但界面古早参数配置坑不少。第三类是easy_handeye它把数据采集、求解、验证、TF 发布都做了 GUI 化配合 aruco_ros 识别 ArUco 码是目前社区里成功率最高、最省事的方案。本文章用第三套。整体架构是这样相机实时识别固定在某个位置的 ArUco 标定板发布标定板相对于相机光学坐标系的 TFUR 驱动发布 base 到 tool0 的 TFeasy_handeye 在 rqt 面板里同时监听这两个 TF每采一个样本就记录一对变换关系积累足够样本后调用标定库求解输出 cam 到 tool0 的变换矩阵并生成 YAML 配置文件。安装 easy_handeyecd ~/ur_ws/src git clone https://github.com/IFL-CAMP/easy_handeye.git cd ~/ur_ws catkin_make2.4 标定板准备ArUco 参数与打印注意事项ArUco 标定板的获取可以直接生成并打印。在 easy_handeye 的示例里它用的是一个单 marker但实际标定中我建议用aruco_ros配合单个 marker 就够了不用 board。选择一个 4x4_50 字典里的 markerID 随意比如 10。去这个在线生成器比如 OpenCV 官方示例或第三方 ArUco 生成网站下载对应字典的 marker 图片打印在 A4 纸上然后贴到平整的硬纸板或者铝板上。关键要求有三条打印尺寸要精确。marker 的实际边长远用卡尺量不要相信打印设置里的名义值。标定输入尺寸差 1mm平移结果可能差好几 mm。板子必须平整。不要贴在软纸上或弯曲的表面上ArUco 位姿解算假设 marker 是平面板子弯曲会直接引入姿态误差。尺寸要合理。以 D435i 的视野和常用工作距离300~800mm来说marker 边长 30~50mm 比较合适。太大导致在近距离出画太小导致远距离检测不稳定。启动 aruco_ros 节点时把实际测量值填进去launch node pkgaruco_ros typesingle namearuco_single remap from/camera_info to/camera/color/camera_info/ remap from/image to/camera/color/image_raw/ param namemarker_size value0.037/ param namemarker_id value10/ param namereference_frame valuecamera_color_optical_frame/ param namecamera_frame valuecamera_color_optical_frame/ /node /launch这里的reference_frame一定填camera_color_optical_frame而不是camera_link。这是个高频坑Realsense 发布的 RGB 图的光学坐标系是 x 向右、y 向下、z 向前的右手系而相机硬件坐标系是 z 向后。如果填错aruco_ros 解算出的 marker 位姿会有 180° 的旋转或镜像错位标定结果必错。3. 实操过程从数据采集到求解手眼矩阵3.1 机械臂末端位姿获取UR 驱动的两种方式easy_handeye 默认通过 TF 树来读取机械臂末端的位姿也就是它会在 TF 里查找tool0相对于base的变换。因此你的 UR 驱动必须正确地发布这段 TF。UR 的 ROS 驱动有两条路线路线一ur_robot_driver ExternalControl URCap推荐这是 Universal Robots 官方推荐的方案通过示教器上安装 ExternalControl URCapROS 端通过以太网向控制器发送控制指令同时接收反馈并发布 TF 和关节状态。cd ~/ur_ws/src git clone https://github.com/UniversalRobots/Universal_Robots_ROS_Driver.git cd ~/ur_ws catkin_make在示教器上安装 ExternalControl-URCap 之后启动roslaunch ur_robot_driver ur5_bringup.launch robot_ip:192.168.1.50注意你的实际 IP 要改成示教器上查到的机械臂 IP。路线二ur_rtde 自行发布 TF适合你没装 URCapur_rtde 是一个 Python/C 库直接通过 RTDE 协议从控制器读数据。它不发布 TF需要你自己写一个节点把getActualTCPPose转成 TF。如果只是做标定我更推荐路线一省事且数据同步性更好。还有一个容易踩的坑标定时要确保 TF 树里base到tool0是准的。UR 驱动默认发布的 tool0 是法兰中心坐标系。如果你的机械臂末端加装了夹具并且做了 TCP 标定这里要格外小心工具坐标系变换是否已经被包含在 TF 里。标定相机到 tool0 的变换时我建议先不要加其他工具直接把相机安装在法兰盘上让相机坐标系对应驱动发布的 tool0 或者一个明确的工具坐标系。后面如果想加夹爪再单独标夹爪 TCP 就行。3.2 数据采集要点20~30 个位姿怎么规划手眼标定能不能成数据质量占七成。我的建议是 20~30 个样本覆盖不同高度、不同角度、不同距离而且要保证每个位姿下 ArUco 标定板都完整出现在画面中、清晰不失焦。机械臂位姿规划时我有几条实践经验旋转变化要充分。相邻位姿之间尽量有明确的姿态变化比如绕末端相机坐标系的 x 轴转 20°再绕 y 轴转 20°不要只是平移。姿态变化不足会让旋转矩阵求解病态。工作空间分散。让相机在标定板前方、上方、侧方都采集距离保持在 300~600mm 之间。如果所有样本集中在很小的一块区域平移分量的约束不足。每个位姿稳定再采。机械臂运动到位之后等 1 秒以上让振动衰减再点 Take Sample。运动过程中的图像是模糊的ArUco 检测的 corner 精度会明显下降。标定板不要太小。如果你相机距离 500mmmarker 边长 37mm 在画面里也就占一小块corner 的亚像素精度有限。条件允许就加大 marker 尺寸。实际采集时我习惯写一段 UR 脚本让机械臂自动走过一系列预设位姿然后在每个位姿停下来等人在易用界面里点采样def move_and_wait(pose): movej(pose, a0.5, v0.2) sleep(1.0) end如果你想省事也可以直接手动用示教器把机械臂挪到目标位姿但 20 多个位姿手动挪会很累自动化脚本体验好很多。无论哪种方式记住一点机械臂没停稳之前绝对不要采集数据。3.3 运行 easy_handeye 完成一次标定下面是我实测下来完整的一次标定流程。先写好一个方便启动所有节点的 launch 文件launch include file$(find realsense2_camera)/launch/rs_camera.launch arg namealign_depth valuetrue/ /include include file$(find aruco_ros)/launch/single.launch/ include file$(find easy_handeye)/launch/setup_eye_on_hand.launch arg namerobot_base_frame valuebase/ arg namerobot_effector_frame valuetool0/ arg nametracking_base_frame valuecamera_color_optical_frame/ arg nametracking_marker_frame valuearuco_marker_frame/ /include /launch然后分终端启动roslaunch ur_robot_driver ur5_bringup.launch robot_ip:192.168.1.50 roslaunch handeye_calib setup.launch接着打开 rqtrqt在 rqt 的插件列表里找到easy_handeye Hand Eye Calibration打开标定面板。界面上主要有几个按钮Take Sample采集当前帧数据、Compute计算手眼矩阵、Save保存结果。操作流程把机械臂移动到第一个位姿确认 rviz 或相机画面上 ArUco marker 被识别。点击Take Sample左侧列表会新增一条样本显示末端位姿和 marker 位姿的时间戳。移动机械臂到下一个位姿重复采样。积累 20~30 个样本后点击Compute。easy_handeye 会调用标定库解算并显示重投影误差。如果误差合理点击Save结果会存到~/.ros/easy_handeye/下的 YAML 文件里。我在实机上跑完一般Compute后看到的误差在 0.1~0.3 像素这个量级。如果误差到几像素甚至十几像素说明数据中有异常样本、marker 尺寸填错或 TF 有断链别急着 Save先排查。3.4 标定结果验证与 TF 发布标定产生的 YAML 文件里保存了transformation_matrix或者四元数平移。要在在线运行时让它生效有两种方式。第一种直接用 static_transform_publisher 发布 TFrosrun tf2_ros static_transform_publisher 0.123 -0.456 0.789 -0.707 0.707 0 0 tool0 camera_color_optical_frame这里的平移、四元数替换成你 YAML 里的实际值。发布之后在 RViz 里看一眼固定坐标系选 base就能看到 camera 坐标系和 aruco_marker_frame 的 TF 关系移动机械臂时 marker 坐标系应该保持在标定板的位置。第二种把 YAML 文件加载到 easy_handeye 自带的 launch 里它会在启动时自动发布静态 TF。对于后期视觉抓取项目我更建议直接在 launch 里加 static_transform_publisher简单直接。验证精度最直观的方法是做一个“点到点”实验把 marker 放在工作台上用相机识别 marker 相对相机的位姿通过手眼矩阵把它变换到 tool0 坐标系再通过 UR 的 TF 变换到 base最后让机械臂末端去到这个点。如果末端中心或者用一根针能精准戳到 marker 中心就说明整个链路是通的。误差控制在 2~3mm 以内算是合格1mm 以内算优秀。4. 常见问题与精度排查实录4.1 ArUco 检测不到或检测不稳定怎么办这是我在实际调试中遇到最多的第一类问题。现象是 rqt 图像里 marker 边框时而出现时而消失或者 corner 抖动剧烈。排查顺序看曝光。Realsense 相机的自动曝光在逆光或光照不足时画面会过曝或偏暗ArUco 的二值化就会不稳定。手动把曝光固定比如rostopic pub /camera/camera_info里调整或者直接在 rs_camera 的 launch 里设auto_exposure:false并手动给一个曝光值能显著提高检测稳定性。看 marker 尺寸参数。第 2.4 节说过marker_size 要用实测值。如果填得偏大或偏小位姿解算的尺度会错这不会让检测失败但会让标定结果平移整体偏移。摄像头图像话题是否正确。aruco_ros 需要的是图像话题和对应的 camera_info 话题确保 remap 正确。很多人改了相机分辨率但 camera_info 里的内参没更新检测出的位姿就会偏。marker 面不要反光或部分遮挡。打印纸在强光下反光会让白边框变灰检测失败。贴到哑光板或者调整拍摄角度。另外强烈建议在完成 aruco_ros 启动后先单独订阅一下话题确认 marker 位姿持续稳定输出rostopic echo /aruco_single/pose看连续输出的四元数是否有明显跳变。如果跳变很大不要开始标定先搞定图像质量。4.2 标定结果偏差大的核心原因先明确“偏差大”的判定标准如果你的重投影误差已经小于 0.3 像素但最后抓取还是偏很多那问题通常不在标定求解本身而在系统别的环节。我整理过自己排过的问题按影响从大到小排问题现象排查与解决机械臂 TCP 标定错误标定误差正常但操作偏差固定用示教器自带的 TCP 标定流程重新标定工具坐标系确认 base 到 tool0 的变换真实准确相机安装松动每次开机标定结果都不一致法兰盘与相机之间的螺丝加弹垫标定完就别再动相机标定板不平整重投影误差偏大角点误差大换硬板用卡尺确认 marker 平整数据姿态变化不够求出的 X 旋转部分异常补足大角度旋转样本避免重复位姿使用了错误的坐标系标定结果看起来很怪重投影误差巨大确认 aruco 的 reference_frame 是 camera_color_optical_frame不要用 camera_link数据同步精度不足机械臂运动过快或采样时机太早机械臂每个位姿稳定 1s 再采集或者在运动停止后再触发采样marker 实际尺寸输入错误平移部分系统性比例偏差用卡尺多次测量取平均填入 marker_size还有一个比较隐蔽的问题机械臂在以movej运动的过程中如果标定程序是自动发布采样指令的可能采到运动模糊帧。所以我前面强调任何自动采样都要和机械臂运动状态互锁。如果不想改驱动的服务接口最简单的办法就是人工点按 Take Sample保证每次都在机械臂停止后采。4.3 精度验证的三种实操方法标定完成不代表能直接用我建议至少做两层验证。第一层是数学意义验证。看 easy_handeye 输出重投影误差一般低于 0.5 像素就是可以接受但这个误差只说明数学解没病态不说明机械臂基座坐标系下物理位置对。第二层是坐标系一致性验证。在 RViz 里固定 base把相机点云和 marker 模型同时显示出来移动机械臂到不同位置观察 marker 在 RViz 里是否始终落在真实标定板的位置。如果发现 marker 在某个方向上有系统性偏移比如后仰或倾斜多半是 ArUco 检测的视觉误差或者 TCP 标定误差。第三层是闭环抓取验证这个最重要。在标定板附近放一个已知尺寸的工件或者让机械臂末端装一根尖针用相机识别目标点坐标走完“像素坐标 → 相机坐标 → 手眼矩阵 → tool0 → base”这条链路让机械臂去指点。如果指偏小于 3mm说明手眼矩阵和机械臂本身的链路是通的后续视觉识别精度另算。如果指偏特别大比如 50mm不用怀疑别的手眼标定数据采集环节一定有问题重新标一次并且这次多检查 marker 尺寸和位姿规划。4.4 数据异常与常见问题速查表最后整理一张速查表建议截图存着标定出问题的时候对着查症状第一步排查第二步排查第三步排查aruco 检测不到图像清晰度、曝光话题 remap 是否正确camera_info 内参分辨率是否匹配marker 检测但位姿乱跳光照、反光marker 尺寸是否过大/过小相机帧率是否过低建议至少 15fpsCompute 重投影误差太大检查是否有异常样本剔除后重算标定板是否平整末端和 marker 位姿时间戳是否同步误差小但抓取偏检查手眼矩阵发布是否正确TCP 标定是否准确夹爪中心和 tool0 的偏移是否被忽略标定结果每次不一样相机螺丝是否松动标定板位置是否被碰到数据集姿态覆盖是否充分重采这套排查思路适用于绝大多数机械臂相机方案不限于 UR。UR、Franka、AUBO、越疆这些机械臂只要 ROS 驱动能发出准确的 TF这套 easy_handeye 流程都能套用。区别只在于robot_effector_frame的值比如有的机械臂末端坐标系叫flange有的叫tool0你要看驱动实际发布的 TF 树来确定。最后再分享一个小技巧标定用的 ArUco 板我通常会在工作区固定好之后就不再动它并且在标定板上留几个永久的识别点。这样每次重新标定都有参照物可以快速判断新的标定结果和上次相差多少能尽早发现机械臂或者相机是否有物理偏移。每更换一次相机安装位置标定必须重做哪怕只是一颗螺丝松了又拧紧位置已经变了不要想当然认为“差不多的位置就不用标”。