3、针孔相机模型:成像原理、焦距与畸变、投影矩阵推导,嵌入式平台下的相机模型简化策略
各位同学,咱们今天聊一个非常基础但又极其重要的东西——针孔相机模型。
为什么说它重要?因为不管你用的是什么BEV感知方案,最终都要把图像上的像素点映射到三维空间中去。这个映射的数学基础,就是针孔模型。说白了,它就是相机成像的“底层逻辑”。
我个人习惯,讲任何模型之前,先抛开公式,把物理过程想清楚。你想想看,一个相机是怎么把三维世界“拍”成一张二维图片的?
3.1 成像原理:小孔成像的数学化
针孔相机模型,本质上就是初中物理课上的小孔成像实验。光线从物体上反射出来,穿过一个小孔,在后面的感光平面上形成一个倒立的像。
但实际相机里没有那个小孔,我们用的是透镜。不过没关系,在数学上我们把它等效成一个针孔。这个等效的针孔,我们叫它光心(Camera Center)。
这里有几个关键概念,我建议你记牢:
- 光心:所有光线的汇聚点,也是坐标系的原点。
- 成像平面:感光元件所在的位置,也就是我们拿到的那张图片。
- 焦距 f:光心到成像平面的距离。注意,这是物理焦距,不是手机宣传里那个等效焦距。
- 主点:光轴与成像平面的交点,通常不在图像正中心,会有偏移。
我在项目中遇到过一个问题:用标定板算出来的内参,主点坐标居然偏离图像中心十几个像素。一开始我以为是标定出错了,后来查了资料才发现,这是镜头装配的公差导致的。嗯,这里要注意,主点偏移是正常的,不要强行把它拉到图像中心。
3.2 焦距与畸变:理想很丰满,现实很骨感
理想情况下,针孔模型是线性的。但现实中的镜头不是完美的针孔,它有两个主要问题:
3.2.1 焦距的物理意义
焦距决定了视场角(FOV)。焦距越短,看得越宽,但远处物体变小;焦距越长,看得越窄,但远处物体被拉近。
在BEV感知中,我们通常用广角镜头(短焦)来获得更大的视野。但代价是边缘畸变非常严重。
| 焦距类型 | 视场角 | 畸变程度 | BEV适用场景 |
|---|---|---|---|
| 短焦(< 8mm) | 大(> 100°) | 严重 | 环视、全景 |
| 中焦(8-25mm) | 中等(60-100°) | 中等 | 前视、侧视 |
| 长焦(> 25mm) | 小(< 60°) | 轻微 | 远距离检测 |
3.2.2 畸变模型
畸变主要分两种:径向畸变和切向畸变。
- 径向畸变:光线经过透镜边缘时弯曲得更厉害。表现为“桶形畸变”(广角镜头常见)或“枕形畸变”(长焦镜头常见)。
- 切向畸变:镜头和成像平面不平行导致的。表现为图像像被“拧”了一下。
我曾经在调试一个环视系统时,发现拼接出来的BEV图总是对不齐。排查了两天,最后发现是切向畸变参数没标定好。从那以后,我养成了一个习惯:畸变标定一定要做完整,径向和切向都要标。
畸变的数学表达通常用多项式模型:
// 径向畸变
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_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)
其中 r 是像素点到主点的距离。k1、k2、k3 是径向畸变系数,p1、p2 是切向畸变系数。
3.3 投影矩阵推导:从世界坐标到像素坐标
好,现在我们把所有东西串起来。一个三维点是怎么变成图像上的一个像素的?
整个过程分四步:
- 世界坐标系 → 相机坐标系:通过外参(旋转矩阵 R 和平移向量 t)
- 相机坐标系 → 归一化平面:除以 Z 坐标,得到 (X/Z, Y/Z, 1)
- 归一化平面 → 畸变平面:应用畸变模型
- 畸变平面 → 像素坐标系:通过内参矩阵 K
投影矩阵 P 可以写成:
P = K * [R | t]
其中:
K = [fx, 0, cx]
[ 0, fy, cy]
[ 0, 0, 1]
fx = f / dx, fy = f / dy
dx, dy 是每个像素的物理尺寸
cx, cy 是主点坐标
这个 K 矩阵,就是相机的内参。你想想看,它把物理世界中的角度信息,转换成了像素坐标。fx 和 fy 通常不相等,因为像素不是正方形的。
像素坐标 = K * [R|t] * 世界坐标
这个公式,就是整个BEV感知的几何基础。所有后续的IPM变换、BEV投影,都源于此。
3.4 嵌入式平台下的相机模型简化策略
好了,理论讲完了。但咱们做嵌入式开发的,最关心的是:怎么在有限的算力下跑起来?
我在一个基于RK3588的项目中,需要实时处理6路摄像头做BEV拼接。如果每帧都做完整的畸变校正和投影,CPU根本扛不住。怎么办?
这里分享几个我常用的简化策略:
3.4.1 查表法(LUT)
这是最常用的方法。既然畸变校正和投影映射是固定的(相机不动,内参不变),那我们可以提前算好每个像素的映射关系,存成一张查找表。
// 伪代码:生成LUT
for each output pixel (u, v):
// 反向映射:从输出像素找到输入像素
(x_world, y_world) = inverse_project(u, v)
(u_src, v_src) = project_with_distortion(x_world, y_world)
lut[u][v] = (u_src, v_src)
// 运行时:直接查表
for each output pixel (u, v):
(u_src, v_src) = lut[u][v]
output[u][v] = input[u_src][v_src]
这样做的好处是:运行时只有内存访问,没有浮点运算。在嵌入式平台上,内存带宽通常比算力更充裕。
3.4.2 降采样+双线性插值
如果对精度要求不是特别高(比如BEV拼接,允许几个像素的误差),可以先对图像降采样,再做投影。比如把1080p的图像降到540p,计算量直接减少到1/4。
但要注意,降采样会丢失高频信息。我建议:只在畸变校正这一步用降采样,后续的特征提取还是用原图。
3.4.3 忽略切向畸变
前面说过,切向畸变通常很小。在嵌入式平台上,我经常只做径向畸变校正,忽略切向畸变。这样每个像素的计算量从5次乘法减少到3次。
3.4.4 定点化计算
如果平台没有FPU(浮点运算单元),或者FPU性能很差,可以考虑把浮点运算转成定点运算。比如把内参矩阵乘以1024,变成整数,计算完再右移10位。
我曾经在一个Cortex-M4的平台上做过相机标定,没有FPU,全靠定点化。虽然精度损失了一点,但速度提升了5倍。
3.4.5 分区域处理
BEV感知中,我们只关心地面附近的区域。对于天空、远处的物体,其实不需要精确的投影。所以可以只对图像的下半部分做畸变校正,上半部分直接跳过。
这个策略在自动驾驶的环视系统中特别有效。因为环视摄像头主要看地面标线和周围车辆,天空部分基本没用。
3.5 总结
针孔相机模型,说白了就是一套数学工具,帮我们把图像和三维空间对应起来。在嵌入式平台上,我们不需要追求极致的精度,而是要在精度和速度之间找到平衡。
我个人建议,初学者先把投影矩阵的推导搞明白,然后动手写一个简单的畸变校正程序。等你真正在板子上跑通了,再考虑优化的事。
下一章,我们会讲IPM(逆透视变换),也就是怎么把图像变成鸟瞰图。那是BEV感知的核心步骤,敬请期待。