3、深度相机标定实战:棋盘格标定板制作、使用OpenCV进行单目/双目相机标定、标定结果评估与重投影误差分析

相机标定,说白了就是给相机做一次「视力矫正」。你想想看,镜头天生就有畸变,像素也不是完美的正方形,这些误差不消除,后面的三维重建全是白搭。我在做第一个深度相机项目时,就吃过这个亏——标定没做好,重建出来的物体像哈哈镜里的效果,歪歪扭扭的。

这一章,咱们就手把手把标定这件事做扎实。从做棋盘格开始,到单目、双目标定,再到评估结果,一条龙走完。

3.1 棋盘格标定板制作——别在这步偷懒

棋盘格是标定的「基准尺」。它的精度直接决定了标定质量。我个人习惯用A4纸打印,但有个坑:打印出来的尺寸和理论尺寸可能有偏差

注意: 不要直接用打印机的默认缩放。打印时务必选择「实际大小」或「100%缩放」,然后用游标卡尺量一下实际格子边长,输入到代码里。

制作要点:

  • 格子数: 内角点数量建议 9×6 或 11×8(宽×高)。奇数×偶数,这样棋盘有明确的方向性。
  • 格子大小: 20mm~30mm 比较合适。太小了相机远了看不清,太大了近处拍不全。
  • 材质: 打印后贴在硬纸板或亚克力板上,保证平整。我见过有人贴在海报上,结果标定板是弯的,标定结果直接废了。
  • 保存: 打印多份,备着。标定过程中板子容易弄脏或折皱。

嗯,这里要强调一下:标定板不要太小。我曾经为了便携做了个很小的棋盘格,结果相机稍微远一点就检测不到角点了,白白浪费了半天时间。

3.2 单目标定——用OpenCV搞定内参和畸变

单目标定,就是求相机的内参矩阵和畸变系数。OpenCV 提供了 cv2.calibrateCamera(),但准备工作要做好。

你需要拍摄 15~20 张不同角度、不同距离的棋盘格照片。注意:

  • 棋盘格要占画面 1/4 以上
  • 角度要丰富:正对、倾斜、旋转、远近都要有
  • 不要只拍中心区域,边缘也要覆盖到

核心代码长这样:

import cv2
import numpy as np

# 棋盘格内角点数量(宽,高)
pattern_size = (9, 6)
# 实际格子边长,单位mm
square_size = 25.0

# 准备世界坐标系中的点
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
objp *= square_size

# 存储所有图片的角点
objpoints = []  # 3D点
imgpoints = []  # 2D点

images = [...]  # 你的图片路径列表

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
    if ret:
        objpoints.append(objp)
        # 亚像素精确化
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners2)
        # 可视化
        cv2.drawChessboardCorners(img, pattern_size, corners2, ret)

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objpoints, imgpoints, gray.shape[::-1], None, None
)

print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
小技巧: 如果 findChessboardCorners 经常找不到角点,试试把图片转成灰度图后做一下直方图均衡化。我在光线不均匀的场景下用过,成功率提升不少。

3.3 双目标定——让两个相机「对齐」

双目标定比单目多了一步:求两个相机之间的旋转矩阵和平移向量。说白了,就是知道左眼和右眼之间的相对位置关系。

OpenCV 用 cv2.stereoCalibrate() 搞定。前提是:你已经完成了左右相机的单目标定。

# 假设已经得到左右相机的内参和畸变
ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate(
    objpoints,          # 世界坐标点
    imgpoints_left,     # 左图角点
    imgpoints_right,    # 右图角点
    cameraMatrix1,      # 左相机内参
    distCoeffs1,        # 左相机畸变
    cameraMatrix2,      # 右相机内参
    distCoeffs2,        # 右相机畸变
    image_size,
    criteria=criteria,
    flags=cv2.CALIB_FIX_INTRINSIC
)

这里有个关键参数 flags。我建议第一次标定时用 cv2.CALIB_FIX_INTRINSIC,只优化外参。如果效果不好,再放开内参一起优化。

注意: 双目标定时,左右相机拍摄的棋盘格图片必须同步。如果用手持拍摄,至少保证两张图片的棋盘格位置大致相同。我见过有人左右相机分别拍不同角度的图片,结果标定出来的外参完全是错的。

3.4 标定结果评估——重投影误差是硬指标

标定完怎么知道好不好?看重投影误差。简单说,就是把世界坐标系中的棋盘格角点,通过标定结果投影回图像,看看和实际检测到的角点差多少像素。

OpenCV 提供了 cv2.projectPoints() 来计算:

def compute_reprojection_error(objpoints, imgpoints, rvecs, tvecs, mtx, dist):
    total_error = 0
    total_points = 0
    for i in range(len(objpoints)):
        imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
        error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
        total_error += error
        total_points += len(imgpoints2)
    return total_error / len(objpoints)

mean_error = compute_reprojection_error(objpoints, imgpoints, rvecs, tvecs, mtx, dist)
print(f"平均重投影误差: {mean_error:.4f} 像素")

一般来说:

误差范围 评价
< 0.3 像素 优秀,可以用于高精度重建
0.3 ~ 0.8 像素 良好,大多数场景够用
> 0.8 像素 需要重新标定,检查图片质量
经验之谈: 如果误差超过 1 像素,先别急着调参数。检查一下:棋盘格是否平整?图片是否模糊?角点检测是否准确?我曾经有一次误差死活降不下来,最后发现是标定板没贴平,中间鼓起来一块。

3.5 核心流程一览

我把整个标定流程画成了流程图,方便你对照着做:

深度相机标定核心流程 1. 制作棋盘格标定板 2. 拍摄多角度棋盘格图片 3. 角点检测与亚像素优化 4. 单目标定(内参+畸变) 5. 双目标定(外参R,T) 6. 重投影误差评估 关键注意事项 • 棋盘格必须平整 • 图片覆盖画面各个区域 • 至少15张有效图片 • 左右相机需同步拍摄 • 误差 < 0.5px 为佳 • 保存标定参数到文件 • 定期重新标定 • 温度变化会影响标定 • 镜头松动需重新标 • 不同分辨率要重标 • 标定板不要反光 • 角点检测失败时 检查光照和清晰度 • 双目标定后做立体校正 • 验证:用标定结果 测量已知距离物体 • 误差大时重新采集图片

3.6 避坑指南——我踩过的那些坑

标定这件事,看起来简单,做起来全是细节。我把自己踩过的坑列出来,你遇到了直接对照:

  • 角点检测失败: 棋盘格反光、图片模糊、格子太小。解决办法:调整光照,用哑光纸打印,保证对焦清晰。
  • 重投影误差大: 先检查标定板是否平整。我曾经用亚克力板夹着打印纸,结果板子本身有弧度,误差死活下不来。
  • 双目标定后立体校正不对: 左右相机的图片没有同步。用双目相机自带的触发模式,或者后期手动筛选时间戳接近的图片对。
  • 标定结果不稳定: 每次标定出来的内参都不一样。说明图片数量不够或者角度不够丰富。加到20张以上,覆盖画面各个角落。
我的习惯: 标定完成后,我会用标定结果去测量一个已知尺寸的物体(比如一个标准方块),看看测量误差。如果误差在1mm以内,说明标定合格。这比单纯看重投影误差更直观。

好了,标定这块就讲到这里。你按照这个流程走一遍,基本不会出大问题。记住:标定是三维重建的基石,这块花的时间,后面都会加倍还给你。


专注资料整理