畸变模型概述:从针孔到鱼眼

聊鱼眼镜头校正之前,咱们得先把“畸变”这回事儿掰扯清楚。说白了,畸变就是镜头拍出来的像,跟理想的小孔成像对不上号了。你想想看,一个完美的针孔相机模型,光线直来直去,像和物是严格的线性关系。但现实中的镜头是一堆透镜组,光线穿来穿去,不出点偏差才怪。

我个人习惯把畸变分成两大类:一类是镜头本身的光学设计带来的,另一类是装配工艺导致的。鱼眼镜头呢,它更特殊——它故意引入巨大的畸变来换取超广视角,所以它的模型跟普通镜头还不一样。

径向畸变:镜头天生的“肚子”

径向畸变是最常见的一种。光线通过透镜边缘时,折射角度跟中心不一样,结果就是画面边缘的直线变弯了。我刚开始做相机标定时,拍了一张棋盘格,一看边缘的格子全歪了,当时还以为相机坏了。

径向畸变分两种:

  • 桶形畸变:画面向外鼓,像木桶。鱼眼镜头尤其明显。
  • 枕形畸变:画面向内缩,像枕头。长焦镜头常见。

数学上,我们用多项式来近似这种畸变。设归一化坐标中的点为 (x, y),畸变后的点为 (xd, yd):

x_d = x * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
y_d = y * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)

其中 r² = x² + y²,k1、k2、k3 就是径向畸变系数。r 越大,畸变越严重。嗯,这里要注意:k3 通常只在鱼眼镜头或大畸变镜头中才用,普通镜头用 k1、k2 就够了。

我的经验:标定普通镜头时,我一般只估计 k1 和 k2。加上 k3 反而容易过拟合,尤其是棋盘格数量不够多的时候。鱼眼镜头则必须用 k3,甚至更高阶项。

切向畸变:装配没对齐的“锅”

切向畸变,也叫离心畸变。原因是透镜组的光心不在一条直线上,或者传感器跟镜头不平行。说白了,就是装配公差。

我曾经遇到过一台工业相机,拍出来的图像总有一边模糊,标定后发现切向畸变系数特别大。拆开一看,果然是镜头座歪了。

切向畸变的模型是这样的:

x_d = x + [2 * p1 * x * y + p2 * (r² + 2 * x²)]
y_d = y + [p1 * (r² + 2 * y²) + 2 * p2 * x * y]

p1、p2 就是切向畸变系数。这个模型在 OpenCV 的 calibrateCamera 里是默认一起估计的。

避坑指南:我曾经在标定一个广角镜头时,发现切向畸变系数异常大。排查了半天,发现是标定板没贴平。切向畸变对装配误差很敏感,但标定板不平也会引入类似的误差。所以标定前,先确认标定板是平整的。

鱼眼镜头特有的畸变模型

普通镜头的畸变模型,本质上是基于“入射角的正切”来建模的。但鱼眼镜头不一样——它的视角可以到 180° 甚至 220°,如果用正切模型,边缘的像素会被拉伸到无穷远,根本没法用。

所以鱼眼镜头采用了不同的投影方式。说白了,就是故意用非线性映射,把大视角压缩到有限的传感器上。常见的投影模型有四种:

投影模型 映射关系 特点
等距投影 r = f * θ 角度与像高成正比,最常用
等立体角投影 r = 2f * sin(θ/2) 单位立体角对应等面积
正交投影 r = f * sin(θ) 边缘压缩最严重
体视投影 r = 2f * tan(θ/2) 保持局部形状

其中 θ 是入射角,f 是焦距,r 是像高。你想想看,当 θ = 90° 时,等距投影的 r = f * π/2,而正交投影的 r = f。同样的视角,正交投影的像高更小,所以边缘压缩更厉害。

我个人在实际项目中最常用的是 等距投影模型。OpenCV 的鱼眼标定模块(fisheye::calibrate)默认用的就是它。为什么呢?因为它的数学形式简单,而且跟实际镜头的物理设计最接近——很多鱼眼镜头的光学设计就是按等距投影来的。

关键点:鱼眼镜头的畸变校正,本质上是在“投影模型”和“畸变参数”之间做解耦。先确定投影模型,再估计畸变系数。不要混为一谈。

四种投影模型的对比

为了让你更直观地理解它们的区别,我画了一张对比图。横轴是入射角 θ(0° 到 180°),纵轴是归一化像高 r/f:

四种鱼眼投影模型对比 入射角 θ (°) 归一化像高 r/f 0° 45° 90° 135° 180° 0 1 2 3 等距投影 等立体角投影 正交投影 体视投影

从图上能看出来:

  • 等距投影(蓝线)是线性关系,最直观。
  • 等立体角投影(红线)在边缘有轻微压缩。
  • 正交投影(绿线)到 180° 时像高为 0,所以实际视角有限。
  • 体视投影(紫线)在边缘拉伸最厉害,但局部形状保持最好。

我在做全景拼接项目时,遇到过一个问题:用等距投影标定后,拼接出来的图像边缘有重影。后来发现是镜头实际用的是等立体角投影,而我用了等距模型去校正。换回等立体角模型后,问题就解决了。所以,选对投影模型很重要。

OpenCV 中的鱼眼畸变模型

OpenCV 的鱼眼模块用的是 等距投影 + 多项式畸变 的混合模型。它的完整形式是:

θ_d = θ * (1 + k1 * θ² + k2 * θ⁴ + k3 * θ⁶ + k4 * θ⁸)
x_d = (θ_d / r) * x
y_d = (θ_d / r) * y

其中 θ 是入射角,r = sqrt(x² + y²)。这个模型的好处是:它把投影模型和畸变解耦了。先通过等距投影把角度映射到像高,再用多项式修正残余畸变。

我的习惯:标定鱼眼镜头时,我一般先估计 4 个畸变系数(k1~k4)。如果标定残差还大,再考虑增加系数。但别超过 6 个,否则容易过拟合。

小结一下

这一章我们聊了:

  • 径向畸变:透镜曲率带来的,用多项式修正。
  • 切向畸变:装配误差带来的,用两个参数修正。
  • 鱼眼投影模型:四种常见模型,等距投影最常用。
  • OpenCV 的鱼眼模型:等距投影 + 多项式畸变。