相机装在机械手末端、跟着手臂一起动,这是「眼在手上」(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 误差、示教误差、平面误差、标定误差全部包进去了,是这套系统真实的综合精度。


九、实施步骤(可照做)

  1. 标定 TCP。 用 4 点法/6 点法把 tool[1] 的 TCP 标到尖点,切到 $TOOL = 1、$BASE = 0。
  2. 铺标定板。 九个标记固定在同一块平板上,标记中心清晰,覆盖视野 2/3 以上,避免共线。
  3. 拍照。 移动到一个能看全九点的位置,停稳,读 $POS_ACT 记为 a0,保存九点的像素坐标。
  4. 示教九点。 换成尖,依次指到九个标记中心,每次读 $POS_ACT 记为 a1 … a9。顺序必须和图像识别顺序一一对应。
  5. 归算。 按 Pi = INV_POS(a0) : ai(KRL 里就是这一行)算出九个点的 X/Y,等价于 R0ᵀ(ti − t0)。
  6. 喂给 VM。 九组「像素 ↔ T0 系坐标」输入标定工具,选择九点透视,查看残差。
  7. 跑自检 A。 Z 极差 < 0.5 mm,否则回查第 2、4 步。
  8. 跑自检 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 的平面标定文件直接出结果。


小结

整套方法就三句话:

  1. 相机和 base 之间没有固定关系,和 tool[1] 才有;
  2. INV_POS(a0) : ai 把点搬进「拍照那一刻」的工具坐标系,左乘取逆,顺序不能反;
  3. 工作时用 $POS_ACT : b 乘回去,$TOOL 前后必须一致。

真正的坑都不在数学上,在 $TOOL 一致性、TCP 精度、九点共面这三件「输入的准备」上 —— 而这三件事,恰恰是公开资料里最不会写的东西。

本文配套 KUKA + 海康 VM 2D 的 eye-in-hand 标定使用。文中的数值来自演示模型,经过简化;现场实施以实际示教读数为准。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐