3. 双目立体视觉基础:对极几何、视差与深度的关系、立体匹配的基本流程
说到深度估计,双目立体视觉是我个人觉得最直观、也最容易上手的方法。你想想看,人类天生就有两只眼睛,大脑就是靠这两只眼睛看到的微小差异来判断距离的。计算机视觉里,我们做的就是模拟这个过程。
这一节,我会带你从最核心的三个概念入手:对极几何、视差与深度的关系,以及立体匹配的基本流程。搞懂了这些,你就能理解为什么两张图片能算出三维深度。
3.1 对极几何:两张图之间的约束关系
先问一个问题:给定左图上的一点,怎么在右图上找到对应的点?
最笨的办法是在整张右图上搜索。但这样太慢了,而且容易出错。对极几何告诉我们:左图上的一个点,在右图上的对应点一定在某一条直线上。这条线就叫「极线」。
核心概念:对极几何描述的是两个相机视图之间的几何关系。它不依赖场景内容,只依赖相机内参和两相机之间的相对位姿。
我刚开始做双目项目时,总觉得对极几何是纯理论,没什么用。直到有一次,我需要在嵌入式设备上做实时深度估计,搜索范围太大导致帧率上不去。后来我用了极线约束,把搜索从二维降到了一维,速度直接提升了十几倍。嗯,从那以后我再也不敢小看它了。
3.1.1 基本元素
- 基线:两个相机光心的连线
- 极平面:空间点与两个光心构成的平面
- 极线:极平面与成像平面的交线
- 极点:基线与成像平面的交点
说白了,对极几何就是告诉我们:左图上的点,它的匹配点一定在右图的某条极线上。反过来也一样。这个约束叫「极线约束」。
3.1.2 本质矩阵与基础矩阵
实际工程中,我们通常用矩阵来表达这种约束关系:
- 本质矩阵 E:描述两个相机在归一化坐标系下的对极几何关系。它只包含旋转和平移信息。
- 基础矩阵 F:描述两个相机在像素坐标系下的对极几何关系。它包含了内参和外参。
公式很简单:
// 极线约束公式
// 左图点 p1 和右图点 p2 满足:
p2^T * F * p1 = 0
// 如果相机已标定,可以用本质矩阵:
p2^T * E * p1 = 0
我的经验:实际项目中,我建议先做立体校正(Stereo Rectification),把两张图变成「行对齐」的状态。这样极线就变成了水平线,搜索时只需要在同一行上找匹配点。省事多了。
3.2 视差与深度的关系
好,现在假设我们已经找到了左右图上的匹配点。接下来怎么算深度?
这里有个非常直观的几何关系。你看,当物体离相机近时,它在左右图上的位置差异大;离得远时,差异小。这个差异就是「视差」。
3.2.1 三角测量原理
我画个图你就明白了:
从图上你能看到:
- 空间点 P 在左图成像为 x₁,在右图成像为 x₂
- 视差 d = x₁ - x₂(注意:这里 x₁ 和 x₂ 是像素坐标)
- 深度 Z 与视差 d 成反比
3.2.2 深度计算公式
公式其实很简单:
Z = (f * B) / d
其中:
Z = 深度(单位:毫米/米)
f = 相机焦距(单位:像素)
B = 基线长度(单位:毫米)
d = 视差(单位:像素)
注意:这个公式成立的前提是两张图已经做了立体校正,即左右图的行完全对齐。如果没有校正,公式会更复杂,需要用到基础矩阵。
我曾经在一个项目中,直接用原始图像算深度,结果出来的深度图全是扭曲的。后来才发现是忘了做立体校正。嗯,这个坑我替你们踩过了。
3.2.3 视差与深度的关系曲线
| 视差(像素) | 深度(米) | 说明 |
|---|---|---|
| 1 | 100.0 | 远处物体,视差小 |
| 5 | 20.0 | 中等距离 |
| 10 | 10.0 | 常见场景 |
| 50 | 2.0 | 近处物体 |
| 100 | 1.0 | 非常近,视差大 |
(假设 f = 1000 像素,B = 0.1 米)
你看,视差和深度是反比关系。这意味着:近处的物体深度精度高,远处的物体深度精度低。这是双目视觉的固有限制。
3.3 立体匹配的基本流程
好,现在理论讲完了。我们来看看实际工程中怎么做。立体匹配,说白了就是「找对应点」的过程。
3.3.1 整体流程
我习惯把立体匹配分成四个步骤:
- 图像预处理:去噪、增强对比度、直方图均衡化
- 立体校正:让左右图行对齐,极线变成水平线
- 匹配代价计算:计算每个像素在不同视差下的相似度
- 视差优化:从代价中选出最优视差,做平滑和后处理
下面这张图能帮你理清思路:
3.3.2 匹配代价计算
这一步是核心。我们要判断左图的一个像素和右图的一个像素是不是同一个点。常用的方法有:
- SAD(绝对差和):计算窗口内像素差的绝对值之和。简单快速,但对光照敏感。
- SSD(平方差和):类似SAD,但用平方放大差异。对噪声更敏感。
- NCC(归一化互相关):对光照变化不敏感,但计算量大。
- Census变换:用局部二值模式描述纹理,鲁棒性好。我项目中常用这个。
// SAD 匹配代价计算示例
// 左图窗口 L,右图窗口 R,窗口大小 w
float computeSAD(Image L, Image R, int u, int v, int d, int w) {
float cost = 0.0;
int half = w / 2;
for (int i = -half; i <= half; i++) {
for (int j = -half; j <= half; j++) {
float l_val = L.getPixel(u + j, v + i);
float r_val = R.getPixel(u + j - d, v + i); // 注意:视差 d 导致水平偏移
cost += fabs(l_val - r_val);
}
}
return cost;
}
我的建议:如果场景光照变化大,别用SAD。我试过在户外用SAD做双目匹配,结果一塌糊涂。换成Census变换后,效果好了很多。
3.3.3 视差优化
计算完代价后,我们得到的是一个「代价立方体」。接下来要从中选出最优视差。常用的方法有:
- 赢家通吃(WTA):直接选代价最小的视差。简单,但噪声大。
- 动态规划(DP):在极线上做全局优化。效果好,但有条纹伪影。
- 半全局匹配(SGM):在多个方向做DP,然后融合。这是目前工程中最常用的方法。
- 图割(Graph Cut):全局优化,效果好但慢。
我个人最推荐SGM。它在精度和速度之间取得了很好的平衡。我在多个项目中都用它,效果稳定。
3.3.4 后处理
最后一步,对得到的视差图做后处理:
- 左右一致性检查:从左到右和从右到左的视差应该一致。不一致的就是遮挡点,需要剔除。
- 中值滤波:去除孤立噪声点。
- 亚像素插值:用二次曲线拟合,得到亚像素精度的视差。
避坑指南:我曾经在项目中忽略了左右一致性检查,结果深度图上全是遮挡区域的错误值。后来加上这个检查,虽然丢掉了一些像素,但剩下的都是可靠的。记住:宁可少,不可错。
3.4 小结
这一节我们讲了三个核心内容:
- 对极几何:用极线约束把二维搜索降为一维
- 视差与深度:Z = fB / d,反比关系,近处精度高
- 立体匹配流程:预处理 → 校正 → 代价计算 → 优化 → 后处理
搞懂了这些,你就掌握了双目立体视觉的骨架。下一节我们会深入SGM算法,看看它到底是怎么做到又快又准的。
公众号:蓝海资料掘金营,微信deep3321