2. 相机成像模型:针孔相机模型、四大坐标系及其转换关系
做双目测距,绕不开一个最基础的问题——相机是怎么把三维世界变成二维图像的?
说白了,这就是相机成像模型要回答的事。我刚开始接触双目视觉时,觉得这东西太理论了,不如直接调库。结果呢?标定出来的参数怎么用都不对,测距误差大得离谱。后来老老实实把针孔模型和坐标系转换啃了一遍,才发现问题出在哪。
这一节,我们就来彻底搞懂它。
2.1 针孔相机模型——最朴素的成像原理
先别被名字吓到。针孔相机模型,其实就是你小时候玩过的那个小孔成像实验。
想象一个密闭的盒子,前面戳一个小孔,光线穿过小孔,在后面的感光面上形成一个倒立的像。这就是最原始的相机。
数学上怎么描述这个过程?
假设三维空间中有一个点 P(X, Y, Z),它通过小孔(光心)投影到成像平面上,得到像点 p(x, y)。根据相似三角形,有:
x = f * (X / Z)
y = f * (Y / Z)
其中 f 是焦距——光心到成像平面的距离。
核心要点:针孔模型是一个线性模型,但它丢失了深度信息 Z。你从一张 2D 图像里,无法直接知道物体离你有多远。这也是为什么我们需要双目——用两个相机来恢复深度。
我在项目中遇到过一个问题:用广角镜头时,针孔模型就不太够用了。因为镜头畸变会让光线弯曲,这时候需要引入畸变模型来修正。不过那是后话,我们先打好基础。
2.2 四大坐标系——从世界到像素的“四步走”
一个三维点要变成图像上的像素,中间要经历四次坐标变换。我习惯把这四个坐标系串成一条流水线:
- 世界坐标系 → 你定义的那个“真实世界”的参考系
- 相机坐标系 → 以相机光心为原点的坐标系
- 图像坐标系 → 成像平面上的物理坐标(单位:毫米)
- 像素坐标系 → 你最终看到的图像数组坐标(单位:像素)
你想想看,这就像把一个人从“地球上的经纬度”一步步换算成“照片上的第几行第几列”。每一步都有对应的数学变换。
2.3 坐标系转换的数学细节
2.3.1 世界坐标系 → 相机坐标系(刚体变换)
这一步说白了就是“把物体摆到相机面前”。世界坐标系是你随便定义的,比如以棋盘格左上角为原点。相机坐标系则是以相机光心为原点,Z轴指向镜头前方。
变换公式很简单:
|Xc| |R11 R12 R13| |Xw| |tx|
|Yc| = |R21 R22 R23| |Yw| + |ty|
|Zc| |R31 R32 R33| |Zw| |tz|
其中 R 是旋转矩阵,t 是平移向量。这两个合起来就是相机外参。
我的经验:标定外参时,我习惯用棋盘格法。但要注意,棋盘格必须放在相机视野的不同位置和角度,至少拍10-15张。我曾经偷懒只拍了6张,结果标出来的外参在边缘区域误差很大。
2.3.2 相机坐标系 → 图像坐标系(透视投影)
这一步就是针孔模型的核心。把三维点投影到成像平面上:
x = f * (Xc / Zc)
y = f * (Yc / Zc)
注意这里 Zc 在分母上。这就是为什么远处的物体看起来小——因为 Zc 大了,x 和 y 就小了。
⚠ 避坑指南:我曾经在写代码时,忘了检查 Zc 是否为零。结果有个点正好在相机侧面,Zc=0,程序直接崩溃。后来我加了一个判断:如果 Zc < 一个极小值(比如 1e-6),就跳过这个点。
2.3.3 图像坐标系 → 像素坐标系(仿射变换)
图像坐标系是以毫米为单位的物理坐标,像素坐标系是以像素为单位的数组坐标。它们之间的转换是:
u = x / dx + u0
v = y / dy + v0
其中:
- dx, dy:每个像素的物理尺寸(毫米/像素)
- u0, v0:图像中心(主点)的像素坐标
这四个参数(f, dx, dy, u0, v0)合起来就是相机内参。注意,f 和 dx, dy 通常合并成 fx = f/dx, fy = f/dy,单位是像素。
2.4 完整的投影公式——从世界到像素一步到位
把上面三步串起来,就得到了完整的投影公式:
Zc * |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 |
这个 3×4 矩阵就是投影矩阵 P。它把世界坐标系中的点,一步映射到像素坐标系。
记住:这个公式是双目测距的数学基础。左相机有一个 P 矩阵,右相机也有一个 P 矩阵。通过两个 P 矩阵,我们就能解出三维点的坐标。这就是三角测量的原理。
2.5 畸变模型——现实世界没那么完美
针孔模型是理想情况。现实中的镜头有畸变,主要是两种:
- 径向畸变:光线通过镜头边缘时弯曲,产生“桶形”或“枕形”效果
- 切向畸变:镜头和成像平面不平行,产生“梯形”效果
畸变模型用多项式来修正:
x_corrected = x * (1 + k1*r² + k2*r⁴ + k3*r⁶) + [2*p1*x*y + p2*(r² + 2*x²)]
y_corrected = y * (1 + k1*r² + k2*r⁴ + k3*r⁶) + [p1*(r² + 2*y²) + 2*p2*x*y]
其中 k1, k2, k3 是径向畸变系数,p1, p2 是切向畸变系数。
我的建议:标定畸变参数时,一定要用棋盘格覆盖整个图像区域。我见过有人只把棋盘格放在画面中央,结果边缘畸变根本没标出来,测距时边缘区域误差大得离谱。
2.6 本章小结——你需要记住的
| 坐标系 | 变换类型 | 关键参数 | 我的备注 |
|---|---|---|---|
| 世界 → 相机 | 刚体变换 | R, t(外参) | 标定时棋盘格要多角度 |
| 相机 → 图像 | 透视投影 | f(焦距) | 注意 Zc 不能为零 |
| 图像 → 像素 | 仿射变换 | fx, fy, u0, v0(内参) | 主点不一定在图像正中心 |
| 畸变修正 | 非线性 | k1, k2, k3, p1, p2 | 边缘区域一定要覆盖到 |
嗯,这一节的内容就到这里。相机成像模型是双目测距的基石,搞不懂它,后面的立体匹配和三角测量就是空中楼阁。我个人习惯是把这些公式手推一遍,再写代码验证,这样印象最深。
下一节我们会聊立体标定——怎么把左右两个相机的关系标定出来。到时候这些坐标系的知识会反复用到。
公众号:蓝海资料掘金营,微信deep3321