第二章 相机标定:让机器人看懂三维世界的第一步

做双目视觉避障,第一步不是调算法,而是标定相机。

这话听起来有点枯燥,但我敢说,很多新手在这上面栽过跟头。我自己刚入行那会儿,拿着一个双目摄像头,对着棋盘格拍了上百张图,结果跑出来的深度图全是扭曲的——后来才发现,标定参数差了那么零点几个像素,整个三维重建就全偏了。

所以这一章,咱们把相机标定这件事彻底讲透。

2.1 相机标定到底在干什么?

说白了,相机标定就是搞清楚两件事:

  • 内参:镜头本身的特性,比如焦距、光心位置、畸变系数
  • 外参:两个相机之间的相对位置和朝向

你想想看,机器人要避障,得知道障碍物在三维空间里到底在哪儿。但相机拍到的只是二维像素点。怎么从像素坐标反推三维坐标?

嗯,这就需要内参和外参搭一座桥。

核心公式(针孔模型):

s * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T

其中 K 是内参矩阵,R 和 t 是外参。没有标定,这个公式就是空壳。

我在项目里见过有人直接用厂家给的标称焦距,结果误差大到30%。记住:每一颗镜头都是独一无二的,必须现场标定。

2.2 张正友标定法:为什么它成了行业标准?

1998年,张正友提出了这个方法。到现在二十多年了,OpenCV里还在用。为什么?

  • 只需要一个棋盘格,打印出来贴在平板上就行
  • 不需要知道相机和棋盘格的精确位置
  • 精度足够高,误差能控制在0.1像素以内

它的核心思路其实很巧妙:

  1. 先忽略畸变,用线性方法估算内参和外参
  2. 再考虑畸变,用非线性优化把参数调准
  3. 最后用最大似然估计收尾

我刚开始学的时候觉得这方法太绕了。后来自己手推了一遍公式才明白——先粗后精,这是工程上最实用的策略。

我的经验:张正友法要求棋盘格必须是平面。我见过有人用弯曲的纸板当标定板,结果标出来的畸变参数全是错的。标定板一定要贴在刚性平板上,玻璃板最好。

2.3 标定板制作:别在这步省事

标定板看着简单,但坑不少。我列几个关键点:

参数 推荐值 为什么
棋盘格尺寸 7×9 或 8×10 内角点 太少精度不够,太多容易遮挡
方格边长 20-30mm 太小看不清,太大覆盖不了视野
打印精度 600dpi以上 边缘模糊会导致角点检测偏移
底板材质 亚克力或玻璃 保证绝对平面

我曾经用A4纸贴在纸箱上标定,结果标出来的焦距偏差了5%。后来换了亚克力板,一次就过了。

避坑指南:标定板不要反光!我吃过这个亏——在强光下用光面纸打印,角点检测全被高光吃掉。用哑光纸,或者喷一层哑光漆。

2.4 OpenCV标定实践:手把手走一遍

理论说完了,咱们直接上代码。我用的是OpenCV 4.x,C++版本。Python也类似,但嵌入式上C++更常见。

// 1. 读取图像并检测角点
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(
    image, 
    cv::Size(patternCols, patternRows), 
    corners,
    cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE
);

// 2. 亚像素精化
cv::cornerSubPix(
    gray, 
    corners, 
    cv::Size(11,11), 
    cv::Size(-1,-1),
    cv::TermCriteria(cv::TermCriteria::EPS+cv::TermCriteria::COUNT, 30, 0.1)
);

// 3. 标定
double rms = cv::calibrateCamera(
    objectPoints,    // 世界坐标系中的点
    imagePoints,     // 图像坐标系中的点
    imageSize,       // 图像尺寸
    cameraMatrix,    // 输出:内参矩阵
    distCoeffs,      // 输出:畸变系数
    rvecs, tvecs,    // 输出:外参
    flags = cv::CALIB_FIX_PRINCIPAL_POINT
);

这里有个细节:CALIB_FIX_PRINCIPAL_POINT这个flag,我建议初学者先用上。为什么?因为光心位置如果自由优化,在图像数量不够时容易跑偏。等你有20张以上有效图像了,再放开它。

标定完成后,一定要看重投影误差:

std::cout << "重投影误差: " << rms << " 像素" << std::endl;
// 一般要求 < 0.5 像素,严格项目 < 0.3

我记得有一次标出来误差0.8像素,查了半天发现是标定板没放正,有几张图角度太单一。后来补拍了不同角度的图,误差降到0.2。

2.5 知识体系总览

下面这张图是我自己整理的标定流程,你照着走就不会乱:

双目相机标定知识体系 输入:棋盘格图像 角点检测 + 亚像素精化 单目标定:内参 + 畸变系数 + 外参 双目标定:R(旋转) + T(平移) 输出:立体校正 + 深度图 关键检查点 ✓ 重投影误差 < 0.5 ✓ 图像数量 > 15张 ✓ 覆盖视野各区域 ✓ 角度变化丰富 ✓ 标定板绝对平面

2.6 标定后的验证:别信数字,信眼睛

标定程序跑完了,重投影误差0.3,看起来很漂亮。但别急着高兴。

我习惯做两件事验证:

  • 畸变校正可视化:把标定前后的图像叠在一起看,边缘的直线有没有变直
  • 立体校正测试:把左右图校正后,在水平方向画一条线,看对应点是否在同一行

有一次标定误差0.2,但立体校正后左右图差了3个像素。后来发现是标定板在拍摄时移动了——棋盘格和相机之间有相对运动,导致外参算错了。

我的习惯:标定完成后,拍一张新的棋盘格(不参与标定),用标定参数去重投影。如果误差和标定时差不多,说明参数泛化能力好。如果差很多,那就是过拟合了。

好了,相机标定这部分就到这里。记住:标定是双目视觉的基石,这一步省事,后面全是坑。下一章咱们聊特征点匹配——有了准确的标定参数,匹配才能又快又准。


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