4. OpenCV相机标定:cv2.findChessboardCorners、cv2.calibrateCamera详解

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

我刚开始接触这个领域时,总觉得标定是个黑盒子——拍几张棋盘格照片,跑几个函数,内参外参就出来了。但后来在实际项目中吃过亏,才明白每个参数背后的门道。

今天咱们就掰开揉碎,把OpenCV里最核心的两个标定函数讲透。

4.1 标定的本质:你在求解什么?

先问一个问题:相机为什么需要标定?

因为镜头有畸变,传感器有安装误差。说白了,真实世界的一条直线,拍出来可能是弯的。我们需要找到一组参数,把弯的拉直。

标定求解的核心是两套参数:

  • 内参矩阵:焦距、光心位置——这是相机本身的「出厂设置」
  • 畸变系数:径向畸变、切向畸变——这是镜头带来的「变形因子」

嗯,这里要注意:外参(旋转矩阵、平移向量)每次拍摄都会变,但内参和畸变系数是固定的。所以标定一次,可以反复用。

4.2 cv2.findChessboardCorners:找到棋盘格的角点

这个函数是标定的第一步。它的任务很简单:在图像里找到棋盘格的内角点。

函数签名:

ret, corners = cv2.findChessboardCorners(image, patternSize, flags=None)

参数说明:

参数 含义 我的经验
image 输入图像(灰度图效果更好) 我习惯先转成灰度再传入
patternSize 内角点数量,如(9,6)表示9列6行 注意是内角点,不是棋盘格数量
flags 可选标志,如自适应阈值 光线不好时我会用CALIB_CB_ADAPTIVE_THRESH

返回值:

  • ret:布尔值,True表示找到了角点
  • corners:角点坐标,形状为(N,1,2)

我的小技巧:

如果棋盘格反光严重,可以试试先做直方图均衡化。我在一个工厂项目中遇到过强光照射,棋盘格一半亮一半暗,加个cv2.equalizeHist()就解决了。

4.3 亚像素级角点优化

找到角点后,我建议再做一步亚像素优化。为什么?因为整数像素坐标不够精确。

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)

这段代码把角点精度从像素级提升到亚像素级。说白了,就是让角点坐标带小数,更接近真实位置。

我曾经做过对比实验:用亚像素优化后,标定重投影误差从0.5像素降到了0.15像素。效果很明显。

4.4 cv2.calibrateCamera:核心标定函数

这个函数才是真正的「重头戏」。它根据多张棋盘格图像,计算出相机内参和畸变系数。

函数签名:

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objectPoints, imagePoints, imageSize, 
    cameraMatrix=None, distCoeffs=None, 
    flags=None, criteria=None
)

参数详解:

  • objectPoints:世界坐标系中的3D点。注意,这里每个棋盘格角点的Z坐标都是0
  • imagePoints:对应的图像2D点,就是findChessboardCorners找到的那些
  • imageSize:图像尺寸,格式为(width, height)
  • cameraMatrix:内参矩阵初始值,一般传None让OpenCV自己算
  • distCoeffs:畸变系数初始值,同样传None
  • flags:标定选项,后面细说
  • criteria:迭代终止条件

返回值:

返回值 含义 形状
ret 重投影误差(越小越好) 标量
mtx 内参矩阵 3x3
dist 畸变系数 (5,1)或更多
rvecs 每张图的旋转向量 列表,每个元素是(3,1)
tvecs 每张图的平移向量 列表,每个元素是(3,1)

4.5 标定流程:从拍照到出参

我整理了一个标准流程,你照着做就行:

  1. 准备棋盘格:打印一张棋盘格,贴在硬纸板上。格子数量建议10x7或9x6
  2. 拍摄多张照片:从不同角度拍15-20张。注意:棋盘格要占画面1/4以上
  3. 提取角点:对每张图调用findChessboardCorners
  4. 亚像素优化:用cornerSubPix提高精度
  5. 执行标定:调用calibrateCamera
  6. 评估结果:看重投影误差,一般小于0.3像素就算合格

避坑指南:

我曾经犯过一个低级错误:拍摄时棋盘格离镜头太近,导致角点提取失败。后来我总结了一个经验:棋盘格在画面中的大小,至少要有200x200像素。

另外,照片数量不是越多越好。我试过拍50张,结果因为角度重复,反而降低了标定精度。15-20张不同角度的照片,效果最好。

4.6 标定选项 flags 详解

这个参数容易被忽略,但它能显著影响标定结果。

flags = cv2.CALIB_RATIONAL_MODEL  # 使用更精确的畸变模型
# 或者组合使用
flags = (cv2.CALIB_FIX_K1 | cv2.CALIB_FIX_K2 | cv2.CALIB_FIX_K3)

常用选项:

  • CALIB_RATIONAL_MODEL:使用6个径向畸变系数(k1-k6),精度更高
  • CALIB_FIX_K1:固定某个畸变系数不变
  • CALIB_ZERO_TANGENT_DIST:假设切向畸变为0
  • CALIB_USE_INTRINSIC_GUESS:使用你提供的内参初始值

我个人习惯:对于普通镜头,用默认参数就够了。但对于广角镜头,一定要加上CALIB_RATIONAL_MODEL,否则边缘畸变校正不干净。

4.7 完整代码示例

import cv2
import numpy as np
import glob

# 棋盘格内角点数量
pattern_size = (9, 6)
# 准备世界坐标系中的3D点
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)

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

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, 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)

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

print(f"重投影误差: {ret:.4f}")
print(f"内参矩阵:\n{mtx}")
print(f"畸变系数:\n{dist}")

4.8 知识体系总览

下面这张图,帮你理清整个标定流程的逻辑关系:

相机标定知识体系 输入:棋盘格图像 cv2.findChessboardCorners cv2.cornerSubPix cv2.calibrateCamera 输出参数 内参矩阵 (mtx) 畸变系数 (dist) 旋转向量 (rvecs) 平移向量 (tvecs) 重投影误差 (ret) patternSize: (9,6) flags: 标定选项 criteria: 迭代条件

4.9 标定结果验证

标定完成后,一定要验证结果。我常用的方法是:

# 用标定结果去畸变
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('校正结果', dst)
cv2.waitKey(0)

如果校正后的图像边缘直线变直了,说明标定成功。如果还有弯曲,说明畸变系数没算准,需要重新标定。

经验之谈:

重投影误差小于0.3像素,基本就够用了。但如果你做的是高精度测量,比如工业检测,我建议控制在0.1像素以内。

另外,标定完成后记得保存参数:

np.savez('calib_params.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)

好了,关于cv2.findChessboardCorners和cv2.calibrateCamera的核心内容,就讲到这里。这两个函数是相机标定的基石,掌握了它们,你就能自己动手给任何相机做「视力矫正」了。


专注资料整理