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 方向整体反了。
怎么在一分钟内查出来
别去推公式,做一次实验:
- 让 X 轴走
+10 mm; - 看图像里标记点移动了多少像素、往哪个方向;
- 用你的变换把这个像素位移换算回 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; // 下标首尾相接,一眼看出对不对这条规矩看着啰嗦,但它把「符号搞反」从机器撞上去变成了代码评审里的一条评论。