3. 相机内参矩阵
聊到相机标定,内参矩阵K是绕不开的核心。说白了,它就是描述相机内部光学和几何特性的“身份证”。我刚开始接触视觉时,总觉得这堆公式很抽象,直到第一次亲手标定完一个畸变严重的工业镜头,看到校正后的图像,才真正理解它的分量。
3.1 内参矩阵K到底在描述什么?
想象一下,现实世界中的一个三维点,是怎么跑到你的二维图像上的?中间经历了两次变换:
- 相机坐标系 → 图像物理坐标系:把三维坐标投影到成像平面上,单位是毫米。
- 图像物理坐标系 → 像素坐标系:把毫米坐标离散化成像素坐标,单位是像素。
内参矩阵K,就是打包了第二步的数学关系。它告诉你:成像平面上的物理点,对应到图像矩阵的哪个像素上。
核心公式:
λ * [u, v, 1]^T = K * [X_c, Y_c, Z_c]^T
其中:
(u, v) —— 像素坐标
(X_c, Y_c, Z_c) —— 相机坐标系下的三维点
λ —— 深度缩放因子(其实就是Z_c)
3.2 内参矩阵K的数学定义
标准的3x3内参矩阵长这样:
K = | fx 0 cx |
| 0 fy cy |
| 0 0 1 |
每个参数都有明确的物理意义,咱们一个一个拆开看。
3.2.1 焦距:fx 和 fy
fx和fy是x和y方向上的等效焦距,单位是像素。注意,它不是镜头上的物理焦距f(毫米),而是:
fx = f / dx
fy = f / dy
其中dx、dy是单个像素在x、y方向上的物理尺寸(毫米/像素)。
我遇到过不少新手,拿着镜头参数上的4mm焦距直接往fx里填,结果标定出来的内参完全不对。记住,fx和fy是像素单位,不是毫米。
我的经验: 工业相机中,fx和fy通常很接近,但不完全相等。如果两者差异超过5%,先检查一下像素是不是正方形,或者镜头有没有明显的畸变。
3.2.2 主点:cx 和 cy
主点是光轴与成像平面的交点,理论上应该在图像正中心。但实际生产中,由于镜头安装误差、传感器封装偏差,主点往往偏离中心几个甚至几十个像素。
我记得有一次做高精度测量项目,默认把主点设在图像中心(640, 512),结果测量误差总是偏大。后来重新标定,发现实际主点在(648, 518) —— 就这8个像素的偏差,直接导致测量精度从0.1mm掉到了0.3mm。
避坑指南: 千万不要为了省事把主点硬设为图像中心。我曾经吃过这个亏,标定出来的内参在图像边缘区域误差特别大。老老实实跑一遍标定流程,主点自然会收敛到正确位置。
3.2.3 像素尺寸:dx 和 dy
dx和dy是单个像素的物理尺寸,通常由传感器手册给出。比如常见的IMX264传感器,像素尺寸是3.45μm × 3.45μm。
但这里有个坑:dx和dy并不直接出现在K矩阵里,而是通过fx、fy间接体现。你真正需要关心的是:
- 像素是不是正方形(dx == dy)?
- 如果是,fx和fy应该非常接近。
- 如果不是(比如某些线阵相机),fx和fy会有明显差异。
3.3 内参矩阵的物理意义
咱们用一个具体的例子来理解。假设:
fx = 1200 px, fy = 1200 px
cx = 640 px, cy = 512 px
这意味着:
- 一个在相机前方1米处、偏离光轴1米的点,在图像上会偏离主点1200个像素。
- 主点位于图像的第640列、第512行。
说白了,K矩阵就是把三维空间中的“角度”映射成图像上的“像素偏移”。
3.4 内参矩阵与图像分辨率的关系
同一个相机,如果改变输出图像的分辨率(比如从1280×1024缩放到640×512),内参矩阵会怎么变?
| 参数 | 变化规律 |
|---|---|
| fx, fy | 按缩放比例等比缩放(缩小一半,fx也减半) |
| cx, cy | 按缩放比例等比缩放 |
| 畸变系数 | 不变(畸变是镜头本身的物理特性) |
重要: 如果你在项目中改变了图像分辨率,一定要重新标定内参,或者手动缩放K矩阵。我见过有人直接拿1280分辨率下标定的内参去处理640分辨率的图像,结果投影误差大了好几倍。
3.5 内参矩阵的标定方法
实际工程中,我们通常用张正友标定法来求解K矩阵。核心步骤:
- 打印一张棋盘格标定板,贴在平面上。
- 从不同角度拍摄10-20张图像。
- 提取每张图像中的角点坐标。
- 利用角点坐标和已知的棋盘格物理尺寸,求解单应性矩阵。
- 通过多张图像的单应性矩阵,联合求解内参矩阵K。
我个人习惯用OpenCV的cv2.calibrateCamera(),它内部已经实现了完整的张正友标定流程。但要注意,标定板的精度直接影响内参的准确性。我建议用陶瓷或玻璃基板的标定板,不要用打印纸贴在纸板上——纸会变形。
3.6 内参矩阵知识体系
下面这张图梳理了内参矩阵K的完整知识结构,帮你建立整体认知:
3.7 一个完整的标定代码示例
下面是我常用的OpenCV标定代码片段,你可以直接拿去用:
import cv2
import numpy as np
import glob
# 棋盘格参数
CHECKERBOARD = (9, 6) # 内角点数量
SQUARE_SIZE = 25.0 # 棋盘格方格尺寸(毫米)
# 准备世界坐标系中的点
objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[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, CHECKERBOARD, 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, K, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("内参矩阵 K:")
print(K)
print("\n畸变系数 dist:")
print(dist)
我的建议: 标定完成后,一定要做重投影误差验证。如果平均误差超过0.5像素,说明标定质量不够好,需要重新采集图像。我一般要求重投影误差控制在0.2像素以内,才能用于高精度测量。
3.8 内参矩阵的典型数值范围
不同相机、不同分辨率下,内参矩阵的数值差异很大。下面是一些常见配置的参考值:
| 相机型号 | 分辨率 | 像素尺寸 | fx / fy (约) | cx / cy (约) |
|---|---|---|---|---|
| Basler acA1300-60gm | 1280×1024 | 5.3 μm | 1200 / 1200 | 640 / 512 |
| FLIR BFS-U3-50S5C | 2448×2048 | 3.45 μm | 2400 / 2400 | 1224 / 1024 |
| 海康威视 MV-CA050-10GC | 2592×1944 | 2.2 μm | 3600 / 3600 | 1296 / 972 |
嗯,到这里内参矩阵的核心内容就讲完了。记住,K矩阵是相机标定的基石,后面的畸变校正、三维重建、手眼标定,全都建立在它的基础之上。标定的时候多花点心思,后面能省下大把调试时间。