4. 相机成像模型
做手眼标定,绕不开相机成像模型。说白了,你得知道相机是怎么把三维世界“拍”成一张二维图片的。我刚开始接触这个领域时,总觉得标定就是算个矩阵,后来发现,不理解成像模型,标定结果很容易飘。
这一章,我们就把相机成像这件事拆开揉碎了讲。核心就三个东西:针孔模型、内参矩阵、畸变模型。
4.1 针孔相机模型——最朴素的成像原理
针孔模型,是所有相机模型的基础。它假设光线是直线传播的,穿过一个小孔,在后面的成像平面上形成倒像。嗯,听起来很简单,但数学上非常优雅。
我们通常把成像平面“虚拟”到相机前方,这样图像就是正的了,方便计算。这个虚拟平面到光心的距离,就是焦距 f。
空间中的一点 P(X, Y, Z),投影到成像平面上的点 p(x, y),关系如下:
x = f * (X / Z)
y = f * (Y / Z)
这里有个关键点:Z 是深度。你想想看,物体离相机越远,它在图像上就越小,这就是透视效应。我在做机器人抓取时,经常需要根据这个关系反推物体的三维位置,如果深度不准,抓取点就偏了。
核心理解:针孔模型建立了“世界点 → 图像点”的线性映射关系。但实际相机有镜头,不是针孔,所以后面需要畸变模型来修正。
4.2 内参矩阵——把物理坐标变成像素坐标
上面那个公式得到的是物理单位(毫米)的坐标。但图像是由像素组成的,我们需要把物理坐标映射到像素坐标。这个映射,就是内参矩阵 K。
内参矩阵长这样:
K = [ fx 0 cx
0 fy cy
0 0 1 ]
其中:
- fx, fy:分别是 x 和 y 方向上的焦距(像素单位)。通常 fx ≈ fy,但也不。
- cx, cy:主点坐标,也就是光轴与成像平面的交点。理想情况下在图像中心,但实际会有偏移。
完整的投影公式就变成了:
u = fx * (X / Z) + cx
v = fy * (Y / Z) + cy
写成齐次坐标形式更简洁:
s * [u, v, 1]^T = K * [X, Y, Z, 1]^T
这里的 s 是尺度因子,其实就是 Z。
我的习惯:标定完相机后,第一件事就是看 cx, cy 是否接近图像中心的一半。如果偏差超过 10 个像素,我会检查标定板是否拍全了。有一次偏差很大,结果是标定板边缘被切掉了,重拍后就好了。
4.3 畸变模型——镜头带来的“变形”
实际镜头不是完美的针孔,光线经过透镜会发生折射,导致图像变形。这就是畸变。畸变主要分两类:径向畸变和切向畸变。
4.3.1 径向畸变
径向畸变是光线在透镜边缘弯曲得更厉害造成的。离光心越远,变形越明显。常见的有两种:
- 桶形畸变:图像向外膨胀,像桶一样。常见于广角镜头。
- 枕形畸变:图像向内收缩,像枕头一样。常见于长焦镜头。
数学模型用泰勒级数展开来修正:
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 是像素点到光心的距离,k1, k2, k3 是径向畸变系数。通常 k1 和 k2 就够了,k3 用于鱼眼镜头等大畸变情况。
我曾经踩过的坑:用 OpenCV 标定时,默认只标定 k1, k2, p1, p2。如果镜头畸变很大(比如广角),一定要把 k3 加上,否则图像边缘的标定误差会很大,直接影响手眼标定的精度。
4.3.2 切向畸变
切向畸变是由于镜头和成像平面不平行造成的。说白了,就是镜头装歪了。这种畸变在工业相机中比较常见,尤其是安装不当时。
修正公式:
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 就是切向畸变系数。
4.4 畸变校正流程
实际应用中,我们先用标定板得到畸变系数,然后对每一帧图像做校正。流程如下:
- 获取原始图像:从相机读取带畸变的图像。
- 计算畸变映射:根据内参和畸变系数,计算每个像素从畸变位置到校正位置的映射。
- 重映射:用映射关系重新采样图像,得到无畸变图像。
OpenCV 中,用 cv::initUndistortRectifyMap() 和 cv::remap() 两步搞定。
重要提醒:手眼标定时,一定要用校正后的图像提取角点。否则畸变会引入系统性误差,标定结果会偏。我见过有人直接用原始图像标定,结果误差大了 3 倍。
4.5 知识结构总览
下面这张图,把相机成像模型的整体脉络画出来了。你可以对照着看,心里有个谱。
4.6 畸变系数与内参的标定关系
标定相机时,内参和畸变系数是一起求解的。你不能先算内参再算畸变,它们是耦合的。常用的标定方法是张正友标定法,用棋盘格多角度拍摄,然后优化求解。
标定结果通常包含以下参数:
| 参数 | 符号 | 说明 | 典型值范围 |
|---|---|---|---|
| 焦距 x | fx | x 方向焦距(像素) | 500 ~ 3000 |
| 焦距 y | fy | y 方向焦距(像素) | 500 ~ 3000 |
| 主点 x | cx | 光心在 x 方向偏移 | 图像宽度的一半 ± 20 |
| 主点 y | cy | 光心在 y 方向偏移 | 图像高度的一半 ± 20 |
| 径向畸变 1 | k1 | 二阶径向畸变系数 | -0.5 ~ 0.5 |
| 径向畸变 2 | k2 | 四阶径向畸变系数 | -0.5 ~ 0.5 |
| 径向畸变 3 | k3 | 六阶径向畸变系数(可选) | -0.5 ~ 0.5 |
| 切向畸变 1 | p1 | 切向畸变系数 | -0.1 ~ 0.1 |
| 切向畸变 2 | p2 | 切向畸变系数 | -0.1 ~ 0.1 |
我的经验:标定完成后,一定要做重投影误差检查。如果平均误差超过 0.5 像素,说明标定质量有问题。我一般要求 0.2 像素以下才放心用于手眼标定。
4.7 小结
相机成像模型,说白了就是回答一个问题:三维空间中的点,到底落在图像哪个像素上?针孔模型给了我们一个线性框架,内参矩阵完成了物理到像素的转换,畸变模型修正了镜头的非线性误差。这三者合在一起,才是完整的相机模型。
理解了这个,后面做手眼标定时,你才能知道误差从哪里来,怎么去消除它。