相机内参标定:张正友标定法原理、棋盘格角点检测、内参矩阵与畸变系数计算

大家好,我是你们的自动驾驶感知工程师。今天我们来聊聊相机内参标定。

说实话,在激光雷达和相机融合这个领域,我踩过最大的坑就是标定没做好。你想想看,如果相机自己都「看」不准,那融合出来的数据能靠谱吗?所以这一章,咱们把相机内参标定这件事彻底讲透。

张正友标定法:为什么它成了行业标准?

张正友标定法,说白了就是拿一个棋盘格,从不同角度拍几张照片,然后算出相机的内参。这个方法牛在哪?

  • 操作简单:不需要精密的3D标定物,一张打印的棋盘格就行
  • 精度够用:对于大多数自动驾驶场景,误差在亚像素级别
  • 鲁棒性好:我试过在光照不均匀的环境下拍,结果依然稳定

它的核心思想其实很直观:利用棋盘格上已知的角点坐标,建立从3D世界到2D图像的映射关系。然后通过优化算法,反推出相机的内参矩阵和畸变系数。

核心公式:s * m = A * [R|t] * M

其中:m是图像坐标,M是世界坐标,A是内参矩阵,[R|t]是外参,s是尺度因子

我个人习惯用至少15-20张不同角度的棋盘格照片。太少的话,标定结果容易过拟合。太多呢?其实也没必要,20张左右就够用了。

棋盘格角点检测:从像素到亚像素

角点检测是标定的第一步,也是最容易出问题的一步。

我记得刚开始做这个的时候,直接用OpenCV的findChessboardCorners函数,结果发现有些照片检测不到角点。后来才明白,光照、棋盘格大小、拍摄角度都会影响检测效果。

这里分享几个我踩过的坑:

避坑指南:

  • 棋盘格不要太小,建议每个格子边长30-50mm
  • 拍摄时棋盘格要占图像面积的1/4到1/3
  • 避免反光,最好用哑光纸打印
  • 角度变化要大,但不要超过45度

检测到角点后,还需要做亚像素精细化。OpenCV的cornerSubPix函数就是干这个的。它通过迭代优化,把角点精度从像素级提升到亚像素级。

import cv2
import numpy as np

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

# 棋盘格尺寸(内角点数量)
pattern_size = (9, 6)

# 查找角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)

if ret:
    # 亚像素精细化
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    
    # 绘制角点
    cv2.drawChessboardCorners(img, pattern_size, corners_sub, ret)

小技巧:如果检测失败,可以试试先做直方图均衡化,或者调整棋盘格的对比度。我曾经在弱光环境下拍了一组照片,怎么都检测不到,后来用cv2.equalizeHist处理了一下,问题就解决了。

内参矩阵与畸变系数计算

有了角点坐标,接下来就是计算内参矩阵和畸变系数了。

内参矩阵包含四个参数:fx, fy(焦距),cx, cy(主点坐标)。它描述了相机坐标系到图像坐标系的映射关系。

畸变系数则包括径向畸变(k1, k2, k3)和切向畸变(p1, p2)。说白了,就是镜头本身的光学缺陷导致的图像变形。

OpenCV的calibrateCamera函数一步到位:

# 准备世界坐标系中的点
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点

# 对每张图像执行角点检测并添加点
# ...(省略循环代码)

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

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

输出结果中,mtx就是内参矩阵,dist是畸变系数。我一般会检查一下重投影误差,如果大于0.5像素,说明标定质量不够好,需要重新拍一组照片。

重投影误差:把标定得到的3D点重新投影到图像上,计算与原始检测点的像素距离。这个值越小,标定越准。

知识体系总览

下面这张图,是我自己总结的相机内参标定知识体系。你可以把它当作一个快速索引:

相机内参标定知识体系 张正友标定法 棋盘格角点检测 内参矩阵与畸变 原理:3D-2D映射关系 优势:操作简单、精度高 要求:15-20张不同角度照片 粗检测:findChessboardCorners 精检测:cornerSubPix亚像素 常见问题:光照、反光、角度 内参矩阵:fx, fy, cx, cy 畸变系数:k1,k2,k3,p1,p2 质量评估:重投影误差 标定流程 拍摄棋盘格 → 角点检测 → 亚像素优化 → 计算内参 → 评估质量 建议:每次标定后保存内参矩阵和畸变系数,方便后续融合使用

实战中的注意事项

最后,再分享几个实战中的小经验:

  • 棋盘格要平整:贴在硬纸板上,不要贴在软布上。我见过有人贴在显示器上,结果曲面导致标定误差很大
  • 覆盖整个视场:棋盘格要出现在图像的各个角落,尤其是边缘。因为畸变在边缘最明显
  • 保持焦距不变:标定过程中不要变焦,否则内参就变了
  • 保存标定结果:把内参矩阵和畸变系数保存成文件,方便后续融合时直接加载

我的习惯:每次标定完,我都会用标定结果去矫正一张照片,看看矫正后的图像直线是不是真的变直了。如果还有弯曲,说明标定不够好,需要重做。

好了,这一章的内容就到这里。相机内参标定是融合的基础,一定要打好。下一章我们会讲激光雷达和相机的外参标定,到时候见。

可可精品资料库