2、相机成像模型:针孔模型、畸变模型(径向/切向)、内参矩阵深度解析

做3D视觉定位,第一步就是搞懂相机是怎么“看”世界的。说白了,相机成像就是把三维空间里的点,映射到二维图像上。这个过程看似简单,但里面藏着不少坑。今天我就把针孔模型、畸变模型和内参矩阵这几个核心概念,掰开了揉碎了讲清楚。

2.1 针孔模型:相机成像的“理想国”

针孔模型是最基础的成像模型。它假设光线沿直线传播,穿过一个小孔,在后面的成像平面上形成倒立的像。嗯,这里要注意,我们通常把成像平面“虚拟”到相机前方,这样图像就是正立的,计算起来也方便。

我个人习惯把针孔模型理解成一个“相似三角形”问题。你看,物体高度、物体到相机的距离、像的高度、焦距,这四个量构成了一个比例关系:

像的高度 / 物体高度 = 焦距 / 物体到相机的距离

这个公式虽然简单,但它是整个相机几何的基石。我在项目里做手眼标定时,经常用这个关系来估算相机的大致安装高度和视野范围。

针孔模型的数学表达,其实就是坐标变换。一个世界坐标系下的点 P(X, Y, Z),经过相机坐标系、图像坐标系,最后变成像素坐标系下的点 p(u, v)。这个过程可以写成:

Z * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T

其中 K 就是内参矩阵,[R | t] 是外参矩阵。今天我们先聚焦内参。

2.2 内参矩阵:相机的“身份证”

内参矩阵 K 描述了相机内部的几何特性。它长这样:

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

这里 fx 和 fy 是焦距在像素单位下的值,cx 和 cy 是光心在像素坐标系中的坐标。为什么会有 fx 和 fy 两个值?因为相机传感器的像素不一定是正方形的,所以 x 和 y 方向的焦距可能不同。

我记得有一次,一个同事拿了个工业相机,标定出来的 fx 和 fy 差了将近 5%。他以为是标定出错了,反复标了好几次。我告诉他,这很正常,因为那个相机的像素尺寸是 5.2μm × 5.0μm,不是正方形。所以,fx 和 fy 的差异,直接反映了像素的宽高比。

内参矩阵的核心作用:

  • 将相机坐标系下的点,映射到像素坐标系
  • 描述了焦距、光心位置和像素尺寸
  • 是去畸变、3D重建、手眼标定的基础

2.3 畸变模型:现实世界的“哈哈镜”

针孔模型是理想情况,但实际镜头都有畸变。说白了,就是光线经过镜头时发生了弯曲,导致图像变形。畸变主要分两类:径向畸变和切向畸变。

2.3.1 径向畸变:镜头“肚子”惹的祸

径向畸变是由镜头的形状引起的。光线离光心越远,弯曲得越厉害。它有两种表现:

  • 桶形畸变:图像边缘向外凸出,像木桶一样。常见于广角镜头。
  • 枕形畸变:图像边缘向内凹陷,像枕头一样。常见于长焦镜头。

径向畸变的数学模型用泰勒级数展开来描述:

x_distorted = x * (1 + k1 * r^2 + k2 * r^4 + k3 * r^6)
y_distorted = y * (1 + k1 * r^2 + k2 * r^4 + k3 * r^6)

其中 (x, y) 是归一化平面上的理想坐标,r 是点到光心的距离,k1、k2、k3 是径向畸变系数。一般用 k1 和 k2 就够了,鱼眼镜头才需要 k3。

避坑指南:我曾经在标定一个广角相机时,只用了 k1 和 k2,结果去畸变后图像边缘还是有明显的扭曲。后来加上 k3,效果就好多了。所以,如果你的镜头视场角很大(超过 90°),建议把 k3 也标出来。

2.3.2 切向畸变:装配“歪了”的结果

切向畸变是由于镜头和成像平面不平行引起的。说白了,就是镜头装歪了,或者传感器贴歪了。它的数学模型是:

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

其中 p1 和 p2 是切向畸变系数。切向畸变通常比径向畸变小得多,但在高精度应用中不能忽略。

2.4 畸变校正:把“哈哈镜”变回平面镜

畸变校正的过程,其实就是把畸变图像上的点,映射回理想位置。具体步骤是:

  1. 获取像素坐标 (u, v)
  2. 用内参矩阵反算归一化坐标 (x, y)
  3. 用畸变模型计算畸变后的坐标 (x_distorted, y_distorted)
  4. 用内参矩阵把畸变坐标映射回像素坐标 (u_distorted, v_distorted)
  5. 在畸变图像上采样,得到校正后的像素值

这个过程在 OpenCV 里用 cv::undistort() 或者 cv::initUndistortRectifyMap() 就能搞定。但我建议你理解背后的原理,因为在实际部署时,你可能需要自己实现去畸变,或者优化去畸变的性能。

注意:去畸变后的图像,边缘区域会被拉伸,导致分辨率下降。如果你在边缘区域做特征提取,精度会受影响。我的做法是:在标定时就尽量让目标区域位于图像中心,或者使用更高分辨率的相机。

2.5 知识结构总览

下面这张图,把相机成像模型的核心逻辑串起来了。从世界坐标到像素坐标,每一步都离不开内参和畸变模型。

相机成像模型知识结构 世界坐标系 (X,Y,Z) 外参 [R|t] 相机坐标系 (Xc,Yc,Zc) 内参矩阵 K 归一化平面 (x,y) 畸变模型 (k1,k2,p1,p2) 像素坐标 (u,v) 关键说明: 1. 世界坐标 → 相机坐标:由外参 [R|t] 完成刚体变换 2. 相机坐标 → 归一化坐标:除以 Zc,得到 (Xc/Zc, Yc/Zc) 3. 归一化坐标 → 畸变坐标:用径向/切向畸变模型计算偏移 4. 畸变坐标 → 像素坐标:由内参矩阵 K 完成仿射变换

2.6 内参与畸变参数对比

为了方便查阅,我把内参和畸变参数整理成了表格。你在做标定和部署时,可以对照着看。

参数 符号 含义 典型值范围 影响
焦距 x fx x 方向焦距(像素单位) 500 ~ 5000 决定水平方向放大倍数
焦距 y fy y 方向焦距(像素单位) 500 ~ 5000 决定垂直方向放大倍数
光心 x cx 光心在像素坐标系中的 x 坐标 图像宽度的一半 ± 50 影响投影中心位置
光心 y cy 光心在像素坐标系中的 y 坐标 图像高度的一半 ± 50 影响投影中心位置
径向畸变 1 k1 径向畸变一阶系数 -0.5 ~ 0.5 主导径向畸变方向与程度
径向畸变 2 k2 径向畸变二阶系数 -0.3 ~ 0.3 修正高阶径向畸变
径向畸变 3 k3 径向畸变三阶系数(鱼眼用) -0.1 ~ 0.1 用于大视场角镜头
切向畸变 1 p1 切向畸变一阶系数 -0.05 ~ 0.05 修正镜头与传感器不平行
切向畸变 2 p2 切向畸变二阶系数 -0.05 ~ 0.05 修正镜头与传感器不平行

2.7 实战中的几点建议

讲完了理论,说点实际的。我在做无序抓取项目时,总结了几条关于相机标定和成像模型的经验:

  • 标定板要覆盖整个视野:别只拍中间,边缘的畸变信息同样重要。我曾经为了省事,只拍了中心区域,结果去畸变后边缘误差很大。
  • 多角度拍摄:标定图片要有不同的角度和距离,这样才能解算出稳定的内参和畸变系数。我一般拍 20-30 张。
  • 检查重投影误差:标定完成后,看看重投影误差。如果大于 0.5 像素,说明标定质量可能有问题,需要重新拍。
  • 固定镜头光圈:标定时的光圈要和实际使用时一致。因为光圈变化会影响景深和畸变程度。

小技巧:在部署时,如果计算资源有限,可以预先计算好去畸变映射表(mapx, mapy),运行时直接用 cv::remap() 查表,比实时计算畸变模型快得多。