1. 相机成像原理:从世界到像素的坐标之旅

做相机标定,首先得搞清楚一个问题:相机到底是怎么把三维世界变成一张二维照片的?

说白了,这就是个坐标变换的过程。我刚开始接触这个领域时,也被一堆坐标系绕得头晕。后来跟着项目做了几轮标定,才慢慢理清了这条线。今天咱们就把这条线彻底捋顺。

1.1 小孔成像模型——最朴素的相机

先回到最原始的相机形态。你想想看,一个暗箱,前面戳个小孔,光线透过小孔在后面的感光面上成像。这就是小孔成像模型。

核心特点:

  • 所有光线都穿过同一个点——光心(小孔位置)
  • 成像是倒立的,左右也颠倒
  • 没有镜头畸变,理论上最纯净的成像

但现实中没有完美的针孔相机。小孔太小,进光量不够,曝光时间太长;小孔太大,图像又模糊。所以现代相机都用镜头来替代小孔,这就引出了针孔相机模型。

1.2 针孔相机模型——数学化的理想相机

针孔相机模型是对小孔成像的数学抽象。它假设相机是完美的,没有畸变,光轴垂直于成像平面。虽然现实相机做不到这么理想,但它是所有标定算法的基础。

我记得第一次用OpenCV做标定时,输出的内参矩阵就是基于这个模型算出来的。当时没理解透,结果标定出来的参数用在畸变校正上,图像边缘全是扭曲的。后来才意识到,模型假设和实际相机之间的差距,就是我们需要标定去弥补的。

我的经验:针孔模型是标定的"基准坐标系"。所有畸变参数、外参,都是在这个模型基础上做修正。理解了这个,后面学张正友标定法就轻松多了。

1.3 四个坐标系——坐标变换的链条

从三维世界到二维像素,中间要经过四次坐标变换。我习惯把这四个坐标系串成一条链:

坐标系 维度 单位 作用
世界坐标系 3D (Xw, Yw, Zw) 物理单位(米/mm) 描述物体在真实世界中的位置
相机坐标系 3D (Xc, Yc, Zc) 物理单位(米/mm) 以相机光心为原点,Z轴指向镜头前方
图像坐标系 2D (x, y) 物理单位(米/mm) 成像平面上的物理位置
像素坐标系 2D (u, v) 像素(pixel) 图像数组中的行列索引

这条链的变换顺序是:世界 → 相机 → 图像 → 像素。每一步都有对应的数学公式,咱们一个一个看。

1.3.1 世界坐标系 → 相机坐标系(外参变换)

世界坐标系是人为定义的。你可以把原点放在棋盘格的角点上,也可以放在房间的角落。相机坐标系则以光心为原点,Z轴沿着光轴方向。

从世界到相机,需要做一次刚体变换:旋转 + 平移。

// 数学形式
[Xc, Yc, Zc, 1]^T = [R | t] * [Xw, Yw, Zw, 1]^T

// 其中:
// R:3x3 旋转矩阵
// t:3x1 平移向量
// [R | t]:3x4 外参矩阵

这里的R和t,就是相机的外参。每个相机位置、角度不同,外参就不同。我在做多相机拼接项目时,需要标定每个相机相对于世界坐标系的外参,这一步如果标不准,后面拼接出来的全景图全是错位的。

1.3.2 相机坐标系 → 图像坐标系(投影变换)

这一步是三维到二维的投影,用的是相似三角形原理。

x = f * Xc / Zc
y = f * Yc / Zc

// f:焦距(物理单位)
// (x, y):图像坐标系下的坐标

注意这里有个除法——除以Zc。这就是为什么远处的物体看起来小,近处的看起来大。透视效应就体现在这里。

避坑指南:我曾经在写标定脚本时,把这一步的Zc当成了常数,结果算出来的投影点全偏了。记住,Zc是每个三维点对应的深度值,不是固定的。

1.3.3 图像坐标系 → 像素坐标系(离散化)

图像坐标系是连续的物理坐标,单位是毫米。像素坐标系是离散的整数坐标,单位是像素。这一步就是把物理位置映射到感光元件的像素格点上。

u = x / dx + u0
v = y / dy + v0

// dx, dy:每个像素的物理尺寸(mm/pixel)
// (u0, v0):主点坐标(光轴与成像平面的交点)

把上面三步合并,就得到了完整的投影公式:

s * [u, v, 1]^T = K * [R | t] * [Xw, Yw, Zw, 1]^T

// K:内参矩阵(3x3)
//   [fx,  0, u0]
//   [ 0, fy, v0]
//   [ 0,  0,  1]
// fx = f/dx, fy = f/dy:以像素为单位的焦距
// s:尺度因子(等于Zc)

1.4 内参矩阵——相机的"身份证"

内参矩阵K包含了相机的固有属性:焦距、主点、像素尺寸。每个相机出厂时都不一样,而且会随着温度、震动等因素变化。这就是为什么我们需要标定。

参数 含义 典型值(举例)
fx x方向焦距(像素) 1200 ~ 2000
fy y方向焦距(像素) 1200 ~ 2000
u0 主点x坐标 图像宽度的一半
v0 主点y坐标 图像高度的一半

我习惯把内参矩阵比作相机的"身份证"。每台相机都有唯一的内参,标定的核心任务之一就是把这个身份证号算出来。

1.5 知识结构总览

下面这张图把整个成像模型和坐标系变换串起来了。建议你保存下来,后面学标定算法时随时对照。

相机成像模型与坐标系变换总览 相机成像模型与坐标系变换总览 世界坐标系 (Xw, Yw, Zw) 相机坐标系 (Xc, Yc, Zc) 图像坐标系 (x, y) 像素坐标系 (u, v) 外参 [R | t] 投影 (x=f·Xc/Zc) 离散化 (u=x/dx+u0) 内参矩阵 K(相机固有属性) K = [fx, 0, u0; 0, fy, v0; 0, 0, 1] fx = f/dx, fy = f/dy(像素焦距) | (u0, v0) 主点坐标 小孔成像模型(物理原理) 光线穿过光心,在成像面上形成倒像 像距 = 焦距 f | 物距 >> 像距时近似为针孔模型 倒像 → 数学处理时翻转为正像 实际相机用镜头组替代小孔,引入畸变 标定要解出的参数 内参:fx, fy, u0, v0 畸变:k1, k2, p1, p2, k3 外参:每张标定图的 R, t 目标:最小化重投影误差

1.6 为什么需要标定?

搞清楚了成像模型,标定的意义就很清楚了:

  1. 内参未知:fx, fy, u0, v0 出厂时可能有,但精度不够,而且会变化
  2. 畸变存在:镜头不是完美的,径向畸变和切向畸变需要校正
  3. 外参需要:相机相对于世界坐标系的位置和姿态,需要从标定中获取

说白了,标定就是用已知的三维点(棋盘格角点)和对应的二维像素点,反推出相机的内参、外参和畸变参数。这是一个典型的"由果推因"的过程。

我的建议:刚开始学标定,不要急着写代码。先把这四个坐标系的关系画在纸上,用手算一遍投影过程。我当年就是这么干的,虽然慢,但后面写脚本时思路特别清晰。