4、对极几何与基础矩阵:对极几何约束、本质矩阵与基础矩阵、八点法求解基础矩阵、极线校正、三角化原理、线性三角化方法

4.1 对极几何约束——两张照片之间的“隐藏连线”

我们先从最直观的问题入手:给你两张不同角度拍摄的照片,你怎么知道同一个空间点在这两张图上的对应关系?

对极几何就是干这个的。它描述的是两个相机视图之间的内在几何关系。说白了,就是当你看到左图上有一个点,它在右图上可能出现在哪条线上——而不是漫无目的地全图搜索。

我刚开始做双目重建时,总觉得对极几何是个理论玩具。直到有一次,我在处理一个室外大场景的稀疏重建,特征点匹配错得一塌糊涂。后来用对极几何约束一筛,错误匹配直接砍掉了一半以上。嗯,从那以后我再也不敢小看它了。

核心概念:

  • 基线:两个相机光心的连线
  • 对极点:基线与成像平面的交点
  • 对极平面:空间点与两个光心确定的平面
  • 对极线:对极平面与成像平面的交线

关键约束就一句话:左图上的点,在右图上的匹配点一定位于对应的对极线上。这就是对极几何约束,也叫对极线约束。

你想想看,这个约束把二维搜索降到了一维搜索。原来你要在整张图上找匹配,现在只需要沿着一条线找。效率提升是数量级的。

4.2 本质矩阵与基础矩阵——数学上的“对极约束”

对极几何约束用数学怎么表达?这就引出了两个核心矩阵:本质矩阵 E 和基础矩阵 F。

本质矩阵 E:它编码了两个相机之间的相对旋转和平移。注意,它工作在归一化图像坐标下,也就是说它假设你已经知道了相机的内参。

基础矩阵 F:它更通用,直接工作在像素坐标下。它包含了相机的内参信息,所以不需要事先知道内参。

两者的关系很简单:F = K-T E K-1,其中 K 是相机内参矩阵。

我的经验:在实际项目中,如果你已经标定了相机,直接用本质矩阵 E 会更稳定。如果相机内参未知或者不准确,那就老老实实用基础矩阵 F。我曾经在一个项目中偷懒用了未标定的相机,结果用 E 算出来的结果一塌糊涂,换成 F 就好多了。

对极约束的数学形式:对于一对匹配点 x(左图)和 x'(右图),有 x'T F x = 0。这个方程就是整个对极几何的数学核心。

4.3 八点法求解基础矩阵——经典算法

怎么从一堆匹配点中算出 F 矩阵?最经典的方法就是八点法。

为什么是八个点?因为 F 矩阵有 9 个元素,但尺度是自由的(乘以任意非零常数不影响约束),所以实际上有 8 个自由度。理论上 8 对匹配点就够了。

算法步骤很简单:

  1. 对每一对匹配点 (x, y) 和 (x', y'),构造一个方程
  2. 把所有方程堆起来,得到一个 8×9 的矩阵(或者更多点就是 n×9)
  3. 用 SVD 分解求解,最小特征值对应的特征向量就是 F 矩阵的元素
  4. 强制 F 矩阵的秩为 2(因为本质矩阵的秩就是 2)
# 八点法的核心代码(Python 伪代码)
def compute_fundamental_matrix(pts1, pts2):
    # pts1, pts2: N×2 的匹配点坐标
    N = pts1.shape[0]
    A = np.zeros((N, 9))
    for i in range(N):
        x, y = pts1[i]
        xp, yp = pts2[i]
        A[i] = [x*xp, x*yp, x, y*xp, y*yp, y, xp, yp, 1]
    
    # SVD 分解
    U, S, Vt = np.linalg.svd(A)
    F = Vt[-1].reshape(3, 3)
    
    # 强制秩为 2
    U, S, Vt = np.linalg.svd(F)
    S[2] = 0
    F = U @ np.diag(S) @ Vt
    
    return F

注意:八点法对数据尺度非常敏感。我建议在求解前先对坐标做归一化处理——把坐标平移到原点,再缩放到均值为 sqrt(2)。这一步不做,结果可能差一个数量级。我曾经踩过这个坑,后来养成了归一化的习惯。

4.4 极线校正——让匹配变得简单

有了 F 矩阵,我们可以做一件很酷的事:极线校正。

极线校正的目的是:通过图像变换,让两幅图像的对极线变成水平的。这样一来,左图上的点,在右图上的匹配点就在同一行上。搜索从二维变成了一维,而且就是简单的行搜索。

校正的过程本质上是对两幅图像做单应性变换。算法有很多种,最常用的是 Bouguet 算法:

  • 把两个相机的光心旋转到平行
  • 再旋转使得对极线水平
  • 最后调整尺度使得视差范围合理

校正后的图像,你可以直接做行匹配。这在立体视觉中几乎是标配操作。

我的习惯:在做极线校正时,我会先检查校正后的图像质量。如果校正后图像变形太大,说明原始图像质量或者匹配点有问题。这时候不要急着往下走,先回头检查数据。

4.5 三角化原理——从二维到三维

好了,现在我们有了匹配点,也知道相机位姿(从 E 或 F 可以分解出来)。怎么得到空间点的三维坐标?这就是三角化要解决的问题。

三角化的原理其实很简单:从两个相机光心分别发出射线,经过图像上的匹配点,这两条射线在空间中的交点就是空间点的位置。

但现实中没有完美的交点——因为噪声的存在,两条射线往往不会精确相交。所以我们需要找一个“最接近”的点,让它到两条射线的距离之和最小。

这就是三角化的核心:在噪声下寻找最优的三维点

4.6 线性三角化方法——简单粗暴但有效

最直接的三角化方法是线性三角化,也叫 DLT 方法。

思路是这样的:对于空间点 X,它在两个相机上的投影方程可以写成:

  • x = P X(左图投影)
  • x' = P' X(右图投影)

其中 P 和 P' 是 3×4 的投影矩阵。每个方程给出两个独立约束(因为齐次坐标有尺度),总共四个方程,三个未知数。超定方程组,用最小二乘解。

# 线性三角化核心代码
def linear_triangulation(P1, P2, pt1, pt2):
    # P1, P2: 3×4 投影矩阵
    # pt1, pt2: 2D 齐次坐标
    A = np.zeros((4, 4))
    A[0] = pt1[0] * P1[2] - P1[0]
    A[1] = pt1[1] * P1[2] - P1[1]
    A[2] = pt2[0] * P2[2] - P2[0]
    A[3] = pt2[1] * P2[2] - P2[1]
    
    # SVD 求解
    _, _, Vt = np.linalg.svd(A)
    X = Vt[-1]
    X = X / X[3]  # 齐次转欧氏
    
    return X[:3]

线性三角化的优缺点:

  • 优点:简单、快速、不需要迭代
  • 缺点:对噪声敏感,尤其是当相机接近平行时(视差小的情况)

我个人的经验是:线性三角化适合作为初始解。如果后续要做优化(比如 Bundle Adjustment),用它来初始化就够了。但如果你的匹配点噪声很大,或者相机位姿不太准,线性三角化的结果可能惨不忍睹。这时候我会改用迭代方法,比如基于重投影误差的非线性优化。

知识体系总览

下面这张图展示了本章的核心逻辑链条:

对极几何与稀疏重建核心流程 匹配点对 (至少8对) 八点法求解 SVD + 秩约束 F / E 矩阵 对极几何约束 极线校正 水平对极线 相机位姿分解 从E矩阵提取R, t 线性三角化 DLT方法 → 3D点 图:从匹配点到三维点的完整流程

整个流程环环相扣:匹配点 → 八点法求 F → 极线校正 → 分解位姿 → 三角化得到三维点。每一步都依赖上一步的结果,所以每一步都要做扎实。

好了,对极几何和基础矩阵的内容就讲到这里。这些是稀疏重建的基石,理解了它们,后面的 Bundle Adjustment 和稠密重建才能顺利推进。


专注资料整理