3. 坐标系与相机模型

鱼眼相机的畸变模型,说白了就是描述「光线怎么被镜头掰弯」的数学公式。跟普通针孔相机不一样,鱼眼镜头为了看到更宽的画面,故意让光线在进入传感器前发生剧烈弯曲。我刚开始接触这玩意儿时,总觉得这些模型长得差不多,直到在实车上踩了坑才明白——选错模型,矫正出来的图像直接没法看。

今天咱们就把三种最主流的模型掰开揉碎讲清楚:等距投影、等立体角投影,还有工业界最常用的 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 都能用多项式系数拟合出来。我在做车载环视系统时,四个鱼眼相机来自不同供应商,统一用这个模型标定,省了不少事。

避坑指南: 我曾经遇到过一个问题——用 Kannala-Brandt 模型标定后,矫正图像中心区域很完美,但边缘出现波浪状扭曲。排查了半天,发现是多项式阶数选高了(用了 9 阶),过拟合了噪声。建议从 4 阶开始试,不够再加。

3.5 三种模型的对比与选择

我整理了一张对比表,方便你快速决策:

对比维度 等距投影 等立体角投影 Kannala-Brandt
数学复杂度 低(线性) 中(三角函数) 中高(多项式)
标定参数数 1(f) 1(f) 4~6(k₁~k₆)
边缘精度 一般 较好 优秀
光照均匀性 较差 好 取决于拟合
工业应用 少 部分消费级 主流(OpenCV、ROS)

我的建议: 如果你在做产品原型验证,直接用 Kannala-Brandt 模型,OpenCV 的 fisheye 模块已经内置了。如果只是做学术对比或快速演示,等距模型够用。等立体角模型更适合对光照一致性有严格要求的场景,比如多相机拼接时的亮度融合。

3.6 核心知识结构图

下面这张图帮你理清三种模型的关系和适用场景:

鱼眼相机畸变模型核心结构 鱼眼投影模型 等距投影 等立体角投影 Kannala-Brandt 特性 • 线性关系 • 数学简单 特性 • 立体角相等 • 亮度均匀 特性 • 多项式拟合 • 通用性强 工业首选:Kannala-Brandt 模型

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;
}
小技巧: 实际标定时,OpenCV 的 fisheye::calibrate() 会同时输出 Kannala-Brandt 系数和针孔模型的畸变系数。我习惯先看重投影误差——如果 Kannala-Brandt 的误差大于 0.5 像素,说明标定板图像质量有问题,或者镜头本身有非对称畸变。

3.8 总结

三种模型各有各的脾气。等距模型简单但精度有限,等立体角模型照顾了亮度均匀性,Kannala-Brandt 则是工业界的万金油。我个人建议:

  • 快速原型:等距模型 + 手动调参
  • 消费级产品:等立体角模型(如果相机支持)
  • 车载/工业级:Kannala-Brandt 模型 + 多帧标定