3. 相机外参标定(世界坐标系到相机坐标系)

任务目标

已知标定板上若干特征点的世界坐标及其在图像中的像素坐标,求解相机外参(旋转矩阵 R 和平移向量 t),将世界坐标系中的点变换到相机坐标系。

核心原理

相机成像模型为:

s * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T

其中 K 为内参矩阵(本案例假设已知),(X,Y,Z) 为世界坐标,(u,v) 为像素坐标。通过至少 6 组 3D-2D 对应点,使用 PnP(Perspective-n-Point)算法即可求解外参。本案例使用 OpenCV 的 solvePnP 实现。

环境依赖

  • Python 3.7+
  • OpenCV(pip install opencv-python)
  • NumPy(pip install numpy)

关键完整代码

import cv2
import numpy as np

# ========== 1. 相机内参(示例值,实际需通过标定获得) ==========
K = np.array([[800, 0, 320],
              [0, 800, 240],
              [0, 0, 1]], dtype=np.float64)
dist_coeffs = np.zeros((4, 1))  # 假设无畸变

# ========== 2. 标定板特征点:世界坐标(单位:mm) ==========
# 例:9x6 棋盘格,格边长 25mm,Z=0 平面
pattern_size = (9, 6)
square_size = 25.0

world_points = []
for j in range(pattern_size[1]):
    for i in range(pattern_size[0]):
        world_points.append([i * square_size, j * square_size, 0])
world_points = np.array(world_points, dtype=np.float64)

# ========== 3. 对应像素坐标(示例数据,实际由图像检测获得) ==========
# 此处为模拟数据,仅演示流程;实际使用时替换为角点检测结果
image_points = np.array([
    [320, 240], [345, 241], [370, 242], [395, 243], [420, 244],
    [445, 245], [470, 246], [495, 247], [520, 248],
    [321, 265], [346, 266], [371, 267], [396, 268], [421, 269],
    [446, 270], [471, 271], [496, 272], [521, 273],
    # ... 实际需提供全部 54 组对应点
], dtype=np.float64)

# 若使用模拟数据,需保证点数匹配;此处仅取前 18 点演示
world_points = world_points[:len(image_points)]

# ========== 4. 使用 PnP 求解外参 ==========
success, rvec, tvec = cv2.solvePnP(
    world_points, image_points, K, dist_coeffs,
    flags=cv2.SOLVEPNP_ITERATIVE
)

if success:
    # 旋转向量转旋转矩阵
    R, _ = cv2.Rodrigues(rvec)
    print("旋转矩阵 R (世界→相机):")
    print(R)
    print("\n平移向量 t (世界→相机):")
    print(tvec.reshape(3, 1))
    
    # ========== 5. 验证:重投影误差 ==========
    proj_points, _ = cv2.projectPoints(
        world_points, rvec, tvec, K, dist_coeffs
    )
    proj_points = proj_points.reshape(-1, 2)
    errors = np.linalg.norm(image_points - proj_points, axis=1)
    mean_error = np.mean(errors)
    print(f"\n重投影均方根误差: {mean_error:.4f} 像素")
    print("判定标准:误差 < 0.5 像素,标定结果可靠")
else:
    print("PnP 求解失败")

运行与验证

  1. 数据准备:将 image_points 替换为实际图像中检测到的棋盘格角点像素坐标(使用 cv2.findChessboardCorners 获取),并确保与 world_points 一一对应。
  2. 执行代码:直接运行上述 Python 脚本。
  3. 通过条件:程序输出 R 和 t,且重投影均方根误差小于 0.5 像素。误差越小,外参标定越精确。

常见问题

  • 点数不足:solvePnP 至少需要 4 组非共面点或 6 组共面点,建议使用完整棋盘格(54 个角点)以获得稳定结果。
  • 内参不准:内参 K 的误差会直接影响外参精度,应先用棋盘格完成内参标定。
  • 畸变未处理:若镜头畸变明显,需提供畸变系数,否则重投影误差会偏大。