4. 鱼眼相机模型:从成像原理到NPU部署前的标定

环视系统里,鱼眼相机是真正的“眼睛”。普通相机视角窄,想看到车身周围360°,至少得装6-8个。鱼眼相机呢?4个就够了,每个能拍190°以上。但代价是什么?——图像严重变形。你看到的那张圆圆的、像哈哈镜一样的画面,就是鱼眼相机的原始输出。

这一章,我们就来拆解鱼眼相机的成像原理、畸变模型,以及怎么给它们做内参标定。这些知识,是后续做NPU加速环视算法的基础。说白了,你连相机怎么成像的都不清楚,后面做图像校正、做BEV(鸟瞰图)变换,肯定要踩坑。

4.1 鱼眼相机成像原理

普通相机遵循小孔成像模型——光线直线传播,传感器平面接收。但鱼眼相机不一样,它前面有一组特殊的光学透镜,能把大角度光线“弯”进传感器里。

我刚开始接触鱼眼相机时,有个问题困扰了我很久:为什么鱼眼镜头能把那么宽的场景拍进来?后来拆了一个镜头模组才明白,它的前组镜片是凸出的,像个鱼眼,所以叫这个名字。光线经过这组镜片后,入射角被压缩了,原本180°的光线,最终落在传感器上的位置,可能只相当于普通相机90°的投影位置。

鱼眼相机的成像过程,可以简化为三步:

  1. 世界坐标系中的3D点,通过刚体变换转到相机坐标系
  2. 相机坐标系下的点,通过投影模型映射到归一化平面
  3. 归一化平面上的点,通过内参矩阵转到像素坐标系

这里面最关键的是第二步——投影模型。普通相机用的是透视投影(也叫针孔投影),鱼眼相机用的是非透视投影。常见的鱼眼投影模型有四种:等距投影、等立体角投影、正交投影、体视投影。环视系统里,最常用的是前两种。

核心区别一句话总结: 普通相机是“光线直来直去”,鱼眼相机是“光线拐了个弯”。这个“弯”怎么拐,就是由投影模型决定的。

4.2 畸变模型详解

鱼眼相机的畸变,比普通相机的径向畸变、切向畸变要复杂得多。普通相机的畸变可以用多项式模型(比如Brown模型)来近似,但鱼眼相机的畸变太大,多项式模型往往需要高阶项才能拟合,容易过拟合。

所以,学术界和工业界更倾向于用投影模型+畸变模型的方式来描述鱼眼相机。先选一个投影模型作为“理想模型”,然后在这个基础上加畸变。

4.2.1 等距投影模型

等距投影(Equidistant Projection)是最常用的鱼眼模型。它的核心思想是:入射角与像高成正比。

公式很简单:

r = f * θ

其中:

  • r:像点到图像中心的距离(像高)
  • f:焦距
  • θ:入射角(光线与光轴的夹角)

你看,这个公式多简洁。入射角θ从0°到90°,像高r从0线性增长到f*π/2。如果θ继续增大到180°,r就增长到f*π。这意味着,理论上鱼眼相机可以拍到180°甚至更大的视角。

我在项目中遇到过一个问题:用等距模型标定出来的焦距f,和镜头标称的焦距对不上。后来发现,鱼眼相机的“焦距”和普通相机不是一个概念。普通相机的焦距是物理焦距,鱼眼相机的f是投影参数,不是真正的焦距。嗯,这个坑我踩过,大家注意一下。

4.2.2 等立体角投影模型

等立体角投影(Equisolid Angle Projection)是另一种常用模型。它的公式是:

r = 2 * f * sin(θ/2)

这个模型的特点是:单位立体角对应的像面积相等。说白了,就是场景中相同大小的物体,不管在画面中心还是边缘,成像面积都一样。

你想想看,这有什么好处?——图像的光照均匀性更好。等距投影在边缘区域会有明显的亮度衰减(因为边缘的像素对应更大的入射角,光线能量分散了),但等立体角投影能缓解这个问题。

两种模型的对比:

特性 等距投影 (r = fθ) 等立体角投影 (r = 2f sin(θ/2))
像高与入射角关系 线性 正弦
边缘畸变程度 较大 较小
光照均匀性 较差 较好
常见应用 全景相机、环视系统 天文摄影、安防监控
最大视角 可达180°+ 通常≤180°

我的建议: 做车载环视系统,优先选等距投影模型。原因有两个:一是它的数学形式简单,后续做图像校正、BEV变换时计算量小;二是大多数鱼眼镜头厂商(比如Sunex、舜宇)的镜头设计就是基于等距模型的。选对模型,能省很多标定和调试的功夫。

4.3 内参标定方法

标定,就是求相机的内参(焦距、主点、畸变系数)。鱼眼相机的内参标定,比普通相机麻烦一些。普通相机用张正友标定法,拍几张棋盘格就行。鱼眼相机呢?棋盘格在边缘区域变形太严重,角点检测容易失败。

我常用的方法有两种:

4.3.1 基于棋盘格的标定(OpenCV方法)

OpenCV从3.0版本开始,提供了专门的鱼眼相机标定模块 cv::fisheye。它用的是等距投影模型+多项式畸变模型。

标定步骤:

  1. 准备一个棋盘格标定板(建议A3大小,格子数8×6或9×7)
  2. 从不同角度拍摄20-30张图像(注意:要让棋盘格出现在画面的各个区域,尤其是边缘)
  3. 用 cv::findChessboardCorners 检测角点
  4. 用 cv::fisheye::calibrate 进行标定

代码示例:

#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/ccalib/omnidir.hpp>

// 标定参数
cv::Mat K, D;  // 内参矩阵和畸变系数
std::vector<cv::Mat> rvecs, tvecs;

// 执行标定
double rms = cv::fisheye::calibrate(
    objectPoints,      // 世界坐标系中的角点坐标
    imagePoints,       // 图像坐标系中的角点坐标
    imageSize,         // 图像尺寸
    K,                 // 输出:内参矩阵
    D,                 // 输出:畸变系数(4个参数)
    rvecs, tvecs,      // 输出:旋转和平移向量
    cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC |
    cv::fisheye::CALIB_CHECK_COND |
    cv::fisheye::CALIB_FIX_SKEW,
    cv::TermCriteria(3, 20, 1e-6)
);

我曾经踩过的坑: 用棋盘格标定鱼眼相机时,如果棋盘格太靠近画面边缘,角点检测会失败。我的经验是:拍摄时让棋盘格覆盖画面60%-80%的区域,边缘留一些空白。另外,标定板要尽量平整,不要用手拿着,最好贴在硬板上。有一次我用A4纸打印的棋盘格,纸有点皱,标定出来的内参偏差很大,后来换了亚克力板才搞定。

4.3.2 基于圆形标定板的方法

棋盘格在鱼眼图像边缘变形严重时,角点检测容易失败。圆形标定板(圆点阵列)是个更好的选择。圆形的中心检测比角点检测更鲁棒,即使变形很大,也能准确找到圆心。

OpenCV的 cv::findCirclesGrid 函数支持圆形标定板检测。配合 cv::omnidir::calibrate(全向相机标定模块),可以标定鱼眼相机。

我个人习惯用圆形标定板,尤其是在车载环视项目中。因为车载鱼眼相机的视场角通常在190°以上,棋盘格在边缘区域基本看不清角点,但圆形标定板还能用。

4.3.3 标定结果验证

标定完,怎么知道结果准不准?

我一般做三件事:

  • 重投影误差:计算标定点重投影的均方根误差。鱼眼相机一般要求<0.5像素,如果超过1像素,说明标定质量有问题。
  • 图像校正测试:用标定结果对一张原始图像做校正,看校正后的直线是不是直的。如果直线还是弯的,说明畸变参数不对。
  • 多帧一致性:用不同时间、不同位置拍摄的图像分别标定,看内参是否一致。如果差异很大,可能是标定板不平或者拍摄角度不够。

标定结果的核心指标:

  • 重投影误差 < 0.5 px
  • 主点坐标偏差 < 10 px(不同帧之间)
  • 焦距偏差 < 5%(不同帧之间)
  • 校正后图像中直线无明显弯曲

4.4 鱼眼相机模型与NPU加速的关系

你可能会问:标定和NPU加速有什么关系?

关系大了。NPU上跑的环视算法,第一步就是图像校正——把鱼眼图像变成普通透视图像。校正的质量,直接取决于标定的精度。如果内参标偏了,校正出来的图像是歪的,后面的目标检测、车道线识别全都会出错。

另外,在NPU上做校正时,我们需要把校正映射表(remap table)提前算好,存成查找表(LUT)。这个LUT的生成,就需要用到标定得到的内参和畸变系数。我在NPU上实现过鱼眼校正,用双线性插值+查表法,单帧1920×1080的图像,在NPU上只需要3-5ms就能完成校正。但前提是——标定参数必须准。

所以,别小看标定这一步。它决定了整个环视系统的“地基”稳不稳。

4.5 本章小结

鱼眼相机模型,说白了就是回答三个问题:

  • 怎么成像的?——通过特殊透镜,把大角度光线弯进传感器
  • 畸变长什么样?——等距投影(r=fθ)或等立体角投影(r=2f sin(θ/2))
  • 怎么标定?——棋盘格或圆形标定板,用OpenCV的fisheye或omnidir模块

下一章,我们会讲鱼眼图像的校正算法,以及在NPU上怎么高效实现。但这一章的内容,是后续所有工作的基础。建议你动手标定一下自己手头的鱼眼相机,感受一下整个流程。

鱼眼相机模型知识体系 鱼眼相机模型 成像原理 光线经透镜组“弯折”进入传感器 3D世界→相机坐标系→归一化平面→像素 畸变模型 等距投影:r = f·θ 等立体角投影:r = 2f·sin(θ/2) 内参标定 棋盘格标定(OpenCV fisheye) 圆形标定板(OpenCV omnidir) NPU加速:校正映射表(LUT)生成 + 双线性插值 标定精度决定校正质量 → 校正质量决定环视算法效果 重投影误差 < 0.5px | 主点偏差 < 10px | 焦距偏差 < 5%