2. 相机成像模型:从世界到像素的“翻译官”
做双目视觉,第一个绕不开的坎儿就是相机成像模型。说白了,就是搞明白“真实世界里的一个点,是怎么变成照片上那个像素的”。
我个人习惯把相机成像比作一个“翻译”过程。世界坐标系是“中文”,像素坐标是“英文”,中间需要经过好几层翻译。今天我们就一层层拆开看。
核心一句话:相机成像模型 = 针孔模型(理想) + 畸变模型(现实) + 内外参(翻译规则)
2.1 针孔相机模型:最朴素的理想情况
针孔相机模型,是所有成像模型的“老祖宗”。它假设光线是直线传播的,穿过一个小孔,在后面的成像平面上形成倒立的像。
我记得刚入行时,师傅让我先把这个模型画一百遍。当时觉得枯燥,后来才发现——所有复杂的标定算法,底层逻辑都绕不开这个模型。
数学上,它用一个简单的相似三角形关系来描述:
// 针孔模型核心公式
// 假设相机光心在原点,光轴沿Z方向
// 三维点 P(X, Y, Z) 投影到成像平面上的点 p(x, y)
x = f * X / Z
y = f * Y / Z
// 其中 f 是焦距(光心到成像平面的距离)
我的经验:实际项目中,我们很少直接用这个公式。但它是一切推导的起点。你想想看,如果连理想情况都搞不明白,怎么去处理现实中的畸变?
2.2 透镜畸变模型:现实总是不完美的
针孔模型很完美,但现实中的相机用的是透镜,不是针孔。透镜一上场,问题就来了——畸变。
畸变主要分两种:
- 径向畸变:光线经过透镜边缘时弯曲得更厉害。表现为“桶形畸变”(照片中间鼓起来)或“枕形畸变”(照片边缘拉长)。
- 切向畸变:透镜和成像平面不平行。表现为图像像被“拧”了一下。
我曾经在一个项目中,用廉价摄像头做双目测距。标定完一看,误差大得离谱。排查了三天,最后发现是畸变参数没给对。嗯,从那以后我再也不敢轻视畸变模型了。
畸变修正的数学表达:
// 径向畸变修正(常用3阶或5阶模型)
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)
// 切向畸变修正
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]
// 其中 r^2 = x^2 + y^2
// k1, k2, k3 是径向畸变系数
// p1, p2 是切向畸变系数
避坑指南:我曾经见过有人用5阶径向畸变模型去标定手机摄像头,结果过拟合了。一般来说,3阶模型(k1, k2, k3)就够用。除非你用鱼眼镜头,否则别轻易加高阶项。
2.3 相机内参:相机的“身份证”
相机内参,描述的是相机内部的光学属性。说白了,就是告诉你“相机自己长什么样”。
内参矩阵通常写成这样:
// 相机内参矩阵 K
K = [fx 0 cx]
[ 0 fy cy]
[ 0 0 1]
// fx, fy: 焦距(以像素为单位)
// cx, cy: 主点坐标(光轴与成像平面的交点)
这里有个细节要注意:fx 和 fy 通常不相等。为什么?因为像素不是正方形的。我遇到过不少新手,上来就假设 fx = fy,结果标定出来的结果根本不能用。
| 内参参数 | 物理含义 | 常见范围 |
|---|---|---|
| fx | 水平方向焦距(像素) | 500 ~ 3000 |
| fy | 垂直方向焦距(像素) | 500 ~ 3000 |
| cx | 主点x坐标 | 图像宽度的一半附近 |
| cy | 主点y坐标 | 图像高度的一半附近 |
我的习惯:拿到一个新相机,第一件事就是标定内参。别偷懒,这一步省了,后面所有精度都是空中楼阁。
2.4 相机外参:相机在世界中的“位置”
外参描述的是相机坐标系和世界坐标系之间的关系。说白了,就是“相机放在哪里,朝哪个方向看”。
外参由两部分组成:
- 旋转矩阵 R:3x3 的正交矩阵,描述相机的朝向
- 平移向量 t:3x1 的向量,描述相机的位置
// 外参变换公式
// 将世界坐标系中的点 P_w 转换到相机坐标系 P_c
P_c = R * P_w + t
// 或者用齐次坐标写成
[P_c] = [R t] * [P_w]
[ 1 ] [0 1] [ 1 ]
你想想看,双目视觉的核心就是两个相机的外参关系——我们叫它“双目外参”。左相机和右相机之间的 R 和 t,决定了我们能不能算出深度来。
避坑指南:我曾经在室外做双目标定,风太大,三脚架晃了一下。结果外参标出来,左右相机的旋转差了0.5度。别小看这0.5度,在10米外,深度误差能到半米。所以,标定时的机械稳定性,比你想的重要得多。
2.5 世界坐标系到像素坐标的完整转换
好了,现在我们把所有东西串起来。一个世界坐标系中的点,是怎么变成像素坐标的?
一共四步:
- 世界 → 相机:用外参(R, t)把点从世界坐标系转到相机坐标系
- 相机 → 归一化平面:用针孔模型,把三维点投影到归一化平面(Z=1)
- 畸变修正:对归一化平面上的点做畸变修正
- 归一化平面 → 像素:用内参(fx, fy, cx, cy)把点映射到像素坐标
// 完整转换流程(伪代码)
function worldToPixel(P_world, K, R, t, distCoeffs):
// 1. 世界坐标系 → 相机坐标系
P_cam = R * P_world + t
// 2. 投影到归一化平面
x = P_cam.x / P_cam.z
y = P_cam.y / P_cam.z
// 3. 畸变修正
r2 = x*x + y*y
x_dist = x * (1 + k1*r2 + k2*r2*r2) + 2*p1*x*y + p2*(r2 + 2*x*x)
y_dist = y * (1 + k1*r2 + k2*r2*r2) + p1*(r2 + 2*y*y) + 2*p2*x*y
// 4. 归一化平面 → 像素坐标
u = fx * x_dist + cx
v = fy * y_dist + cy
return (u, v)
核心要点:这四步是相机成像的“标准流程”。无论你用 OpenCV、MATLAB 还是自己手写代码,底层逻辑都是这个。搞懂了它,你就掌握了相机成像的“翻译官”技能。
我个人觉得,理解这个转换过程,比背公式重要得多。公式忘了可以查,但流程逻辑一旦刻在脑子里,遇到任何成像问题你都能快速定位——是外参不对?内参没标好?还是畸变模型选错了?
嗯,这一章的内容就到这里。记住:成像模型是双目视觉的“地基”,地基不稳,楼盖得再高也得塌。