1. 相机内参标定基础

任务目标

使用OpenCV对单目相机拍摄的棋盘格标定板图像进行内参标定,输出相机内参矩阵(焦距、主点)和畸变系数,并通过重投影误差评估标定质量。

环境依赖

  • Python 3.8+
  • OpenCV(opencv-python)
  • NumPy

安装命令:pip install opencv-python numpy

实现思路

采集10~20张不同角度、距离拍摄的棋盘格图像(本案例使用9×6内角点棋盘格,方格边长25mm)。对每张图像提取角点,利用多张图像的角点对应关系求解相机内参和畸变系数,最后计算所有图像的平均重投影误差作为标定质量指标。

关键完整代码

import cv2
import numpy as np
import glob

# ========== 配置 ==========
CHESSBOARD_SIZE = (9, 6)   # 内角点数
SQUARE_SIZE = 25.0         # 方格边长(mm)
IMAGE_PATH = "calib_images/*.jpg"   # 标定图像路径

# ========== 准备角点世界坐标 ==========
objp = np.zeros((CHESSBOARD_SIZE[0] * CHESSBOARD_SIZE[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHESSBOARD_SIZE[0], 0:CHESSBOARD_SIZE[1]].T.reshape(-1, 2)
objp *= SQUARE_SIZE

obj_points = []   # 世界坐标
img_points = []   # 图像坐标

# ========== 提取角点 ==========
images = glob.glob(IMAGE_PATH)
if len(images) < 5:
    raise ValueError(f"至少需要5张标定图像,当前仅找到{len(images)}张")

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, CHESSBOARD_SIZE, None)
    if ret:
        obj_points.append(objp)
        corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
                                           (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
        img_points.append(corners_refined)
        # 可视化角点(可选)
        cv2.drawChessboardCorners(img, CHESSBOARD_SIZE, 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)):
    imgpoints_proj, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv2.norm(img_points[i], imgpoints_proj, cv2.NORM_L2) / len(imgpoints_proj)
    total_error += error

mean_error = total_error / len(obj_points)

# ========== 输出结果 ==========
print("=== 相机内参矩阵 ===")
print(mtx)
print("\n=== 畸变系数 (k1, k2, p1, p2, k3) ===")
print(dist.ravel())
print(f"\n平均重投影误差: {mean_error:.4f} px")

# 保存结果
np.savez("camera_calib.npz", mtx=mtx, dist=dist, mean_error=mean_error)
print("\n结果已保存至 camera_calib.npz")

运行方式

  1. 创建目录 calib_images/,放入10~20张不同角度拍摄的棋盘格照片(JPG格式)。
  2. 将上述代码保存为 calibrate.py,与 calib_images 目录放在同一路径下。
  3. 执行:python calibrate.py

验证方法与通过标准

  • 角点检测:运行过程中弹出窗口显示每张图像的角点绘制结果,确认棋盘格内角点被正确标记。
  • 标定输出:程序打印内参矩阵、畸变系数和平均重投影误差。
  • 通过条件:平均重投影误差 < 0.5 px 表示标定质量良好;若误差大于1.0 px,应剔除模糊或角点提取不准的图像后重新标定。

常见问题

  • 角点检测失败:确保棋盘格完整出现在画面中,避免强烈反光或过暗区域,拍摄时保持棋盘格平整。
  • 图像数量不足:少于5张有效图像时程序会报错,建议拍摄15张左右覆盖不同角度和距离。
  • 误差偏大:检查是否存在运动模糊图像,或棋盘格在画面中占比过小(建议宽度占比超过1/3)。