4. 对极几何与基础矩阵:对极约束、本质矩阵与基础矩阵、八点法求解、单应矩阵
各位同学,欢迎来到第四章。这一章我们要聊的,是视觉SLAM里一个非常核心的几何问题——对极几何。
说白了,就是当相机从两个不同位置拍摄同一场景时,这两张图像之间到底存在什么样的数学关系?搞懂这个,我们才能从2D图像中恢复出3D信息,也就是所谓的运动估计。
我个人觉得,这部分内容是整个视觉里程计的基石。你想想看,机器人要导航,首先得知道自己动了多少,对吧?对极几何就是帮我们算出这个“动了多少”的数学工具。
4.1 对极约束:两张图之间的“红线”
我们先来看一个最简单的场景。假设相机从位置O₁移动到位置O₂,拍摄了同一空间点P。那么,P在左图上的投影点是p₁,在右图上的投影点是p₂。
这里有一个非常重要的几何关系:O₁、O₂、P三点共面。这个平面叫做极平面。
为什么会这样?因为光线从P出发,经过O₁到达p₁,也经过O₂到达p₂。所以这三个点天然就在同一个平面上。
这个共面关系,用数学语言表达出来,就是对极约束。它的核心公式是:
p₂ᵀ * F * p₁ = 0
其中,F就是我们要讲的基础矩阵。这个公式告诉我们:左图上的任意一个点p₁,它在右图上的匹配点p₂,一定位于一条特定的直线上。这条线叫做极线。
核心理解:对极约束的本质,就是把一个2D点的匹配搜索范围,从整张图像缩小到一条线上。这大大降低了特征匹配的计算量。
我在项目中遇到过一个问题:当相机纯旋转时,对极约束会失效。因为此时O₁和O₂重合,极平面退化成一条线,基础矩阵变成零矩阵。嗯,这里要注意,纯旋转情况下,我们需要用单应矩阵来处理。
4.2 本质矩阵与基础矩阵:一对“双胞胎”
接下来我们聊聊这两个矩阵。很多初学者容易搞混,我当年也花了点时间才理清楚。
本质矩阵E:它只包含相机的旋转R和平移t信息,是在归一化相机坐标系下定义的。说白了,它假设相机内参是单位矩阵。
基础矩阵F:它包含了相机的内参K,以及R和t。是在像素坐标系下定义的。
它们之间的关系很简单:
F = K⁻ᵀ * E * K⁻¹
其中K是相机内参矩阵。你看,本质矩阵E加上内参K,就变成了基础矩阵F。
| 矩阵 | 坐标系 | 自由度 | 包含信息 |
|---|---|---|---|
| 本质矩阵E | 归一化相机坐标系 | 5 | R, t(尺度模糊) |
| 基础矩阵F | 像素坐标系 | 7 | K, R, t(尺度模糊) |
我个人习惯是先求解基础矩阵F,然后利用已知的内参K反算出本质矩阵E,最后从E中分解出R和t。这样流程比较清晰。
避坑指南:我曾经在分解本质矩阵E时,得到四个可能的R、t解。只有通过三角化验证,才能选出正确的那个。千万别忘了这一步!
4.3 八点法求解:最经典的线性方法
好了,理论讲完了,怎么实际求解基础矩阵F呢?最经典的方法就是八点法。
为什么叫八点法?因为基础矩阵F有7个自由度(3x3矩阵,减去一个尺度因子,再减去一个秩为2的约束),理论上7个点就够了。但8个点可以构成一个线性方程组,求解起来更方便。
具体步骤是这样的:
- 从两张图像中,找到至少8对匹配点 (p₁, p₂)。
- 构建线性方程组 Af = 0,其中f是F的9个元素组成的向量。
- 用SVD(奇异值分解)求解f。
- 将得到的F矩阵强制满足秩为2的约束(即最小奇异值置零)。
代码实现起来其实很简洁:
// 伪代码:八点法求解基础矩阵
Matrix3d computeFundamentalMatrix(vector<Point2d> pts1, vector<Point2d> pts2) {
int N = pts1.size();
MatrixXd A(N, 9);
for (int i = 0; i < N; i++) {
double x1 = pts1[i].x, y1 = pts1[i].y;
double x2 = pts2[i].x, y2 = pts2[i].y;
A.row(i) << x2*x1, x2*y1, x2, y2*x1, y2*y1, y2, x1, y1, 1;
}
// SVD分解
JacobiSVD<MatrixXd> svd(A, ComputeThinU | ComputeThinV);
VectorXd f = svd.matrixV().col(8);
// 重塑为3x3矩阵
Matrix3d F;
F << f(0), f(1), f(2),
f(3), f(4), f(5),
f(6), f(7), f(8);
// 强制秩为2
JacobiSVD<Matrix3d> svdF(F, ComputeFullU | ComputeFullV);
Vector3d singularValues = svdF.singularValues();
singularValues(2) = 0; // 最小奇异值置零
F = svdF.matrixU() * singularValues.asDiagonal() * svdF.matrixV().transpose();
return F;
}
注意:八点法对噪声非常敏感。在实际使用前,一定要对匹配点坐标进行归一化处理。我建议使用OpenCV的normalize函数,或者自己实现一个平移缩放变换。否则,结果可能会差得离谱。
4.4 单应矩阵:平面场景的“救星”
最后,我们聊聊单应矩阵H。它描述的是同一平面在两个相机视图之间的投影变换关系。
什么时候用单应矩阵?
- 场景中的特征点都位于同一个平面上(比如地面、墙面)。
- 相机发生纯旋转(此时对极约束失效)。
单应矩阵的约束是:
p₂ = H * p₁
注意,这里是等式,不是零约束。所以单应矩阵的求解比基础矩阵更直接,只需要4对匹配点(不共线)即可。
我记得有一次做无人机视觉导航,地面是平坦的跑道。用基础矩阵算出来的运动一塌糊涂,换成单应矩阵后,结果瞬间就对了。这就是选对工具的重要性。
单应矩阵的求解方法:
- 构建线性方程组 Ah = 0,其中h是H的9个元素。
- 用SVD求解。
- 从H中分解出R和t(需要已知内参K)。
从H分解R和t比从E分解要复杂一些,因为H有8个自由度,包含了更多的几何信息。具体分解方法可以参考Faugeras的论文,这里不展开。
总结一下:
- 场景是非平面且相机有平移 → 用基础矩阵/本质矩阵。
- 场景是平面或相机纯旋转 → 用单应矩阵。
好了,这一章的内容就到这里。对极几何是视觉SLAM的“硬骨头”,但啃下来之后,你会发现后面的内容都变得顺理成章了。