4、畸变模型:径向畸变(桶形/枕形)、切向畸变、畸变系数(k1,k2,k3,p1,p2)、畸变校正的数学原理
好,咱们接着聊相机标定里最绕不开的一个话题——畸变。
说实话,我刚开始做视觉项目那会儿,拿到一张照片就直接往上怼算法,结果发现检测精度怎么都上不去。后来才意识到,镜头本身就有「毛病」,拍出来的图像是变形的。你想想看,一个完美的矩形在照片里变成了桶形或者枕形,这还怎么玩?
所以,理解畸变模型,是做好图像校正的第一步。我个人习惯把畸变分成两大类:径向畸变和切向畸变。咱们一个一个说。
4.1 径向畸变:桶形与枕形
径向畸变,说白了就是光线经过透镜时,在径向方向上发生了偏移。为什么会有这个?因为透镜的曲率不是完美的,尤其是广角镜头和鱼眼镜头,这个问题特别明显。
径向畸变有两种典型表现:
- 桶形畸变:图像中心向外「鼓」出来,像木桶一样。常见于广角镜头。我记得有一次用GoPro拍标定板,棋盘格边缘的格子都快飞出去了,这就是典型的桶形畸变。
- 枕形畸变:图像边缘向内「凹」进去,像枕头一样。常见于长焦镜头。我在做远距离测量时遇到过,棋盘格中间正常,四角却往里缩。
为什么会这样?
嗯,这里要注意:径向畸变只与像素点到图像中心的距离有关。离中心越远,畸变越严重。数学上,我们用泰勒级数来建模:
x_corrected = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_corrected = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)
其中,r 是像素点到图像中心的距离。k1、k2、k3 就是径向畸变系数。
关键点:对于普通镜头,通常只用 k1 和 k2 就够了。k3 是给鱼眼镜头这类大畸变镜头准备的。我在项目中一般先试 k1、k2,如果校正效果不够好,再加 k3。
4.2 切向畸变:镜头与传感器不平行
切向畸变,很多人容易忽略。它产生的原因是镜头和成像传感器(CMOS/CCD)没有严格平行。说白了,就是装配公差导致的。
我在一个工业检测项目里遇到过这种情况:标定板明明摆正了,但校正后的图像总有一边偏。折腾了半天,发现是镜头拧歪了。嗯,这就是切向畸变在作怪。
切向畸变的数学模型:
x_corrected = x + [2*p1*x*y + p2*(r^2 + 2*x^2)]
y_corrected = y + [p1*(r^2 + 2*y^2) + 2*p2*x*y]
这里 p1、p2 就是切向畸变系数。你看,它跟 x、y 的乘积有关,所以是「切向」的。
我的经验:切向畸变通常比径向畸变小一个数量级。如果你用的是质量还不错的工业相机,p1、p2 往往接近零。但如果是手机摄像头或者廉价模组,切向畸变就不能忽略。
4.3 畸变系数:k1, k2, k3, p1, p2
好了,咱们把五个畸变系数捋一捋:
| 系数 | 类型 | 作用 | 典型范围 |
|---|---|---|---|
| k1 | 径向 | 主要畸变项,控制桶形/枕形 | -0.5 ~ 0.5 |
| k2 | 径向 | 高阶畸变项,修正边缘 | -0.2 ~ 0.2 |
| k3 | 径向 | 更高阶畸变项,鱼眼镜头用 | -0.1 ~ 0.1 |
| p1 | 切向 | 水平方向倾斜 | -0.05 ~ 0.05 |
| p2 | 切向 | 垂直方向倾斜 | -0.05 ~ 0.05 |
避坑指南:我曾经在标定时发现 k1 的值特别大(超过 1.0),结果发现是标定板图片没拍好。记住,畸变系数过大往往意味着标定数据有问题,而不是镜头真的那么差。先检查标定板是否清晰、覆盖是否全面。
4.4 畸变校正的数学原理
畸变校正,说白了就是「反推」过程。我们已知畸变后的像素坐标,想找到它原本应该在的位置。
具体步骤是这样的:
- 建立映射关系:从畸变图像上的点 (u, v),通过畸变模型反算出理想坐标 (u', v')。
- 插值计算:因为反算出来的坐标往往是小数,需要用双线性插值或最近邻插值来得到像素值。
- 遍历所有像素:对图像中的每个像素重复上述过程。
数学上,校正过程可以写成:
// 伪代码示意
for each pixel (u, v) in corrected image:
// 1. 归一化坐标
x = (u - cx) / fx
y = (v - cy) / fy
// 2. 计算径向和切向畸变
r^2 = x^2 + y^2
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y
// 3. 映射回像素坐标
u_distorted = fx * x_distorted + cx
v_distorted = fy * y_distorted + cy
// 4. 插值获取像素值
corrected_image[u][v] = interpolate(distorted_image, u_distorted, v_distorted)
你可能会问:为什么是从校正图反推到畸变图,而不是反过来?
嗯,这个问题问得好。因为如果从畸变图正向映射到校正图,校正图上会有很多「空洞」——有些像素找不到来源。反推就能保证校正图的每个像素都有值。
核心思想:畸变校正的本质是一个「重映射」过程。OpenCV 里的 cv::remap() 函数就是干这个的。你只需要提供畸变系数和相机内参,它就能帮你算好映射表,然后一次性校正整张图。
4.5 实际项目中的建议
最后,分享几个我在项目里踩过的坑:
- 标定板要拍全:畸变在图像边缘最明显,所以标定板一定要出现在画面的四个角。我曾经偷懒只拍了中心区域,结果校正效果很差。
- 多拍几张:我一般拍 15-20 张不同角度的标定板图片。太少的话,畸变系数不稳定。
- 检查重投影误差:标定完成后,看看重投影误差。如果大于 0.5 像素,说明标定质量有问题,需要重新拍。
- 不要盲目用 k3:对于普通镜头,k3 设为 0 就好。强行加上 k3 反而可能引入过拟合,让校正结果更糟。
好了,畸变模型这部分就聊到这儿。下一节咱们会讲如何用 OpenCV 实际做标定和校正,到时候我会带着你一步步写代码。到时候你就知道,这些数学原理到底是怎么落地的。