1. 相机内参标定基础
任务目标
使用棋盘格标定板对单目相机进行内参标定,获得焦距 \(f_x, f_y\)、主点 \(c_x, c_y\) 以及畸变系数 \(k_1, k_2, p_1, p_2, k_3\),并通过重投影误差评估标定质量。
环境与依赖
- Python 3.8+
- OpenCV(含 contrib 模块):
pip install opencv-python opencv-contrib-python - NumPy:
pip install numpy
实现思路
采集 10~20 张不同角度、距离和姿态的棋盘格图像(本案例使用 9×6 内角点棋盘格,方格边长 25mm)。对每张图像使用 cv2.findChessboardCorners 提取角点,再通过 cv2.calibrateCamera 求解内参和畸变系数。最后用 cv2.projectPoints 将三维角点重投影到图像平面,计算均方根重投影误差作为标定质量指标。
关键完整代码
import cv2
import numpy as np
import glob
# 棋盘格参数
CHESS_COLS = 9 # 内角点列数
CHESS_ROWS = 6 # 内角点行数
SQUARE_SIZE = 25.0 # 方格边长(mm)
# 准备三维角点坐标(z=0平面)
objp = np.zeros((CHESS_COLS * CHESS_ROWS, 3), np.float32)
objp[:, :2] = np.mgrid[0:CHESS_COLS, 0:CHESS_ROWS].T.reshape(-1, 2) * SQUARE_SIZE
# 存储所有图像的三维点和二维点
obj_points = [] # 三维点
img_points = [] # 二维点
image_files = sorted(glob.glob('calib_images/*.jpg'))
if len(image_files) < 5:
raise ValueError(f"至少需要5张标定图像,当前仅找到 {len(image_files)} 张")
for fname in image_files:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (CHESS_COLS, CHESS_ROWS), None)
if ret:
obj_points.append(objp)
# 亚像素精化角点
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
img_points.append(corners_refined)
# 可视化角点(可选)
cv2.drawChessboardCorners(img, (CHESS_COLS, CHESS_ROWS), corners_refined, ret)
cv2.imshow('Corners', img)
cv2.waitKey(100)
else:
print(f"跳过未检测到棋盘格的图像: {fname}")
cv2.destroyAllWindows()
if len(obj_points) < 5:
raise RuntimeError(f"有效标定图像不足5张,实际 {len(obj_points)} 张")
# 相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points, gray.shape[::-1], None, None
)
# 计算重投影误差
total_error = 0.0
for i in range(len(obj_points)):
proj_points, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(img_points[i], proj_points, cv2.NORM_L2) / len(proj_points)
total_error += error
mean_error = total_error / len(obj_points)
# 输出标定结果
print("=== 相机内参矩阵 ===")
print(mtx)
print("\n=== 畸变系数 [k1, k2, p1, p2, k3] ===")
print(dist.flatten())
print(f"\n=== 平均重投影误差: {mean_error:.4f} px ===")
# 保存标定结果
np.savez('camera_calib.npz', mtx=mtx, dist=dist, rms=mean_error)
print("\n标定结果已保存至 camera_calib.npz")
运行方式
- 创建目录
calib_images/,放入 10~20 张棋盘格照片(JPG 格式),确保棋盘格完整可见且角度、距离有变化。 - 将上述代码保存为
calibrate.py,在终端运行:
python calibrate.py
验证方法与通过标准
- 角点检测:运行过程中每张图像应弹出窗口并绘制检测到的角点,若某张图像未检测到棋盘格,程序会打印跳过信息。
- 标定输出:程序应输出 3×3 内参矩阵、5 个畸变系数和平均重投影误差。
- 通过条件:平均重投影误差小于 0.5 像素,说明标定精度良好;若误差大于 1.0 像素,应剔除模糊或棋盘格不完整的图像后重新标定。
- 结果文件:当前目录下生成
camera_calib.npz,可用np.load('camera_calib.npz')验证保存的内参和畸变系数。
常见问题
- 检测不到棋盘格:确认棋盘格为奇数×偶数内角点(如 9×6),图像不过曝或过暗,棋盘格占图像面积不小于 1/4。
- 重投影误差偏大:剔除边缘模糊、运动模糊或棋盘格部分遮挡的图像;增加图像数量至 15 张以上并覆盖画面边缘区域。
- 标定结果不稳定:确保所有图像使用同一相机分辨率,且未经过数字变焦或裁剪。