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")

运行方式

  1. 创建目录 calib_images/,放入 10~20 张棋盘格照片(JPG 格式),确保棋盘格完整可见且角度、距离有变化。
  2. 将上述代码保存为 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 张以上并覆盖画面边缘区域。
  • 标定结果不稳定:确保所有图像使用同一相机分辨率,且未经过数字变焦或裁剪。