4、单摄像头标定:棋盘格标定法、张正友标定法、OpenCV标定流程、畸变校正实战

单摄像头标定,说白了就是给相机做「体检」。你得知道它的内参(焦距、主点)、畸变系数,才能把拍到的扭曲画面还原成真实世界的样子。我刚开始做多摄像头融合时,觉得标定就是跑个脚本的事。结果呢?两台相机各自标定误差差了 0.3 个像素,融合出来的点云全是重影。嗯,从那以后我再也不敢小看这一步了。

4.1 棋盘格标定法:为什么选棋盘格?

你想想看,标定需要知道「世界坐标系中的 3D 点」和「图像上的 2D 点」之间的对应关系。棋盘格的好处是:角点检测算法成熟、精度高,而且黑白相间的图案对光照变化不敏感。

我个人习惯用 9×6 内角点的棋盘格(也就是 10×7 个格子)。为什么?因为 OpenCV 的 findChessboardCorners 默认支持这个尺寸,省得改参数。我在项目中遇到过用 7×5 的棋盘格,结果标定出来的焦距偏差 2%,后来换成 9×6 就稳了。

棋盘格选择建议:
  • 内角点数量:至少 8×6,推荐 9×6 或 11×8
  • 格子大小:20-30mm 最常用,太大或太小都会影响角点检测精度
  • 打印后贴在平整硬板上,不要用软纸——我见过有人贴在海报纸上,标定结果飘忽不定

4.2 张正友标定法:核心思想

张正友在 1998 年提出的这个方法,解决了「用 2D 平面标定板标定 3D 相机」的问题。核心就三步:

  1. 计算单应性矩阵:每张棋盘格图像都能算出一个 H 矩阵,它连接了世界坐标和图像坐标
  2. 求解内参初值:利用多张图像的 H 矩阵,构建线性方程组,解出内参矩阵 K
  3. 非线性优化:用 Levenberg-Marquardt 算法最小化重投影误差,同时优化畸变系数

我曾经用 15 张图像标定,重投影误差 0.12 像素;换成 5 张图像,误差直接跳到 0.4 像素。所以我的经验是:至少拍 10-15 张不同角度、不同距离的棋盘格图像。

张正友标定法的关键假设:
  • 标定板是平面(Z=0)
  • 相机模型为针孔模型(带径向和切向畸变)
  • 图像数量足够覆盖视场各个区域

4.3 OpenCV 标定流程:从图像到参数

OpenCV 把张正友法封装得非常好用。我一般按这个流程走:

步骤 函数/操作 说明
1. 准备棋盘格 打印并拍摄 10-15 张图像 覆盖视场中心、边缘、倾斜、旋转
2. 角点检测 cv::findChessboardCorners 返回亚像素精度的角点坐标
3. 角点细化 cv::cornerSubPix 提升到亚像素级别,我一般设 winSize=11
4. 标定计算 cv::calibrateCamera 输出内参、畸变系数、旋转/平移向量
5. 评估误差 计算重投影误差 小于 0.3 像素算合格,超过 0.5 像素要重拍
避坑指南:我曾经在角点检测后直接调用 calibrateCamera,结果误差 0.8 像素。排查了半天,发现是 cornerSubPix 的迭代次数设成了 1。改成 30 次后,误差降到 0.15 像素。所以——亚像素细化这一步千万别省。

4.4 畸变校正实战:代码与效果

标定完成后,畸变校正就很简单了。OpenCV 提供了两个函数:

  • cv::undistort:一步到位,适合单张图像
  • cv::initUndistortRectifyMap + cv::remap:适合视频流,提前算好映射表

我个人习惯用第二种方案。为什么?因为视频流里每帧都调用 undistort 太慢了,而 remap 是查表操作,速度快一个数量级。

// C++ 示例:畸变校正(视频流方案)
cv::Mat cameraMatrix, distCoeffs;
// 从标定结果加载内参和畸变系数

cv::Mat map1, map2;
cv::Size imageSize(1920, 1080);
cv::initUndistortRectifyMap(
    cameraMatrix, distCoeffs, cv::Mat(),
    cameraMatrix, imageSize,
    CV_32FC1, map1, map2
);

// 每帧调用 remap
cv::Mat frame, undistortedFrame;
cv::remap(frame, undistortedFrame, map1, map2, cv::INTER_LINEAR);

校正后的效果:原本弯曲的直线变直了,边缘的色差也减轻了。我在一个项目中用这个方案处理 4 路 1080p 视频流,每路耗时不到 3ms,完全满足实时要求。

实战小技巧:
  • 标定后保存内参和畸变系数到 YAML 文件,方便后续加载
  • 如果校正后图像边缘出现黑色区域,可以调整 newCameraMatrix 的缩放因子(alpha 参数)
  • alpha=0 时裁剪所有黑色区域,alpha=1 时保留全部像素

4.5 知识体系:单摄像头标定全景图

下面这张图把整个标定流程串起来了。从棋盘格准备到畸变校正,每一步都有对应的 OpenCV 函数和注意事项。

单摄像头标定知识体系 棋盘格图像(10-15张) findChessboardCorners + cornerSubPix calibrateCamera(张正友法) 内参矩阵 + 畸变系数 + 重投影误差 initUndistortRectifyMap + remap 关键参数 • 棋盘格内角点:9×6 • 亚像素窗口:11×11 • 重投影误差:<0.3px 避坑提醒 • 棋盘格必须平整 • 图像覆盖视场各区域

嗯,到这里单摄像头标定的核心内容就讲完了。记住:标定精度直接影响后续多摄像头融合的效果。我见过太多人跳过这一步直接做立体匹配,结果重投影误差 2 个像素,点云全是噪声。所以,老老实实把棋盘格拍好、把参数标准,后面的事才能顺起来。