☰
UR5机械臂与Realsense D435i手眼标定完整实战指南
2026/10/8 9:00:05 网站建设 项目流程

去年我在实验室给UR5装了一台Realsense D435i,准备做视觉引导抓取。硬件接好、驱动跑通,我在图像里标出工件,把像素坐标转成相机坐标,再发给机械臂去抓——结果差得离谱,偏了七八厘米。查了两天资料才发现,位置偏不是因为相机精度差,而是因为相机坐标系和机械臂坐标系之间缺少一环固定的几何关系,这环关系就是手眼标定。

手眼标定(Hand-Eye Calibration)要做的事,用一句话概括:求相机坐标系与机械臂坐标系之间的固定刚体变换。UR机械臂配上Realsense深度相机,是很多机器人项目入门的首选组合。这篇文章分享我从零跑通、中间踩了无数坑的完整流程,适合准备做视觉抓取、装配、分拣的开发者参考。

1. 手眼标定之前:你缺的不是"识别",是坐标系变换链

1.1 为什么相机数据不能直接发给机械臂

Realsense输出的深度点云里,每个点的坐标是相对相机光心定义的。机械臂运动学给出的位置是相对机械臂基座定义的。两者没有天然联系。比如相机看到目标点在相机坐标系下是(0.3m, 0.2m, 0.8m),机械臂基座相对相机向右偏0.4m,那机械臂真正该去的位置就是(0.7m, 0.2m, 0.8m)。这个偏移加上三个方向的旋转,就是一个外参矩阵。

有朋友会问:能不能不标定,直接用尺子量相机装在哪?可以,但精度通常不够。相机安装角度偏1°,在1m的工作距离上末端误差就是1.7cm;再叠加安装位置测量误差,最终偏几厘米很正常。手眼标定的思路是用多组机械臂位姿和标定板观测数据,数值求解这个变换,理论上可以把误差控制在毫米量级。

这里有一个容易混淆的概念:手眼标定和相机内参标定不是一回事。内参标定解决的是"像素坐标到相机坐标"的问题,描述的是相机本身的焦距、畸变、光心;手眼标定解决的是"相机坐标到机械臂坐标"的问题,描述的是相机和机械臂之间的刚体变换。Realsense出厂自带内参,但如果在手眼标定前不确认内参是否还准,后面外参也会被带偏。我建议至少做一次重投影验证,再用棋盘格或Kalibr重新标定一次,成本很低,收益很大。

1.2 eye-in-hand 和 eye-to-hand 的本质区别

手眼标定分为两种模式,对应两种相机安装方式。

eye-in-hand是把相机固定在机械臂末端法兰盘上,相机跟着机械臂一起动。求解目标是相机坐标系和末端坐标系之间的变换。这种模式适合近距离识别、目标位置固定或需要贴近工件的场景,比如螺丝锁付、焊缝跟踪。优点是可以靠近目标,精度高;缺点是机械臂运动过程中视场会移动,而且相机重量会影响末端负载。

eye-to-hand是把相机固定在外部支架上,机械臂在相机视野里作业。求解目标是相机坐标系和机械臂基座坐标系之间的变换。这种模式适合视觉分拣、码垛、上下料,相机可以覆盖较大工作区域。优点是视野稳定,不随机械臂运动变化;缺点是距离远、精度略低,并且机械臂可能遮挡工件。

两种模式我都跑过,UR+Realsense做桌面抓取,绝大多数项目建议用eye-to-hand,因为相机固定在高处,视野覆盖整个工作台,标定一次能管很久。如果你的项目是机械臂末端带相机去识别插孔、定位孔位,那eye-in-hand更合适。

对比项eye-in-handeye-to-hand
相机位置机械臂末端外部固定支架
求解目标相机到末端的变换相机到基座的变换
标定板位置固定在工作台固定在机械臂末端或手工移动
接近目标能力好,可贴近一般,受工作距离限制
视野稳定性随机械臂运动变化固定不变
典型应用螺丝锁付、焊缝跟踪分拣、码垛、上下料

2. 环境搭建:UR、Realsense与ROS的兼容性配置

2.1 驱动版本与ROS版本的对应关系

2024年这个时间点,我建议用Ubuntu 20.04 + ROS Noetic,这是一条非常成熟的路线。UR官方驱动和Realsense官方驱动都对Noetic有现成的二进制包。如果非要用ROS 2,则推荐Ubuntu 22.04 + ROS 2 Humble,UR有ur_robot_driver的ROS 2分支,Realsense也有realsense2_ros的humble分支,但easy_handeye2等标定工具链相对没有ROS 1那么成熟,折腾成本会高不少。

UR机械臂的驱动包现在统一叫ur_robot_driver,它对Polyscope的版本有要求。我手上一台UR5的示教器系统是3.12,驱动包能直接识别;如果碰到老版本比如3.5以下,建议先升级系统再跑新驱动,否则可能报控制器版本不匹配。启动驱动前,可以先在有线网络里固定机械臂的IP,比如192.168.1.100,电脑用192.168.1.2,保证ping通,这是最容易被忽略的一步。

2.2 Realsense相机驱动安装与内参确认

安装librealsense和ROS包,命令如下:

# 添加Intel官方软件源(Ubuntu 20.04) 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 focal main" | sudo tee /etc/apt/sources.list.d/librealsense-public.list sudo apt-get update sudo apt-get install librealsense2-dkms librealsense2-utils sudo apt-get install ros-noetic-realsense2-camera

安装完后用rs-enumerate-devices查设备序列号和固件版本,确认相机能被识别。然后启动ROS驱动:

roslaunch realsense2_camera rs_camera.launch

正常情况下列表里会出现/camera/color/image_raw、/camera/depth/image_rect_raw、/camera/color/camera_info等话题。手眼标定用彩色图就够了,我建议把align_depth打开,方便后面做点云配准或抓取规划。

注意:不要跳过内参验证。Realsense在出厂时给的内参一般能凑合用,但如果你换过镜头前的玻璃片、或者相机被摔过,内参可能已经变了。验证方法是打印一张棋盘格,在距离相机50cm左右拍十几张不同角度的照片,用OpenCV的重投影误差脚本跑一下。如果重投影误差超过0.5像素,建议重新标定内参。

手眼标定过程中,相机的话题名和坐标系名必须记清楚。默认彩色图像的坐标系是camera_color_optical_frame,深度图是camera_depth_optical_frame。相机驱动还发布camera_link,注意别混淆。标定时统一用彩色相机,配置文件里就填camera_color_optical_frame。

2.3 UR机械臂驱动启动步骤

安装UR驱动:

sudo apt-get install ros-noetic-ur-robot-driver

启动:

roslaunch ur_robot_driver ur5_bringup.launch robot_ip:=192.168.1.100

这时候需要在示教器上打开Program,找到External Control程序并运行。如果没有这个程序,需要从UR官网或者驱动包里把externalcontrol-*.urp文件拷贝到示教器里。程序运行后,终端会出现连接成功日志。

UR驱动会发布/tf,包含base到tool0的变换,这是手眼标定的核心输入。如果出现/tf缺失或者坐标系名不对,先检查ur_robot_driver的参数配置。

我遇到过一个问题:UR驱动默认的末端坐标系是tool0,但有些项目里大家习惯用wrist_3_link,这两个坐标系在法兰盘中心有微小但关键的偏移。如果标定配置填错末端坐标系,标定结果必然不准。所以在配置easy_handeye时,一定要确认当前驱动实际发布的坐标系名,可以用下面的命令实时查看:

rosrun tf tf_echo base tool0

如果这段tf输出的平移和旋转在机械臂静止时保持不变,说明驱动正常。

另外,如果不想让机械臂在标定过程中按规划路径瞎跑,建议先在示教器上手动切换到远程控制模式,并且用ur_robot_driver的ur_driver节点保持连接。手眼标定通常需要人手动操纵机械臂到不同位姿,所以不需要MoveIt规划,反而手动模式更灵活。

3. 标定板选择与标定方程:把"拍到的板"变成"可计算的矩阵"

3.1 标定板怎么选、怎么量

标定板质量直接决定标定精度。对比市面上常见的三种标定板:

  • 棋盘格(Checkerboard):OpenCV检测成熟,但必须完整出现在画面里,部分遮挡就识别失败,而且受镜头畸变影响大,远距离角点提取容易偏。
  • ChArUco板:在棋盘格上叠加ArUco码,允许部分遮挡,即使画面里只露出一部分也能解算位姿,鲁棒性好,我主力推荐。
  • AprilTag板:识别速度快、遮挡鲁棒,但对运动模糊敏感,适合在线定位,不太适合做高精度标定。

使用ChArUco板时,有两个关键参数必须精确:棋盘格方块边长和ArUco码边长。不要买便宜打印店打出来的模糊版,用打印机300dpi以上输出,然后贴在硬质平板(铝板、亚克力板)上,保证板面平整。测量边长时用数显卡尺量取10个格子的总长取平均,比单独量一格再乘10靠谱得多。

生成ChArUco板可以用OpenCV脚本,注意OpenCV 4.7以上和4.6以下的API有差异:

import cv2 import numpy as np # OpenCV 4.7+ 用法 dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) board = cv2.aruco.CharucoBoard((11, 8), 0.023, 0.017, dictionary) img = board.generateImage((2480, 1754)) cv2.imwrite("charuco_11x8_23mm_17mm.png", img)

这里的0.023是方块边长,单位米;0.017是ArUco码边长。打印出来重新量,确认实际值和代码参数一致。如果实际打印边长变了,必须把真实值填到后面标定配置里。差1毫米,在1米距离上可能造成几厘米的转换误差。

3.2 AX=XB 到底是怎么推出来的

手眼标定的核心方程是AX=XB。具体推导如下:

以eye-in-hand为例,机械臂从位姿1移动到另一个位姿2:

  • 基座到末端的变换由机械臂驱动给出,记作H_base->tool
  • 末端到相机的变换是待求的X,固定不变
  • 相机到标定板的变换由标定板识别得到,记作Z
  • 标定板固定在工作台上,所以基座到标定板的总变换保持恒定

从基座到标定板有两条路径:H1 * X * Z1 和 H2 * X * Z2。这两个路径相等,于是:

H1 * X * Z1 = H2 * X * Z2 => (H2^{-1} * H1) * X = X * (Z2 * Z1^{-1}) => A * X = X * B

其中A是两个机械臂位姿之间的相对运动,B是两个标定板观测位姿之间的相对运动。A和B都是已知量或可测量,通过多组数据求解X。经典算法有Tsai-Lenz、Park、Daniilidis,easy_handeye内部集成了这些方法。

eye-to-hand的推导类似,但待求变换变成基座到相机的X,标定板可以固定在机械臂末端,同样能凑出AX=XB的形式。所以不管哪种模式,本质都是通过多组相对运动约束来求一个未知刚体变换。

3.3 数据采集中最影响精度的三个因素

第一是样本数量。理论上3组数据就能求解,但噪声太大,实际至少采12到15组。我一般会采20组,每组之间保证机械臂的姿态和位置都明显变化。

第二是数据姿态分集。这个最容易踩坑。如果每组数据里相机都正对着标定板、距离差不多、朝向也差不多,标定方程就退化或者病态,算出来的矩阵数学上可行,实际一验证就露馅。正确做法是让相机在不同距离(近、中、远)、不同角度(俯仰、偏航、翻滚都变化)下采集标定板。eye-in-hand模式里,机械臂带着相机绕标定板转圈,像个半球面一样包住板子;eye-to-hand模式里,让标定板在机械臂末端做不同姿态的移动,尽量覆盖相机的整个视野。

第三是标定板的成像质量。标定板不能过曝,不能有反光,不能太远导致角点模糊。Realsense的RGB相机自动曝光有时会把高光区域过曝,可以在rs_camera.launch里设置camera_color_auto_exposure为false,然后手动调曝光值,保证板面纹理清楚。

4. 用easy_handeye把整套流程跑完

4.1 安装与launch文件配置

推荐用开源包easy_handeye,它对UR和Realsense的支持很完善。安装方式:

cd ~/catkin_ws/src git clone https://github.com/IFL-CAMP/easy_handeye cd ~/catkin_ws catkin_make source devel/setup.bash

或者直接二进制安装:

sudo apt-get install ros-noetic-easy-handeye

新建一个launch文件,把相机驱动、机械臂驱动和标定节点串起来。我这里给一个实际能跑的eye-to-hand示例:

<launch> <!-- Realsense 相机驱动 --> <include file="$(find realsense2_camera)/launch/rs_camera.launch"> <arg name="align_depth" value="true"/> </include> <!-- UR 机械臂驱动(如果已经单独启动,这里可以注释掉) --> <include file="$(find ur_robot_driver)/launch/ur5_bringup.launch"> <arg name="robot_ip" value="192.168.1.100"/> </include> <!-- easy_handeye 参数 --> <arg name="eye_on_hand" value="false"/> <arg name="namespace" value="ur5_realsense_eye_to_hand"/> <arg name="robot_base_frame" value="base"/> <arg name="robot_effector_frame" value="tool0"/> <arg name="tracking_base_frame" value="camera_color_optical_frame"/> <arg name="tracking_marker_frame" value="our_board"/> <node name="easy_handeye" pkg="easy_handeye" type="eye_to_hand" output="screen"> <param name="eye_on_hand" value="$(arg eye_on_hand)"/> <param name="namespace" value="$(arg namespace)"/> <param name="robot_base_frame" value="$(arg robot_base_frame)"/> <param name="robot_effector_frame" value="$(arg robot_effector_frame)"/> <param name="tracking_base_frame" value="$(arg tracking_base_frame)"/> <param name="tracking_marker_frame" value="$(arg tracking_marker_frame)"/> </node> </launch>

eye-in-hand模式就把eye_on_hand改为true。注意tracking_base_frame填的是相机坐标系名,tracking_marker_frame是标定板坐标系名,easy_handeye会让标定板的TF挂在它下面。

4.2 数据采集与计算的具体操作

启动launch后,再打开rqt标定界面:

rosrun rqt_easy_handeye rqt_easy_handeye

界面左边显示相机图像,右边是标定板检测结果和机械臂状态。操作流程:

  1. 用示教器把机械臂移动到第一个位姿,让标定板完整出现在画面中央。
  2. 观察界面上的角点检测结果,确认标定板被正确识别。如果识别失败,先调整曝光或机械臂位姿。
  3. 点击"Take Sample"采集当前数据。程序会自动保存机械臂末端位姿和标定板位姿。
  4. 移动机械臂到下一个位姿,注意大幅改变距离和角度,让标定板出现在画面不同区域。
  5. 重复采集12到20组数据。
  6. 点击"Compute",程序调用手眼标定算法计算变换矩阵,并显示残差。
  7. 满意后点击"Save",结果会保存为YAML文件。

一个小建议:采集的时候不要着急,每采一组就记录下来是第几组、标定板出现在画面哪个区域、机械臂大概什么姿态。这样后面如果标定结果不理想,可以清楚地知道是哪个数据点造成的,直接删掉重采。我之前就是因为连续采了8组"正对棋盘格、距离几乎一样"的数据,导致标定结果在地面附近的精度尚可,换一个高度就完全偏了。

4.3 读懂输出结果并发布TF

标定结果通常是一组四元数加平移向量,例如eye-to-hand模式下YAML文件里保存的内容:

transformation: translation: x: 0.521 y: -0.341 z: 0.783 rotation: x: 0.691 y: -0.048 z: 0.182 w: 0.698

这个变换的含义是:从相机坐标系到机械臂基座坐标系(或者反过来,取决于版本),在保存时easy_handeye会提示对应的坐标系关系。如果你搞反了方向,最简单的方法是用一个静态变换发布器发布出来,再在rviz里看相机和机械臂模型的相对位置是否符合物理实际。

rosrun tf static_transform_publisher 0.521 -0.341 0.783 0.691 -0.048 0.182 0.698 camera_color_optical_frame base 100

如果rviz里Realsense相机出现在机械臂旁边正确的位置,说明方向正确。如果相机跑到了完全相反的位置,把四元数取逆,平移取反,重新发布即可。

标定完成后,建议用YAML里的参数生成一个静态TF发布节点,每次启动时自动加载:

<node pkg="tf2_ros" type="static_transform_publisher" name="handeye_static_tf" args="0.521 -0.341 0.783 0.691 -0.048 0.182 0.698 camera_color_optical_frame base"/>

这样在后续视觉抓取程序里,所有相机坐标系下的点都可以直接通过TF转换到机械臂基座坐标系下,不用自己手动做矩阵运算。

5. 验证与排坑:标定结果靠不靠谱,一测便知

5.1 三种验证方式

拿到标定结果先别急着写抓取逻辑,先做三步验证:

第一步,rviz可视化。加载机械臂模型、相机模型、标定板模型,看三者空间位置是否一致。如果相机模型出现在机械臂前面的半空中,而实际相机是吊在工作台上方,那肯定标定有问题。

第二步,手动移动验证。在相机前方放一个已知坐标的ArUco标记,用相机识别出它在相机坐标系下的位置,然后通过标定矩阵转换到机械臂基座坐标系。再用示教器把机械臂末端移到这个坐标,看末端是否对准标记中心。如果偏差在5毫米以内,基本合格;如果偏差达到几厘米,需要重新标定或排查数据采集中枢。

第三步,自动抓取测试。让机械臂走一套完整的"识别-转换-规划-抓取"流程,放一个空盒子,抓10次统计成功率。这一步能暴露所有细节问题,比如坐标系方向反了、深度误差、机械臂运动学误差等。

我见过很多人标定完看YAML文件里平移量是几十厘米、旋转角是十几度,觉得数值合理就认为成功了。实际上,旋转部分差零点几度,到工作边缘就会偏出1厘米以上。所以最终还是要做实际抓取测试,不能只看矩阵本身。

5.2 我遇到过的典型问题和处理路径

第一个问题,标定板检测不稳定。现象是Take Sample按钮经常灰色,角点检测时好时坏。排查后是Realsense自动曝光导致的过曝。解决办法是把自动曝光关掉,手动设曝光值。Realsense的曝光参数范围是1到100000,我在室内灯光下通常设40左右,再根据画面微调。也可以打开rs_camera.launch里的enable_auto_exposure参数改为false,配合color_exposure参数。

第二个问题,标定结果在某个区域准、换个区域不准。这是因为数据样本空间覆盖不够,算法解出来的变换只在样本点附近"看起来合理",外推就废了。解决方法是重新采集,把机械臂移动到更远的角落、更高的位置、更偏的角度,保证标定板在整个相机视场里均匀出现。有一个小技巧:采集数据时可以刻意让标定板出现在图像的四个角和中心,每个区域至少采三四组。

第三个问题,坐标系名填错导致结果非常离谱。我一开始把tracking_base_frame填成了camera_link而不是camera_color_optical_frame,结果算出来的平移量有1.4米。这两个坐标系之间有一个固定的旋转偏移,直接导致标定矩阵完全错误。排查方法很简单,在配置launch前先用rostopic echo /camera/color/camera_info看看frame_id字段,以实际话题里的为准。

第四个问题,UR驱动在标定过程中偶尔会断连。多数原因是示教器上的External Control程序被意外退出,或者网线松动。处理办法是标定前检查网络连接,用ping 192.168.1.100 -t保持监控。如果频繁断连,检查路由器和网线质量,不要为省事用Wi-Fi连接机械臂,必须用有线。

5.3 更进一步:用RTDE读取真实末端位姿

easy_handeye默认是通过/tf读取base到tool0的变换。但如果你是先规划目标位姿、让机械臂运动结束后才采标定数据,/tf里的是实际读数,没有问题。可如果你在机械臂运动过程中就触发采样,或者机械臂是旧型号、控制器反馈有延迟,可能引入运动学误差。

更稳妥的做法是直接通过RTDE(Real-Time Data Exchange)接口读取机械臂关节角,然后用UR运动学模型计算末端位姿。这样可以在标定前先校准机械臂本身。UR驱动的ur_robot_driver本身使用RTDE,所以连接没问题,主要是在程序里确认用到的末端位置确实是实际关节角反解出来的,而不是规划缓存。

对于普通项目,直接信任/tf已经够用。只有当抓手需要极高重复精度或者机械臂用了很长时间、关节间隙较大时,才有必要做机械臂运动学参数的识别和补偿,这个过程叫机器人标定(Robot Calibration),比手眼标定更复杂。

做完整套标定流程,我自己最深的体会是:手眼标定不神秘,但很考验耐心。坐标系填对、数据样本散开、曝光调好,这三点做到位,标定基本一遍过。如果标定结果不对,不要反复重算同一批数据,而是回去检查数据质量和配置参数。标定程序只会照着输入数据求解,数据不行,算法再强也没用。

最后分享一个值得养成的习惯:标定完成后的YAML文件,按日期和机器人编号保存好,同时给相机支架画一个安装位置标记。以后每次拆装过相机、移动过支架,都要回到标定流程重新走一遍。很多视觉抓取项目突然"失灵",最后查来查去,都是因为手眼标定参数过期了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询