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 标定结果评估

标定完了,别急着用。你得先评估一下结果靠不靠谱。

我常用的评估方法有三个:

  1. 重投影误差:上面提到的 RMS,越小越好
  2. 畸变矫正效果:用 cv::undistort() 矫正一张图像,看看直线是不是变直了
  3. 内参稳定性:用不同的图像子集做标定,看内参变化大不大

举个例子,你可以这样检查畸变矫正效果:

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 本章知识体系

下面这张图总结了相机标定的核心流程:

相机标定核心流程 ① 采集棋盘格图像 至少10-20张不同角度 ② 检测棋盘格角点 findChessboardCorners ③ 亚像素角点优化 cornerSubPix ④ 求解内参与畸变 calibrateCamera ⑤ 标定结果评估 重投影误差 < 0.3px 不合格则重新采集 输出结果 内参矩阵 + 畸变系数

整个流程其实不复杂,但每一步都有坑。我个人觉得,最容易被忽视的是第三步——亚像素优化。很多人觉得 findChessboardCorners 检测到的角点已经够用了,但实际标定下来,优化前后的 RMS 能差 0.1 到 0.2 个像素。对于高精度重建来说,这个差距就是能不能用的分水岭。

好了,这节课的内容就到这里。记住一句话:标定做得好,重建差不了。下一节我们会讲如何用标定结果去矫正图像,以及怎么把矫正后的图像用于三维重建。


公众号:蓝海资料掘金营,微信deep3321