2. 坐标系与刚体变换:世界坐标系、相机坐标系、图像坐标系、像素坐标系;旋转矩阵、平移向量、齐次坐标

好,咱们正式开始聊视觉定位里最基础、也最绕不开的一环——坐标系与刚体变换。

说实话,我刚开始做SLAM那会儿,最头疼的就是这几个坐标系来回倒腾。明明一个三维点,愣是在四个坐标系里转来转去,稍不留神就搞反了方向。后来我养成了一个习惯:拿到任何视觉数据,第一件事就是画坐标系转换链路图。这招帮我省了太多debug的时间。

2.1 四个坐标系,一个都不能少

视觉定位里,我们打交道的一共四个坐标系。它们之间的关系,说白了就是一条流水线:世界→相机→图像→像素

核心链路:

世界坐标系 (3D) → 相机坐标系 (3D) → 图像坐标系 (2D) → 像素坐标系 (2D)

我习惯用下面这张图来理解整个流程,你仔细看看:

世界坐标系 (Xw, Yw, Zw) 相机坐标系 (Xc, Yc, Zc) 图像坐标系 (x, y) 像素坐标系 (u, v) R, t 投影 量化 坐标系转换链路 刚体变换(外参)→ 透视投影(内参)→ 离散化(像素) 误差来源:外参标定误差 + 内参标定误差 + 量化误差

嗯,这张图我建议你保存下来。每次做定位误差分析时,对着它捋一遍,思路会清晰很多。

2.2 世界坐标系与相机坐标系——刚体变换

世界坐标系是我们定义场景的"绝对"参考系。相机坐标系呢?它以相机光心为原点,Z轴指向相机前方。

这两个坐标系之间是什么关系?刚体变换。说白了,就是旋转加平移,不改变物体的形状和大小。

刚体变换公式:

Pc = R * Pw + t

其中:Pw 是世界坐标点,Pc 是相机坐标点,R 是3x3旋转矩阵,t 是3x1平移向量。

我记得有一次做视觉里程计,发现轨迹一直在漂。查了半天,原来是旋转矩阵的符号搞反了——我把从世界到相机的R写成了从相机到世界的R。你想想看,这一反,整个轨迹都镜像了。

旋转矩阵 R

旋转矩阵是个3x3的正交矩阵,满足 R^T * R = I,行列式为+1。它有3个自由度,对应绕X、Y、Z轴的三个旋转角。

绕Z轴旋转θ角的矩阵长这样:

Rz(θ) = [cosθ  -sinθ  0]
        [sinθ   cosθ  0]
        [0      0     1]

绕X轴和Y轴类似,只是把sin和cos放到对应位置。实际中我们常用的是欧拉角或四元数来表示旋转,但最终都要转成矩阵形式参与运算。

我的小技巧: 调试时如果发现旋转结果不对,先检查旋转矩阵的行列式是不是1。如果不是,说明矩阵不是正交的,大概率是代码里数值精度出了问题。

平移向量 t

平移向量 t 表示世界坐标系原点在相机坐标系下的位置。注意方向——是从世界到相机的平移。

我曾经犯过一个低级错误:把平移向量直接当成了相机在世界坐标系下的位置。这两个是互为相反数的关系,千万别搞混了。

2.3 齐次坐标——让变换变得优雅

你发现没有?刚体变换公式 Pc = R * Pw + t 里,旋转是乘法,平移是加法。这种"混合运算"在连续变换时特别麻烦。

齐次坐标就是来解决这个问题的。它给三维点加一个维度,变成四维:

Pw_homogeneous = [Xw, Yw, Zw, 1]^T
Pc_homogeneous = [Xc, Yc, Zc, 1]^T

然后刚体变换就变成了一个4x4矩阵:

[Xc]   [R11 R12 R13 tx] [Xw]
[Yc] = [R21 R22 R23 ty] [Yw]
[Zc]   [R31 R32 R33 tz] [Zw]
[1 ]   [0   0   0   1 ] [1 ]

写成简洁形式:Pc = T * Pw,其中T是4x4的变换矩阵。

为什么用齐次坐标?

  • 把旋转和平移统一成矩阵乘法,方便连续变换
  • 可以表示无穷远点(最后一个分量为0)
  • 在SLAM后端优化中,便于求导和线性化

2.4 相机坐标系到图像坐标系——透视投影

这一步是把三维的相机坐标点投影到二维图像平面上。用的是小孔成像模型。

x = f * Xc / Zc
y = f * Yc / Zc

其中 f 是焦距,(x, y) 是图像坐标系下的坐标,单位是毫米。

这里有个关键点:深度 Zc 在分母上。这意味着距离越远,同样的三维位移在图像上产生的位移越小。这就是透视效应的本质。

注意: 当 Zc = 0 时,投影无定义。实际中如果遇到 Zc 接近0的点,要么是点在相机后面,要么是标定板放得太近了。我在做近距离三维重建时就踩过这个坑,后来加了深度阈值过滤才解决。

2.5 图像坐标系到像素坐标系——离散化

图像坐标系是连续的,单位是毫米。像素坐标系是离散的,单位是像素。转换关系很简单:

u = x / dx + u0
v = y / dy + v0

其中 dx、dy 是每个像素的物理尺寸,u0、v0 是主点(光轴与图像平面的交点)的像素坐标。

写成矩阵形式就是相机内参矩阵 K:

[u]   [fx  0  u0] [x]
[v] = [0  fy  v0] [y]
[1]   [0   0   1] [1]

其中 fx = f/dx,fy = f/dy,是焦距的像素表示。

避坑指南: 我曾经在标定时发现 fx 和 fy 差了好几个像素,一开始以为是标定板不平。后来才发现是相机传感器像素不是正方形。所以拿到相机先看传感器规格,别默认 dx = dy。

2.6 完整的投影方程

把上面所有步骤串起来,就是一个三维世界点到像素坐标的完整投影:

s * [u]   [fx  0  u0  0] [R11 R12 R13 tx] [Xw]
    [v] = [0  fy  v0  0] [R21 R22 R23 ty] [Yw]
    [1]   [0   0   1   0] [R31 R32 R33 tz] [Zw]
                          [0   0   0   1 ] [1 ]

简化写法:s * p = K * [R|t] * Pw

其中 s 是尺度因子(其实就是 Zc),K 是内参矩阵,[R|t] 是外参矩阵。

误差分析的关键:

  • 外参 [R|t] 的误差 → 世界到相机的刚体变换不准
  • 内参 K 的误差 → 焦距、主点标定不准
  • 像素坐标 (u,v) 的误差 → 特征点提取精度有限

这三类误差叠加在一起,就是视觉定位误差的根源。

2.7 我的一点经验总结

做视觉定位这些年,我最大的体会是:坐标系转换看似简单,但90%的bug都出在这里

给你几个实用建议:

  1. 画图:每次做新项目,先把坐标系转换链路图画出来,贴在工位上
  2. 验证:用已知点反算,检查投影结果对不对。我习惯在标定板上选几个角点,手动算一遍投影坐标,跟代码输出对比
  3. 统一:整个项目组用同一套坐标系约定。左手系还是右手系?Y轴向上还是Z轴向上?这些必须提前定好
  4. 记录:每次标定完,把内参、外参、重投影误差都记下来。方便后面排查问题

嗯,坐标系与刚体变换就聊到这儿。这部分内容虽然基础,但真的是视觉定位的基石。你把这些搞透了,后面学误差分析、补偿方法就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321