机器人手眼标定入门:眼在手上与眼在手外
手眼标定求解相机与机器人法兰或基座之间的固定变换。本文讲解眼在手上与眼在手外两种方式、AX=XB 与 AX=ZB 两种数学形式、OpenCV 手眼标定函数的用法与方向约定,以及数据采集和标定板安装要点。
要点
- 眼在手上:相机装在机器人末端,求“相机→法兰”的变换;眼在手外:相机固定不动,求“相机→机器人基座”的变换。
- 经典形式 AX = XB 只求手眼变换;AX = ZB 同时求手眼变换和“机器人基座→标定板(世界)”的变换。
- OpenCV 提供 calibrateHandEye 和 calibrateRobotWorldHandEye 两个函数,输入是机器人位姿和标定板位姿,变换方向必须传对。
- 采集时各位姿之间要有足够大、方向多样的旋转,只有平移是解不出来的。
- 先把相机内参标好;标定板安装要刚性、轻量,并选用方向唯一的图案。
两种安装方式
眼在手上(eye-in-hand):相机装在机器人法兰或工具上,随机器人一起运动,标定板固定在工作台上。待求的是相机坐标系到法兰(gripper)坐标系的变换。
眼在手外(eye-to-hand):相机固定在机器人外部,例如工作站上方的支架,观察机器人的工作区域;标定板装在机器人法兰上。待求的是相机坐标系到机器人基座(base)坐标系的变换。
本文用 T_a→b 表示把 a 坐标系中的点变换到 b 坐标系的 4×4 齐次矩阵,也就是 a 在 b 中的位姿,与 OpenCV 参数名中的 a2b 含义相同。
手眼标定的数学形式
AX = XB
以眼在手上为例。第 i 个位姿下,机器人控制器给出法兰在基座中的位姿 G_i = T_gripper→base,相机通过 PnP 算出标定板在相机中的位姿 C_i = T_target→cam。待求的手眼变换记为 X = T_cam→gripper。由于标定板相对基座固定不动:
G_i · X · C_i = T_target→base(对所有 i 都相同)
取任意两个位姿 i、j,消去右边的常量,得到
(G_j⁻¹ · G_i) · X = X · (C_j · C_i⁻¹)
也就是 A · X = X · B,其中 A 是法兰的相对运动,B 是对应的相机相对运动。求解方法分为两类:先求旋转、再求平移的分步法(如 Tsai、Park、Horaud 方法),以及同时求解旋转和平移的方法(如基于对偶四元数的 Daniilidis 方法、Andreff 方法)。
一个重要结论:至少需要两次旋转轴不平行的相对运动,也就是至少 3 个位姿;实际应使用多得多的位姿来平均误差。
AX = ZB
如果不消去常量,而是把它也作为未知量,就得到机器人–世界–手眼(robot-world-hand-eye)标定的形式。OpenCV 中的写法是:
T_world→cam · T_base→world = T_gripper→cam · T_base→gripper
两边都等于“基座→相机”的变换。其中 X = T_base→world(基座到标定板)和 Z = T_gripper→cam(法兰到相机)是两个未知量,A、B 分别由相机和机器人测得。它不需要构造相对运动,一次同时求出两个变换。
用 OpenCV 求解
眼在手上:calibrateHandEye
下面的代码假设已经准备好两个列表:gripper2base[i] 是第 i 个位姿下机器人读数换算成的 4×4 矩阵,target2cam[i] 是同一时刻用 solvePnP 等求得的标定板位姿,长度单位统一为 mm。
import cv2
import numpy as np
def to_Rt(T): # 4×4 齐次矩阵 -> (R, t)
return T[:3, :3], T[:3, 3].reshape(3, 1)
# gripper2base[i]:第 i 个位姿下法兰在基座坐标系中的位姿(由机器人读数换算成 4×4 矩阵,mm)
# target2cam[i]:同一时刻标定板在相机坐标系中的位姿(solvePnP 等求得,mm)
R_g2b, t_g2b = zip(*[to_Rt(T) for T in gripper2base])
R_t2c, t_t2c = zip(*[to_Rt(T) for T in target2cam])
R_c2g, t_c2g = cv2.calibrateHandEye(R_g2b, t_g2b, R_t2c, t_t2c,
method=cv2.CALIB_HAND_EYE_PARK)
X = np.eye(4)
X[:3, :3], X[:3, 3] = R_c2g, t_c2g.ravel() # 相机 -> 法兰
# 自检:用每个位姿推算“标定板在基座中的位置”,离散度应接近测量噪声水平
p = np.array([(G @ X @ C)[:3, 3] for G, C in zip(gripper2base, target2cam)])
print("标定板位置标准差 (mm):", p.std(axis=0).round(3))
method 可选 CALIB_HAND_EYE_TSAI、PARK、HORAUD、ANDREFF、DANIILIDIS。建议用几种方法分别求解,比较结果和自检离散度;结果相差很大,通常说明数据质量有问题。
眼在手外:同一个函数,换一下输入
眼在手外时,约束关系变成 T_base→gripper(i) · T_cam→base · T_target→cam(i) = T_target→gripper,形式与眼在手上完全相同。因此仍然调用 calibrateHandEye,只是把机器人位姿取逆后传入(即传入 base2gripper),输出的就是 T_cam→base。
同时求两个变换:calibrateRobotWorldHandEye
calibrateRobotWorldHandEye 的输入是 R_world2cam, t_world2cam(标定板在相机中的位姿)和 R_base2gripper, t_base2gripper(机器人读数取逆),输出 R_base2world, t_base2world 与 R_gripper2cam, t_gripper2cam,可选 Shah、Li 两种方法。
以上用法和方向约定都已用已知真值的仿真数据在 OpenCV 4.7–4.10 上验证过。OpenCV 不同版本的模块划分有所调整,如果所用版本中找不到这两个函数,请查阅对应版本的文档。
数据采集要点
- 位姿数量:一般采集 10–20 个以上的位姿(经验值),越多越稳定。
- 旋转要大、方向要多:各位姿之间要绕不同的轴转动,相对转角达到几十度的量级(经验值)。只做平移,或者只绕一个轴转动,问题会退化,解不出来或误差很大。
- 标定板位姿要准:每个位姿下标定板都要清晰、检测完整,在画面中占较大比例并带有倾斜,PnP 的结果才可靠。
- 读数与图像严格对应:机器人到位、静止后再拍照,确认每张图对应的是哪一组机器人读数。
- 单位和姿态表示:统一使用 mm 或 m;不同机器人控制器的欧拉角顺序(如 ZYX 或 XYZ)、角度单位、四元数的元素顺序各不相同,换算错误是手眼标定失败的常见原因。
- 先标内参:先用多角度图像把相机内参标好并固定,例如使用 BDB 相机标定工作台或 OpenCV,再用它求每个位姿下的标定板位姿。
- 机器人自身的精度:机器人的绝对定位误差会直接进入结果,高精度场合要关注机器人本体的标定状态。
标定板装在法兰上时要注意什么?
眼在手外时,标定板随机器人运动:
- 刚性连接:标定板与法兰之间不能有晃动或弹性变形,转接件要有足够刚度;
- 重量:注意机器人负载,大尺寸标定板可以选碳纤维等轻质基材;
- 尺寸与视场:在所有位姿下,标定板都应在相机视场中占据足够的比例;
- 图案:机器人大角度运动时,标定板经常只有部分在视场内,ChArUco、AprilGrid 这类允许部分可见且方向唯一的图案更合适;
- 不必对中:标定板相对法兰的位姿正是 AX = XB 中被消去的量,不需要事先测量,也不要求标定板中心与法兰中心对齐;
- 固定后不再拆动:整个采集过程中保持安装状态不变。
怎样检验手眼标定结果?
- 自检离散度:上面代码最后一步,用每个位姿推算标定板在基座中的位置。手眼变换正确时,这些位置应几乎重合,离散程度应与机器人的定位精度和视觉测量精度处于同一量级;如果某几个位姿明显偏离,优先检查它们的数据。
- 多种方法对比:Tsai、Park、Daniilidis 等方法的结果应当接近。
- 实物验证:让相机识别一个点(例如标定板上的某个角点),换算到机器人坐标后,控制机器人用尖端工具去触碰它,测量偏差。在工作空间的不同位置、不同姿态下多做几次,比单一位置更能反映真实精度。
- 留出验证:保留几个位姿不参与求解,用求得的手眼变换预测这些位姿下标定板的位置,与实测比较。
常见的失败原因
- 变换方向传反:例如把
cam2target当成target2cam传入,或者眼在手外时忘了对机器人位姿取逆。结果往往是平移量明显不合理,自检离散度也会很大。 - 姿态换算错误:欧拉角顺序、内旋与外旋、角度与弧度、四元数元素顺序,任何一处弄错都会导致失败。可以先用一两个位姿手工核对:把换算出的旋转矩阵变回控制器显示的形式,看是否一致。
- 单位不一致:机器人读数用 m、PnP 结果用 mm,或者反过来。
- 数据配对错位:图像与机器人读数的顺序没有对齐,或者拍照时机器人尚未完全停稳。
- 旋转不足:位姿之间的转角太小或转轴单一,问题接近退化,结果对噪声极其敏感。
- 标定板方向翻转:使用对称图案时,个别图像中的标定板位姿差了 180°,会严重污染结果。
标定结果怎么用?
- 眼在手上:相机测得的点 P_cam 转换到基座坐标为
P_base = T_gripper→base(拍照时的读数)· T_cam→gripper · P_cam。 - 眼在手外:
P_base = T_cam→base · P_cam。 - 机器人实际控制的往往是工具中心点(TCP),不是法兰,抓取时还要计入法兰到 TCP 的变换。