第四节:相机标定(上)——标定板设计与制作、张正友标定法原理、OpenCV标定流程

相机标定,说白了就是给相机做一次「视力矫正」。

你想想看,我们做双目视觉,核心任务是从两幅图像里算出深度信息。但如果相机本身有畸变,或者两个相机的内参都不知道,那后面的三角测量、视差计算全是空中楼阁。我刚开始做双目项目时,就吃过这个亏——标定没做好,出来的深度图全是歪的,折腾了两周才发现是标定板尺寸写错了。

所以这一节,咱们把标定这件事彻底讲透。

4.1 标定板设计与制作

标定板是标定的「尺子」。它的质量直接决定了标定精度。

4.1.1 棋盘格 vs 圆点阵

目前主流的有两种:

  • 棋盘格:黑白相间,角点检测稳定。我个人习惯用棋盘格,因为OpenCV的findChessboardCorners非常成熟。
  • 圆点阵:圆形图案,适合大视角或鱼眼镜头。但圆心检测精度受透视变形影响较大。

我的建议是:普通双目系统用棋盘格就够了。鱼眼或广角镜头,可以考虑圆点阵。

4.1.2 设计参数

参数推荐值说明
棋盘格内角点数9×6 或 11×8行列数不要相等,避免方向歧义
方格尺寸20mm~30mm根据场景距离调整,太大或太小都不好
打印精度600dpi以上用激光打印,别用喷墨,容易变形
底板材质亚克力或铝板贴平,不能有褶皱
⚠️ 避坑指南
我曾经用普通A4纸打印棋盘格,贴在纸箱上标定。结果因为纸面不平,标定出来的焦距偏差了3%。后来换了亚克力板,一次就过了。记住:标定板一定要平整、平整、平整。

4.1.3 制作流程

  1. 用OpenCV的cv2.drawChessboardCorners生成图案,或者用棋盘格PDF模板。
  2. 打印后测量实际方格尺寸,用游标卡尺量三次取平均。
  3. 贴在刚性底板上,确保无气泡、无褶皱。
  4. 拍摄时,标定板要占图像面积的1/4到1/3,太远角点检测不准。

4.2 张正友标定法原理

张正友老师在1998年提出的这个方法,可以说是相机标定领域的「标准答案」。它只需要一个平面标定板,拍几张不同角度的照片,就能算出内参和外参。

为什么能这么简单?核心思想就一句话:利用平面上的点,建立世界坐标到像素坐标的映射关系

4.2.1 数学模型

我们先把公式摆出来:

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

其中:

  • s 是尺度因子
  • K 是内参矩阵(fx, fy, cx, cy)
  • [R | t] 是外参(旋转和平移)
  • [X, Y, 0] 是标定板上的点,Z=0是因为它是平面

嗯,这里要注意:因为Z=0,所以旋转矩阵的第三列其实被消掉了。这就把问题简化成了单应性矩阵的求解。

4.2.2 求解步骤

  1. 检测角点:从每张图像中提取棋盘格角点坐标。
  2. 计算单应性矩阵:每张图像都有一个H矩阵,连接世界坐标和像素坐标。
  3. 约束内参:利用旋转矩阵的正交性,建立关于内参的线性方程组。
  4. 求解内参:通过SVD分解得到内参的闭式解。
  5. 非线性优化:用Levenberg-Marquardt算法最小化重投影误差。
💡 个人经验
我见过很多人只做前四步,觉得重投影误差差不多就行了。其实最后一步的非线性优化非常关键。我曾经对比过,加上LM优化后,重投影误差从0.8像素降到了0.15像素。别省这一步。

4.2.3 畸变模型

实际镜头都有畸变,主要是径向畸变和切向畸变。张正友法用以下模型:

x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y

其中k1、k2、k3是径向畸变系数,p1、p2是切向畸变系数。一般标定5个参数就够了,k3只在鱼眼镜头里用。

4.3 OpenCV标定流程

理论讲完了,咱们直接上代码。OpenCV把张正友法封装得非常好,核心就几个函数。

4.3.1 完整标定代码

import cv2
import numpy as np
import glob

# 1. 准备世界坐标
chessboard_size = (9, 6)  # 内角点数
square_size = 25.0        # 方格尺寸,单位mm

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

# 2. 存储所有图像的点
objpoints = []  # 世界坐标
imgpoints = []  # 像素坐标

images = glob.glob('calib_images/*.jpg')

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:
        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, chessboard_size, corners2, ret)
        cv2.imshow('img', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

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

print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)

# 4. 评估精度
mean_error = 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)
    mean_error += error

print("平均重投影误差: ", mean_error/len(objpoints))

4.3.2 关键参数说明

参数作用我的建议
chessboard_size内角点数9×6最常用,别用对称的
square_size方格实际尺寸用游标卡尺量,别信打印尺寸
cornerSubPix亚像素优化必须做,精度提升明显
calibrateCamera核心标定函数返回内参、畸变、外参
🔧 实用技巧
拍摄标定图像时,我一般拍15~20张。角度要覆盖:正对、左偏、右偏、上仰、下俯。千万别只拍正面的,那样解出来的内参不稳定。我见过有人拍了30张全是正面,标定结果还不如10张多角度的。

4.3.3 标定结果验证

标定完别急着用,先验证一下:

# 去畸变
img = cv2.imread('test.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

# 裁剪有效区域
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv2.imshow('undistorted', dst)

去畸变后,图像边缘的直线应该变直了。如果还有弯曲,说明畸变参数没标好。

4.4 知识体系总览

我把这一节的核心逻辑画成了流程图,方便你理解整个标定流程:

相机标定知识体系 标定板设计与制作 多角度图像采集 角点检测与亚像素优化 张正友标定法求解内参+畸变 输出内参、畸变、外参 准备阶段 数据采集 特征提取 数学求解 结果输出

这张图把整个标定流程串起来了。从标定板制作开始,到最终输出内参和畸变系数,每一步都环环相扣。你只要按这个流程走,标定结果不会差。

📌 核心要点回顾
  • 标定板要平整,尺寸要实测,别信打印值
  • 张正友法的核心是利用平面Z=0简化单应性求解
  • OpenCV的calibrateCamera一步到位,但别忘了亚像素优化
  • 重投影误差控制在0.2像素以内才算合格
  • 多角度拍摄比多张数更重要

好了,这一节的内容就到这儿。标定是双目视觉的基石,花时间把这一步做扎实了,后面的深度估计才能稳。下一节我们继续讲双目立体标定和极线校正,那才是真正把两个相机「对齐」的关键一步。


公众号:蓝海资料掘金营,微信deep3321