联合kuka机器人, 海康visonmaster 的手眼9点标定方法
相机装在机械手末端、跟着手臂一起动,这是「眼在手上」(Eye-in-Hand)。它带来的唯一麻烦是:相机没有一个固定的参考系,你无法直接拿它和 base 建立映射。
下面这套方法绕开了这个限制 —— 先标到工具坐标系,再靠实时位姿还原回 base。整套动作只有三件事:拍一次照、示教九个点、做九次矩阵乘法。
为什么写这篇
这套东西公开资料极少,不是搜不到,是卡在三条都不管的缝里:
- 学术论文只写
AX = XB,默认 A、B 两组变换已知,从不讲这两组数从哪来; - 厂商手册讲到「指令语义」为止(KUKA 有
INV_POS(),海康 VM 有九点标定向导),跨品牌那一段没人有动机去填; - 真正踩过坑的产线工程师不写文档,写教程的人多半没在产线上标过。
所以本文不讲 AX = XB,只讲从示教器读数到 VM 输入这一段路上,每一步具体该按什么、乘什么、为什么是这个顺序。
一、为什么不能直接和 base 标定
假设你把相机坐标和 base 坐标直接做了九点标定,得到一张映射表 M。这张表成立的隐含前提是:相机和 base 之间的相对位姿固定不变。
眼在手上的结构恰恰破坏了这个前提 —— 机械手一动,相机相对 base 的位置和姿态全变。标定时相机在 P_cal,工作时相机跑到 P_now,同一个像素对应的 base 坐标差了整整 P_now − P_cal。
结论:如果直接和 base 标定,只有一个办法能用 —— 让机器人在「标定时的那个位置」拍照,一点不能动。这样相机就白装了,视野固定死了,单相机覆盖不到整台设备。
所以正确的对象不是 base,而是相机刚性连接的那个坐标系:工具坐标系 tool[1]。相机相对 tool[1] 是固定的,这个关系不随机械手运动而变。
二、标定的三步
严格按顺序做,三步操作的对象完全不同:第 1 步只拍一次照,拿到九个像素坐标和当时的工具位姿 a0;第 2 步完全不拍照,用 tool[1] 的尖依次去指这九个点,读出九个位姿 a1…a9;第 3 步做九次乘法和一次求逆,把这九个点搬到 a0 那个坐标系下。

第 1 步 · 拍照(1 次)
相机移到能看全九个点的位置,拍一张。此时保持 $TOOL = 1、$BASE = 0,读出的 $POS_ACT 就是 tool[1] 在 base 中的位姿 a0。
同一张图里,VM 一次性给出九个点的图像坐标 (u1,v1) … (u9,v9)。
第 2 步 · 示教(9 次,全程不拍照)
相机退开,换成工具尖。用 tool[1] 的尖依次去指第 1…9 个点,每指住一个点读一次 $POS_ACT,得到 a1 … a9。
尖和点物理重合,所以 ai 的平移分量就是该点在 base 下的坐标。这九次示教跟拍照没有任何关系。
注意上图里最关键的一点:a0 停在拍照位不动(虚线坐标系),而 a1…a9 在这个台面上到处跑。两者位置完全不同 —— 正因为不同,才需要第 3 步把它们搬回同一个坐标系里才能比较。
第 3 步 · 归算到拍照时的坐标系(9 次乘法)
每个点都搬进「拍照那一刻的 tool[1]」里:
Pi = INV_POS(a0) : ai i = 1…9
这九个数才是 VM 要的物理坐标,和九个像素坐标一一配对送进去。
三、这三步在 KUKA KRL 里怎么落成代码
KUKA 里两个位姿相乘用的是几何运算符 :,求逆用函数 INV_POS()。笔记里常见的 @ 等价于矩阵乘,但 KRL 里没有这个符号,敲进去会直接编译报错。
DEF calib_9pt( )
DECL FRAME a0, a1, a2, a3, a4, a5, a6, a7, a8, a9
DECL FRAME P1, P2, P3, P4, P5, P6, P7, P8, P9
$TOOL = 1
$BASE = 0
; ---------- 第 1 步:拍照,记下 a0 ----------
; 手动把相机移到拍照位(能看全九个点),然后:
a0 = $POS_ACT ; tool[1] 在 base 中的位姿
; 同一张图由 VM 给出九个像素坐标 (u1,v1) … (u9,v9)
; ---------- 第 2 步:尖指九点,逐个记录(不拍照)----------
; 手动移到第 1 个点,尖接触
a1 = $POS_ACT
; ……依次做到第 9 个点
a9 = $POS_ACT
; ---------- 第 3 步:归算到拍照时刻的坐标系 ----------
P1 = INV_POS(a0) : a1
; P2 到 P8 同理
P9 = INV_POS(a0) : a9
; P1..P9 的 X、Y 与 (u1,v1)..(u9,v9) 配对,导入 VM 九点标定
END
跑完以后每个点会有两组数,一组是图像坐标、一组是 T0 系下的物理坐标,这就是 VM 九点标定要的输入:
| 点 | 像素(VM 输出) | ai 的 X, Y(base 系,第 2 步直接读) | INV_POS(a0) : ai 的 X, Y(T0 系,第 3 步算出来) |
|---|---|---|---|
| 1 | (u1, v1) | (70.0, 70.0) | (-220.0, -10.0) |
| 5 | (u5, v5) | (160.0, 160.0) | (-130.0, 80.0) |
| 9 | (u9, v9) | (250.0, 250.0) | (-40.0, 170.0) |
表中的数与上图完全一致:a0 = (290.0, 80.0),每个点减去它就是 T0 系坐标。演示里相机在点 5 的右上方,实际现场 a0 的 Z 是拍照高度、还带 A/B/C 姿态。
四、核心推导:为什么是 INV_POS(a0) : ai
把位姿写成 4×4 齐次矩阵,a0 = [R0 t0; 0 1],ai = [Ri ti; 0 1]。
第一步:标定点在 base 下就是 ti
用尖去指第 i 个点时,TCP 和该点在物理上重合。KUKA 读出的 ai 平移分量就是 TCP 在 base 下的位置,也就是标定点在 base 下的绝对坐标。
这一步很多人会绕远路去算什么反变换 —— 不必要,因为「尖指到哪,点就在哪」。
第二步:换到 T0 系
要把 ti 表达在「拍照那一刻的工具坐标系」里,做一次坐标变换(不是位姿合成,是点的变换):
Pi^T0 = R0ᵀ · (ti − t0)
而 INV_POS(a0) : ai(等价写法 inv(a0) · ai)展开正好是:
inv(a0) = [ R0ᵀ , −R0ᵀ·t0 ; 0 , 1 ]
inv(a0) · ai = [ R0ᵀRi , R0ᵀ(ti − t0) ; 0 , 1 ]
↑旋转块没用 ↑这就是要的坐标
所以平移列就是你要的 Pi^T0。旋转块 R0ᵀRi 跟平面标定没有关系,可以直接丢掉。
一条容易忽略的好消息:平移列里只有
ti、t0、R0,完全不包含Ri。意思是 —— 示教那九次,工具姿态随便怎么摆都行,歪着点、斜着点都不影响结果,只要尖能落到点上。这个自由度比很多人以为的大得多。
第三步:顺序不能写反
常见笔误是写成 ai : INV_POS(a0)(也就是笔记里的 ai @ inv(a0))。两者的平移分量完全不同:
| 写法 | 平移分量 | 含义 |
|---|---|---|
INV_POS(a0) : ai ✅即 inv(a0) · ai | R0ᵀ(ti − t0) | 标定点在 T0 系下的坐标 —— 要的就是它 |
ai : INV_POS(a0) ❌即 ai · inv(a0) | ti − Ri·R0ᵀ·t0 | 相对变换表达在 base 系下,跟点坐标无关 |
记忆方法:把点搬进谁的坐标系,就把谁放在左边取逆。要把点搬进 T0,左边就是 INV_POS(a0)。
关于运算符,避免在 KRL 里踩空:
:—— KUKA 的几何运算符,两个 FRAME 相乘就是位姿合成,这是唯一正确的写法。@—— 通用数学记号(矩阵乘),笔记里用它没问题,但 KRL 里没有这个符号。*—— KRL 里用作数值乘法,不要拿来做位姿合成。INV_POS()—— KUKA 自带的求逆函数,返回FRAME,等价于 4×4 矩阵求逆。
第四步:这九个数就是 VM 的输入
VM 九点标定需要的是「图像坐标 ↔ 物理坐标」的成对数据:
- 输入 A:九个点的像素坐标
(ui, vi),直接取拍照结果; - 输入 B:九个点的物理坐标
Pi^T0的 X、Y 分量。
输出就是标定文件。注意这里用的物理坐标是 T0 系下的,不是 base 系下的 —— 这是整套方法能和 base 解耦的关键。
五、使用时怎么还原回 base
工作时相机在任意位置拍照,VM 按标定文件输出坐标 b。这个 b 是 T0 系下的表达,也就是「相对于拍照姿态」的偏移量。
要变回 base,乘上当时的实时位姿 c:
P_base = c : b KRL: P = $POS_ACT : b
c 就是拍照瞬间从机器人实时读出来的 $POS_ACT($TOOL = 1、$BASE = 0)。它随机器人运动时时刻刻在变,这正是这套方法的核心资产。

验证有效性的方法很简单:换两个位置拍同一个点,两次得到的 b 不同,但还原出的 base 坐标必须完全一致。不一致就说明标定文件或 $TOOL 有问题。
这里有个最容易出错的地方:
b必须先补成(X, Y, Z0)再做完整 4×4 变换,不能只把c的 XYZ 加上去。姿态恒为垂直向下时这个错看不出来,一旦为了避让把工具转了角度,立刻暴露。
六、三个必须满足的前提
这套方法本质是 2.5D:把相机坐标系的旋转部分当单位阵处理,只标定 XY 平面的仿射/单应关系。以下三条不满足,就会出现「标定时很准,换个位置就偏」。
| 前提 | 为什么必须 | 不满足的后果 |
|---|---|---|
| 工作平面 Z 恒定 | 2D 标定描述的是「某一张平面」上的映射,平面外推不成立 | 工件厚度变了,像素当量跟着变,XY 整体缩放 |
| 拍照高度恒定 | 相机到平面的距离决定尺度,这是标定文件里写死的 | 拍照高度差 5 mm,0.1 mm 级的精度就没了 |
| 九点共面且分布开 | 仿射/透视拟合需要有效的数据分布 | 点挤在视野中间,边缘外推误差被放大数倍 |
三条都满足时,这套方法在 XY 平面上是精确的,不是近似。
七、现场最容易踩的四个坑
坑 1:TCP 没标定,标出来永远差一个固定偏置
ai 的平移分量是 tool[1] 的 TCP,不是法兰中心。必须先:
- 用 4 点法或 6 点法把
tool[1]的 TCP 标到尖点上; - 确认机器人当前
$TOOL = 1。
否则九个点整体带同一个偏置 —— 标定文件看起来正常,但结果系统性偏移,而且你怎么调都对不上。
坑 2:相机装歪超过 3° 时,别用三点仿射
相机光轴与工具 Z 轴有夹角时,真实的「像素 → 平面」关系是单应(homography),不是仿射。VM 的九点标定支持两种解算:
- 三点仿射:只吸收平移、旋转、缩放、剪切 —— 倾斜误差全部留下;
- 九点透视:能把倾斜吸收进去(前提是九点共面、不共线)。
相机目视是垂直的,也建议先用九点透视试,比较残差再定。
坑 3:A/B/C 转矩阵的旋转顺序
KUKA 的 A、B、C 是 Z-Y-X 固定角:
R = Rz(A) · Ry(B) · Rx(C)
自己写 C# 算 inv(a0) 时顺序搞错,整组数全废。而且现象很隐蔽 —— 点看着还在附近,只是整体被拧了一下,不像明显报错那样好排查。
更省事的做法:让机器人直接给出齐次矩阵,或者用现成库(MathNet、Eigen)而不是自己拼旋转。
坑 4:姿态不同时不能只加平移
见第五节。P_base = c : b 是完整 4×4 变换,时刻记住这一点。
八、两条自检,标完立刻做
自检 A:九点的 Z 一致性
把九个 Pi^T0 的第三分量打出来,看极差:
max(Zi) − min(Zi) < 0.5 mm ← 合格
超出说明三种情况之一:
- 台面不平,或靶点贴在不同高度;
- 尖只是「接近」了标记,没真正接触(Z 方向示教不准);
- 拍照姿态
R0不垂直,导致投影被拉伸。
这是成本最低的质量探针,几行代码就能出结果,强烈建议加进标定流程里做前置校验。
自检 B:闭环验一点
标定完成后,让机器人把尖移到「相机算出来的第 5 点坐标」,看尖是否正好落在第 5 点上。
这个残差比 VM 界面给的像素残差可信得多 —— 它把 TCP 误差、示教误差、平面误差、标定误差全部包进去了,是这套系统真实的综合精度。
九、实施步骤(可照做)
- 标定 TCP。 用 4 点法/6 点法把
tool[1]的 TCP 标到尖点,切到$TOOL = 1、$BASE = 0。 - 铺标定板。 九个标记固定在同一块平板上,标记中心清晰,覆盖视野 2/3 以上,避免共线。
- 拍照。 移动到一个能看全九点的位置,停稳,读
$POS_ACT记为a0,保存九点的像素坐标。 - 示教九点。 换成尖,依次指到九个标记中心,每次读
$POS_ACT记为a1 … a9。顺序必须和图像识别顺序一一对应。 - 归算。 按
Pi = INV_POS(a0) : ai(KRL 里就是这一行)算出九个点的 X/Y,等价于R0ᵀ(ti − t0)。 - 喂给 VM。 九组「像素 ↔ T0 系坐标」输入标定工具,选择九点透视,查看残差。
- 跑自检 A。 Z 极差 < 0.5 mm,否则回查第 2、4 步。
- 跑自检 B。 闭环验一点,记录综合精度,作为验收依据。
十、这套方法的边界
适用:相机垂直向下、工作距离固定、平面工件、精度要求 0.5 ~ 1 mm。补偿式定位、平面工件完全够用。
遇到以下情况必须升级成标准三维手眼标定(Tsai-Lenz,解 AX = XB):
| 场景 | 为什么这套不行 |
|---|---|
| 工件高度会变(不同厚度型号混线) | 单一平面的标定文件无法表达高度变化带来的尺度变化 |
| 相机带倾角且要求 < 0.3 mm | 平面仿射/单应无法在任意高度上保持正确 |
| 需要输出三维位姿给抓取 | 2D 标定只给 XY,没有姿态 |
升级后的形式是 P_base = a · X · P_cam,其中 X 是相机到工具的固定 4×4 变换,全 3×3 旋转。代价是必须做相机内参标定和畸变校正,不能再用 VM 的平面标定文件直接出结果。
小结
整套方法就三句话:
- 相机和 base 之间没有固定关系,和
tool[1]才有; INV_POS(a0) : ai把点搬进「拍照那一刻」的工具坐标系,左乘取逆,顺序不能反;- 工作时用
$POS_ACT : b乘回去,$TOOL前后必须一致。
真正的坑都不在数学上,在 $TOOL 一致性、TCP 精度、九点共面这三件「输入的准备」上 —— 而这三件事,恰恰是公开资料里最不会写的东西。
本文配套 KUKA + 海康 VM 2D 的 eye-in-hand 标定使用。文中的数值来自演示模型,经过简化;现场实施以实际示教读数为准。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)