3. 鱼眼相机畸变模型:等距投影、等立体角投影、多项式模型(Kannala-Brandt)
鱼眼相机的畸变模型,说白了就是描述「光线怎么被镜头掰弯」的数学公式。跟普通针孔相机不一样,鱼眼镜头为了看到更宽的画面,故意让光线在进入传感器前发生剧烈弯曲。我刚开始接触这玩意儿时,总觉得这些模型长得差不多,直到在实车上踩了坑才明白——选错模型,矫正出来的图像直接没法看。
今天咱们就把三种最主流的模型掰开揉碎讲清楚:等距投影、等立体角投影,还有工业界最常用的 Kannala-Brandt 多项式模型。
3.1 为什么鱼眼不能用针孔模型?
先问个问题:针孔相机假设光线直线传播,成像关系是 r = f * tan(θ)。但鱼眼镜头的视场角动不动就 180° 甚至 200° 以上,θ 接近 90° 时 tan(θ) 直接爆炸到无穷大。你想想看,传感器就那么点大,怎么可能装得下?
所以鱼眼镜头换了个思路——用投影函数 r = f * ω(θ) 代替 tan(θ),其中 ω(θ) 是一个有界函数。这样不管 θ 多大,r 都能落在传感器范围内。这就是鱼眼畸变模型的核心思想。
3.2 等距投影模型(Equidistant Projection)
等距投影的公式特别简单:
r = f * θ
其中 r 是像点到主点的距离,f 是焦距,θ 是入射角。说白了,入射角每增加一度,像点就往外挪固定距离。这个模型的好处是数学上最直观,我在做全景拼接时经常用它做基准对比。
实际项目中的坑: 等距模型在视场角小于 120° 时精度还行,一旦超过 150°,边缘的畸变矫正会残留明显的「桶形」效应。我曾经在一个 190° 鱼眼项目里试过纯等距模型,结果矫正完的图像边缘像哈哈镜一样——后来果断换了 Kannala-Brandt。
| 模型 | 投影公式 | 最大视场角 | 典型应用 |
|---|---|---|---|
| 等距投影 | r = fθ | ~180° | 全景拼接、基础矫正 |
| 等立体角投影 | r = 2f sin(θ/2) | ~180° | 光照均匀性要求高的场景 |
| 正交投影 | r = f sin(θ) | ~160° | 特殊光学设计(较少用) |
| 体视投影 | r = 2f tan(θ/2) | ~180° | 天文摄影(较少用) |
3.3 等立体角投影模型(Equisolid Angle Projection)
公式长这样:
r = 2f * sin(θ/2)
这个模型的设计初衷很有意思——它保证每个像素对应的立体角相等。说白了,就是画面中心跟边缘的亮度一致性更好。我在做夜间环视拼接时发现,用等立体角模型的相机,边缘暗角明显比等距模型轻。
但要注意: 等立体角模型在 θ 接近 180° 时,sin(θ/2) 接近 1,r 趋于 2f。这意味着画面边缘的像素密度会下降,导致分辨率损失。嗯,这里有个取舍问题——要亮度均匀还是要边缘清晰度,得看具体场景。
3.4 Kannala-Brandt 多项式模型(工业标准)
前面两种模型都是理想化的数学近似,实际镜头的光学设计没那么规整。Kannala 和 Brandt 在 2006 年提出了一种通用多项式模型,用一个奇次多项式来拟合任意鱼眼镜头的投影曲线:
r(θ) = k₁θ + k₂θ³ + k₃θ⁵ + k₄θ⁷ + k₅θ⁹ + ...
实际应用中,取到 5 阶(k₁~k₄)就足够覆盖绝大多数消费级鱼眼镜头了。k₁ 其实就是等距模型的焦距 f,后面的高次项用来修正实际镜头与理想模型的偏差。
为什么它成了工业标准? 说白了就三个字——通用性。不管你的镜头是哪种投影方式,Kannala-Brandt 都能用多项式系数拟合出来。我在做车载环视系统时,四个鱼眼相机来自不同供应商,统一用这个模型标定,省了不少事。
3.5 三种模型的对比与选择
我整理了一张对比表,方便你快速决策:
| 对比维度 | 等距投影 | 等立体角投影 | Kannala-Brandt |
|---|---|---|---|
| 数学复杂度 | 低(线性) | 中(三角函数) | 中高(多项式) |
| 标定参数数 | 1(f) | 1(f) | 4~6(k₁~k₆) |
| 边缘精度 | 一般 | 较好 | 优秀 |
| 光照均匀性 | 较差 | 好 | 取决于拟合 |
| 工业应用 | 少 | 部分消费级 | 主流(OpenCV、ROS) |
我的建议: 如果你在做产品原型验证,直接用 Kannala-Brandt 模型,OpenCV 的 fisheye 模块已经内置了。如果只是做学术对比或快速演示,等距模型够用。等立体角模型更适合对光照一致性有严格要求的场景,比如多相机拼接时的亮度融合。
3.6 核心知识结构图
下面这张图帮你理清三种模型的关系和适用场景:
3.7 代码实战:OpenCV 中的模型选择
OpenCV 的 fisheye 模块默认使用 Kannala-Brandt 模型。下面这段代码展示了如何用三种模型计算投影点:
#include <opencv2/opencv.hpp>
#include <cmath>
// 等距投影:r = f * theta
float equidistantProjection(float theta, float f) {
return f * theta;
}
// 等立体角投影:r = 2f * sin(theta/2)
float equisolidProjection(float theta, float f) {
return 2.0f * f * std::sin(theta / 2.0f);
}
// Kannala-Brandt 多项式模型(4阶)
float kannalaBrandtProjection(float theta, float k1, float k2, float k3, float k4) {
float theta2 = theta * theta;
float theta3 = theta2 * theta;
float theta5 = theta3 * theta2;
float theta7 = theta5 * theta2;
return k1 * theta + k2 * theta3 + k3 * theta5 + k4 * theta7;
}
int main() {
float theta = 1.2f; // 约 68.8°
float f = 300.0f;
float r_eq = equidistantProjection(theta, f);
float r_es = equisolidProjection(theta, f);
float r_kb = kannalaBrandtProjection(theta, 300.0f, -0.05f, 0.01f, -0.002f);
printf("等距投影 r = %.2f\n", r_eq);
printf("等立体角投影 r = %.2f\n", r_es);
printf("Kannala-Brandt r = %.2f\n", r_kb);
return 0;
}
fisheye::calibrate() 会同时输出 Kannala-Brandt 系数和针孔模型的畸变系数。我习惯先看重投影误差——如果 Kannala-Brandt 的误差大于 0.5 像素,说明标定板图像质量有问题,或者镜头本身有非对称畸变。
3.8 总结
三种模型各有各的脾气。等距模型简单但精度有限,等立体角模型照顾了亮度均匀性,Kannala-Brandt 则是工业界的万金油。我个人建议:
- 快速原型:等距模型 + 手动调参
- 消费级产品:等立体角模型(如果相机支持)
- 车载/工业级:Kannala-Brandt 模型 + 多帧标定