2. 畸变校正数学基础:针孔模型、径向畸变与切向畸变模型
聊到内窥镜的畸变校正,咱们得先回到最基础的成像模型上。说白了,就是搞清楚「理想情况下图像应该长什么样」,然后才能去修正那些「不理想」的变形。
我个人习惯,每次拿到一个新的内窥镜镜头,第一件事不是急着调算法,而是先理解它的数学模型。你想想看,连相机是怎么成像的都没搞明白,怎么去校正畸变呢?
2.1 针孔模型:理想世界的成像法则
针孔模型,是所有相机模型的起点。它假设光线通过一个无限小的孔,在成像平面上投影。虽然现实中不存在完美的针孔相机,但这个模型给了我们一个「理想基准」。
数学上,针孔模型描述的是三维空间点 P(X, Y, Z) 到二维图像点 p(u, v) 的映射关系。公式长这样:
u = f * (X / Z) + cx
v = f * (Y / Z) + cy
这里 f 是焦距,(cx, cy) 是图像中心(也叫主点)。
嗯,这里要注意:这个公式假设镜头是完美的,光线是直线传播的。但现实中的内窥镜镜头,尤其是广角镜头,光线经过透镜组后会发生偏折。这就引出了畸变。
核心理解:针孔模型是「理想状态」,畸变是「现实偏差」。我们的任务就是找到这个偏差,然后把它消除。
2.2 径向畸变:镜头弯曲带来的「桶形」与「枕形」
径向畸变,是内窥镜图像中最常见、也最明显的畸变。我在项目中遇到过很多次,尤其是那些大视场角的镜头,拍出来的图像边缘都是弯的。
为什么会这样?因为光线通过透镜边缘时,折射角度和中心区域不一样。离中心越远,变形越严重。
径向畸变主要分两种:
- 桶形畸变:图像边缘向外凸出,像木桶的形状。内窥镜广角镜头最常见。
- 枕形畸变:图像边缘向内凹陷,像枕头。长焦镜头容易出现。
数学上,我们用多项式来建模径向畸变。假设 (x, y) 是理想坐标,(x_distorted, y_distorted) 是实际畸变后的坐标:
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^2 = x^2 + y^2,k1, k2, k3 是径向畸变系数。
我的经验:对于大多数内窥镜镜头,只用 k1 和 k2 就够用了。k3 通常用于鱼眼镜头那种极端畸变。我曾经为了省事,直接用了三个参数,结果过拟合了,校正后的图像反而出现奇怪的波纹。后来我学乖了,先试两个参数,不行再加。
2.3 切向畸变:镜头装配误差带来的「倾斜」
切向畸变,说白了就是镜头和成像平面不平行造成的。你想想看,镜头在装配时,如果有一点点倾斜,光线穿过透镜后就会产生切向的偏移。
这种畸变不像径向畸变那么明显,但在高精度测量场景下,绝对不能忽略。我记得有一次做手术导航项目,径向畸变校正后图像看起来挺直的,但三维重建的误差就是降不下来。查了半天,最后发现是切向畸变在作怪。
切向畸变的数学模型:
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)
这里 p1, p2 是切向畸变系数。
避坑指南:我曾经在标定一个内窥镜时,发现切向畸变系数特别大。一开始以为是镜头质量问题,后来检查发现是标定板没放平。所以,标定前一定要确保标定板与镜头光轴垂直。否则你校正的不是镜头畸变,而是标定板的倾斜。
2.4 完整畸变模型:把两个畸变合在一起
实际应用中,我们通常把径向畸变和切向畸变合并成一个模型。OpenCV 里用的就是这种模型:
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)
这个模型有 5 个畸变参数:k1, k2, k3, p1, p2。标定的过程,就是通过拍摄多张标定板图像,求解出这些参数。
| 参数 | 含义 | 典型范围 |
|---|---|---|
| k1, k2, k3 | 径向畸变系数 | -1.0 ~ 1.0 |
| p1, p2 | 切向畸变系数 | -0.1 ~ 0.1 |
关键点:畸变参数的大小和镜头本身有关。广角内窥镜的 k1 通常是负数(桶形畸变),而长焦镜头可能是正数(枕形畸变)。拿到一个新镜头,先看看它的畸变参数符号,心里就有数了。
2.5 从理论到实践:一个简单的畸变校正流程
理论讲完了,咱们看看实际怎么用。我个人习惯的流程是这样的:
- 标定:拍摄 10-20 张不同角度的棋盘格图像,用 OpenCV 的
calibrateCamera()求出内参和畸变系数。 - 生成映射表:用
initUndistortRectifyMap()生成从畸变图像到校正图像的映射关系。 - 重映射:用
remap()对每一帧图像进行校正。
这里给一段简单的代码示例,展示核心步骤:
import cv2
import numpy as np
# 假设你已经通过标定得到了以下参数
camera_matrix = np.array([[fx, 0, cx],
[0, fy, cy],
[0, 0, 1]], dtype=np.float32)
dist_coeffs = np.array([k1, k2, p1, p2, k3], dtype=np.float32)
# 生成映射表
h, w = image.shape[:2]
mapx, mapy = cv2.initUndistortRectifyMap(
camera_matrix, dist_coeffs, None, camera_matrix,
(w, h), cv2.CV_32FC1
)
# 校正图像
undistorted_img = cv2.remap(image, mapx, mapy, cv2.INTER_LINEAR)
小技巧:如果你处理的视频流,建议只生成一次映射表,然后对每一帧重复使用 remap()。这样能省下大量计算时间。我最早做实时内窥镜系统时,每帧都重新生成映射表,结果帧率惨不忍睹。后来改成预生成,帧率直接翻倍。
好了,这一章的内容就到这里。针孔模型是基础,径向畸变和切向畸变是我们要解决的核心问题。下一章,我会带你手把手实现一个完整的标定流程,到时候咱们再细聊。