4. 相机模型
相机模型,说白了就是回答一个问题:三维世界里的一个点,是怎么跑到二维图像上的?
我刚开始做感知的时候,觉得这事很简单——不就是小孔成像嘛。后来在实际项目中踩了无数坑才发现,这里面的门道比想象中深得多。尤其是当你做多传感器融合时,相机模型的理解深度,直接决定了标定精度。
今天咱们就把这块彻底讲透。
4.1 针孔相机模型——最朴素的成像原理
针孔相机模型是所有相机模型的基石。它假设光线通过一个小孔,在后面的成像平面上形成倒像。
数学上,这个过程可以用一个简单的投影公式描述:
// 三维点 (X, Y, Z) 投影到成像平面 (x, y)
x = f * X / Z
y = f * Y / Z
其中 f 是焦距,也就是小孔到成像平面的距离。
你想想看,这个公式其实很直观:物体离得越远(Z 越大),成像越小;焦距越长,成像越大。这就是为什么长焦镜头能把远处的物体拉近。
4.2 从世界到像素——四个坐标系的变换
一个三维点要变成像素坐标,需要经过四次变换。我画了一张图,帮你理清这个流程:
这四个坐标系,我建议你记牢。因为后面做多视角标定、传感器融合,本质上就是在这些坐标系之间来回变换。
4.3 内参矩阵——相机的"身份证"
内参矩阵描述了相机内部的几何特性。它把相机坐标系下的点,映射到像素坐标系。
标准形式是这样的:
K = [fx 0 cx]
[ 0 fy cy]
[ 0 0 1]
其中:
- fx, fy:焦距在 x 和 y 方向的像素尺度(单位:像素)
- cx, cy:主点坐标,也就是光轴与成像平面的交点
核心理解:内参矩阵是相机的固有属性。同一台相机,内参是固定的。换一台相机,内参就变了。所以标定的第一步,就是求内参。
我记得有一次做项目,同事直接用厂家给的标称焦距,结果融合出来的点云全是偏的。后来一测,实际焦距和标称差了 3%。从那以后,我坚持每台相机都做内参标定,绝不偷懒。
4.4 外参矩阵——相机在哪儿?朝哪儿看?
外参矩阵描述的是相机在世界坐标系中的位置和朝向。它由两部分组成:
- 旋转矩阵 R(3×3):描述相机的朝向
- 平移向量 t(3×1):描述相机的位置
组合起来就是:
[R | t] (3×4 矩阵)
外参的作用,就是把世界坐标系下的点,变换到相机坐标系下:
Xc = R * Xw + t
注意:外参是相对于某个世界坐标系定义的。在多相机系统中,通常选择一个相机作为参考,其他相机的外参都是相对于这个参考相机。
我曾经犯过一个低级错误——把旋转矩阵和平移向量的顺序搞反了。结果标定出来的外参,投影误差看起来很小,但实际三维重建全是错的。嗯,从那以后我每次都会检查 R 和 t 的维度。
4.5 畸变模型——现实世界没有完美的针孔
现实中的镜头不是完美的针孔。光线通过镜头时会发生弯曲,导致图像变形。这就是畸变。
畸变主要分两种:
| 畸变类型 | 产生原因 | 视觉效果 | 数学模型 |
|---|---|---|---|
| 径向畸变 | 镜头曲面导致光线弯曲 | 直线变弯,越靠近边缘越明显 | k1, k2, k3 三个参数 |
| 切向畸变 | 镜头与成像平面不平行 | 图像倾斜、梯形变形 | p1, p2 两个参数 |
4.5.1 径向畸变
径向畸变是最常见的。你想想看,广角镜头拍出来的照片,边缘的直线都是弯的——那就是径向畸变。
数学上,它用泰勒级数来近似:
x_distorted = x * (1 + k1*r² + k2*r⁴ + k3*r⁶)
y_distorted = y * (1 + k1*r² + k2*r⁴ + k3*r⁶)
其中 r 是点到主点的距离。k1, k2, k3 是畸变系数。
我的经验:对于普通镜头,k1 和 k2 就够了。鱼眼镜头才需要 k3。我一般先标定 k1, k2,如果重投影误差还大,再加 k3。
4.5.2 切向畸变
切向畸变相对少见,但一旦出现就很麻烦。它通常是因为镜头安装时没有完全垂直于成像平面。
模型是这样的:
x_distorted = x + [2*p1*x*y + p2*(r² + 2*x²)]
y_distorted = y + [p1*(r² + 2*y²) + 2*p2*x*y]
p1, p2 就是切向畸变系数。
避坑指南:我曾经在一个项目中,发现标定出来的切向畸变系数特别大。排查了半天,发现是镜头没拧紧。拧紧之后重新标定,p1, p2 直接降了一个数量级。所以,先检查硬件,再怀疑算法。
4.6 完整的投影过程——从世界到像素
把上面所有内容串起来,一个三维点变成像素坐标的完整过程是这样的:
- 世界 → 相机:用外参 [R|t] 变换
- 相机 → 归一化平面:除以 Z,得到 (x, y)
- 加畸变:用径向和切向畸变模型修正
- 归一化 → 像素:用内参矩阵 K 映射
写成代码就是:
def project_point(Xw, R, t, K, dist_coeffs):
# 1. 世界到相机
Xc = R @ Xw + t
# 2. 归一化平面
x = Xc[0] / Xc[2]
y = Xc[1] / Xc[2]
# 3. 加畸变
r2 = x*x + y*y
k1, k2, p1, p2, k3 = dist_coeffs
x_dist = x * (1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + 2*p1*x*y + p2*(r2 + 2*x*x)
y_dist = y * (1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + p1*(r2 + 2*y*y) + 2*p2*x*y
# 4. 像素坐标
u = K[0,0] * x_dist + K[0,2]
v = K[1,1] * y_dist + K[1,2]
return u, v
关键点:畸变是在归一化平面上加的,不是在像素平面上。很多人搞错这个顺序,结果标定出来的参数怎么调都不对。
4.7 标定流程——怎么拿到这些参数?
理论讲完了,咱们说说实操。标定相机参数,最常用的方法是张正友标定法。流程如下:
| 步骤 | 操作 | 输出 |
|---|---|---|
| 1 | 打印棋盘格标定板 | 已知尺寸的角点 |
| 2 | 从不同角度拍摄 15-20 张照片 | 多视角图像 |
| 3 | 检测每张图的角点 | 像素坐标 (u, v) |
| 4 | 用最小二乘法求解内参和畸变 | K, dist_coeffs |
| 5 | 优化:最小化重投影误差 | 最终参数 |
我的建议:拍照时,让标定板覆盖图像的各个区域——四个角和中心都要有。如果只拍中心区域,边缘的畸变参数就标不准。我一般会拍 20 张,确保覆盖全面。
标定完成后,一定要检查重投影误差。正常情况下,平均误差应该在 0.5 像素以内。如果超过 1 个像素,说明标定质量有问题,需要重新来。
嗯,相机模型这部分就讲到这里。这些内容看起来基础,但它是整个传感器融合的基石。后面的多视角标定、激光雷达与相机联合标定,全都建立在这个模型之上。把今天的内容吃透,后面的路就好走了。