3、单目标定原理:内参矩阵与外参矩阵、单应性矩阵、张正友标定法、棋盘格角点检测
聊到单目标定,很多新手第一反应就是「跑个OpenCV的calibrateCamera就完事了」。嗯,确实能出结果,但一旦结果不对,你根本不知道问题出在哪。我当年第一次标定双目时,重投影误差0.3像素,自我感觉良好,结果三维重建出来全是扭曲的——后来才发现是内参矩阵里的主点偏移量算错了。
所以这一章,咱们把单目标定的核心原理掰开揉碎。说白了,就是搞清楚相机怎么把三维世界映射到二维像素的,以及我们怎么反过来推算这个映射关系。
3.1 内参矩阵:相机自己的「基因」
内参矩阵描述的是相机内部的几何特性。它只跟相机本身有关,跟你把相机放在哪儿、对着哪儿拍,都没关系。
标准形式长这样:
K = [fx 0 cx]
[ 0 fy cy]
[ 0 0 1]
其中:
- fx, fy:焦距在x、y方向上的像素尺度。注意,不是物理焦距,是「物理焦距 × 像素密度」。
- cx, cy:主点坐标,也就是光轴与成像平面的交点。理想情况下在图像正中心,但实际总有偏移。
我在项目中遇到过一台工业相机,标出来的cx偏离中心整整15个像素。一开始以为是标定板没放正,反复标了五次,结果都一样。后来查手册才发现,那款相机的传感器封装本身就有偏移——嗯,这就是内参矩阵存在的意义。
3.2 外参矩阵:相机在空间中的「位置和姿态」
外参矩阵描述的是相机坐标系相对于世界坐标系的旋转和平移。它由两部分组成:
- 旋转矩阵 R:3×3,描述三个轴的旋转
- 平移向量 t:3×1,描述相机原点在世界坐标系中的位置
外参矩阵通常写成齐次形式:
[R | t] = [r11 r12 r13 | tx]
[r21 r22 r23 | ty]
[r31 r32 r33 | tz]
你想想看,每次移动相机,外参就变了。所以标定的时候,我们拍多张不同角度的棋盘格,本质上就是在求解不同的外参矩阵。
3.3 单应性矩阵:连接两个平面的「桥梁」
单应性矩阵H,是张正友标定法的核心。它描述的是两个平面之间的透视变换关系。
在标定场景中,棋盘格是一个平面(世界坐标系的Z=0平面),成像平面是另一个平面。所以:
s * [u, v, 1]^T = K * [R | t] * [X, Y, 0, 1]^T
因为Z=0,可以简化成:
s * [u, v, 1]^T = K * [r1, r2, t] * [X, Y, 1]^T
这里的K * [r1, r2, t]就是单应性矩阵H,3×3的矩阵。
说白了,H把棋盘格上的每个角点,映射到图像上的像素坐标。我们通过检测到的角点对,就能解出H。
3.4 张正友标定法:为什么它成了行业标准?
张正友标定法,说白了就是「用平面棋盘格代替三维标定块」。它最大的贡献是:
- 不需要精密的3D标定块——打印一张棋盘格就行,成本几乎为零
- 算法稳定——先闭式求解,再非线性优化,不容易陷入局部最优
- 考虑了畸变——在优化阶段同时估计径向畸变和切向畸变
它的流程大致是:
- 检测棋盘格角点
- 计算每张图的单应性矩阵H
- 利用H的约束,求解内参矩阵的闭式解
- 利用内参,反算每张图的外参
- 加入畸变参数,做全局非线性优化(Levenberg-Marquardt)
我记得第一次手动实现这个流程时,卡在「从H提取内参」那一步。公式推导了整整两天,最后发现OpenCV一行代码就搞定了——但自己推导一遍,对理解帮助极大。
3.5 棋盘格角点检测:成败在此一举
角点检测是标定的第一步,也是最容易出问题的一步。如果角点坐标不准,后面所有计算都是白搭。
OpenCV提供了两个主要函数:
- cv2.findChessboardCorners():检测棋盘格角点
- cv2.cornerSubPix():亚像素精炼,把角点精度从像素级提升到亚像素级
使用示例:
import cv2
import numpy as np
# 棋盘格内角点数量(内角点,不是格子数)
pattern_size = (9, 6)
# 读取图像
img = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
# 亚像素精炼
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_refined = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
# 绘制并显示
cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret)
cv2.imshow('Corners', img)
cv2.waitKey(0)
3.6 本章知识体系
下面这张图,把单目标定的核心逻辑串起来了:
从图上你能看到,整个流程是串行的。角点检测的质量,直接影响H的精度;H的精度,又决定了内参和外参的初始值。最后一步全局优化,把所有参数(内参、外参、畸变)一起做非线性最小二乘,得到最终结果。
好了,单目标定的原理就聊到这儿。下一章我们会把这些原理落地,手把手教你用OpenCV完成一次完整的单目标定。