☰
工业机器人手眼标定实战:UR机械臂与RealSense D435避坑指南
2026/10/6 1:25:52 网站建设 项目流程

搞工业机器人手眼标定,最怕的不是算法推不下去,而是各种小坑一个接一个。我前段时间做了一套基于 Intel RealSense D435 和 UR(Universal Robots)机械臂的视觉抓取系统,前后卡在手眼标定上快一个星期,回头再看,80% 的坑都出在数据采集和坐标系定义上,真正调算法的反而不多。这篇记录把原理、步骤、坑都写清楚,给做工业机器人、系统集成或者相关毕设的同学一个可以直接照做的参考。如果你是第一次接触手眼标定,建议先把坐标系关系理清楚再动手;如果你已经标过但精度一直不理想,也可以直接跳到第 4 节的避坑清单对照排查。

1. 手眼标定原理:别急着写代码,先想清楚坐标系关系

1.1 眼在手上和眼在手外,究竟怎么选

手眼标定里的“手”指机械臂末端,“眼”指视觉传感器。根据相机安装位置,分成两种经典构型:

构型相机位置标定板位置典型应用
eye-in-hand(眼在手上)固定在机械臂末端固定在工作空间内移动相机定位、工件抓取
eye-to-hand(眼在手外)固定在工作台上方/侧面固定在机械臂末端固定视觉工位、传送带跟踪

我这次用的是 eye-to-hand:D435 固定在铝型材支架上,朝向机械臂工作区域,标定板用 3D 打印的夹具装在 UR5e 的法兰盘上。这样做的原因很简单,抓取场景里相机位置不变,只要标定一次,机械臂运动过程中不会改变视野,系统稳定性好。如果你做的是移动相机引导,比如机械臂带着相机去接近工件,那就用 eye-in-hand。二者没有绝对优劣,主要看后续算法需要相机坐标和哪个坐标系发生关系。

这里顺便提一句,最近看到有人拿 piper 机械臂做手眼标定,流程和 UR 几乎一样,UR 的优势是 ROS 驱动成熟、SDK 文档全,新手拿它练习会少踩很多环境层面的坑。

1.2 手眼标定的数学本质:AX=XB 是怎么来的

手眼标定不管哪种构型,本质上都是通过多次运动,求解一个固定不变的刚体变换矩阵。

拿最常见的 eye-in-hand 举例。相机装在机械臂末端,标定板固定不动。机械臂从位姿 1 运动到位姿 2,我们已知两个位姿下末端在基座坐标系下的变换 ( A_1 )、( A_2 ),同时通过视觉能够计算出两个位姿下标定板在相机坐标系下的变换 ( B_1 )、( B_2 )。设相机与末端之间的固定变换为 ( X ),那么相邻两次运动之间满足:

[ (A_2 \cdot A_1^{-1}) \cdot X = X \cdot (B_2 \cdot B_1^{-1}) ]

这就是经典的 ( AX = XB ) 问题。

eye-to-hand 可以通过坐标变换整理成同一个形式,最终也是求解一个 4x4 的齐次变换矩阵。所以很多开源库只提供一个核心函数,比如 OpenCV 的calibrateHandEye,就是解这个方程。理解了这一点,你就不会被各种教程里的代码绕晕——不管输入输出怎么包装,本质都是在找“相机坐标”和“机器人坐标”之间的旋转矩阵 ( R ) 和平移向量 ( t )。

1.3 手眼标定要的数据到底是什么

很多人问“手眼标定到底要采集什么数据”,一句话回答:要的是多组“机械臂末端位姿”和“标定板在相机坐标系下的位姿”的配对数据。

具体到 my 项目:

  • 机械臂末端位姿:通过 UR 的 RTDE 接口读取,是一组 ( (x, y, z, rx, ry, rz) ),其中位置单位是米,旋转部分是旋转向量,单位是弧度。
  • 标定板在相机坐标系下的位姿:对 D435 拍摄的 RGB 图像做角点检测,再用solvePnP求解出棋盘格或 ArUco 板在相机坐标系下的旋转向量和平移向量。
  • 相机内参:D435 出厂自带内参,但要想精度好,建议用 ROS 的camera_calibration工具重新标一次,尤其是图像分辨率、焦距和畸变系数。

一组数据里包含的信息看似不多,但组合起来就是一个 4x4 矩阵。注意“配对”很关键:机器人的位姿和相机的图像必须是同一时刻采的,保证机械臂完全静止后再触发拍照,这直接决定后续求解结果对不对。

2. 实验环境与准备工作

2.1 硬件清单与安装注意事项

我用到的硬件如下:

  • UR5e 机械臂一台,控制器版本 CB3,固件版本 5.x
  • Intel RealSense D435 深度相机一台
  • 棋盘格标定板一块,7x9 内角点,格子边长 30mm
  • 铝型材支架,用于固定相机
  • 3D 打印的标定板固定座,安装在机械臂法兰盘上

这里有两个容易踩的坑。第一,标定板不能用普通的 A4 纸打印就完事。手眼标定对特征点的物理尺寸非常敏感,打印纸受潮会变形,尺寸误差直接进入平移向量。建议用铝合金或亚克力背板的工业标定板,或者至少用平整的硬质基材把打印纸贴平。第二,标定板固定在法兰上时必须锁紧,不能有任何松动。法兰上如果有螺纹孔,尽量用带定位销的夹具,保证标定板平面与法兰轴线垂直。这个装好后不要频繁拆卸,否则标定结果会漂。

相机安装的时候,要让机械臂在大部分工作范围内运动时,标定板都能落在画面中间附近。不要只照顾一个角落,否则求解出的变换矩阵在远离采样区域的地方误差会很大。

2.2 软件环境:ROS + realsense-ros + UR 驱动

软件环境我建议直接用 ROS 生态,省心很多。我的环境是:

  • Ubuntu 20.04
  • ROS Noetic
  • realsense-ros 驱动
  • ur_robot_driver(配套 ur_rtde)
  • OpenCV 4.2.0
  • Python 3.8 + numpy

安装顺序上,先装 ROS,再装 RealSense 驱动,最后装 UR 驱动。UR 官方现在的推荐是使用ur_robot_driver包,它通过 RTDE 与控制器通信,能直接发布tcp_pose这样的话题。如果你不想用 ROS,只做标定实验也可以直接用官方 SDK 读 TCP 位姿,但后面的图像采集、角点检测、坐标变换全都要自己拼,工作量会大不少。

这里提醒一句:电脑最好用有线网络连接 UR 控制器,不要用 WiFi。RTDE 对实时性有要求,无线网络一旦抖动,采集到的机械臂位姿就会出错,而且这种错误很难从数据上看出来。

2.3 相机内参和标定板位姿获取

D435 的 RGB 内参在出厂时已经标定好,但我在实际测试中发现,出厂内参和重新标定后的结果在重投影误差上能差 0.3 到 0.5 个像素,别小看这点误差,经过手眼矩阵放大到机器人坐标系里可能就是几毫米。

标定内参用 ROS 的camera_calibration包,对着棋盘格录一段 20 秒左右的数据,自动求解。注意 D435 的 RGB 和深度成像共用一个镜头组,但输出内参不同,手眼标定只用 RGB,所以内参要选 RGB 对应的参数。

标定板位姿获取,我推荐用 ArUco 而不是普通棋盘格。原因在 D435 这种低分辨率 RGB 上太明显:分辨率一低,棋盘格在画面边缘很容易糊,角点检测经常失败;ArUco 的黑色边框编码鲁棒性更好,稍微有点模糊也能检测出来。当然棋盘格也有优势,它可以直接用findChessboardCorners配合cornerSubPix做亚像素优化,精度上限更高。我最后是白天用棋盘格,晚上测试 ArUco,两个流程都会写。

2.4 UR 机械臂位姿的读取方法

UR 的位姿表示必须重点强调:控制器示教器上显示的pose是位置 ( (x,y,z) ) 加旋转向量 ( (rx,ry,rz) ),这里的旋转向量不是欧拉角,也叫轴角表示。很多人直接把rx, ry, rz当成欧拉角去转旋转矩阵,结果当然不对。

把轴角转旋转矩阵,可以用 OpenCV 的Rodrigues函数,也可以自己写:

import cv2 import numpy as np rvec = np.array([rx, ry, rz], dtype=np.float64).reshape(3, 1) R, _ = cv2.Rodrigues(rvec) t = np.array([x, y, z], dtype=np.float64).reshape(3, 1) T_base_to_tool = np.hstack((R, t)) T_base_to_tool = np.vstack((T_base_to_tool, [0, 0, 0, 1]))

从 UR 读取位姿的另外一点,是要确认读的是“法兰盘中心”还是“工具中心点”(TCP)。如果机器人系统里加载了工具坐标系,RTDE 返回的tcp_pose是工具坐标系的位姿,不是法兰盘中心。手眼标定过程中,你的标定板装在法兰上,如果工具坐标系设置不对,读回来的位姿和标定板之间的关系就会差一个固定偏移,最终手眼矩阵会多出一个未知偏差。我建议直接读法兰盘位姿,也就是把工具坐标系设为空或默认,这样最干净。

3. 手眼标定实操流程:从数据采集到求解

3.1 数据采集步骤

这一步是整个手眼标定里工作量最大、也最容易翻车的地方。我直接用一段 Python 脚本完成采集,流程如下:

  1. 控制 UR 机械臂运动到第一个位姿,停下 0.5 秒。
  2. 通过 RTDE 读取当前法兰位姿T_base_to_tool。
  3. 触发 D435 拍照,保存当前 RGB 图像。
  4. 对图像做角点检测,得到标定板在相机坐标系下的位姿T_cam_to_target。
  5. 重复上述步骤,采集 18 到 22 组数据。

采集时让机械臂走不同的姿态,不要只是在原地小范围平移。要让末端姿态有较大差异,旋转角度尽量拉开,比如 30 度、60 度、90 度都来几组。这里说个经验:至少 15 组,少于 10 组求解结果会很不稳,超过 30 组收益递减,20 组左右是最合适的。

我采集时还有一个小习惯:把机械臂的轨迹设置成“移动到目标点后等待”,等完全停止再采集。因为自由度高的机械臂在运动末段往往会有微小抖动,急停瞬间拍照的图像是糊的,角点检测精度直接崩盘。

下面是一段示例数据,展示配对关系:

组号法兰位置 (m)法兰旋转向量 (rad)标定板平移向量 (m)标定板旋转向量 (rad)
1(0.312, -0.145, 0.523)(0.012, 0.531, -0.078)(-0.023, 0.041, 0.356)(0.151, -0.219, 2.938)
2(0.287, -0.009, 0.541)(-0.181, 0.487, 0.092)(-0.031, 0.028, 0.372)(0.098, -0.205, 2.956)
3(0.342, 0.118, 0.508)(0.024, 0.612, -0.213)(-0.017, 0.035, 0.348)(0.174, -0.246, 2.921)

注意,上面的旋转向量是轴角表示,不是欧拉角。你如果习惯用旋转矩阵,存数据前就可以转好。

3.2 使用 OpenCV calibrateHandEye 求解

OpenCV 4.x 里集成了calibrateHandEye,里面有多套算法,默认是CALIB_HAND_EYE_TSAI,精度足够。使用前要明确输入格式。我们这里用的是眼在手外(eye-to-hand),需要传入机械臂末端到基座的变换以及标定板到相机的变换。写代码时我用的是最不容易出错的写法:

import cv2 import numpy as np R_gripper2base_list = [] t_gripper2base_list = [] R_target2cam_list = [] t_target2cam_list = [] # 遍历采集到的每组数据 for data in datasets: # 机械臂法兰位姿 -> 4x4 -> R, t T_base_to_tool = data["T_base_to_tool"] R_gripper2base_list.append(T_base_to_tool[:3, :3]) t_gripper2base_list.append(T_base_to_tool[:3, 3].reshape(3, 1)) # 标定板在相机坐标系下 -> 需要转换成 target to cam 形式 # solvePnP 返回的 rvec/tvec 是标定板坐标系在相机坐标系下的表示, # 有的教程会取逆,取决于你解算时怎么定义 object points。 T_cam_to_target = data["T_cam_to_target"] R_target2cam_list.append(T_cam_to_target[:3, :3].T) # 根据实际定义决定是否需要转置 t_target2cam_list.append(-T_cam_to_target[:3, :3].T @ T_cam_to_target[:3, 3].reshape(3, 1)) R_cam2gripper, t_cam2gripper, _ = cv2.calibrateHandEye( R_gripper2base_list, t_gripper2base_list, R_target2cam_list, t_target2cam_list, method=cv2.CALIB_HAND_EYE_TSAI )

这里我特意没有把代码写“死”,因为 OpenCV 对R_target2cam的定义在不同版本和不同教程里有微妙区别。最稳妥的办法是:用第一组数据手算一次变换,验证一下你的输入到底应该用T_cam_to_target还是它的逆。实际上,手眼标定绝大多数“结果不对”,都是在这个环节把某个矩阵取反了。

求解完成后得到的是相机相对于机器人基座的变换矩阵。在我的眼在手外配置里,还可以直接通过这个矩阵把相机坐标下的物体坐标转到机器人基座下,供抓取使用。

3.3 用 Halcon 做一次交叉验证

如果你手头有 Halcon 的授权,我非常建议用它做一次交叉验证。Halcon 的手眼标定算子封装得很成熟,界面化操作,可以直接加载机器人位姿和标定板图像,自动输出变换矩阵。我这次在 OpenCV 求解完成后,用 Halcon 的calibrate_hand_eye跑了一遍,结果和 OpenCV 解出来的平移差在 1mm 以内,旋转差 0.1 度以内,这说明两边流程都没搞错。

Halcon 的好处是它自带一套数据采集助手,会提示你“当前位姿是否有效”,能帮你过滤掉姿态变化不够大的样本。如果你的毕设或者项目允许用商业软件,用 Halcon 标定可以省掉很多调试时间。但如果你是想深入掌握原理,OpenCV 手写一遍会让你对坐标系的理解更深。

3.4 标定结果精度评估

标定矩阵算出来只是第一步,怎么评价结果才是关键。我用了两种验证方式。

第一种是重投影误差。把标定板在机器人基座下的位姿,通过手眼矩阵投影回相机坐标系,再和视觉直接观测到的位姿做差,计算旋转和平移误差。正常情况下,平移误差应该在 1 到 2mm 以内,旋转误差在 0.2 度以内。

第二种是实际点验证。让机械臂带着标定板运动到一个任意的新位姿,视觉识别出标定板在相机坐标系下的位姿,然后通过手眼矩阵转换到机器人基座坐标系,和 UR 控制器里读到的真实法兰位姿比较。这个验证更直观,也更让人放心。

我当时标完第一版,平移误差能到 8mm,以为是算法问题,后来才发现是机械臂读错了 TCP,修正之后一下降到 1.2mm。所以精度评估遇到问题,先回头查数据源,不要急着调算法。

4. 避坑指南与常见问题排查

4.1 数据采集阶段最常犯的错误

首先,姿态变化不够。很多人采 20 组数据全是平动,旋转向量几乎没有变化。方程 ( AX=XB ) 需要充分的旋转激励,否则方程组秩亏,求出来的解是无数个里面的某一个。让机械臂末端绕着不同轴多转几个“大角度”,每组之间至少相差 10 到 20 度。

其次,标定板出视野。有的采样点位姿虽然满足旋转要求,但标定板跑到画面边缘甚至出去了,视觉提取会失败或者精度很低。建议采样前先手动走一遍程序,确认所有点都能看到完整标定板。

第三,标定板松动。这个问题很隐蔽,机械臂运动过程中如果标定板夹具出现微小滑动,相当于每次标定板在末端坐标系下的位姿都变了,数据自相矛盾。标定前用力晃一晃夹具检查一下,花不了几秒钟。

4.2 D435 相机的特有坑

D435 虽然是深度相机,但手眼标定实际用的是 RGB 图像。此时要格外注意两点。

第一,自动曝光一定要关掉。棋盘格在不同角度下亮度差异很大,自动曝光会导致角点检测的精度波动。我习惯把曝光时间固定在一个中间值,比如 200ms,同时把增益也固定。

第二,RGB 图像分辨率不要盲目拉高。D435 的 RGB 在 640x480 下刷新率更高,帧更稳,实际角点检测精度反而不比 1280x720 差多少,因为高分辨率下 USB 带宽容易不足,图像会丢帧,丢帧会导致配对数据错位,这个坑比分辨率带来的精度损失严重得多。

另外,如果你后面还要把深度和 RGB 对齐做抓取,那要记得手眼标定是建立在 RGB 相机坐标上的,深度对齐到 RGB 后,深度图上的坐标才能用手眼矩阵直接转换。这也是很多人容易忽略的地方。

4.3 UR 机械臂位姿读取的坑

UR 控制器的位姿接口看着简单,实际坑不少。

一个是旋转表示问题,前文已经强调过,轴角不是欧拉角,这个真不能记混。我用cv2.Rodrigues转矩阵后专门打印了一组数据核对,发现如果当成欧拉角转,旋转矩阵会奇异,手眼结果必然发散。

另一个是 TCP 设置问题。UR 的控制器里默认有 Tool 坐标系,如果之前项目里装过末端工具,TCP 就变成了工具中心。读出来的tcp_pose是工具的位姿,不是法兰盘的位姿。手眼标定要求的是“末端执行器坐标系”下的变换,如果你标定板固定在法兰上,就应该以法兰盘为准。我后来在 UR 的安装界面里把 Tool 转换设成 0,直接读法兰位姿,结果一下子就稳定了。

还有一个细节是采样时刻的同步。如果机器人还在运动时去读位姿,读到的值和图像上标定板的实际位置不是同一个时刻,这个偏差会作为噪声混进求解过程。我之前用while循环一直读,没加停止判断,采集了 20 组,标定结果差到离谱。后来改成“运动完成后延时 0.3 秒再采集”,误差立刻下降。

4.4 标定结果发散或误差大的常见原因

手眼标定结果发散的常见原因,按优先级排一下:

  1. 数据配对错误:机械臂位姿和图像位姿不是同一时刻。
  2. 旋转向量转旋转矩阵的方式不对:把轴角当成欧拉角。
  3. 标定板位姿的方向定义反了:solvePnP返回的平移向量方向搞反。
  4. 机械臂 TCP 设置错误:读的位姿不是标定板所在坐标系。
  5. 采集点位姿变化不够:旋转矩阵条件数太大。
  6. 标定板尺寸写错:格子长度差了毫米级,平移误差就到厘米级。
  7. 图像模糊:运动模糊或者对焦不准,角点亚像素精度丢失。

我遇到过最头疼的一次是标定板尺寸写错。标定板是自制的,标称 30mm 格子,实际打印出来是 29.8mm,就这么 0.2mm 的误差,到手眼矩阵上就变成了 5mm 级别的平移偏差。后来换成工业级标定板,用游标卡尺量了每一个角点的间距才算解决。

4.5 一个典型问题的排查实录

有一次标定出来的矩阵,重投影误差只有 1.5mm,但实际抓取偏差有 5cm。当时我怀疑是手眼标定问题,反复重测了五六遍,结果都稳定在 1.5mm 左右。后来我才意识到,问题出在机械臂抓取程序里用了另一个工具坐标系,把手眼矩阵的结果再叠加了一次工具偏移,相当于补偿了两遍。把抓取代码里的冗余偏移去掉后,偏差立刻正常。

这个案例给我的教训是:手眼标定结果差,不一定就是标定本身的问题。如果标定矩阵的重投影误差很小,但机器人实际引导偏差很大,先查上层是不是多次叠加了坐标系变换,不要反复重标定浪费时间。

回头说一个小经验:手眼标定不是一锤子买卖。只要相机位置、机械臂基座位置、标定板安装方式任何一个变了,就必须重新标定。就算什么都没动,运行三个月后也建议重新标一次,因为机械臂自身的绝对定位精度会随温度和磨损漂移。最后再分享一个笨但有效的验证方法——标定完成后,用机械臂带着一个尖点去碰相机画面里的一个固定物体尖角,看看视觉估算的位置和实际碰到的位置差多少。这个方法不需要任何复杂工具,比看误差曲线更直观,我每次标完都要去“摸一下”才放心。

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

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

立即咨询