3、相机标定实战:张正友标定法、内参外参求解、畸变校正与重投影误差评估
好,到了真正动手的环节了。前面我们聊了标定板怎么设计、角点怎么检测,但那些都是准备工作。这一章,咱们要把相机标定的核心流程走一遍——从数学原理到代码实现,从参数求解到效果评估。
说实话,我第一次完整跑通张正友标定法的时候,看着重投影误差从几个像素降到零点几,那种感觉还是挺爽的。但后来踩的坑也不少,比如棋盘格图片拍得不够多、标定板放得太近……这些我都会在下面一一提到。
3.1 张正友标定法:核心思想与流程
张正友标定法,说白了就是“用平面标定板来解相机参数”。你不需要一个三维的标定物,只需要在不同角度拍几张棋盘格照片就行。这个方法最大的好处就是——操作简单,精度够用。
它的核心流程可以概括为四步:
- 拍摄多张标定板图像(至少10-20张,角度要丰富)
- 提取每张图像中的角点坐标(用我们上一章讲的方法)
- 利用单应性矩阵求解内参初值(闭式解)
- 非线性优化所有参数(包括畸变系数)
嗯,这里要注意:第三步和第四步是分开的。先求一个粗略的解,再用优化方法精调。为什么?因为直接做非线性优化容易掉进局部最优,有个好的初值就稳多了。
关键理解:张正友标定法假设标定板是平面(Z=0),这样三维到二维的映射就简化为一个单应性矩阵。每张图片提供一个单应性矩阵,多个单应性矩阵联合求解,就能解出内参。
3.2 内参与外参的求解过程
咱们来拆解一下具体的数学过程。别怕,我会尽量用大白话讲清楚。
3.2.1 单应性矩阵与内参的关系
每张标定板图像,我们都能建立一个从“棋盘格平面”到“图像平面”的映射,这就是单应性矩阵 H。H 是 3x3 的矩阵,它包含了内参和外参的信息:
H = K * [r1 r2 t]
其中 K 是内参矩阵,r1、r2 是旋转矩阵的前两列,t 是平移向量。因为棋盘格平面 Z=0,所以第三列旋转向量用不上。
这里有个重要的约束:r1 和 r2 是正交的单位向量。利用这个约束,我们可以从 H 中解出 K。
我的经验:在实际项目中,我习惯先检查 H 矩阵的质量。如果某张图像的 H 矩阵条件数很大(比如 > 100),说明这张图像质量不好,我会直接剔除,不参与后续求解。
3.2.2 内参矩阵的闭式解
张正友的方法给出了一个优雅的闭式解。简单说,就是构造一个关于内参的线性方程组,然后用最小二乘法求解。具体公式我就不展开了,但核心思想是:
- 利用旋转向量的正交约束,得到关于内参的线性方程
- 至少需要 3 张图像才能解出全部 5 个内参(fx, fy, cx, cy, skew)
- 实际中建议用 10-20 张图像,用最小二乘法求最优解
3.2.3 外参的求解
内参求出来后,每张图像的外参(旋转矩阵 R 和平移向量 t)就可以单独求解了。方法也很直接:
- 从 H 和 K 反算出 r1, r2, t
- r3 = r1 × r2(叉积)
- 对旋转矩阵做 SVD 分解,强制满足正交性
嗯,这里有个细节:因为噪声的存在,直接算出来的 R 可能不满足正交性,所以需要用 SVD 做一次“正交化”处理。
3.3 畸变校正:径向畸变与切向畸变
镜头畸变是绕不开的问题。我见过不少项目,标定完内参就以为完事了,结果畸变校正没做好,测量精度直接崩了。
畸变主要分两类:
| 畸变类型 | 产生原因 | 数学模型 | 典型表现 |
|---|---|---|---|
| 径向畸变 | 镜头曲率不完美 | k1, k2, k3 三个参数 | 画面边缘的直线变弯(桶形/枕形) |
| 切向畸变 | 镜头与传感器不平行 | p1, p2 两个参数 | 画面倾斜、梯形失真 |
在张正友标定法中,畸变参数是和非线性优化一起求解的。具体做法是:
- 先用闭式解求出内参和外参的初值
- 然后把这些参数作为初始值,加上畸变参数,一起做非线性最小二乘优化
- 优化的目标函数就是——所有角点的重投影误差之和最小
我曾经踩过的坑:有一次我用广角镜头做标定,只用了 k1 和 k2 两个径向畸变参数,结果边缘区域的校正效果很差。后来加上 k3 参数,效果才明显改善。所以,如果你的镜头视场角比较大(比如 > 90°),建议用三个径向畸变参数。
3.4 重投影误差:标定质量的试金石
标定做得好不好,不能光看参数有没有求出来。我们需要一个量化的指标——重投影误差。
什么是重投影误差?简单说就是:
- 用标定出来的内参和外参,把三维空间中的角点投影到图像上
- 计算投影点与实际检测到的角点之间的像素距离
- 对所有角点取平均,就是重投影误差
这个值越小,说明标定越准。一般来说:
- < 0.5 像素:非常优秀
- 0.5 - 1.0 像素:良好,大多数应用可接受
- > 1.0 像素:需要检查标定过程是否有问题
我的判断标准:我个人习惯把重投影误差控制在 0.3 像素以内。如果超过 0.5 像素,我会重新检查标定板图片的质量——是不是有模糊的?是不是标定板放得太偏了?是不是角点检测精度不够?
3.5 实战代码:OpenCV 标定流程
理论讲完了,咱们直接上代码。OpenCV 提供了完整的标定工具,核心函数就几个:
import cv2
import numpy as np
import glob
# 1. 准备棋盘格角点的三维坐标
checkerboard_size = (9, 6) # 内角点数
square_size = 25.0 # 棋盘格边长,单位 mm
objp = np.zeros((checkerboard_size[0] * checkerboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:checkerboard_size[0],
0:checkerboard_size[1]].T.reshape(-1, 2)
objp *= square_size
# 2. 存储所有图像的三维点和二维点
objpoints = [] # 三维点
imgpoints = [] # 二维点
images = glob.glob('calibration_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 提取角点
ret, corners = cv2.findChessboardCorners(gray, checkerboard_size, None)
if ret:
objpoints.append(objp)
# 亚像素精化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners2)
# 3. 执行标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
# 4. 计算重投影误差
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(
objpoints[i], rvecs[i], tvecs[i], mtx, dist
)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print("平均重投影误差: {:.3f} 像素".format(total_error / len(objpoints)))
代码说明:
cv2.calibrateCamera是核心函数,它内部实现了张正友标定法的完整流程- 亚像素精化步骤(cornerSubPix)非常关键,能显著提升标定精度
- 重投影误差的计算用
cv2.projectPoints把三维点投影回图像平面
3.6 畸变校正与图像去畸变
标定完成后,我们就可以对任意图像做畸变校正了。OpenCV 提供了两个函数:
# 方法一:直接去畸变
undistorted = cv2.undistort(img, mtx, dist, None, mtx)
# 方法二:先计算映射表,再重映射(适合视频流)
mapx, mapy = cv2.initUndistortRectifyMap(
mtx, dist, None, mtx, (w, h), cv2.CV_32FC1
)
undistorted = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
方法二的好处是——映射表可以提前算好,后面每帧图像只需要做一次 remap,速度更快。我在做实时视频标定系统时,用的就是方法二。
注意:去畸变后,图像边缘区域会被拉伸或裁剪。如果你需要保留完整的视场角,可以考虑用 cv2.getOptimalNewCameraMatrix 来调整内参矩阵,平衡畸变校正效果和视场角损失。
3.7 标定质量检查清单
最后,我整理了一份标定质量的检查清单。每次做完标定,我都会对照着检查一遍:
| 检查项 | 合格标准 | 常见问题 |
|---|---|---|
| 重投影误差 | < 0.5 像素 | 角点检测不准、图像模糊 |
| 图像数量 | ≥ 15 张 | 太少导致参数不稳定 |
| 角度覆盖 | 俯仰、偏航各 ±30° | 角度单一导致外参退化 |
| 标定板占比 | 占图像 20%-60% | 太小角点精度差,太大超出景深 |
| 畸变校正效果 | 直线变直,边缘无异常 | 畸变模型不够(如缺 k3) |