2. 相机成像模型
做3D视觉定位,第一步就是搞懂相机是怎么“看”世界的。说白了,相机成像就是把三维空间里的点,映射到二维图像上。这个过程看似简单,但里面藏着不少坑。今天我就把针孔模型、畸变模型和内参矩阵这几个核心概念,掰开了揉碎了讲清楚。
2.1 针孔模型:相机成像的“理想国”
针孔模型是最基础的成像模型。它假设光线沿直线传播,穿过一个小孔,在后面的成像平面上形成倒立的像。嗯,这里要注意,我们通常把成像平面“虚拟”到相机前方,这样图像就是正立的,计算起来也方便。
我个人习惯把针孔模型理解成一个“相似三角形”问题。你看,物体高度、物体到相机的距离、像的高度、焦距,这四个量构成了一个比例关系:
像的高度 / 物体高度 = 焦距 / 物体到相机的距离
这个公式虽然简单,但它是整个相机几何的基石。我在项目里做手眼标定时,经常用这个关系来估算相机的大致安装高度和视野范围。
针孔模型的数学表达,其实就是坐标变换。一个世界坐标系下的点 P(X, Y, Z),经过相机坐标系、图像坐标系,最后变成像素坐标系下的点 p(u, v)。这个过程可以写成:
Z * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T
其中 K 就是内参矩阵,[R | t] 是外参矩阵。今天我们先聚焦内参。
2.2 内参矩阵:相机的“身份证”
内参矩阵 K 描述了相机内部的几何特性。它长这样:
K = [fx, 0, cx;
0, fy, cy;
0, 0, 1]
这里 fx 和 fy 是焦距在像素单位下的值,cx 和 cy 是光心在像素坐标系中的坐标。为什么会有 fx 和 fy 两个值?因为相机传感器的像素不一定是正方形的,所以 x 和 y 方向的焦距可能不同。
我记得有一次,一个同事拿了个工业相机,标定出来的 fx 和 fy 差了将近 5%。他以为是标定出错了,反复标了好几次。我告诉他,这很正常,因为那个相机的像素尺寸是 5.2μm × 5.0μm,不是正方形。所以,fx 和 fy 的差异,直接反映了像素的宽高比。
内参矩阵的核心作用:
- 将相机坐标系下的点,映射到像素坐标系
- 描述了焦距、光心位置和像素尺寸
- 是去畸变、3D重建、手眼标定的基础
2.3 畸变模型:现实世界的“哈哈镜”
针孔模型是理想情况,但实际镜头都有畸变。说白了,就是光线经过镜头时发生了弯曲,导致图像变形。畸变主要分两类:径向畸变和切向畸变。
2.3.1 径向畸变:镜头“肚子”惹的祸
径向畸变是由镜头的形状引起的。光线离光心越远,弯曲得越厉害。它有两种表现:
- 桶形畸变:图像边缘向外凸出,像木桶一样。常见于广角镜头。
- 枕形畸变:图像边缘向内凹陷,像枕头一样。常见于长焦镜头。
径向畸变的数学模型用泰勒级数展开来描述:
x_distorted = x * (1 + k1 * r^2 + k2 * r^4 + k3 * r^6)
y_distorted = y * (1 + k1 * r^2 + k2 * r^4 + k3 * r^6)
其中 (x, y) 是归一化平面上的理想坐标,r 是点到光心的距离,k1、k2、k3 是径向畸变系数。一般用 k1 和 k2 就够了,鱼眼镜头才需要 k3。
避坑指南:我曾经在标定一个广角相机时,只用了 k1 和 k2,结果去畸变后图像边缘还是有明显的扭曲。后来加上 k3,效果就好多了。所以,如果你的镜头视场角很大(超过 90°),建议把 k3 也标出来。
2.3.2 切向畸变:装配“歪了”的结果
切向畸变是由于镜头和成像平面不平行引起的。说白了,就是镜头装歪了,或者传感器贴歪了。它的数学模型是:
x_distorted = x + [2 * p1 * x * y + p2 * (r^2 + 2 * x^2)]
y_distorted = y + [p1 * (r^2 + 2 * y^2) + 2 * p2 * x * y]
其中 p1 和 p2 是切向畸变系数。切向畸变通常比径向畸变小得多,但在高精度应用中不能忽略。
2.4 畸变校正:把“哈哈镜”变回平面镜
畸变校正的过程,其实就是把畸变图像上的点,映射回理想位置。具体步骤是:
- 获取像素坐标 (u, v)
- 用内参矩阵反算归一化坐标 (x, y)
- 用畸变模型计算畸变后的坐标 (x_distorted, y_distorted)
- 用内参矩阵把畸变坐标映射回像素坐标 (u_distorted, v_distorted)
- 在畸变图像上采样,得到校正后的像素值
这个过程在 OpenCV 里用 cv::undistort() 或者 cv::initUndistortRectifyMap() 就能搞定。但我建议你理解背后的原理,因为在实际部署时,你可能需要自己实现去畸变,或者优化去畸变的性能。
注意:去畸变后的图像,边缘区域会被拉伸,导致分辨率下降。如果你在边缘区域做特征提取,精度会受影响。我的做法是:在标定时就尽量让目标区域位于图像中心,或者使用更高分辨率的相机。
2.5 知识结构总览
下面这张图,把相机成像模型的核心逻辑串起来了。从世界坐标到像素坐标,每一步都离不开内参和畸变模型。
2.6 内参与畸变参数对比
为了方便查阅,我把内参和畸变参数整理成了表格。你在做标定和部署时,可以对照着看。
| 参数 | 符号 | 含义 | 典型值范围 | 影响 |
|---|---|---|---|---|
| 焦距 x | fx | x 方向焦距(像素单位) | 500 ~ 5000 | 决定水平方向放大倍数 |
| 焦距 y | fy | y 方向焦距(像素单位) | 500 ~ 5000 | 决定垂直方向放大倍数 |
| 光心 x | cx | 光心在像素坐标系中的 x 坐标 | 图像宽度的一半 ± 50 | 影响投影中心位置 |
| 光心 y | cy | 光心在像素坐标系中的 y 坐标 | 图像高度的一半 ± 50 | 影响投影中心位置 |
| 径向畸变 1 | k1 | 径向畸变一阶系数 | -0.5 ~ 0.5 | 主导径向畸变方向与程度 |
| 径向畸变 2 | k2 | 径向畸变二阶系数 | -0.3 ~ 0.3 | 修正高阶径向畸变 |
| 径向畸变 3 | k3 | 径向畸变三阶系数(鱼眼用) | -0.1 ~ 0.1 | 用于大视场角镜头 |
| 切向畸变 1 | p1 | 切向畸变一阶系数 | -0.05 ~ 0.05 | 修正镜头与传感器不平行 |
| 切向畸变 2 | p2 | 切向畸变二阶系数 | -0.05 ~ 0.05 | 修正镜头与传感器不平行 |
2.7 实战中的几点建议
讲完了理论,说点实际的。我在做无序抓取项目时,总结了几条关于相机标定和成像模型的经验:
- 标定板要覆盖整个视野:别只拍中间,边缘的畸变信息同样重要。我曾经为了省事,只拍了中心区域,结果去畸变后边缘误差很大。
- 多角度拍摄:标定图片要有不同的角度和距离,这样才能解算出稳定的内参和畸变系数。我一般拍 20-30 张。
- 检查重投影误差:标定完成后,看看重投影误差。如果大于 0.5 像素,说明标定质量可能有问题,需要重新拍。
- 固定镜头光圈:标定时的光圈要和实际使用时一致。因为光圈变化会影响景深和畸变程度。
小技巧:在部署时,如果计算资源有限,可以预先计算好去畸变映射表(mapx, mapy),运行时直接用 cv::remap() 查表,比实时计算畸变模型快得多。