第一次带着UR5和D435i做手眼标定时,我盯着标定结果里的平移向量看了半天,感觉哪里都不对——x、y、z全是厘米级,而且和机械臂末端法兰的真实结构完全对不上。排查了整整一个下午,最后发现是相机光学坐标系选错了,选成了深度相机的body frame而不是彩色相机的optical frame。类似这样的坑,在这个流程里至少有七八个。这篇把自己从零开始搭ROS环境、装驱动、跑easy_handeye到最终验证通过的完整过程写清楚,包括所有我踩过的坑和处理思路,希望能帮后来的人少走几个晚上。
网上关于手眼标定的资料很多,但大多只讲理论或者只给个命令,没人告诉你UR5的ExternalControl插件没装会报什么错,也没人告诉你D435i的彩色图和深度图时间戳对不上会导致标定板检测抽风。这篇按实际操作的顺序来,从环境搭建到标定结果验证,每个环节都给你能直接照着做的命令和配置。
1. “眼在手上”到底在算什么:先理解手眼标定的本质
1.1 机器人的眼睛长在手上,坐标就乱了一套
UR5配合D435i做抓取,常见的安装方式是相机固定在机械臂末端法兰上,跟着机械臂一起动,这就是典型的eye-in-hand,眼在手上。
这个构型有个很直观的问题:Realsense看到物体时,物体的坐标是在相机坐标系下表达的,相机坐标系本身就是运动的。你让机械臂去抓这个物体,机械臂只认识自己base_link坐标系下的坐标。从一个运动的相机坐标系,换算到固定的机械臂基座坐标系,中间必须经过几层坐标变换:
- 像素坐标 → 相机光学坐标系(依赖相机内参,由标定或出厂参数给出)
- 相机光学坐标系 → 机械臂末端tool0坐标系(这就是手眼标定要求的东西)
- 机械臂末端tool0坐标系 → 机械臂基座base_link坐标系(依赖机械臂正向运动学,UR5自己会算)
手眼标定解决的就是中间那一层:求出相机装在末端上之后,相对tool0的固定位姿变换,也就是手眼矩阵。
1.2 AX=XB:不同姿态的约束方程
手眼标定不需要任何额外的测量设备,只需要机械臂带着相机移动到多个不同的姿态,在每个姿态下记录两件事:机械臂的正向运动学结果,也就是tool0相对于base的位姿;通过相机检测标定板得到的标定板相对于相机的位姿。
当机械臂从姿态1运动到姿态2时,从机械臂的角度看,tool0从P1变到了P2,这是一个相对运动,记作A。从相机的角度看,标定板在相机坐标系下的位姿从B1变到了B2,这也是一个相对运动,记作B。手眼矩阵X是固定不变的。于是就有了经典的A·X = X·B。
A,B都是已知量,X是未知量,两组以上的运动就能构造出约束。实际求解常用Tsai-Lenz算法、Daniilidis四元数方法等,easy_handeye工具里默认用的是visp求解器,背后就是这些数学方法。
1.3 标定结果到底长什么样
标定结果的最终产物是一组6自由度位姿:平移向量x,y,z加上旋转的四元数或者旋转矩阵。它的含义是相机坐标系在tool0坐标系下的位姿描述。
得到这个结果后,你在运行时把相机检测到的物体坐标先变换到tool0,再结合UR5实时给出的tool0在base下的位姿,就能算出物体在base坐标系下的坐标,机械臂才能去规划抓取路径。
注意:很多人看到标定结果里有旋转矩阵和平移向量,不知道怎么看是否正确。平移向量的物理意义很直接:它应该跟你实际安装相机时相机相对法兰的位置大致匹配。比如相机装在法兰前方5厘米,那z方向通常应该在0.05米左右。如果结果完全离谱,说明标定过程有问题,赶紧回去检查。
2. 环境搭建:UR5、D435i、ROS三件套的详细配置
2.1 系统与ROS版本:Ubuntu 20.04配Noetic最省心
如果你不是对ROS特别熟练,我建议直接用Ubuntu 20.04 + ROS Noetic。UR5的ur_robot_driver、MoveIt和Realsense的ros驱动在Noetic下兼容性都最好,网上资料也最多。
ROS安装网上教程很多,核心步骤就几步:
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' sudo apt install curl curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo apt update sudo apt install ros-noetic-desktop-full sudo rosdep init rosdep update echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc装完后验证一下:
roscore能正常启动就没问题。如果你在国内,ROS源建议换成国内镜像,不然sudo apt update那一步能卡到怀疑人生。
2.2 UR5端:ExternalControl插件是绕不过去的坎
UR5要通过ROS直接控制,需要在机器人示教器上安装Universal Robots官方的ExternalControl URCap插件。
首先在示教器上确认软件版本。我的UR5当时是CB3系统,软件版本3.14.1,对应的URCap版本是1.0.4。下载好.urcap文件后,通过示教器屏幕右上角的小图标进入设置页面,选择URCap,把文件装进去。装完后重启示教器,会在程序选项卡里多出一个ExternalControl程序指令。
然后在UR5的程序里先添加ExternalControl指令,设定好要连接的IP和端口。UR5的IP一般默认是192.168.1.50,电脑的IP要设成同一网段,比如192.168.1.100。ExternalControl指令里的IP要填电脑的IP,因为机器人要主动连过来。
电脑端需要编译ur_robot_driver:
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/UniversalRobots/Universal_Robots_ROS_Driver.git git clone https://github.com/ros-industrial/ur_msgs.git cd ~/catkin_ws && catkin_make启动UR5驱动:
source ~/catkin_ws/devel/setup.bash roslaunch ur_robot_driver ur5_bringup.launch robot_ip:=192.168.1.50启动后在示教器上点运行程序,让UR5进入ExternalControl模式。如果驱动节点没有报错,说明通信正常。注意UR5必须处于运行状态,不然程序不执行,驱动会一直等待。
2.3 D435i驱动安装与固件检查
Realsense D435i的驱动分两层:底层librealsense2,上层ROS封装realsense-ros。
librealsense2推荐用Intel官方源安装:
sudo mkdir -p /etc/apt/keyrings curl -sSf https://librealsense.intel.com/Debian/librealsense.pgp | sudo tee /etc/apt/keyrings/librealsense.pgp > /dev/null echo "deb [signed-by=/etc/apt/keyrings/librealsense.pgp] https://librealsense.intel.com/Debian/apt-repo `lsb_release -cs` main" | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt update sudo apt install librealsense2-dkms librealsense2-utils安装完插入相机,运行:
realsense-viewer能看到画面并显示固件版本就说明底层没问题。D435i的深度画面和彩色画面都应该正常。如果提示固件过旧,可以在realsense-viewer里升级固件,但不能中断断电。
然后是realsense-ros:
cd ~/catkin_ws/src git clone https://github.com/IntelRealSense/realsense-ros.git cd realsense-ros git checkout ros1-development cd ~/catkin_ws catkin_make启动相机节点:
roslaunch realsense2_camera rs_camera.launch查看话题:
rostopic list | grep camera正常情况下会看到/camera/color/image_raw、/camera/color/camera_info、/camera/depth/image_rect_raw、/camera/aligned_depth_to_color/image_raw等话题。
提示:如果你是第一次配环境,建议先把相机和机械臂分别驱动起来,确认各自没问题再往下走。混合在一起排查问题会很痛苦。
2.4 时间戳不同步:一个很隐蔽的干扰源
UR5和相机各发各的话题,如果电脑性能一般,图像话题和机械臂状态话题的时间戳差太多,会导致采集标定数据时机械臂位姿和图像对不上,标定结果就会飘。
我是这样处理的:所有节点用同一个use_sim_time不算最优解,因为真实硬件下use_sim_time设置为true反而可能引入时钟问题。正确做法是确保电脑时间准确,用NTP服务同步系统时间,并把相机的时间戳时钟设置为wall clock。
在启动realsense-ros时,可以加参数:
roslaunch realsense2_camera rs_camera.launch initial_reset:=true这样做能让相机在启动时进行一次内部时钟重置,减少时间戳跳变。
3. 标定工具链:easy_handeye + visp组合
3.1 为什么选easy_handeye,而不是自己写
手眼标定的核心求解的数学就是AX=XB方程。你完全可以自己用Python的numpy或者ROS的tf库写一个离线求解脚本,网上也有不少现成算法。但实际标定过程中最麻烦的不是解方程,而是采集数据:每个采样点要保证标定板在相机视野中心区域,机械臂姿态要足够多样,有时候一个下午的努力因为某一组数据有噪声,标定结果就崩了。
easy_handeye这个工具最大的价值在于它把整个流程工程化了:一边通过MoveIt控制机械臂运动,一边通过aruco或者visp检测标定板,自动记录姿态对,最后调用求解器完成计算,并且自动把结果保存成TF静态变换,下次启动直接加载使用。整个过程在RViz里可视化,操作直观很多。
当然,easy_handeye也不是万能的。它对标定板检测比较敏感,光照变化大或者标定板模糊时容易检测失败。这时候需要人工干预,或者换aruco标定板方案。
3.2 安装easy_handeye和visp
我用的组合是:easy_handeye2调用visp用于标定板检测,aruco用于标定板生成。easy_handeye官方同时支持visp和aruco,visp检测棋盘格更稳,aruco更方便。
安装依赖:
sudo apt install ros-noetic-visp ros-noetic-aruco ros-noetic-aruco-ros编译easy_handeye:
cd ~/catkin_ws/src git clone https://github.com/andre-woeltjen/easy_handeye.git cd ~/catkin_ws catkin_make注意:老版本的easy_handeye在很多ROS发行版下编译会报错,你如果用的是我上面这个仓库分支,基本不会有问题。如果编译报
Eigen相关的错误,通常是缺少libeigen3-dev,安装一下就好。
3.3 修改launch文件,把坐标系串起来
这个环节最容易出错。easy_handeye的launch文件里,你需要明确告诉它:
- 机器人臂的规划组名称,我用的是MoveIt配置里的
arm_group - 机械臂末端的TF坐标系,UR5里一般是
tool0 - 相机坐标系的名称,D435i彩色图对应的是
camera_color_optical_frame - 标定板是Aruco还是棋盘格
我改过之后的eye_on_hand.launch核心部分大概是这样:
<launch> <arg name="namespace_prefix" default="ur5_d435i" /> <arg name="robot_base_frame" default="base_link" /> <arg name="robot_effector_frame" default="tool0" /> <arg name="tracking_base_frame" default="camera_color_optical_frame" /> <arg name="tracking_marker_frame" default="aruco_marker" /> <include file="$(find easy_handeye)/launch/eye_on_hand.launch"> <arg name="namespace_prefix" value="$(arg namespace_prefix)" /> <arg name="robot_base_frame" value="$(arg robot_base_frame)" /> <arg name="robot_effector_frame" value="$(arg robot_effector_frame)" /> <arg name="tracking_base_frame" value="$(arg tracking_base_frame)" /> <arg name="tracking_marker_frame" value="$(arg tracking_marker_frame)" /> <arg name="move_group" value="arm_group" /> <arg name="tracking_type" value="aruco" /> </include> </launch>关键点在于tracking_base_frame填的是相机的光学坐标系。很多人标定结果漂移,就是因为填成了camera_link。D435i的camera_link在相机的物理中心,而camera_color_optical_frame在彩色相机镜头的光心位置,两者差了几厘米甚至更多。
4. 实战演练:从机械臂运动到出标定结果的完整流程
4.1 准备标定板:Aruco还是棋盘格
标定板的质量直接影响标定精度。棋盘格需要打印后用硬纸板裱起来,保证平整。Aruco板是单个标记,打印一张A4纸贴在硬板上就行,但尺寸要精确测量。
我用的是Aruco标记,边长0.06米。打印时要注意设置成实际尺寸100%比例,很多打印机会默认缩放。
4.2 启动三套节点,一个都不能少
按顺序执行:
终端1,启动相机:
roslaunch realsense2_camera rs_camera.launch终端2,启动UR5驱动:
roslaunch ur_robot_driver ur5_bringup.launch robot_ip:=192.168.1.50终端3,启动MoveIt:
roslaunch ur5_moveit_config ur5_moveit_planning_execution.launch sim:=false终端4,启动标定工具:
roslaunch easy_handeye eye_on_hand.launch启动后RViz会加载起来,能看到机械臂模型、相机图像、标定板标记。
4.3 控制机械臂采集姿态:20组是下限
在RViz里用MoveIt的拖拽功能把机械臂末端动起来,保证标定板在相机视野里清晰可见。每移动到一个新姿态,在标定工具的GUI界面点击采集数据。
采样姿态的优劣决定标定精度,具体原则是:
- 姿态要有足够大的角度变化,不能老在一个平面上平移
- 让机械臂末端绕x,y,z三个轴都旋转到不同的角度
- 标定板在图像中的位置要覆盖不同区域,不能总在正中央
- 标定板要足够大,在图像里占1/3以上的面积
- 采样数量20组起步,我一般采30组
有个小技巧:把机械臂移动到靠近工作空间边缘的姿态,然后逐步向中心移动,这样能覆盖更大的姿态范围。移动过程中尽量让机械臂末端多做一些旋转动作,不要做纯平移。
采集完所有数据后,点击标定工具里的Calibrate按钮。计算很快,几秒钟就出结果。
4.4 结果保存:静态TF发布
标定完成后,工具会在当前终端里显示手眼矩阵。确认结果合理后,点击保存按钮,它会生成一个.yaml文件,里面包含了手眼变换的平移和旋转。同时工具会提供一个launch文件,下次启动时自动发布这组TF。
roslaunch easy_handeye ur5_d435i_eye_on_hand_tf.launch这样每次启动系统,标定结果对应的静态TF就会自动发布到ROS网络里,相机检测到的坐标经TF树变换后,直接就能拿到base_link下的坐标。
5. 结果验证:标定是不是成功了,看这三个信号
5.1 旋转矩阵的数学合理性
拿到标定结果,先别急着相信GUI上的成功提示。第一关检查就是旋转矩阵。
旋转矩阵必须是正交矩阵,行列式等于1。自己用Python验证:
import numpy as np R = np.array([[r11, r12, r13], [r21, r22, r23], [r31, r32, r33]]) print(np.linalg.det(R)) print(R @ R.T)如果行列式不是1,或者R乘以R的转置后不是单位矩阵,说明求解过程中有数值问题,标定数据里存在严重噪声或错误匹配,结果不可信。
5.2 重投影误差,标定工具会直接告诉你
visp求解器在calibrate之后会输出一个后验误差,单位一般是像素。这个数值越接近0越好。我个人的经验阈值是这样的:
- 误差小于0.5像素:非常理想
- 误差在0.5到1像素之间:可用,精度够大多数抓取场景
- 误差大于1像素:建议重新采集,先检查标定板检测是否稳定
如果你的误差在0.8像素但机械臂抓取还偏很多,问题可能不在手眼标定,而在机械臂本身的绝对定位精度或者相机内参标定上。
5.3 实物验证:让机械臂去戳一个固定点
最直接的验证方式:在相机视野内放一个尖端物体,比如一支竖起来的笔。先在RViz里通过识别把笔尖的位置转换到base坐标系,然后用机械臂末端去对准这个点。
我验证时习惯让UR5末端带一个尖锐的探头,把探头直接移动到计算出的笔尖坐标位置。如果探头笔尖能和目标在几毫米内对上,这个标定结果就是可靠的。如果偏了几厘米,多半是标定过程中的坐标系搞混了。
6. 踩坑实录:ROS+UR5+D435i标定中的高频错误与处理
6.1 UR5连接失败:ExternalControl没跑起来
启动ur_robot_driver时报:
[ERROR] [URDriver]: Failed to connect to robot这个报错八成是示教器上的程序没运行。UR5的程序必须处于运行状态,ExternalControl指令执行到之后才会开启端口通信。还有一个原因是IP不在同一网段,UR5默认192.168.1.50,电脑要设成192.168.1.x,网关可以不设。
如果你已经运行了程序还连不上,检查一下程序里ExternalControl指令填的IP是不是电脑的IP,端口默认30001,不用改。
6.2 D435i没有图像话题:固件和USB的问题
有时候realsense-viewer正常,但ROS端就是没有话题。先检查有没有启动realsense-ros实例。
常见原因是相机被之前残留的进程占用了,先杀掉:
pkill -f realsense然后重新插拔USB,运行:
roslaunch realsense2_camera rs_camera.launch如果彩色图像话题有了但深度图话题没有,把enable_depth参数显式设成true:
roslaunch realsense2_camera rs_camera.launch enable_depth:=true还有一个坑是USB带宽不足,D435i是USB3.0设备,必须插在蓝色USB口上,否则带宽不够,深度图和彩色图会交替丢帧。
6.3 标定板检测不到:动态范围和高光问题
Aruco标定板检测不到,最常见的原因是打印的标定板太小、太反光或者光照太暗。D435i的彩色相机在逆光环境下动态范围有限,标定板容易过曝或者发暗。
我的处理方法是,在标定板上方打一个柔光台灯,保证标记区域亮度均匀。另外标定板不要离相机太近,D435i的近距离对焦范围在0.1米以上,太近了图像模糊。
如果用的是棋盘格,还容易出现角点漏检。这时候把棋盘格的行列数配置好,并且尽量保持标定板平面正对相机的视角,不要倾角太大,visp检测的鲁棒性才会高。
6.4 MoveIt拖拽时机械臂不动或者规划失败
MoveIt里给UR5设目标姿态后规划失败的常见原因:
- 目标姿态位于工作空间外或者接近奇异点
- MoveIt的采样参数设置太严格
- 碰撞检测误判
在RViz里先把评估采样数Goal Tolerance放宽,具体在MotionPlanning插件里的Planning选项卡里调整。把Goal Orientation Tolerance从默认的0.01放宽到0.05,大部分情况下就能规划出来。
还有一个实际经验:UR5的奇异点主要看手腕的三个轴,如果腕关节角度在某些姿态下接近0度或180度,规划失败是必然的。这时候手动把腕关节姿态调开再重新规划。
6.5 标定结果蹦出离谱矩阵:优先排查坐标系
标定结果平移量是好几米或者旋转矩阵完全不符合物理直觉,几乎可以断定坐标系配置错误。
按照优先级检查这几项:
- D435i的
tracking_base_frame是不是camera_color_optical_frame - UR5的
robot_effector_frame是不是tool0 - MoveIt的规划组名称是不是
arm_group - 标定板标记的实际边长和配置里的尺寸是否一致
记得有一次我把标定板边长填成了0.08米,实际打印出来是0.06米,标定结果就出现了约1.5倍的缩放感,平移向量整体偏差明显。这个问题排查了很久才发现,所以打印之后一定要用尺子量一下。
6.6 数据采集时崩溃:图像与机械臂状态的不同步
在采集数据时,机械臂还在缓慢运动状态,图像已经采到,但机械臂的TF还没更新到位,这时记录的tool0位姿和相机图像不对应,标定出来全是废数据。
正常流程是把机械臂移动到目标姿态后,完全静止2到3秒,等RViz里的模型稳定了再点击采集。我见过有人图省事,机械臂运动过程中连续采集,结果误差大到完全没法用。
6.7 Easiest to overlook:external control程序执行完就断开
UR5的ExternalControl有一个特性:程序执行到末尾后,如果程序结束或者循环条件不再满足,端口会断开,驱动节点就报超时错误。示教器上写程序时建议用一个无限循环或者往返运动的空程序,保证ExternalControl一直保持运行:
loop: ExternalControl sleep 5.0 end这样机器人会在ExternalControl和sleep之间来回跑,通信不会断。
7. 经验沉淀:怎样让标定一次通过且精度更高
7.1 采样姿态的黄金组合
我调了几次标定后总结出一个比较稳的采样策略:把工作空间按前上、前下、左前、右前、左上、右上划分成6片,每片取4到6个姿态。每个姿态下,机械臂末端绕当前工具坐标系的不同轴旋转30到60度,保证标定板在图像中的倾角有足够变化。
这样一组采下来大概30组数据,覆盖了足够的旋转和平移范围。实测标定重投影误差稳定在0.3到0.5像素之间,比随意采样好了很多。
7.2 机械臂奇异点要提前规避
采集标定数据时,UR5容易出现手腕奇异点。一旦接近奇异点,MoveIt规划速度变慢,关节速度也不稳定,还会出现实际运动轨迹跟规划路径不符的风险。
我的习惯是先手动试运行一遍计划的姿态路径,确认所有点都远离奇异点区域才开始正式采集。UR5的奇异点区主要发生在腕关节,表现为某个腕关节角度接近0度或者180度,需要换个姿态。
7.3 定期重新标定:什么时候需要重做
手眼标定不是一劳永逸的。这些情况下必须重新标定:
- 相机被拆装过,哪怕只是拧松了螺丝重新固定
- 相机或法兰之间加了新的转接板、垫片
- 机械臂在碰撞后出现精度问题
- 标定结果在实物验证中越来越偏
我自己的项目基本是三个月重标一次,每次重新标定只需要20分钟左右,成本很低,但能避免很多后续问题。
7.4 保存标定参数:做好版本管理
标定结果保存后,我会把生成的yaml文件重命名,加上日期和相机安装位置的描述,比如ur5_d435i_tool0_20250115.yaml。这样如果后面调整过相机位置,还能对比新旧参数,判断调整量是否符合预期。
同时把机械臂型号、相机序列号、驱动版本、标定板尺寸都记录到这个文件对应目录下的说明文档里。后面排查问题时会发现这些看起来不起眼的信息其实非常救命。
整个这套流程走下来,从零开始到标定验证通过,新手大概需要两三个下午。熟悉之后,从装好环境开始计算,一次完整的标定只需要大半个小时。如果你在操作中遇到我这篇没提到的报错,不用慌,先检查坐标系配置和时间戳,这两个问题几乎覆盖了80%的手眼标定疑难杂症。