标定与参数获取:棋盘格标定法、OpenCV标定流程、内参矩阵与畸变系数
做实时畸变校正,第一步不是写代码,而是拿到相机的「身份证」——内参和畸变系数。没有这组参数,后面的校正全是空谈。
我见过不少团队,上来就调校正算法,折腾半天效果不对,最后发现是标定参数没算准。说白了,标定是地基,地基歪了,楼盖得再高也得塌。
棋盘格标定法:为什么是棋盘格?
棋盘格标定法,是目前最主流的相机标定方式。你想想看,为什么偏偏是棋盘格?
- 角点检测稳定:黑白交界的角点,在图像中梯度变化明显,算法容易定位。
- 几何关系已知:每个格子的大小是固定的,角点之间的物理距离我们事先量好了。
- 制作成本低:一张A4纸打印出来,贴在平板上就能用。
我个人习惯用 9×6 的棋盘格,内角点就是 8×5。为什么不是 10×7?因为边缘的格子容易被裁切,留一圈余量更保险。
我的经验:打印棋盘格时,尽量用哑光纸,别用光面纸。光面纸在强光下会反光,角点检测容易失败。我曾经在户外测试时吃过这个亏,后来改用哑光纸,问题就解决了。
OpenCV标定流程:四步走
OpenCV 把标定流程封装得很干净,核心就四个步骤。我按实际项目中的顺序给你捋一遍:
- 准备棋盘格图像:拍摄 15-20 张不同角度、不同位置的棋盘格照片。
- 查找角点:用
cv::findChessboardCorners()找到每张图中的内角点。 - 亚像素精化:用
cv::cornerSubPix()把角点精度提升到亚像素级别。 - 执行标定:调用
cv::calibrateCamera(),得到内参矩阵和畸变系数。
这里有个坑:很多人拍完照片直接跑标定,结果误差很大。为什么?因为照片数量不够,或者角度变化太小。
避坑指南:我曾经只拍了 8 张照片就急着标定,结果重投影误差到了 0.8 像素。后来老老实实拍了 20 张,误差降到了 0.15 像素。记住:15-20 张是底线,角度要覆盖画面的四个角和中心区域。
核心代码:标定流程实现
下面这段代码,是我在项目中反复用过的模板。你直接拿去改改就能用:
// 1. 准备棋盘格尺寸
cv::Size boardSize(8, 5); // 内角点数
float squareSize = 30.0f; // 格子边长,单位mm
// 2. 生成物理坐标系中的角点坐标
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; i++) {
for (int j = 0; j < boardSize.width; j++) {
objectPoints.push_back(cv::Point3f(j * squareSize, i * squareSize, 0));
}
}
// 3. 遍历所有图像,查找角点
std::vector<std::vector<cv::Point2f>> imagePoints;
std::vector<std::vector<cv::Point3f>> objectPointsSet;
for (auto& filename : imageFiles) {
cv::Mat img = cv::imread(filename);
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(img, boardSize, corners);
if (found) {
cv::cornerSubPix(img, corners, cv::Size(5,5), cv::Size(-1,-1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.001));
imagePoints.push_back(corners);
objectPointsSet.push_back(objectPoints);
}
}
// 4. 执行标定
cv::Mat cameraMatrix, distCoeffs;
std::vector<cv::Mat> rvecs, tvecs;
double rms = cv::calibrateCamera(objectPointsSet, imagePoints,
cv::Size(1920, 1080),
cameraMatrix, distCoeffs,
rvecs, tvecs);
std::cout << "重投影误差: " << rms << std::endl;
std::cout << "内参矩阵:\n" << cameraMatrix << std::endl;
std::cout << "畸变系数:\n" << distCoeffs << std::endl;
关键指标:重投影误差 RMS 应小于 0.3 像素。如果超过 0.5,说明标定质量有问题,需要检查照片质量或重新拍摄。
内参矩阵:相机的「出厂设置」
内参矩阵,说白了就是描述相机内部光学结构的 3×3 矩阵。它长这样:
| fx 0 cx |
| 0 fy cy |
| 0 0 1 |
- fx, fy:焦距,单位是像素。fx = f / dx,其中 f 是物理焦距,dx 是像素物理尺寸。
- cx, cy:主点坐标,通常是图像中心附近。
我遇到过一个问题:同一款相机,两次标定得到的 fx 差了 2%。后来发现是标定板没贴平,微微翘起导致的。所以标定板一定要贴在刚性平板上,别用软纸板。
畸变系数:镜头天生的「缺陷」
镜头不是完美的,畸变就是它的「生理缺陷」。OpenCV 用 5 个系数来描述:
| 系数 | 含义 | 典型范围 |
|---|---|---|
| k1 | 径向畸变一阶 | -0.5 ~ 0.5 |
| k2 | 径向畸变二阶 | -0.5 ~ 0.5 |
| p1 | 切向畸变一阶 | -0.1 ~ 0.1 |
| p2 | 切向畸变二阶 | -0.1 ~ 0.1 |
| k3 | 径向畸变三阶 | -0.5 ~ 0.5 |
径向畸变让直线变弯,切向畸变让图像倾斜。广角镜头的 k1 通常比较大,我见过鱼眼镜头 k1 能到 -0.4 以上。
小技巧:标定完成后,用
cv::undistort() 对一张棋盘格图像做校正,然后肉眼检查边缘的直线是否变直了。这是最直观的验证方法。
标定流程全景图
下面这张图,把整个标定流程串起来了。从拍摄到参数输出,每一步都离不开:
标定质量检查清单
标定完成后,别急着往下走。我习惯按这个清单检查一遍:
- 重投影误差:RMS 是否小于 0.3 像素?
- 内参合理性:fx 和 fy 是否接近?cx、cy 是否在图像中心附近?
- 畸变系数量级:k1、k2 是否在合理范围内?如果 k1 超过 ±0.5,可能标定有问题。
- 视觉验证:校正后的图像边缘直线是否变直?有没有出现过度校正的「枕形」或「桶形」?
我曾经踩过的坑:有一次标定结果 RMS 只有 0.12,看起来很好。但实际校正时,画面边缘出现了明显的紫色伪影。后来发现是标定板本身印刷精度不够,格子边缘模糊导致角点定位有系统偏差。所以标定板最好用高精度打印,或者买现成的陶瓷标定板。