2. 相机成像模型:从光线到像素的数学之旅
相机是怎么把三维世界变成二维图像的?这个问题,我刚开始做视觉时也琢磨了很久。说白了,相机成像就是一套坐标变换的数学游戏。今天咱们就把针孔模型、畸变模型、坐标系转换这几个核心概念掰开揉碎讲清楚。
2.1 针孔相机模型:最朴素的成像原理
针孔相机模型是所有成像模型的基础。它假设光线沿直线传播,穿过一个小孔后在成像平面上形成倒立的像。嗯,虽然真实相机有透镜,但这个模型在数学上非常好用。
核心公式: 从三维世界点 (X, Y, Z) 到图像点 (u, v) 的映射关系
// 针孔相机模型投影方程
u = f * X / Z + u0
v = f * Y / Z + v0
其中:
f —— 焦距(像素单位)
(u0, v0) —— 主点坐标(光轴与图像平面的交点)
(X, Y, Z) —— 相机坐标系下的三维点
我在项目中遇到过不少新手,上来就套这个公式,结果标定出来的参数一塌糊涂。为什么?因为他们忽略了单位统一。焦距 f 的单位是像素,而三维坐标的单位是毫米,中间差了一个像素尺寸的换算。
我的习惯: 做标定前先把像素尺寸(像元大小)查清楚,比如 3.45μm/pixel,这样焦距的物理意义才明确。
2.2 透镜畸变模型:现实总是不完美的
针孔模型是理想情况。真实相机有透镜,透镜就会引入畸变。你想想看,广角镜头拍出来的照片边缘是不是有点弯?那就是畸变。
畸变主要分两类:径向畸变和切向畸变。我最早做工业检测时,就因为没处理好畸变,导致测量误差达到了 0.5mm,后来才意识到畸变校正的重要性。
2.2.1 径向畸变
径向畸变是透镜形状引起的,越靠近图像边缘越明显。它分为两种:
- 桶形畸变: 图像向外凸出,像桶一样。常见于广角镜头。
- 枕形畸变: 图像向内凹陷,像枕头。常见于长焦镜头。
数学上用泰勒级数来建模:
// 径向畸变校正公式
x_corrected = x * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
y_corrected = y * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
其中:
(x, y) —— 畸变图像坐标(归一化平面)
r² = x² + y²
k1, k2, k3 —— 径向畸变系数
我曾经踩过的坑: 只标定 k1 和 k2,忽略了 k3。结果在图像边缘区域,校正后的误差反而更大。对于高精度测量,k3 不能省。
2.2.2 切向畸变
切向畸变是透镜与成像平面不平行造成的。说白了,就是透镜装歪了。这种畸变在消费级相机里比较常见,工业相机相对好一些。
// 切向畸变校正公式
x_corrected = x + [2 * p1 * x * y + p2 * (r² + 2 * x²)]
y_corrected = y + [p1 * (r² + 2 * y²) + 2 * p2 * x * y]
其中:
p1, p2 —— 切向畸变系数
完整的畸变模型需要 5 个参数:k1, k2, k3, p1, p2。OpenCV 的标定函数默认就是这 5 个参数。
2.3 坐标系转换:从世界到像素的完整链路
一个三维点要变成图像上的像素,需要经过 4 次坐标变换。我习惯把这套流程画成一张图,每次做标定都对照着看。
2.4 四大坐标系详解
坐标系转换涉及 4 个坐标系,我整理了一张对比表,方便你对照理解:
| 坐标系 | 符号 | 维度 | 单位 | 说明 |
|---|---|---|---|---|
| 世界坐标系 | (Xw, Yw, Zw) | 3D | mm / m | 用户自定义,标定板所在空间 |
| 相机坐标系 | (Xc, Yc, Zc) | 3D | mm / m | 光心为原点,Z 轴沿光轴方向 |
| 图像坐标系 | (x, y) | 2D | mm | 物理成像平面,主点为原点 |
| 像素坐标系 | (u, v) | 2D | pixel | 图像左上角为原点,u 向右,v 向下 |
2.5 完整投影方程:把一切串起来
把上面所有步骤合并,就得到了完整的相机投影方程。我个人习惯用齐次坐标来写,简洁又清晰:
// 完整投影方程(齐次坐标形式)
s * [u, v, 1]^T = K * [R | t] * [Xw, Yw, Zw, 1]^T
其中:
K = [fx, 0, u0;
0, fy, v0;
0, 0, 1] —— 内参矩阵
[R | t] —— 外参矩阵(3×4)
s —— 尺度因子(Zc 的值)
我的经验: 实际标定时,内参矩阵 K 里的 fx 和 fy 通常不相等。为什么?因为像素不是正方形的。工业相机还好,手机相机的像素长宽比经常不是 1:1。
2.6 畸变校正的工程实现
畸变校正在 OpenCV 里已经封装好了,但理解底层实现很重要。我拆解一下标准流程:
- 提取角点: 从标定板图像中提取亚像素角点坐标 (u, v)
- 归一化: 用内参逆矩阵将像素坐标转到归一化平面 (x, y)
- 畸变校正: 用畸变参数计算校正后的坐标 (x', y')
- 重投影: 用内参矩阵将校正后的坐标转回像素坐标 (u', v')
// OpenCV 畸变校正核心代码(C++)
cv::Mat cameraMatrix, distCoeffs;
cv::Mat map1, map2;
// 生成校正映射表
cv::initUndistortRectifyMap(
cameraMatrix, distCoeffs, cv::Mat(),
cameraMatrix, imageSize, CV_32FC1, map1, map2
);
// 应用校正
cv::remap(distortedImg, undistortedImg, map1, map2, cv::INTER_LINEAR);
性能优化建议: 不要每帧都调用 initUndistortRectifyMap。这个函数计算量大,只调用一次生成映射表,之后每帧直接用 remap 查表。我在一个实时检测项目里,用这个优化把帧率从 15fps 提到了 30fps。
2.7 本章小结
相机成像模型是视觉标定的数学基础。记住三个核心:
- 针孔模型 是理想情况,给出从 3D 到 2D 的线性映射
- 畸变模型 修正透镜带来的径向和切向误差
- 坐标系转换 是连接世界、相机、图像、像素的桥梁