2、相机成像模型:从三维世界到二维像素

做双目深度相机标定,第一个绕不开的坎就是相机成像模型。说白了,就是搞明白「三维空间里的一个点,是怎么变成照片上那个像素的」。我刚开始接触这个的时候,也觉得公式一堆挺唬人,但后来发现,只要把几个坐标系的关系理清楚,剩下的就是套公式了。

核心一句话:相机成像,本质上是把世界坐标系里的点,经过四次坐标变换,最终映射到像素坐标系上。

2.1 针孔相机模型——最朴素的成像原理

针孔相机模型,是所有相机模型的「老祖宗」。它假设光线是直线传播的,穿过一个小孔,在后面的成像平面上形成倒像。嗯,这里要注意,实际相机用的是透镜,但数学上我们把它等效成针孔模型来处理。

我记得第一次做标定时,导师跟我说:「你先别管畸变,把针孔模型吃透了再说。」后来发现,确实如此。针孔模型是基础,畸变只是在这个基础上加了个「修正项」。

针孔模型的数学表达式很简单:

// 理想针孔成像关系
x = f * (X / Z)
y = f * (Y / Z)

其中 (X, Y, Z) 是三维空间点在相机坐标系下的坐标,(x, y) 是成像平面上的物理坐标,f 是焦距。这个公式我建议你背下来,因为后面所有的推导都从这里出发。

2.2 世界坐标系与相机坐标系——谁在动?

世界坐标系是固定的,你可以把它理解为「绝对坐标」。相机坐标系则是跟着相机走的,相机动,坐标系就动。

那问题来了:世界坐标系里的一个点,怎么转到相机坐标系里?

答案是:旋转 + 平移。也就是我们常说的 外参矩阵

// 世界坐标系 → 相机坐标系
P_cam = R * P_world + T

这里 R 是 3x3 的旋转矩阵,T 是 3x1 的平移向量。我在项目中遇到过一个问题:旋转矩阵的表示方式有好几种(欧拉角、四元数、旋转向量),不同 SDK 用的不一样,标定结果对不上。后来我统一用旋转向量 + Rodrigues 公式转换,才解决了这个问题。

个人习惯:我一般用旋转向量存储外参,因为参数少(3个),优化起来更稳定。需要旋转矩阵时,用 cv::Rodrigues() 转一下就行。

2.3 图像坐标系与像素坐标系——物理单位 vs 像素单位

这两个坐标系最容易搞混。我刚开始学的时候,就栽在这上面。

  • 图像坐标系:单位是毫米(mm),原点在成像平面中心,描述物理位置。
  • 像素坐标系:单位是像素(pixel),原点在图像左上角,描述像素位置。

从图像坐标 (x, y) 到像素坐标 (u, v) 的转换,需要知道两个参数:

  • dx, dy:每个像素在 x 和 y 方向上的物理尺寸(mm/pixel)
  • u0, v0:主点(光轴与成像平面的交点)的像素坐标
// 图像坐标系 → 像素坐标系
u = x / dx + u0
v = y / dy + v0

你想想看,如果 dx 和 dy 不相等,像素就是矩形的,不是正方形的。很多廉价摄像头就是这样,标定的时候必须考虑进去。

2.4 内参矩阵——把上面三步合起来

把世界坐标到像素坐标的整个过程写成矩阵形式,就是大名鼎鼎的 内参矩阵 K

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

其中 fx = f / dx, fy = f / dy。注意,fx 和 fy 不一定相等,原因我刚才说了——像素可能是矩形的。

完整的投影公式:

s * [u, v, 1]^T = K * [R | T] * [X, Y, Z, 1]^T

s 是尺度因子,因为从 3D 到 2D 丢失了深度信息,所以这里有个齐次坐标的归一化过程。

我曾经踩过的坑:标定得到的 fx 和 fy 数值差很多,一开始以为是标定板没拍好。后来查了传感器手册才发现,这款相机的像素确实是 6.0μm × 6.25μm,不是正方形的。所以标定结果是对的,是我自己知识盲区。

2.5 镜头畸变——理想很丰满,现实很骨感

针孔模型假设光线是直线,但实际镜头是透镜,光线会弯曲。这就产生了畸变。畸变主要分两种:

径向畸变(Radial Distortion)

说白了就是「画面中间正常,越往边缘越变形」。常见的有两种:

  • 桶形畸变:画面像被吹鼓了,中间往外凸。广角镜头常见。
  • 枕形畸变:画面像被往里压,中间往里凹。长焦镜头常见。

径向畸变的数学模型用三个参数 k1, k2, k3 来描述:

// 径向畸变修正
x_corrected = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_corrected = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)

其中 r 是像素到主点的距离。k3 一般只在畸变很大的镜头(比如鱼眼)才用,普通镜头用 k1, k2 就够了。

切向畸变(Tangential Distortion)

这个是因为镜头和成像平面不平行造成的。说白了就是「镜头装歪了」。用两个参数 p1, p2 来描述:

// 切向畸变修正
x_corrected = x + [2*p1*x*y + p2*(r^2 + 2*x^2)]
y_corrected = y + [p1*(r^2 + 2*y^2) + 2*p2*x*y]

避坑指南:我曾经在一个项目中,标定出来的切向畸变参数 p1, p2 特别大。一开始以为是镜头质量问题,后来发现是相机外壳的螺丝没拧紧,镜头有点歪。拧紧后重新标定,参数就正常了。所以标定前,先检查硬件。

2.6 本章知识体系总览

下面这张图,把整个相机成像模型的流程串起来了。我建议你多看几遍,把每个坐标系之间的变换关系刻在脑子里。

相机成像模型:坐标系变换流程 世界坐标系 (Xw, Yw, Zw) R, T 相机坐标系 (Xc, Yc, Zc) 透视投影 图像坐标系 (x, y) 单位:mm dx, dy, u0, v0 像素坐标系 (u, v) 单位:pixel 镜头畸变修正 径向(k1,k2,k3) + 切向(p1,p2) 外参 (R, T) 描述相机在世界中的位置 内参 (fx, fy, u0, v0) 描述相机本身的属性 畸变参数 (k1, k2, k3, p1, p2) 修正镜头缺陷 世界坐标系 相机坐标系 图像坐标系 像素坐标系 畸变修正(可选步骤)

2.7 小结

相机成像模型,说白了就是四步走:

  1. 世界坐标 → 相机坐标(外参:R, T)
  2. 相机坐标 → 图像坐标(透视投影:fx, fy)
  3. 图像坐标 → 像素坐标(主点偏移:u0, v0)
  4. 畸变修正(径向 + 切向)

这四步对应的参数,就是我们要标定的全部内容。后面的章节,我们会一步步教你怎么用 OpenCV 把这些参数算出来。

我的建议:刚开始学标定,别急着跑代码。先把今天讲的这个坐标系变换流程,在纸上画三遍。画到闭着眼睛都能说出来每一步的输入输出,再开始写代码。磨刀不误砍柴工。


专注资料整理