3、OpenCV环境搭建与核心函数概览

说实话,标定这事儿,环境没搭好,后面全是白搭。我见过不少新手,代码写好了,一运行报错,结果发现是OpenCV版本不对,或者编译时少了模块。嗯,咱们先把地基打牢。

3.1 环境搭建:别在这步翻车

我个人习惯用Python + OpenCV,调试快,改起来也方便。你如果做嵌入式或者需要极致性能,再考虑C++版本。

推荐组合:

  • Python 3.8 - 3.11(别追最新,有些旧版API不兼容)
  • OpenCV 4.5.x 或 4.6.x(稳定,文档全)
  • NumPy(矩阵运算,OpenCV的底层依赖)
  • Matplotlib(可视化标定结果)

安装其实就一行命令。但我建议你用虚拟环境,别污染系统Python。

# 创建虚拟环境(推荐)
python -m venv opencv_calib
source opencv_calib/bin/activate  # Linux/Mac
opencv_calib\Scripts\activate     # Windows

# 安装核心库
pip install opencv-python==4.6.0.66
pip install opencv-contrib-python==4.6.0.66  # 包含额外模块
pip install numpy matplotlib

我曾经踩过的坑: 直接用 pip install opencv-python 装了最新版,结果 cv2.findChessboardCorners 的参数变了,跑了一周的老代码全崩。所以,版本锁定很重要。

验证安装是否成功,打开Python终端,敲下面两行:

import cv2
print(cv2.__version__)  # 输出应为 4.6.0

看到版本号,环境就通了。就这么简单。

3.2 核心函数初识:三剑客

标定流程说白了就三步:找角点 → 算参数 → 去畸变。对应三个核心API。咱们先混个脸熟,后面章节再深挖。

3.2.1 cv2.findChessboardCorners — 找棋盘格角点

这个函数的作用,就是从图像里找到棋盘格的内角点。注意,是内角点,不是棋盘格边缘的角。

ret, corners = cv2.findChessboardCorners(image, patternSize, flags)
参数 说明 我的经验
image 输入图像(灰度图最好) 彩色图也能用,但内部会转灰度,不如直接传灰度图快
patternSize 棋盘格内角点数,如 (9, 6) 别搞错行列顺序,我见过有人把 (9,6) 写成 (6,9),找了半天找不到角点
flags 可选标志,如自适应阈值 默认值够用,复杂光照下加 CALIB_CB_ADAPTIVE_THRESH
ret 布尔值,是否找到角点 如果返回 False,别硬算,换张图或者调参数
corners 检测到的角点坐标 形状为 (N, 1, 2),N 是角点总数

小技巧: 找到角点后,用 cv2.cornerSubPix() 做亚像素精细化,精度能提升一个量级。后面章节会细讲。

3.2.2 cv2.calibrateCamera — 算相机参数

这是标定的核心。你给它一堆棋盘格的世界坐标和图像坐标,它就能算出相机内参、畸变系数、旋转和平移向量。

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, flags, criteria
)
输出 含义 备注
ret 重投影误差 越小越好,一般 < 0.5 算优秀
mtx 相机内参矩阵 (3x3) 包含 fx, fy, cx, cy
dist 畸变系数 (1x5) k1, k2, p1, p2, k3
rvecs 每张图的旋转向量 用 Rodrigues 转成旋转矩阵
tvecs 每张图的平移向量 表示相机相对于棋盘格的位置

你想想看,这个函数其实是在解一个超定方程组。棋盘格越多,角度越丰富,结果越准。我建议至少拍15-20张不同角度的图。

3.2.3 cv2.undistort — 去畸变

参数算出来了,就该干活了。去畸变就是把拍歪了的图拉直。

undistorted = cv2.undistort(img, mtx, dist, None, newCameraMatrix)

这里有个细节:newCameraMatrix 如果不传,默认用 mtx。但如果你想要完全无畸变的图,可以用 cv2.getOptimalNewCameraMatrix() 算一个更合适的矩阵,它会自动裁剪掉边缘的黑边。

核心流程总结:

  1. 准备棋盘格图片(多角度)
  2. 用 findChessboardCorners 找角点
  3. 用 calibrateCamera 算内参和畸变系数
  4. 用 undistort 去畸变

3.3 知识体系图:标定流程一目了然

下面这张图,把整个标定流程串起来了。你跟着箭头走,就知道每一步该干什么。

相机标定核心流程 准备棋盘格图片 findChessboardCorners cornerSubPix 精细化 calibrateCamera undistort 去畸变 ⚠️ 关键提醒 • 至少15张不同角度图片 • 棋盘格要占画面1/4以上 • 光照均匀,避免反光 📦 标定输出 • 内参矩阵 mtx (3x3) • 畸变系数 dist (1x5) • 旋转向量 rvecs • 平移向量 tvecs • 重投影误差 ret

3.4 第一个完整示例:跑通再说

光说不练假把式。下面这个代码,你复制粘贴就能跑。前提是准备一张棋盘格图片,命名为 chessboard.jpg。

import cv2
import numpy as np

# 1. 读取图片
img = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2. 找角点(棋盘格内角点 9x6)
pattern_size = (9, 6)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)

if ret:
    print(f"找到 {len(corners)} 个角点")
    
    # 3. 亚像素精细化
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    
    # 4. 准备世界坐标(假设棋盘格方格尺寸为 1 单位)
    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)
    
    # 5. 标定(这里只用了一张图,实际需要多张)
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
        [objp], [corners_refined], gray.shape[::-1], None, None
    )
    
    print(f"内参矩阵:\n{mtx}")
    print(f"畸变系数:\n{dist}")
    print(f"重投影误差: {ret:.4f}")
    
    # 6. 去畸变
    h, w = img.shape[:2]
    new_mtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
    undistorted = cv2.undistort(img, mtx, dist, None, new_mtx)
    
    # 显示结果
    cv2.imshow('Original', img)
    cv2.imshow('Undistorted', undistorted)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
else:
    print("没找到角点,检查图片或 pattern_size")

注意: 上面代码只用了一张图做演示,实际标定至少需要15-20张。单张图算出来的参数不靠谱,重投影误差会很大。后面章节我会教你怎么批量处理。

3.5 避坑指南:我踩过的雷

  • 棋盘格打印不平整: 我曾经用普通A4纸打印,贴在硬纸板上,结果角点检测总是飘。后来换了亚光相纸,贴在玻璃板上,精度立马提升。
  • 图片数量不够: 有人拍了5张就开标,重投影误差0.8,怎么调都下不去。加到20张,误差直接降到0.3。说白了,数据量决定上限。
  • 忽略亚像素优化: 直接拿 findChessboardCorners 的粗坐标去标定,精度损失很大。加一行 cornerSubPix,效果天差地别。

嗯,环境搭好了,函数也认识了。接下来咱们就一步步深入,把每个API的细节吃透。