第三章 张正友标定法:棋盘格检测、单应性矩阵与标定流程

张正友标定法,说白了就是用一个棋盘格照片,把相机的内参和外参算出来。这个方法在工业视觉里用得特别多,因为它操作简单,精度也够用。我最早接触这个方法是做手机摄像头标定的时候,那时候还不太理解背后的原理,后来做3D抓取项目,才发现这步要是没做好,后面全白搭。

3.1 棋盘格检测——标定的第一步

棋盘格检测是整个标定流程的入口。你想想看,如果连角点都找不准,后面算出来的参数能靠谱吗?

棋盘格检测的核心任务就是找到黑白格子相交的那些角点。OpenCV里提供了cv::findChessboardCorners这个函数,用起来很方便,但实际项目中坑不少。

棋盘格检测的关键步骤:

  1. 图像预处理——转灰度、直方图均衡化,让黑白格子对比更明显
  2. 粗定位——用图像梯度找到候选角点位置
  3. 亚像素精化——用cv::cornerSubPix把精度推到亚像素级别
  4. 排序与验证——按行按列把角点排好,检查是否漏检或误检

我记得有一次在产线上做标定,光线条件很差,棋盘格反光严重,findChessboardCorners死活检测不出来。后来我加了一步自适应阈值处理,问题就解决了。嗯,这里要注意,棋盘格一定要是非对称的——比如9×6,而不是8×8,否则方向会搞混。

我的经验:标定用的棋盘格最好打印在平整的硬板上,不要贴在软布上。我见过有人用A4纸打印,结果纸一皱,标定出来的焦距偏差了5%以上。

3.2 单应性矩阵——从平面到平面的映射

单应性矩阵(Homography)是张正友标定法的数学核心。它描述的是两个平面之间的透视变换关系。在标定场景里,就是棋盘格平面和相机成像平面之间的映射。

数学上,单应性矩阵是一个3×3的矩阵,有8个自由度。为什么是8个?因为尺度归一化后,最后一个元素固定为1。我刚开始学的时候老搞混这个,后来自己推导了一遍才明白。

// 单应性矩阵的数学形式
// s * [u, v, 1]^T = H * [X, Y, 1]^T
// 其中 H = [h11, h12, h13; h21, h22, h23; h31, h32, 1]

// OpenCV中计算单应性矩阵
cv::Mat H = cv::findHomography(objectPoints, imagePoints, cv::RANSAC);

单应性矩阵的求解至少需要4对匹配点。但实际项目中,我建议用棋盘格上所有的角点来算,然后用RANSAC剔除异常点。这样做出来的H矩阵更稳定。

参数 含义 自由度
h11, h12 旋转与缩放(x方向) 2
h21, h22 旋转与缩放(y方向) 2
h13, h23 平移量 2
h31, h32 透视畸变 2

避坑指南:我曾经在计算单应性矩阵时,直接用DLT(直接线性变换)方法,结果遇到共线点的情况,矩阵奇异了。后来改用SVD分解加最小二乘,才稳定下来。记住,点不能共线,这是基本要求。

3.3 标定流程——从图像到参数

张正友标定法的完整流程,我把它总结成6个步骤。每一步都有它的物理意义,不是随便走个过场。

  1. 准备棋盘格——打印一张棋盘格,测量每个格子的实际尺寸(单位mm)
  2. 采集多张图像——从不同角度拍摄棋盘格,至少10-15张
  3. 检测角点——用findChessboardCorners找到所有角点
  4. 计算单应性矩阵——对每张图像,计算棋盘格平面到成像平面的H矩阵
  5. 求解内参——利用多张图像的H矩阵,通过闭合解算出内参初值
  6. 非线性优化——用Levenberg-Marquardt算法对所有参数做全局优化

这里我特别想强调第2步。很多人随便拍几张就完事了,结果标定出来的参数偏差很大。我个人的习惯是:棋盘格要占图像面积的1/3以上,而且角度要覆盖俯仰、偏航、滚转三个方向。

采集图像的黄金法则:

  • 至少15张图像,20张更保险
  • 棋盘格在图像中心、左上、右上、左下、右下各拍几张
  • 倾斜角度控制在30°到60°之间
  • 确保棋盘格在景深范围内清晰

OpenCV里封装了完整的标定流程,核心函数就是cv::calibrateCamera。它会自动完成从单应性矩阵到内参求解,再到非线性优化的全过程。

// OpenCV标定核心代码
std::vector<cv::Mat> rvecs, tvecs;
cv::Mat cameraMatrix, distCoeffs;

double rms = cv::calibrateCamera(
    objectPoints,      // 世界坐标系中的棋盘格角点
    imagePoints,       // 图像坐标系中的角点
    imageSize,         // 图像尺寸
    cameraMatrix,      // 输出:内参矩阵
    distCoeffs,        // 输出:畸变系数
    rvecs, tvecs,      // 输出:每张图像的旋转和平移
    cv::CALIB_FIX_ASPECT_RATIO  // 标志位
);

标定完成后,一定要做重投影误差检查。这个值通常应该在0.1到0.5像素之间。如果超过1个像素,说明标定质量有问题,需要重新采集数据。

我的判断标准:重投影误差 < 0.3像素,标定质量优秀;0.3-0.8像素,可以接受;> 1像素,建议重做。我在一个3D抓取项目里,标定误差从0.8降到0.2后,抓取成功率从85%提升到了97%。

3.4 标定流程知识图谱

下面这张图把整个标定流程串起来了,从图像采集到参数输出,每一步的输入输出关系都画清楚了。

张正友标定法完整流程 棋盘格准备 多角度图像采集 棋盘格角点检测 计算单应性矩阵 闭合解求内参初值 LM算法全局优化 关键输入 • 棋盘格尺寸(mm) • 内角点数(如9×6) • 至少15张不同角度图像 关键输出 • 内参矩阵(fx, fy, cx, cy) • 畸变系数(k1, k2, p1, p2) • 重投影误差(评估指标) 验证环节 • 重投影误差 < 0.5像素 • 畸变校正后直线不变形

这张图把整个流程串成了一个闭环。从棋盘格准备开始,到非线性优化结束,中间每一步都环环相扣。我建议你在实际项目中,也按照这个流程来走,不要跳步。

最后提醒一句:标定不是一次性的事。相机在使用过程中,镜头松动、温度变化都可能导致参数漂移。我一般建议每3个月重新标定一次,或者在设备维修后立即标定。别等到抓取精度出问题了才想起来。