2. 三维坐标系与变换:世界坐标系、相机坐标系、图像坐标系、像素坐标系

说实话,刚入行做三维视觉那会儿,我被这四个坐标系绕得晕头转向。明明都是坐标,怎么还分这么多种?后来踩了不少坑才明白——坐标系之间的变换,是整个三维感知的基石。你想想看,相机看到的是一张二维图片,但我们要从中还原出三维空间的信息,这中间缺的就是一套严谨的坐标变换体系。

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

我们先来捋一捋这四个坐标系各自扮演什么角色。我个人习惯把它们分成两组:三维组二维组

2.1.1 世界坐标系(World Coordinate System)

这是整个场景的"绝对参考系"。你可以把它想象成现实世界中的经纬度坐标系。不管相机怎么动、物体怎么摆,世界坐标系是固定不变的。我在做AR项目时,通常把房间的某个墙角设为世界坐标系原点,这样所有虚拟物体的位置就有了统一的基准。

2.1.2 相机坐标系(Camera Coordinate System)

这个坐标系的原点在相机的光心,Z轴指向相机的前方(也就是拍摄方向)。说白了,相机坐标系就是"以相机为中心"看世界。你拿着手机自拍时,手机摄像头的位置就是原点,镜头朝向就是Z轴方向。

2.1.3 图像坐标系(Image Coordinate System)

当三维空间中的点投影到相机成像平面上,就进入了图像坐标系。它的原点通常在成像平面的中心,单位是毫米等物理长度单位。嗯,这里要注意:图像坐标系是二维的,只有x和y两个轴。

2.1.4 像素坐标系(Pixel Coordinate System)

这是我们最终看到的数字图像所使用的坐标系。原点在图像的左上角,u轴向右,v轴向下,单位是像素。为什么原点在左上角?这其实是历史遗留问题——早期显示器的扫描方式决定的。

核心要点: 从三维世界到二维像素,本质上是一个"降维打击"的过程。世界→相机→图像→像素,每一步都在丢失信息,但每一步都有严格的数学规则。

2.2 刚体变换:旋转与平移

从世界坐标系到相机坐标系,这个变换叫做刚体变换。为什么叫"刚体"?因为变换过程中物体的形状和大小都不变,只是位置和朝向变了。说白了,就是旋转 + 平移

2.2.1 旋转矩阵

旋转可以用一个3×3的正交矩阵R来表示。我记得刚学的时候,总觉得旋转矩阵很抽象。后来我换了个角度理解:旋转矩阵的每一列,其实就是原坐标系的基向量在新坐标系中的表示

举个简单的例子,绕Z轴旋转θ角:

R_z(θ) = [
  [cosθ, -sinθ, 0],
  [sinθ,  cosθ, 0],
  [0,      0,    1]
]

绕X轴和Y轴的旋转矩阵类似,只是把1和三角函数放在对应的位置上。

我的小技巧: 实际项目中,我很少手动写旋转矩阵。用欧拉角或者四元数更方便。尤其是四元数,可以避免万向锁问题。我在做无人机姿态估计时,就吃过万向锁的亏——飞机明明在水平旋转,突然就"卡住"了。

2.2.2 平移向量

平移就简单多了,就是一个3×1的向量t = [tx, ty, tz]^T。它表示相机坐标系原点在世界坐标系中的位置。

2.2.3 齐次坐标与变换矩阵

为了把旋转和平移统一成一个矩阵运算,我们引入了齐次坐标。把三维点P = [X, Y, Z]^T扩展成P' = [X, Y, Z, 1]^T,这样变换就可以写成:

P_cam = [R | t] * P_world

展开成矩阵形式:
| X_cam |   | r11 r12 r13 tx |   | X_world |
| Y_cam | = | r21 r22 r23 ty | * | Y_world |
| Z_cam |   | r31 r32 r33 tz |   | Z_world |
|  1    |   |  0   0   0   1  |   |   1     |

这个4×4的矩阵,就是传说中的外参矩阵。它描述了相机在世界坐标系中的位置和朝向。

2.3 从相机坐标系到像素坐标系

这一步涉及两个子步骤:相机→图像(透视投影),图像→像素(离散化)。

2.3.1 透视投影

假设相机坐标系中有一个点P = [X, Y, Z]^T,它在成像平面上的投影点坐标为:

x = f * X / Z
y = f * Y / Z

其中f是焦距。这里有个关键点:Z不能为0,否则就除以零了。我在做SLAM系统时,经常要处理深度值为0的异常点,一般直接丢弃。

2.3.2 像素坐标转换

图像坐标到像素坐标,其实就是个缩放加平移:

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

dx和dy是每个像素的物理尺寸,u0和v0是图像坐标系原点在像素坐标系中的位置(通常是图像中心)。

把上面两步合并,就得到了内参矩阵K

K = [
  [fx,  0, u0],
  [ 0, fy, v0],
  [ 0,  0,  1]
]

其中 fx = f/dx, fy = f/dy
我曾经踩过的坑: 内参矩阵中的fx和fy不一定相等!尤其是手机摄像头,由于传感器像素不是正方形,fx和fy会有细微差别。如果不做校正,三维重建出来的物体会被"压扁"或"拉长"。

2.4 完整的变换链路

现在我们把所有环节串起来。一个世界坐标系中的点P_w,最终变成像素坐标p_uv,经历了:

  1. 刚体变换:P_cam = R * P_w + t(外参矩阵)
  2. 透视投影:x = fx * X_cam / Z_cam, y = fy * Y_cam / Z_cam
  3. 像素映射:u = x + u0, v = y + v0

用矩阵形式一步到位就是:

p_uv = K * [R | t] * P_w

这个公式,可以说是三维视觉里最重要的公式之一。你想想看,它把三维世界和二维图像联系在了一起。所有的三维重建、视觉SLAM、AR/VR,底层都在反复使用这个公式。

2.5 知识体系总览

下面这张图,是我自己总结的坐标系变换全流程。每次做项目前,我都会在脑子里过一遍这个流程,确保没有遗漏。

世界坐标系 相机坐标系 图像坐标系 像素坐标系 刚体变换 R, t 透视投影 f 离散化 dx, dy, u0, v0 变换公式汇总 ① 世界 → 相机:P_cam = R · P_world + t ② 相机 → 图像:x = f · X_cam / Z_cam, y = f · Y_cam / Z_cam ③ 图像 → 像素:u = x/dx + u0, v = y/dy + v0 一步到位: p_uv = K · [R | t] · P_world 其中 K 为内参矩阵,[R | t] 为外参矩阵

2.6 实战中的注意事项

常见问题 原因 我的建议
像素坐标出现负值 点在相机后方(Z_cam < 0) 先判断Z_cam的正负,负值直接剔除
图像变形严重 未做畸变校正 用棋盘格标定,获取畸变系数
三维重建精度差 内参标定不准确 至少拍20张不同角度的标定板照片
旋转矩阵不正交 数值计算误差累积 定期做正交化处理,或用四元数
避坑指南: 我曾经在一个项目中,直接用OpenCV的默认内参做三维重建,结果重建出来的物体歪歪扭扭。后来花了一天时间重新标定相机,精度提升了两个数量级。所以,永远不要相信"默认参数"

好了,这一章的内容就到这里。坐标系变换是三维视觉的"九九乘法表",看似简单,但每个细节都值得反复推敲。下一章我们会深入相机标定的具体实现,到时候会用到今天讲的所有知识。


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