4、OpenCV相机标定实战:检测棋盘格角点、亚像素角点优化、求解内参和畸变系数、标定结果评估
相机标定,说白了就是给相机做一次「视力矫正」。
你想想看,每台相机出厂时都有点小毛病——镜头畸变、焦距偏差、光心偏移。如果不做标定,你拍出来的棋盘格是弯的,重建出来的三维模型也是歪的。我在项目里吃过这个亏,第一次做三维重建时,直接拿手机拍了一组照片就跑标定,结果内参矩阵算出来怪怪的,重投影误差高达两个像素。后来才发现,我连棋盘格角点都没检测准。
嗯,这节课我们就来一步步搞定这件事。
4.1 棋盘格角点检测
角点检测是标定的第一步。OpenCV 提供了 cv::findChessboardCorners() 函数,专门用来找棋盘格的内角点。
核心参数:
patternSize:内角点数量,比如 (9,6) 表示每行9个、每列6个内角点flags:检测模式,常用CALIB_CB_ADAPTIVE_THRESH提高鲁棒性
我个人习惯先转成灰度图再检测,这样速度更快,也避免颜色干扰。
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::Size patternSize(9, 6);
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(gray, patternSize, corners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
如果 found 为 true,说明检测成功。但这时候的角点精度还不够——它们是整数像素坐标。
小技巧: 我建议你每次检测后都用 cv::drawChessboardCorners() 把角点画出来看看。有一次我在弱光环境下采集数据,棋盘格反光严重,检测到的角点歪了半个像素。肉眼看不出来,但画出来就一目了然。
4.2 亚像素角点优化
为什么要做亚像素优化?因为整数像素坐标的精度不够。你想想看,一个像素在图像上可能对应真实世界的几毫米甚至几厘米。如果角点偏差半个像素,标定出来的内参就会差很多。
OpenCV 提供了 cv::cornerSubPix() 函数,它基于梯度信息,把角点坐标精确到亚像素级别。
cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.001);
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1), criteria);
这里的关键参数是 winSize(搜索窗口大小)和 criteria(迭代终止条件)。窗口太小可能找不到最优解,窗口太大又容易受噪声干扰。我一般用 11x11,迭代 30 次,精度 0.001 像素。
注意: 亚像素优化必须在灰度图上进行。我曾经犯过在彩色图上直接调用的错误,结果程序没报错,但优化后的角点反而更差了。嗯,OpenCV 有些函数不检查输入格式,你得自己小心。
4.3 求解内参和畸变系数
有了多张图像的角点坐标,我们就可以调用 cv::calibrateCamera() 来求解相机内参和畸变系数了。
cv::Mat cameraMatrix, distCoeffs;
std::vector<cv::Mat> rvecs, tvecs;
double rms = cv::calibrateCamera(objectPoints, imagePoints, imageSize,
cameraMatrix, distCoeffs, rvecs, tvecs);
这个函数会返回一个重投影误差(RMS),单位是像素。它衡量的是:我们把三维世界点投影到图像上,和实际检测到的角点位置差了多少。
| 输出参数 | 含义 | 典型值 |
|---|---|---|
cameraMatrix |
内参矩阵 (3x3) | fx, fy, cx, cy |
distCoeffs |
畸变系数 (1x5 或 1x8) | k1, k2, p1, p2, k3 |
rvecs |
每张图像的旋转向量 | 3x1 向量 |
tvecs |
每张图像的平移向量 | 3x1 向量 |
rms |
重投影误差 | < 0.5 像素为佳 |
经验之谈: 我一般要求 RMS 小于 0.3 像素才算合格。如果超过 0.5,我会检查是不是有图像没检测好,或者棋盘格本身不平整。有一次我用的棋盘格是打印在普通 A4 纸上的,贴到硬纸板上时有点褶皱,结果 RMS 死活降不到 0.4 以下。换成亚克力板后,直接降到 0.15。
4.4 标定结果评估
标定完了,别急着用。你得先评估一下结果靠不靠谱。
我常用的评估方法有三个:
- 重投影误差:上面提到的 RMS,越小越好
- 畸变矫正效果:用
cv::undistort()矫正一张图像,看看直线是不是变直了 - 内参稳定性:用不同的图像子集做标定,看内参变化大不大
举个例子,你可以这样检查畸变矫正效果:
cv::Mat undistorted;
cv::undistort(image, undistorted, cameraMatrix, distCoeffs);
cv::imshow("Original", image);
cv::imshow("Undistorted", undistorted);
如果矫正后的图像边缘的直线不再弯曲,说明标定结果不错。如果反而出现了奇怪的扭曲,那多半是标定出了问题。
避坑指南: 我曾经遇到过一个情况——RMS 只有 0.2,但矫正后的图像边缘有明显的紫色伪影。后来发现是畸变系数里 k3 的值异常大,导致边缘过度矫正。解决办法是改用 CALIB_RATIONAL_MODEL 标志,或者限制畸变系数的阶数。嗯,有时候 RMS 低不代表一切,你得用眼睛看。
4.5 本章知识体系
下面这张图总结了相机标定的核心流程:
整个流程其实不复杂,但每一步都有坑。我个人觉得,最容易被忽视的是第三步——亚像素优化。很多人觉得 findChessboardCorners 检测到的角点已经够用了,但实际标定下来,优化前后的 RMS 能差 0.1 到 0.2 个像素。对于高精度重建来说,这个差距就是能不能用的分水岭。
好了,这节课的内容就到这里。记住一句话:标定做得好,重建差不了。下一节我们会讲如何用标定结果去矫正图像,以及怎么把矫正后的图像用于三维重建。
公众号:蓝海资料掘金营,微信deep3321