第一章 畸变校正原理:从模型到实战
各位同学,大家好。我是你们这门课的老朋友。今天咱们正式开讲《广角镜头像差校正与优化实战》的第一章——畸变校正原理。
说实话,我最早接触畸变校正,是在做车载环视系统的时候。那时候刚拿到一颗180度的鱼眼镜头,拍出来的画面简直像个哈哈镜。嗯,从那时起,我就跟畸变校正杠上了。今天这一章,咱们就把最核心的模型和流程讲透。
1.1 径向畸变模型(Brown-Conrady模型)
先说说最常见的畸变——径向畸变。说白了,就是光线通过镜头时,离中心越远,弯曲得越厉害。你想想看,广角镜头的前组镜片像个大肚子,边缘光线入射角大,折射自然就猛。
径向畸变分两种:
- 桶形畸变:画面向外鼓,像吹气球。广角镜头最常见。
- 枕形畸变:画面向内凹,像枕头。长焦镜头多见。
Brown-Conrady模型是怎么描述这个现象的?它用了一个多项式:
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)
这里 r 是像素到光心的距离。k1、k2、k3 就是径向畸变系数。我个人的习惯是,一般只用 k1 和 k2 就够了,k3 留给那些超广角镜头。我在项目中遇到过,有些廉价镜头 k3 大得离谱,不加的话边缘校正完还是歪的。
核心要点:径向畸变只与距离有关,与方向无关。所以校正时,离中心越远的像素,偏移量越大。
1.2 切向畸变模型
切向畸变,很多人容易忽略。它是什么?说白了,就是镜头装配时,镜片和传感器没完全平行,产生了一个倾斜角。
数学上,切向畸变用两个参数 p1、p2 来描述:
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]
你看,这个公式里出现了 x*y 项,说明切向畸变跟方向有关。我曾经调试过一个工业相机,发现画面左上角总是比右下角更扭曲,查了半天,最后发现是镜头座里垫了个0.1mm的垫片没装好。嗯,这就是典型的切向畸变。
我的经验:切向畸变参数通常很小,p1、p2 的数量级一般在 10^-4 以下。如果标定出来数值很大,先别急着用——检查一下镜头是不是没拧紧。
1.3 畸变校正的数学推导
好,模型有了,怎么校正?核心思路就一句话:从畸变图像反推无畸变图像。
我们通常做的是:
- 假设无畸变图像上有一个点 (u, v)
- 用畸变模型算出它在畸变图像上的位置 (u', v')
- 把畸变图像上 (u', v') 的像素值,赋给无畸变图像的 (u, v)
数学上,这个过程叫「反向映射」。为什么不用正向映射?你想想看,正向映射会导致像素重叠或空洞,图像会有很多黑点。我刚开始做的时候踩过这个坑,校正完的图像跟筛子似的。
完整的推导流程是这样的:
// 假设内参矩阵 K 已知,畸变系数已知
// 1. 将像素坐标转为归一化坐标
x_norm = (u - cx) / fx
y_norm = (v - cy) / fy
// 2. 计算径向和切向畸变
r^2 = x_norm^2 + y_norm^2
x_rad = x_norm * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_rad = y_norm * (1 + k1*r^2 + k2*r^4 + k3*r^6)
x_tan = 2*p1*x_norm*y_norm + p2*(r^2 + 2*x_norm^2)
y_tan = p1*(r^2 + 2*y_norm^2) + 2*p2*x_norm*y_norm
// 3. 得到畸变后的归一化坐标
x_dist = x_rad + x_tan
y_dist = y_rad + y_tan
// 4. 转回像素坐标
u_dist = x_dist * fx + cx
v_dist = y_dist * fy + cy
注意:这里的 (u, v) 是无畸变图像上的整数坐标,而 (u_dist, v_dist) 通常是浮点数。所以下一步必须做插值。
1.4 校正流程:标定 -> 映射 -> 插值
整个畸变校正流程,我把它总结成三步。这三步缺一不可,顺序也不能乱。
第一步:标定
用棋盘格或圆点标定板,拍十几张不同角度的照片。然后用 OpenCV 的 calibrateCamera() 算出内参和畸变系数。我建议至少拍15张,角度要覆盖画面的四个角落和中心。我曾经偷懒只拍了8张,结果校正完边缘还是有残留畸变。
第二步:映射
用上面推导的公式,建立一张映射表(map)。这张表记录了无畸变图像每个像素,对应畸变图像上的哪个位置。实际工程中,我们通常用 initUndistortRectifyMap() 来生成这张表。
第三步:插值
因为映射后的坐标是浮点数,我们需要用插值来估计像素值。常用的方法有:
- 最近邻插值:速度快,但有锯齿。适合预览。
- 双线性插值:质量好,速度适中。我90%的项目都用它。
- 双三次插值:质量最好,但慢。适合离线处理。
OpenCV 里直接用 remap() 函数,指定插值方法就行。
实战建议:如果你做实时视频流,建议把映射表提前算好存起来,运行时只做 remap。我见过有人每帧都重新算映射表,帧率直接掉到个位数。
知识体系总览
下面这张图,是我自己画的畸变校正知识体系。你看一遍,心里就有谱了。
这张图把咱们这一章的内容串起来了。你看,左边是模型,中间是数学,右边是流程。三者环环相扣。
避坑指南:我曾经在标定时用了不对称的棋盘格(比如7x8),结果 OpenCV 报错说找不到角点。后来我改成一边奇数一边偶数(比如6x9),问题就解决了。记住:棋盘格的行列数不能相等。
好了,第一章的内容就到这里。畸变校正的原理和流程,说白了就是「模型 + 映射 + 插值」这三板斧。你把这个框架记住了,后面几章讲具体实现的时候,就不会觉得乱。
下一章,咱们会深入讲标定的具体操作——包括怎么拍标定板、怎么选参数、怎么判断标定结果好不好。到时候我会拿我实际项目中的标定数据给大家看。