4、相机标定与畸变校正:相机内参、外参、畸变模型、张正友标定法、OpenCV标定实践

相机标定,说白了就是给相机做「体检」。

你想想看,我们拿相机去拍物体,目的是要算出它真实的尺寸。但相机镜头天生就有畸变,成像平面也不是完美的。如果不做标定,你测出来的尺寸就是「虚的」。

我个人习惯,拿到一个新相机,第一件事就是标定。哪怕厂家给了参数,我也要自己跑一遍。为什么?因为每台相机都有个体差异,流水线上出来的东西,不可能一模一样。

4.1 相机内参:相机自己的「基因」

内参描述的是相机内部的光学结构。它包含四个核心参数:

  • fx, fy:焦距在像素坐标系下的尺度。说白了,就是镜头把真实世界「拉近」了多少倍。
  • cx, cy:主点坐标。也就是光轴穿过成像平面的那个点,理论上应该在图像正中心,但实际总有偏移。
  • 畸变系数:k1, k2, k3(径向畸变),p1, p2(切向畸变)。

内参矩阵通常写成这样:

K = [[fx,  0, cx],
     [ 0, fy, cy],
     [ 0,  0,  1]]

我在项目中遇到过一台工业相机,标定出来的cx偏了20多个像素。一开始以为是标定板没放好,反复标了三次,结果都一样。后来拆开镜头一看,原来是CMOS传感器装歪了。嗯,这就是内参的意义——它告诉你相机真实的「底子」是什么样的。

4.2 相机外参:相机在空间中的「位置和姿态」

外参描述的是相机坐标系相对于世界坐标系的旋转和平移。说白了,就是相机放在哪里、朝哪个方向看。

外参包含:

  • 旋转矩阵 R:3x3 的正交矩阵,描述三个轴的旋转。
  • 平移向量 t:3x1 的向量,描述相机在世界坐标系中的位置。

为什么外参重要?因为你要把世界坐标系中的点,投影到相机坐标系中。公式很简单:

P_cam = R * P_world + t

我曾经犯过一个低级错误:把旋转矩阵和平移向量的顺序搞反了。结果算出来的三维坐标全部是错的,排查了整整一个下午。后来我养成了一个习惯——每次标定完,先打印出外参看一眼,R矩阵的行列式是不是接近1,t向量的量级是不是合理。这能帮你快速发现标定是否失败。

4.3 畸变模型:镜头为什么「骗人」

镜头畸变主要分两种:

4.3.1 径向畸变

这是最常见的畸变。说白了,就是镜头边缘的放大率跟中心不一样。拍出来的直线会变成曲线。

  • 桶形畸变:图像向外凸出,像水桶。常见于广角镜头。
  • 枕形畸变:图像向内凹陷,像枕头。常见于长焦镜头。

径向畸变的数学模型:

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

其中 r 是像素到主点的距离。k1, k2, k3 就是我们要标定的径向畸变系数。

4.3.2 切向畸变

这个是因为镜头和CMOS传感器没有完全平行安装造成的。说白了,就是镜头「歪了」。

切向畸变的数学模型:

x_distorted = x + [2*p1*x*y + p2*(r^2 + 2*x^2)]
y_distorted = y + [p1*(r^2 + 2*y^2) + 2*p2*x*y]
我的经验: 对于普通工业镜头,径向畸变k1和k2就够用了。k3一般用在鱼眼镜头或畸变特别大的镜头上。切向畸变p1, p2通常很小,但如果你的标定结果里p1或p2大于0.1,那就要检查一下镜头是不是装歪了。

4.4 张正友标定法:最经典的「棋盘格标定」

张正友标定法,是1998年张正友教授提出的。它为什么经典?因为只需要拍几张棋盘格照片,就能标定出全部内参和外参。不需要昂贵的精密设备,一张打印的棋盘格就够了。

核心思路是这样的:

  1. 拍摄多张不同角度的棋盘格图像(一般10-20张)。
  2. 检测每张图像中的角点坐标。
  3. 利用棋盘格已知的物理尺寸(比如每个格子30mm),建立世界坐标系到像素坐标系的映射关系。
  4. 通过最小二乘法求解内参和外参的初始值。
  5. 用非线性优化(比如Levenberg-Marquardt算法)对所有参数进行精调。

为什么需要多张不同角度的照片?因为单张照片只能解出部分参数,多张不同角度的照片才能把内参和外参「解耦」开。我一般会拍15张左右,覆盖相机的四个角落和中心区域。

避坑指南: 我曾经拍标定照片时,棋盘格只占了画面中央一小块。结果标定出来的畸变系数完全不准确。因为畸变在画面边缘最明显,中央区域几乎没有畸变信息。所以,一定要让棋盘格出现在画面的各个位置,尤其是边缘和角落。

4.5 OpenCV标定实践:手把手教你跑通

下面是我常用的标定流程,用OpenCV实现。代码不长,但每一步都有讲究。

4.5.1 准备标定板

打印一张棋盘格,贴在硬纸板上。格子数量建议用 9x6 或 10x7(内角点数量)。每个格子的物理尺寸要精确测量,比如 30mm x 30mm。

4.5.2 拍摄标定图像

用相机从不同角度拍摄棋盘格。注意:

  • 光照要均匀,不要有反光。
  • 棋盘格要清晰,不能模糊。
  • 角度变化要大:俯仰、偏航、旋转都要有。
  • 距离也要变化:近景、中景、远景各拍几张。

4.5.3 核心代码

import cv2
import numpy as np
import glob

# 棋盘格内角点数量(列,行)
pattern_size = (9, 6)
# 每个格子的物理尺寸(单位:mm)
square_size = 30.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 = []  # 世界坐标系中的点
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, 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)
        cv2.imshow('img', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

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

print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
print("重投影误差:", ret)

4.5.4 评估标定质量

标定完不能直接收工。我一般会做两件事:

  • 看重投影误差:ret 值越小越好。一般小于0.5像素就算不错,小于0.3像素就是优秀。
  • 做畸变校正测试:用标定结果对一张新图像做畸变校正,看看直线是不是真的变直了。
# 畸变校正
img = cv2.imread('test_image.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('original', img)
cv2.imshow('undistorted', dst)
cv2.waitKey(0)
关键点: 畸变校正后,图像边缘会被拉伸或压缩,所以会有黑边。用 getOptimalNewCameraMatrix 可以自动计算一个合适的裁剪区域,把黑边去掉。但要注意,裁剪后图像的分辨率会变小。

4.6 知识体系总览

下面这张图,是我自己总结的相机标定知识体系。你可以把它当作一个「导航图」,每次做标定前看一眼,不容易漏掉步骤。

相机标定与畸变校正 · 知识体系 相机标定 相机内参 fx, fy, cx, cy 焦距 + 主点 相机外参 旋转矩阵 R 平移向量 t 畸变模型 径向畸变 k1,k2,k3 切向畸变 p1,p2 张正友标定法 棋盘格角点检测 最小二乘 + 非线性优化 OpenCV 标定实践 虚线:实践环节串联所有理论

这张图把标定的四个核心模块串起来了。内参和外参是「标定什么」,畸变模型是「为什么要校正」,张正友法是「怎么标定」,OpenCV实践是「怎么实现」。你每次做标定,都可以沿着这个流程走一遍。

我的建议: 标定不是一次性的工作。相机在使用过程中,镜头可能会松动、温度变化可能导致参数漂移。我一般每隔三个月重新标定一次,或者在项目关键节点前重新标定。别嫌麻烦,这能省掉后面很多排查问题的时间。

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