4. 相机模型

相机模型,说白了就是回答一个问题:三维世界里的一个点,是怎么跑到二维图像上的?

我刚开始做感知的时候,觉得这事很简单——不就是小孔成像嘛。后来在实际项目中踩了无数坑才发现,这里面的门道比想象中深得多。尤其是当你做多传感器融合时,相机模型的理解深度,直接决定了标定精度。

今天咱们就把这块彻底讲透。

4.1 针孔相机模型——最朴素的成像原理

针孔相机模型是所有相机模型的基石。它假设光线通过一个小孔,在后面的成像平面上形成倒像。

数学上,这个过程可以用一个简单的投影公式描述:

// 三维点 (X, Y, Z) 投影到成像平面 (x, y)
x = f * X / Z
y = f * Y / Z

其中 f 是焦距,也就是小孔到成像平面的距离。

你想想看,这个公式其实很直观:物体离得越远(Z 越大),成像越小;焦距越长,成像越大。这就是为什么长焦镜头能把远处的物体拉近。

我的经验:在实际项目中,我习惯把针孔模型理解为一个"比例映射"。它把三维空间中的射线,映射到二维平面上。这个映射是线性的——前提是忽略畸变。

4.2 从世界到像素——四个坐标系的变换

一个三维点要变成像素坐标,需要经过四次变换。我画了一张图,帮你理清这个流程:

相机成像:四个坐标系的变换流程 世界坐标系 (Xw, Yw, Zw) 外参 相机坐标系 (Xc, Yc, Zc) 投影 图像坐标系 (x, y) 内参 像素坐标系 (u, v) 关键理解: • 外参:把世界坐标系变换到相机坐标系(旋转 + 平移) • 投影:针孔模型,从 3D 到 2D(除以 Z) • 内参:从物理图像坐标 (mm) 到像素坐标 (pixel)

这四个坐标系,我建议你记牢。因为后面做多视角标定、传感器融合,本质上就是在这些坐标系之间来回变换。

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

内参矩阵描述了相机内部的几何特性。它把相机坐标系下的点,映射到像素坐标系。

标准形式是这样的:

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

其中:

  • fx, fy:焦距在 x 和 y 方向的像素尺度(单位:像素)
  • cx, cy:主点坐标,也就是光轴与成像平面的交点

核心理解:内参矩阵是相机的固有属性。同一台相机,内参是固定的。换一台相机,内参就变了。所以标定的第一步,就是求内参。

我记得有一次做项目,同事直接用厂家给的标称焦距,结果融合出来的点云全是偏的。后来一测,实际焦距和标称差了 3%。从那以后,我坚持每台相机都做内参标定,绝不偷懒。

4.4 外参矩阵——相机在哪儿?朝哪儿看?

外参矩阵描述的是相机在世界坐标系中的位置和朝向。它由两部分组成:

  • 旋转矩阵 R(3×3):描述相机的朝向
  • 平移向量 t(3×1):描述相机的位置

组合起来就是:

[R | t]   (3×4 矩阵)

外参的作用,就是把世界坐标系下的点,变换到相机坐标系下:

Xc = R * Xw + t

注意:外参是相对于某个世界坐标系定义的。在多相机系统中,通常选择一个相机作为参考,其他相机的外参都是相对于这个参考相机。

我曾经犯过一个低级错误——把旋转矩阵和平移向量的顺序搞反了。结果标定出来的外参,投影误差看起来很小,但实际三维重建全是错的。嗯,从那以后我每次都会检查 R 和 t 的维度。

4.5 畸变模型——现实世界没有完美的针孔

现实中的镜头不是完美的针孔。光线通过镜头时会发生弯曲,导致图像变形。这就是畸变。

畸变主要分两种:

畸变类型 产生原因 视觉效果 数学模型
径向畸变 镜头曲面导致光线弯曲 直线变弯,越靠近边缘越明显 k1, k2, k3 三个参数
切向畸变 镜头与成像平面不平行 图像倾斜、梯形变形 p1, p2 两个参数

4.5.1 径向畸变

径向畸变是最常见的。你想想看,广角镜头拍出来的照片,边缘的直线都是弯的——那就是径向畸变。

数学上,它用泰勒级数来近似:

x_distorted = x * (1 + k1*r² + k2*r⁴ + k3*r⁶)
y_distorted = y * (1 + k1*r² + k2*r⁴ + k3*r⁶)

其中 r 是点到主点的距离。k1, k2, k3 是畸变系数。

我的经验:对于普通镜头,k1 和 k2 就够了。鱼眼镜头才需要 k3。我一般先标定 k1, k2,如果重投影误差还大,再加 k3。

4.5.2 切向畸变

切向畸变相对少见,但一旦出现就很麻烦。它通常是因为镜头安装时没有完全垂直于成像平面。

模型是这样的:

x_distorted = x + [2*p1*x*y + p2*(r² + 2*x²)]
y_distorted = y + [p1*(r² + 2*y²) + 2*p2*x*y]

p1, p2 就是切向畸变系数。

避坑指南:我曾经在一个项目中,发现标定出来的切向畸变系数特别大。排查了半天,发现是镜头没拧紧。拧紧之后重新标定,p1, p2 直接降了一个数量级。所以,先检查硬件,再怀疑算法。

4.6 完整的投影过程——从世界到像素

把上面所有内容串起来,一个三维点变成像素坐标的完整过程是这样的:

  1. 世界 → 相机:用外参 [R|t] 变换
  2. 相机 → 归一化平面:除以 Z,得到 (x, y)
  3. 加畸变:用径向和切向畸变模型修正
  4. 归一化 → 像素:用内参矩阵 K 映射

写成代码就是:

def project_point(Xw, R, t, K, dist_coeffs):
    # 1. 世界到相机
    Xc = R @ Xw + t
    
    # 2. 归一化平面
    x = Xc[0] / Xc[2]
    y = Xc[1] / Xc[2]
    
    # 3. 加畸变
    r2 = x*x + y*y
    k1, k2, p1, p2, k3 = dist_coeffs
    
    x_dist = x * (1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + 2*p1*x*y + p2*(r2 + 2*x*x)
    y_dist = y * (1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + p1*(r2 + 2*y*y) + 2*p2*x*y
    
    # 4. 像素坐标
    u = K[0,0] * x_dist + K[0,2]
    v = K[1,1] * y_dist + K[1,2]
    
    return u, v

关键点:畸变是在归一化平面上加的,不是在像素平面上。很多人搞错这个顺序,结果标定出来的参数怎么调都不对。

4.7 标定流程——怎么拿到这些参数?

理论讲完了,咱们说说实操。标定相机参数,最常用的方法是张正友标定法。流程如下:

步骤 操作 输出
1 打印棋盘格标定板 已知尺寸的角点
2 从不同角度拍摄 15-20 张照片 多视角图像
3 检测每张图的角点 像素坐标 (u, v)
4 用最小二乘法求解内参和畸变 K, dist_coeffs
5 优化:最小化重投影误差 最终参数

我的建议:拍照时,让标定板覆盖图像的各个区域——四个角和中心都要有。如果只拍中心区域,边缘的畸变参数就标不准。我一般会拍 20 张,确保覆盖全面。

标定完成后,一定要检查重投影误差。正常情况下,平均误差应该在 0.5 像素以内。如果超过 1 个像素,说明标定质量有问题,需要重新来。

嗯,相机模型这部分就讲到这里。这些内容看起来基础,但它是整个传感器融合的基石。后面的多视角标定、激光雷达与相机联合标定,全都建立在这个模型之上。把今天的内容吃透,后面的路就好走了。