1. 相机标定基础

相机标定基础 学习卡片

任务定义

本案例完成激光焊接视觉定位系统中的相机内参标定。输入为棋盘格标定板图像序列,输出为相机内参矩阵与畸变系数,用于后续手眼标定与焊缝特征点的像素-物理坐标映射。标定对象为单目工业相机(如海康MV-CA050-10GC),图像分辨率1280×1024,像元尺寸4.8μm。

标定原理与数据准备

采用张正友平面标定法。相机成像模型为:

s·m = A·[R|t]·M

其中m为像素坐标,M为世界坐标,A为内参矩阵,R、t为外参。畸变模型包含径向畸变(k1,k2,k3)与切向畸变(p1,p2)。

采集12~15张不同位姿的棋盘格图像(棋盘格规格:9×7内角点,方格边长20mm),确保棋盘格覆盖视场各区域并倾斜放置。图像文件命名格式为calib_01.bmp至calib_15.bmp,存放于./calib_images/目录。

标定代码实现


import cv2
import numpy as np
import glob

# 棋盘格参数
CHESS_COLS = 9   # 内角点列数
CHESS_ROWS = 6   # 内角点行数
SQUARE_SIZE = 20.0  # 方格边长(mm)

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

# 存储所有图像的世界坐标与像素坐标
obj_points = []  # 3D点
img_points = []  # 2D点
img_files = sorted(glob.glob('./calib_images/calib_*.bmp'))
print(f'找到 {len(img_files)} 张标定图像')

# 角点提取
for fname in img_files:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, (CHESS_COLS, CHESS_ROWS), None)
    if ret:
        # 亚像素精化
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        obj_points.append(objp)
        img_points.append(corners_sub)
        print(f'{fname}: 角点提取成功')
    else:
        print(f'{fname}: 角点提取失败,跳过')

if len(obj_points) < 5:
    raise RuntimeError('有效标定图像不足5张,请重新采集')

# 执行相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    obj_points, img_points, gray.shape[::-1], None, None)

# 输出标定结果
print('\n=== 标定结果 ===')
print(f'重投影误差: {ret:.4f} 像素')
print('内参矩阵 K =')
print(mtx)
print('畸变系数 [k1, k2, p1, p2, k3] =')
print(dist.flatten())

# 保存标定参数
np.savez('camera_params.npz', mtx=mtx, dist=dist, ret=ret)
print('\n参数已保存至 camera_params.npz')

标定质量验证


# 验证代码:计算每张图像的平均重投影误差
print('\n=== 逐图重投影误差 ===')
total_err = 0
for i in range(len(obj_points)):
    imgpoints2, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
    err = cv2.norm(img_points[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
    total_err += err
    print(f'图像 {i+1}: {err:.4f} 像素')
print(f'平均误差: {total_err/len(obj_points):.4f} 像素')

落地与验证方法

运行环境:Python 3.8+,OpenCV 4.5+,NumPy。安装命令:pip install opencv-python numpy。

执行步骤:

  1. 将标定图像放入./calib_images/目录;
  2. 运行标定主代码,观察每张图像的角点提取是否成功;
  3. 运行验证代码,检查重投影误差。

判定标准:

  • 有效标定图像≥5张,角点提取成功率为100%;
  • 整体重投影误差<0.5像素,且每张图像误差<1.0像素;
  • 内参矩阵主焦距fx、fy接近理论值(焦距=像元尺寸×焦距像素值,典型工业镜头焦距12mm时fx≈2500),主点cx、cy接近图像中心(640,512)。
相机内参标定流程与数据流 标定板图像序列 12~15张不同位姿 1280×1024 BMP 角点检测与精化 findChessboardCorners cornerSubPix 亚像素 内参解算 calibrateCamera 张正友平面法 内参矩阵 畸变系数 重投影误差 重投影误差验证(阈值 < 0.5像素) 标定参数数据结构 内参矩阵 K (3×3) | fx 0 cx | | 0 fy cy | | 0 0 1 | 畸变系数 (5×1) k1, k2 (径向) p1, p2 (切向) k3 (径向高阶) 验证指标 重投影误差 < 0.5 像素 fx ≈ 2500 cx ≈ 640, cy ≈ 512

标定结果判定与后续衔接

标定完成后,camera_params.npz文件中的内参矩阵与畸变系数将直接用于本课程后续案例的畸变校正与手眼标定。若重投影误差超出阈值,应剔除误差最大的图像重新标定,或检查棋盘格是否平整、图像是否清晰。本案例仅完成内参标定,不涉及外参求解与机器人坐标系转换,该部分将在后续案例中实现。