1. 相机内参标定

任务目标

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

环境依赖

  • Python 3.8+
  • OpenCV(含contrib模块):pip install opencv-contrib-python
  • NumPy:pip install numpy

实现方案

采集10~20张不同位姿的棋盘格图像(推荐使用cv2.findChessboardCorners自动检测角点),通过cv2.calibrateCamera计算内参。核心流程:读取图像→角点检测与亚像素细化→传入标定函数→输出内参与畸变系数→计算重投影误差。

关键完整代码

import cv2
import numpy as np
import glob

# ========== 配置 ==========
CHESSBOARD_SIZE = (9, 6)   # 内角点数量(列, 行)
SQUARE_SIZE = 25.0         # 棋盘格边长(单位:mm)
IMAGE_DIR = "calib_images/"  # 存放标定图像的目录(支持jpg/png)

# ========== 准备对象点 ==========
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 = []  # 世界坐标系中的3D点
img_points = []  # 图像中的2D点
gray = None

# ========== 读取图像并检测角点 ==========
images = glob.glob(IMAGE_DIR + "*.jpg") + glob.glob(IMAGE_DIR + "*.png")
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)
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        img_points.append(corners_sub)
        # 可视化检测结果(可选)
        cv2.drawChessboardCorners(img, CHESSBOARD_SIZE, corners_sub, ret)
        cv2.imshow("Corners", img)
        cv2.waitKey(100)
cv2.destroyAllWindows()

# ========== 相机标定 ==========
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("相机内参矩阵 K:")
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张棋盘格照片(不同角度、距离、姿态),确保角点完整可见且光照均匀。
  2. 将上述代码保存为calibrate.py,在终端执行:python calibrate.py

验证方法与通过标准

  • 角点检测:运行过程中每张图像应弹出窗口并正确绘制检测到的角点(红色圆点按顺序连接),若某张图像检测失败会跳过,但有效图像数需≥5。
  • 内参合理性:焦距fx、fy应接近图像宽度量级(例如1920×1080图像,fx约1000~2000);主点cx、cy应接近图像中心坐标。
  • 重投影误差:平均重投影误差小于0.5像素即标定合格;若大于1像素,说明角点检测精度不足或图像数量/质量不够,需补充图像或调整光照。
  • 结果保存:当前目录生成camera_calib.npz文件,可用np.load('camera_calib.npz')验证内含mtx、dist、mean_error三个数组。

常见问题

  • 检测不到角点:确认棋盘格为奇数×偶数内角点(如9×6),避免使用反光或模糊图像,适当调整CHESSBOARD_SIZE与实际棋盘匹配。
  • 误差偏大:增加图像数量至15张以上,覆盖画面边缘区域,并确保棋盘格在每张图像中占据约1/4~1/2画面面积。
  • 标定结果不稳定:固定相机光圈与焦距,标定过程中不得变焦或调整对焦。