跳到正文
PreciSim
返回博客列表
2 分钟阅读

手眼标定里那个负号

带过新人的人都见过这个场景:标定跑完了,残差不大, 一动起来 X 对得上,Y 越走越远

十次里有八次,问题出在一个负号。

冲突从哪来

两个约定同时存在,而且都不能改:

  • 图像坐标系:原点在左上角,u 向右,v 向下。所有相机 SDK 都这样。
  • 世界坐标系:右手系,X 向右,Y 向后(远离操作者),Z 向上。

v 向下,Y 向后。这两个方向是相反的。

于是矩阵里必须有负号

从图像到世界的 2D 相似变换:

[x_W]   [ s·cosθ   s·sinθ ] [ u ]   [tx]
[y_W] = [ s·sinθ  -s·cosθ ] [ v ] + [ty]
                    ↑

这不是一个普通的旋转矩阵(那个应该是 [cos -sin; sin cos]), 它是旋转 + 一次镜像。镜像来自 v 轴方向的翻转。

为什么漏了也不会报错

这才是它阴险的地方。

如果你用 [cos -sin; sin cos](少了镜像),最小二乘仍然会收敛。 它会解出一组参数,让残差看起来正常——因为对于这批采集数据来说, 「旋转 180° − θ」和「镜像后旋转 θ」在某些点上是可以互相补偿的。

标定报告一切正常。然后上机,Y 方向整体反了。

怎么在一分钟内查出来

别去推公式,做一次实验:

  1. 让 X 轴走 +10 mm
  2. 看图像里标记点移动了多少像素、往哪个方向;
  3. 用你的变换把这个像素位移换算回 mm。

应该是 +10, 0。如果是 +10, 0 就对了; 如果是 -10, 0 或者 0, +10,你就知道问题在符号还是在轴映射。

这一步在虚拟设备上做,一分钟;在真机上做,半小时,还有撞机风险。

顺带说一下构型

手眼标定有两种构型:

  • 眼在手上(相机装在运动件上)→ 解 T_Tool_Cam
  • 眼看手(相机固定在机架上)→ 解 T_World_Cam

选错了同样不会报错,同样会给你一个残差很小的错结果。 虚拟设备上有真值,所以能一眼看出来;真机上你只能靠闭环验证。

一条防御性的规矩

代码里所有涉及坐标的变量必须带坐标系后缀

var pTargetW  = ...;   // 世界系,mm
var pTargetI  = ...;   // 图像系,px
var tWorldCam = ...;
var pW = tWorldCam * pI;   // 下标首尾相接,一眼看出对不对

这条规矩看着啰嗦,但它把「符号搞反」从机器撞上去变成了代码评审里的一条评论

延伸