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 本章知识体系总览
下面这张图,把整个相机成像模型的流程串起来了。我建议你多看几遍,把每个坐标系之间的变换关系刻在脑子里。
2.7 小结
相机成像模型,说白了就是四步走:
- 世界坐标 → 相机坐标(外参:R, T)
- 相机坐标 → 图像坐标(透视投影:fx, fy)
- 图像坐标 → 像素坐标(主点偏移:u0, v0)
- 畸变修正(径向 + 切向)
这四步对应的参数,就是我们要标定的全部内容。后面的章节,我们会一步步教你怎么用 OpenCV 把这些参数算出来。
我的建议:刚开始学标定,别急着跑代码。先把今天讲的这个坐标系变换流程,在纸上画三遍。画到闭着眼睛都能说出来每一步的输入输出,再开始写代码。磨刀不误砍柴工。