2. 相机模型与投影几何:针孔相机模型、内参矩阵K、外参矩阵(R,t)、世界坐标系到像素坐标系的投影

各位同学,咱们今天聊点硬核的——相机模型。说白了,就是搞清楚一件事:现实世界里的一个点,是怎么跑到照片上那个像素位置去的?

我刚开始做SLAM那会儿,总觉得这玩意儿太基础,不就是个投影嘛。结果有一次在室外场景做特征匹配,怎么都对不上,折腾了两天才发现是相机内参标定出了问题。嗯,从那以后我再也不敢小看这个环节了。

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

先说说针孔相机模型。你想想看,一个暗箱,前面戳个小孔,光线穿过小孔在后面的感光平面上成像。这就是最原始的相机。

数学上怎么描述?很简单,相似三角形。

假设世界中有个点 P,坐标为 (X, Y, Z)。它通过小孔 O(也就是光心)投影到成像平面上,得到点 p,坐标为 (x, y)。根据相似三角形:

x / X = f / Z
y / Y = f / Z

整理一下:

x = f * X / Z
y = f * Y / Z

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

注意:实际相机里,成像平面在小孔后面,所以成的像是倒立的。但为了方便,我们通常把成像平面“虚拟”地放到小孔前面,这样像就是正立的了。这个操作叫“虚拟像平面”。

2.2 内参矩阵K——从物理坐标到像素坐标

上面得到的 (x, y) 还是物理单位(比如毫米),但我们要的是像素坐标 (u, v)。这中间需要两个转换:

  1. 缩放:把毫米变成像素,需要知道每个像素的物理尺寸(dx, dy)
  2. 平移:像素坐标系的原点通常在左上角,而物理坐标系的原点在主点(光轴与成像平面的交点)

所以:

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

把前面的 x, y 代入,写成矩阵形式:

[u]   [f/dx   0     u0] [X]
[v] = [  0   f/dy   v0] [Y]
[1]   [  0     0     1] [Z]

中间这个 3x3 矩阵,就是大名鼎鼎的内参矩阵 K

我个人习惯把内参矩阵记成 K = [fx, 0, cx; 0, fy, cy; 0, 0, 1]。fx 和 fy 是焦距的像素表示,cx 和 cy 是主点坐标。注意,fx 和 fy 不一定相等,因为像素可能不是正方形。

我在项目中遇到过一台工业相机,标定出来的 fx 和 fy 差了将近 5 个像素。一开始我以为是标定出错了,后来查手册才知道,那个型号的传感器像素确实是矩形的。所以,别想当然地认为 fx = fy。

2.3 外参矩阵(R, t)——从世界坐标系到相机坐标系

内参解决的是“相机坐标系下的点怎么变成像素”。但世界上的点是在世界坐标系下的,我们需要先把它转到相机坐标系下。

这个转换就是外参,包含两部分:

  • 旋转矩阵 R:3x3 的正交矩阵,描述两个坐标系之间的旋转关系
  • 平移向量 t:3x1 的向量,描述两个坐标系原点之间的位移

转换公式:

P_cam = R * P_world + t

写成齐次形式:

[X_cam]   [R  t] [X_world]
[Y_cam] = [    ] [Y_world]
[Z_cam]   [0  1] [Z_world]
[  1  ]         [   1   ]

这个 4x4 矩阵就是外参矩阵,也叫变换矩阵 T。

我曾经踩过一个坑:OpenCV 和 MATLAB 的坐标系定义不一样。OpenCV 是右手系(z 轴朝前),而有些数据集用的是左手系。如果你混着用,外参矩阵会出错,重建出来的点云全是镜像的。所以,拿到数据第一件事,确认坐标系定义。

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

把上面所有步骤串起来,就是完整的投影公式:

s * [u]   [fx  0  cx] [R  t] [X_world]
    [v] = [ 0 fy cy] [    ] [Y_world]
    [1]   [ 0  0  1] [0  1] [Z_world]
                      [    ] [   1   ]

这里 s 是尺度因子,其实就是 Z_cam 的值。为什么会有 s?因为齐次坐标乘以任意非零常数,代表的还是同一个点。

整个流程可以总结为:

  1. 世界坐标系下的点 P_w
  2. 通过外参 (R, t) 转到相机坐标系 P_c
  3. 通过内参 K 投影到像素平面 (u, v)

下面这张图可以帮你理清思路:

单目视觉SLAM:世界坐标系 → 像素坐标系 投影流程 世界坐标系 P_w (X, Y, Z) 外参 相机坐标系 P_c (Xc, Yc, Zc) 内参 像素坐标系 p (u, v) 输出 图像 像素值 外参 (R, t):描述相机在世界中的位置和朝向 内参 K:描述相机的焦距、主点、畸变等内部参数 完整投影公式(齐次坐标形式) s · [u, v, 1]^T = K · [R | t] · [X, Y, Z, 1]^T 其中 s = Zc(相机坐标系下的深度值) K = 内参矩阵,[R | t] = 外参矩阵

2.5 深度丢失与尺度模糊——单目视觉的“原罪”

你看上面的公式,有个细节:s = Zc。这个 Zc 是相机坐标系下的深度值。

问题来了:如果我把 P_w 沿着光轴方向移动,同时缩放它的尺度,投影到像素平面上的位置会不会变?

答案是:不会

举个例子:

  • 一个点距离相机 10 米,物体实际大小 2 米
  • 另一个点距离相机 20 米,物体实际大小 4 米

这两个点在图像上的投影完全一样。这就是单目视觉的尺度模糊问题。

说白了,单目相机只能知道物体的方向,不知道它的绝对深度。你看到的图像,丢失了深度信息。这也是为什么单目SLAM需要额外的处理来恢复尺度。

我记得有一次做AR应用,用户把手机放在桌上,虚拟物体看起来飘在空中。调试了半天才发现,是因为初始化时尺度没定好,导致虚拟物体的位置和真实场景对不上。嗯,从那以后我对尺度问题就特别敏感。

2.6 畸变模型——现实总是不完美的

理想情况下,针孔模型是完美的。但实际镜头有畸变,主要是两种:

  • 径向畸变:光线通过透镜边缘时弯曲得更厉害,造成图像边缘的直线变弯
  • 切向畸变:透镜和成像平面不平行,造成图像倾斜

畸变模型通常用多项式来描述:

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

其中 r^2 = x^2 + y^2,k1, k2, k3 是径向畸变系数,p1, p2 是切向畸变系数。

实际项目中,我一般只用 k1, k2 两个径向畸变参数就够了。k3 通常用于鱼眼镜头,普通镜头加上去反而容易过拟合。p1, p2 也很小,很多时候可以忽略。

2.7 小结——你需要记住的

概念 含义 我的建议
内参矩阵 K 描述相机内部参数:焦距、主点 标定一定要做,别用默认值
外参 (R, t) 描述相机在世界中的位姿 注意坐标系定义,别搞混
尺度模糊 单目丢失绝对深度信息 这是单目SLAM的核心难点
畸变参数 修正镜头带来的变形 k1, k2 必用,其他看情况

好了,这一章的内容就到这里。相机模型是整个视觉SLAM的基石,后面的深度恢复、三角化、BA优化,全都建立在这个基础上。把这一章吃透了,后面的路就好走了。


专注资料整理