2. 畸变数学模型:径向畸变与切向畸变的数学公式(Brown-Conrady模型)
好,咱们进入正题。这一节要聊的,是镜头畸变最核心的数学模型——Brown-Conrady模型。
说白了,这个模型就是用来描述「光线为什么没按我们想的方向走」的数学工具。我最早接触它是在做车载环视系统的时候,当时四个广角相机拼出来的画面,总在拼接缝附近出现重影。查来查去,发现是畸变校正没做干净。嗯,从那以后,我就把这个模型刻在脑子里了。
2.1 畸变从哪来?
先简单说说原理。理想情况下,光线穿过镜头应该直线传播,打在传感器上形成一个完美的像。但现实是,镜头是曲面玻璃,光线经过折射后,实际落点和理论落点之间会有偏移。这个偏移,就是畸变。
畸变主要分两类:
- 径向畸变:光线在镜头边缘弯曲得更厉害,导致图像向外或向内「鼓包」。
- 切向畸变:镜头和传感器不平行,导致图像像被「拧」了一下。
你想想看,如果只校正径向畸变,忽略切向畸变,那画面边缘的直线还是会弯。我见过不少初学者只做径向校正,结果在鱼眼镜头下,画面边缘的建筑物还是歪的。
2.2 Brown-Conrady模型长什么样?
这个模型是业界公认的标准。它把畸变分解成径向和切向两部分,用多项式来拟合。
先定义几个符号:
- \((x_u, y_u)\):理想的无畸变坐标(归一化平面)
- \((x_d, y_d)\):实际观测到的畸变坐标
- \(r^2 = x_u^2 + y_u^2\):点到光轴中心的距离平方
径向畸变公式:
x_radial = x_u * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_radial = y_u * (1 + k1*r^2 + k2*r^4 + k3*r^6)
这里 \(k1, k2, k3\) 就是径向畸变系数。一般用前两个就够了,鱼眼镜头才需要 \(k3\)。
切向畸变公式:
x_tangential = 2*p1*x_u*y_u + p2*(r^2 + 2*x_u^2)
y_tangential = p1*(r^2 + 2*y_u^2) + 2*p2*x_u*y_u
\(p1, p2\) 是切向畸变系数。
合起来就是Brown-Conrady模型:
x_d = x_u * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x_u*y_u + p2*(r^2 + 2*x_u^2)
y_d = y_u * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y_u^2) + 2*p2*x_u*y_u
核心要点:这个模型是从无畸变坐标映射到畸变坐标。实际校正时,我们做的是反向映射——从畸变图像上的像素,反算出它在无畸变图像上的位置。
2.3 参数到底怎么理解?
我习惯用一张表来总结这些参数的作用:
| 参数 | 类型 | 典型值范围 | 物理含义 |
|---|---|---|---|
| \(k1\) | 径向 | -0.5 ~ 0.5 | 主导畸变,正值为枕形,负值为桶形 |
| \(k2\) | 径向 | -0.5 ~ 0.5 | 高阶径向畸变,修正边缘细节 |
| \(k3\) | 径向 | -0.5 ~ 0.5 | 鱼眼镜头专用,修正极边缘 |
| \(p1\) | 切向 | -0.1 ~ 0.1 | 水平方向的倾斜 |
| \(p2\) | 切向 | -0.1 ~ 0.1 | 垂直方向的倾斜 |
我曾经在一个项目中,用OpenCV标定出来的 \(k1\) 是 -0.35,典型的桶形畸变。校正后画面边缘的直线终于直了,但发现中心区域有点「呼吸感」——这是因为 \(k2\) 没调好。后来加了 \(k2\) 的优化,效果才稳定下来。
我的经验:标定得到的参数不要直接拿来用。建议先做一次校正预览,看看边缘直线是否真的直了。如果还有弯曲,可以手动微调 \(k1\) 和 \(k2\),每次改0.01,直到满意为止。
2.4 这个模型怎么用?
实际应用中,我们不会手动算这些公式。OpenCV已经封装好了。但理解原理能帮你调试参数。
举个简单的Python例子:
import cv2
import numpy as np
# 假设标定得到的畸变系数
k1, k2, p1, p2, k3 = -0.35, 0.12, 0.001, -0.002, 0.0
# 相机内参矩阵(假设已知)
K = np.array([[800, 0, 640],
[0, 800, 480],
[0, 0, 1]], dtype=np.float32)
# 畸变系数向量
dist_coeffs = np.array([k1, k2, p1, p2, k3])
# 读取畸变图像
img = cv2.imread('distorted.jpg')
h, w = img.shape[:2]
# 校正
new_K, roi = cv2.getOptimalNewCameraMatrix(K, dist_coeffs, (w, h), 1, (w, h))
undistorted = cv2.undistort(img, K, dist_coeffs, None, new_K)
cv2.imwrite('undistorted.jpg', undistorted)
这段代码里,cv2.undistort 内部就实现了Brown-Conrady模型的反向映射。你只需要提供标定好的参数就行。
注意:getOptimalNewCameraMatrix 的 alpha 参数很关键。alpha=0 会裁剪掉畸变校正后产生的黑色区域,alpha=1 会保留全部像素但边缘会有黑边。我一般设成 0.5,既保留视野又减少黑边。
2.5 知识体系一览
为了让你更直观地理解这一章的结构,我画了一张SVG图:
这张图把模型拆成了三块:径向畸变(k参数)、切向畸变(p参数),以及最终在OpenCV中的应用。你对照着公式看,思路会清晰很多。
2.6 避坑指南
最后分享几个我踩过的坑:
- 参数顺序别搞错:OpenCV中畸变系数的顺序是 [k1, k2, p1, p2, k3],不是 [k1, k2, k3, p1, p2]。我一开始就搞反过,校正出来的画面像哈哈镜。
- 不要过度拟合:如果只用 k1 就能校正到肉眼可接受的程度,就别加 k2 和 k3。参数越多,越容易过拟合,换一张图就崩了。
- 切向畸变别忽略:很多人觉得切向畸变影响小,直接设成0。但我在做高精度测量项目时,p1 和 p2 哪怕只有 0.005,都会导致边缘的测量误差超过 1 个像素。所以,该加还是得加。
嗯,这一章的内容就到这。Brown-Conrady模型是畸变校正的基石,理解了它,后面讲标定和校正算法时,你就能更快上手。