第四章:特征点提取与匹配

各位同学,今天我们来聊聊单目视觉SLAM里最基础、也最核心的一环——特征点提取与匹配。说实话,我做了这么多年视觉SLAM,每次调试新算法,第一步就是看特征点提取得怎么样。这一步要是没做好,后面的位姿估计、建图全是空中楼阁。

4.1 为什么需要特征点?

你想想看,相机拍到的是一堆像素。两帧图像之间,怎么知道哪些像素对应同一个空间点?直接比像素值?不行,光照一变就全乱了。所以我们需要找到一些“特殊”的点——特征点。

特征点要满足两个条件:可重复性可区分性。说白了,就是换个角度、换个光照,还能找到它;而且它得跟周围长得不一样,不然没法匹配。

核心概念:特征点 = 关键点(位置)+ 描述子(特征向量)

4.2 ORB特征:效率之王

ORB是我个人用得最多的特征。为什么?因为它快。在嵌入式设备上,SIFT跑不动,SURF也够呛,但ORB可以实时运行。

ORB = oFAST(方向FAST)+ rBRIEF(旋转BRIEF)。

4.2.1 FAST角点检测

FAST的思路很简单:一个像素,跟它周围一圈16个像素比。如果连续N个点都比它亮或都比它暗,那它就是角点。N通常取9或12。

// FAST角点检测伪代码
for each pixel p:
    int brighter = 0, darker = 0
    for each pixel in circle of 16:
        if pixel > p + threshold: brighter++
        else if pixel < p - threshold: darker++
    if brighter >= 12 or darker >= 12:
        mark p as corner

嗯,这里要注意。FAST没有方向信息,也没有尺度信息。所以ORB做了两件事:构建图像金字塔来获得尺度不变性,计算灰度质心来获得方向。

我的经验:ORB的阈值设置很关键。阈值太小,特征点太多,匹配慢;阈值太大,特征点太少,容易跟丢。我一般从30开始调,根据场景动态调整。

4.2.2 rBRIEF描述子

BRIEF描述子就是在一小块图像里,随机选128对或256对点,比较它们的灰度大小,生成一个二进制串。rBRIEF加上了旋转,让描述子具有旋转不变性。

我曾经在无人机平台上用过ORB,效果不错。但要注意,ORB对光照变化比较敏感。室内还好,一到室外大光比场景,特征点质量会下降。

4.3 SIFT特征:精度之王

SIFT是特征点里的“老大哥”。精度高,但计算量也大。我记得2015年刚入行时,用笔记本跑SIFT,一帧图像要几百毫秒,根本没法实时。

SIFT的核心步骤:

  1. 尺度空间极值检测:用高斯差分金字塔找候选点
  2. 关键点定位:去掉低对比度的点和边缘响应点
  3. 方向分配:统计梯度方向直方图
  4. 生成描述子:16×16窗口,4×4子区域,8方向直方图 → 128维向量

避坑指南:我曾经在纹理很弱的场景(比如白墙)用SIFT,结果提取到的特征点全是噪声。后来加了纹理检测,低于阈值的区域直接跳过,效果好了很多。

SIFT的128维浮点描述子,匹配时用欧氏距离。精度高,但存储和计算开销大。现在很多SLAM系统用SIFT做初始化或回环检测,前端跟踪还是用ORB。

4.4 特征匹配

特征点提出来了,接下来就是匹配。说白了,就是找两帧图像里哪些特征点是同一个。

4.4.1 暴力匹配

最简单的方法:对第一帧的每个特征点,跟第二帧的所有特征点算距离,取最近的那个。

// 暴力匹配伪代码
for each feature in frame1:
    float best_dist = INF
    int best_idx = -1
    for each feature in frame2:
        float dist = compute_distance(desc1, desc2)
        if dist < best_dist:
            best_dist = dist
            best_idx = j
    matches.push_back(best_idx)

暴力匹配的复杂度是O(N²)。特征点少的时候还行,多了就扛不住了。我做过测试,2000个特征点暴力匹配,一帧要30ms,而FLANN只要5ms。

4.4.2 FLANN匹配

FLANN(Fast Library for Approximate Nearest Neighbors)是近似最近邻搜索。它用KD-Tree或K-Means树来加速搜索。

对于ORB这种二进制描述子,FLANN用LSH(局部敏感哈希)效果更好。对于SIFT这种浮点描述子,用KD-Tree。

我的建议:实际项目中,我一般先用FLANN做快速匹配,再用RANSAC剔除误匹配。如果匹配质量不好,再切回暴力匹配做验证。

4.5 RANSAC剔除误匹配

匹配完你会发现,里面混了很多“假匹配”。比如两个不同的点,因为描述子相似被匹配上了。这时候就需要RANSAC(随机采样一致性)来清理。

RANSAC的核心思想:随机选一小撮匹配点,算出一个模型(比如单应矩阵或基础矩阵),然后看其他点符不符合这个模型。符合的多的,就是内点;不符合的,就是外点。

// RANSAC剔除误匹配伪代码
int best_inliers = 0
Matrix best_H

for iter in range(max_iterations):
    // 随机选4对匹配点
    sample = random_sample(matches, 4)
    // 计算单应矩阵
    H = compute_homography(sample)
    // 统计内点
    int inliers = 0
    for each match in matches:
        error = compute_reprojection_error(match, H)
        if error < threshold:
            inliers++
    // 更新最优结果
    if inliers > best_inliers:
        best_inliers = inliers
        best_H = H

我曾经踩过的坑:RANSAC的迭代次数设得太少,结果模型没收敛。后来我根据内点比例动态调整迭代次数,效果好多了。公式是:iterations = log(1-p) / log(1-inlier_ratio^k),其中p是期望成功率,k是采样点数。

4.6 本章知识体系

下面这张图总结了特征点提取与匹配的完整流程:

特征点提取与匹配 · 知识体系 输入图像序列 特征提取 ORB特征 oFAST + rBRIEF SIFT特征 DoG + 128维描述子 其他特征 SURF / BRISK / AKAZE 特征匹配 暴力匹配 FLANN匹配 RANSAC剔除误匹配

从图中可以看到,整个流程是串行的:输入图像 → 特征提取 → 特征匹配 → RANSAC剔除。每一步都有多种选择,具体用哪个,取决于你的应用场景。

4.7 实际项目中的选择建议

场景 推荐特征 匹配方法 说明
手机AR/VR ORB FLANN 实时性要求高,功耗敏感
无人机SLAM ORB 暴力匹配 特征点少,暴力匹配够用
回环检测 SIFT FLANN 精度优先,可以离线处理
弱纹理场景 SIFT 暴力匹配 特征点少,需要高精度
嵌入式设备 ORB FLANN 计算资源有限

我的经验总结:如果你刚开始做SLAM,先用ORB+暴力匹配+RANSAC跑通流程。等理解了整个系统,再根据瓶颈去优化。别一上来就追求完美,先跑起来再说。

好了,这一章的内容就到这里。特征点提取与匹配是SLAM的基石,一定要亲手写代码跑一遍,才能真正理解。下一章我们会讲光流法,那是另一种跟踪思路,很有意思。


公众号:蓝海资料掘金营,微信deep3321