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矩阵。核心步骤:

  1. 打印一张棋盘格标定板,贴在平面上。
  2. 从不同角度拍摄10-20张图像。
  3. 提取每张图像中的角点坐标。
  4. 利用角点坐标和已知的棋盘格物理尺寸,求解单应性矩阵。
  5. 通过多张图像的单应性矩阵,联合求解内参矩阵K。

我个人习惯用OpenCV的cv2.calibrateCamera(),它内部已经实现了完整的张正友标定流程。但要注意,标定板的精度直接影响内参的准确性。我建议用陶瓷或玻璃基板的标定板,不要用打印纸贴在纸板上——纸会变形。

3.6 内参矩阵知识体系

下面这张图梳理了内参矩阵K的完整知识结构,帮你建立整体认知:

内参矩阵K知识体系 内参矩阵K知识体系 内参矩阵 K (3×3) 数学定义 物理意义 工程应用 fx = f / dx fy = f / dy cx, cy 主点 焦距 → 缩放 主点 → 偏移 像素尺寸 → 离散化 张正友标定法 分辨率缩放 畸变校正 核心: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矩阵是相机标定的基石,后面的畸变校正、三维重建、手眼标定,全都建立在它的基础之上。标定的时候多花点心思,后面能省下大把调试时间。