4、畸变校正:径向畸变与切向畸变模型、畸变系数求解、图像去畸变实战
镜头畸变,说白了就是相机“看歪了”。
你想想看,理想的小孔成像模型是完美的直线映射。但现实中的镜头,尤其是广角和廉价镜头,拍出来的画面边缘总会弯曲。这就是畸变。
我刚开始做视觉项目时,总觉得标定就是算个内参矩阵完事。直到有一次,一个高精度定位项目,机械臂死活抓不准。排查了三天,最后发现是图像边缘的畸变没校正,导致像素坐标偏差了十几个像素。嗯,从那以后,我再也不敢跳过畸变校正这一步了。
4.1 畸变从哪来?两类主要畸变
畸变主要分两种:径向畸变和切向畸变。大部分情况下,径向畸变占主导,切向畸变是装配误差引起的。
4.1.1 径向畸变
径向畸变是光线通过镜头时,在边缘处弯曲程度不同造成的。说白了,就是镜头“肚子”的形状不完美。
- 桶形畸变:图像边缘向外凸出,像木桶。常见于广角镜头。
- 枕形畸变:图像边缘向内凹陷,像枕头。常见于长焦镜头。
数学上,我们用泰勒级数来近似这种变形。对于归一化平面上的点 (x, y),校正后的坐标 (x_corrected, y_corrected) 为:
x_corrected = x * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
y_corrected = y * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
其中 r² = x² + y²,k1、k2、k3 就是径向畸变系数。对于普通镜头,k3 通常用不到,只有畸变很大的鱼眼镜头才需要。
4.1.2 切向畸变
切向畸变是镜头和成像平面不平行造成的。说白了,就是镜头装歪了。
校正公式为:
x_corrected = x + [2 * p1 * x * y + p2 * (r² + 2 * x²)]
y_corrected = y + [p1 * (r² + 2 * y²) + 2 * p2 * x * y]
p1、p2 就是切向畸变系数。
4.2 畸变系数怎么求?
畸变系数不是直接测出来的,而是通过优化求解的。我们通常用张正友标定法,在求解内参矩阵的同时,一并算出畸变系数。
基本思路是这样的:
- 用标定板拍多张不同角度的图片。
- 提取每张图片的角点坐标。
- 建立重投影误差方程。
- 用最小二乘法(比如 Levenberg-Marquardt 算法)迭代优化,让所有角点的重投影误差最小。
OpenCV 里,这个工作被封装成了 cv::calibrateCamera() 函数。你只需要输入角点坐标和棋盘格尺寸,它就能返回内参矩阵和畸变系数。
- dist_coeffs:畸变系数向量,格式为 [k1, k2, p1, p2, k3]。
- 重投影误差:一般小于 0.1 像素就算合格。如果大于 0.5 像素,建议重新标定。
4.3 图像去畸变实战
有了畸变系数,下一步就是去畸变。OpenCV 提供了两种主要方法。
4.3.1 方法一:cv::undistort()
这是最直接的方法,一步到位。适合离线处理单张图片。
cv::Mat cameraMatrix = (cv::Mat_<double>(3,3) <<
fx, 0, cx,
0, fy, cy,
0, 0, 1);
cv::Mat distCoeffs = (cv::Mat_<double>(1,5) <<
k1, k2, p1, p2, k3);
cv::Mat undistortedImage;
cv::undistort(distortedImage, undistortedImage, cameraMatrix, distCoeffs);
undistort(),因为它每帧都要重新计算映射,太慢了。我会用下面的方法二。
4.3.2 方法二:cv::initUndistortRectifyMap() + cv::remap()
这个方法分两步:先计算映射表,再用映射表重采样图像。映射表只需要计算一次,之后每帧直接 remap,速度极快。
cv::Mat map1, map2;
cv::initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(),
cameraMatrix, imageSize, CV_32FC1, map1, map2);
cv::Mat undistortedImage;
cv::remap(distortedImage, undistortedImage, map1, map2, cv::INTER_LINEAR);
undistort() 做实时处理,结果帧率从 30fps 掉到了 5fps。换成 initUndistortRectifyMap + remap 后,帧率回到了 28fps。记住,映射表是固定的,只要相机不动,就不需要重新计算。
4.4 知识结构:畸变校正全流程
下面这张图,是我自己总结的畸变校正全流程。你可以把它当作一个检查清单。
4.5 畸变系数与校正方法对比
| 参数/方法 | 说明 | 典型值范围 | 使用建议 |
|---|---|---|---|
| k1 | 径向畸变主系数 | -0.5 ~ 0.5 | 必须标定 |
| k2 | 径向畸变次系数 | -0.3 ~ 0.3 | 建议标定 |
| k3 | 径向畸变高阶系数 | -0.1 ~ 0.1 | 仅鱼眼镜头使用 |
| p1, p2 | 切向畸变系数 | -0.05 ~ 0.05 | 标定即可,通常很小 |
| undistort() | 一步到位,简单 | — | 离线处理,单张图片 |
| initUndistortRectifyMap + remap | 分两步,效率高 | — | 实时视频流,嵌入式 |
- 畸变校正不是可选项,是必选项。尤其是做高精度测量或手眼标定时,不校正畸变,后面的精度全是空谈。
- 径向畸变用 k1、k2 就够了,别贪多。切向畸变通常很小,不用过度关注。
- 实时项目一定要用
initUndistortRectifyMap + remap,这是性能的关键。
好了,畸变校正这块就聊到这。你只要把上面的流程跑一遍,基本就能搞定大部分场景。记住,标定不是一次性的,每次更换镜头或调整相机位置,都要重新标定。别偷懒,不然坑的是你自己。