ORB特征提取原理:ORB_SLAM2如何实现超快速特征匹配?

ORB特征提取原理:ORB_SLAM2如何实现超快速特征匹配?

【免费下载链接】ORB_SLAM2 Real-Time SLAM for Monocular, Stereo and RGB-D Cameras, with Loop Detection and Relocalization Capabilities 【免费下载链接】ORB_SLAM2 项目地址: https://gitcode.com/gh_mirrors/or/ORB_SLAM2

引言:SLAM系统中的特征提取瓶颈

在实时同步定位与地图构建(Simultaneous Localization and Mapping, SLAM)系统中,特征提取与匹配的效率直接决定了系统的实时性。传统视觉特征如SIFT(尺度不变特征变换)虽然具有良好的尺度和旋转不变性,但其计算复杂度高,难以满足实时性要求。ORB(Oriented FAST and Rotated BRIEF)特征通过结合FAST关键点检测和BRIEF描述子,在保持旋转不变性和尺度不变性的同时,显著提升了计算速度,成为ORB_SLAM2等实时SLAM系统的核心组件。

本文将深入解析ORB_SLAM2中ORB特征提取的实现原理,重点探讨其如何通过图像金字塔构建、关键点检测、方向赋值和描述子生成等步骤,实现超快速特征匹配,并通过代码示例和流程图展示关键算法细节。

ORB特征提取的核心流程

ORB特征提取主要包括以下四个步骤:

  1. 图像金字塔构建:生成多尺度图像,以实现尺度不变性。
  2. 关键点检测:使用FAST算法检测候选关键点,并通过八叉树分配策略保证关键点均匀分布。
  3. 方向赋值:计算关键点的主方向,以实现旋转不变性。
  4. 描述子生成:基于关键点邻域像素灰度比较生成二进制描述子。

mermaid

1. 图像金字塔构建

为实现尺度不变性,ORB_SLAM2首先构建图像金字塔。图像金字塔由不同尺度的图像组成,通过对原始图像进行下采样得到。ORBextractor类的构造函数中,通过scaleFactornlevels参数控制金字塔的尺度和层数。

ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels,
         int _iniThFAST, int _minThFAST):
    nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),
    iniThFAST(_iniThFAST), minThFAST(_minThFAST)
{
    mvScaleFactor.resize(nlevels);
    mvLevelSigma2.resize(nlevels);
    mvScaleFactor[0] = 1.0f;
    mvLevelSigma2[0] = 1.0f;
    for(int i=1; i<nlevels; i++)
    {
        mvScaleFactor[i] = mvScaleFactor[i-1] * scaleFactor;
        mvLevelSigma2[i] = mvScaleFactor[i] * mvScaleFactor[i];
    }
    // ... 其他初始化代码
}

ComputePyramid函数负责生成金字塔的每一层图像:

void ORBextractor::ComputePyramid(cv::Mat image)
{
    for (int level = 0; level < nlevels; ++level)
    {
        float scale = mvInvScaleFactor[level];
        Size sz(cvRound(image.cols * scale), cvRound(image.rows * scale));
        Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2);
        Mat temp(wholeSize, image.type(), Scalar::all(0));
        Mat layer(temp, Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));
        if (level == 0)
            resize(image, layer, sz, 0, 0, INTER_LINEAR);
        else
            resize(mvImagePyramid[level-1], layer, sz, 0, 0, INTER_LINEAR);
        mvImagePyramid.push_back(temp);
    }
}

图像金字塔的各层尺度通过mvScaleFactor数组定义,相邻层的尺度比例为scaleFactor。为避免边界效应,每层图像在边缘添加了EDGE_THRESHOLD个像素的填充。

2. 关键点检测与分布

ORB_SLAM2使用FAST(Features from Accelerated Segment Test)算法检测关键点。FAST算法通过比较候选点周围16个像素的灰度值与中心像素的灰度值,快速判断该点是否为关键点。ORBextractor中提供了两种FAST阈值:iniThFAST(初始阈值)和minThFAST(最小阈值),当使用初始阈值检测不到足够关键点时,会降低阈值至minThFAST

FAST关键点检测

FAST关键点检测在ComputeKeyPointsOldComputeKeyPointsOctTree函数中实现。以下是FAST检测的核心逻辑:

// 伪代码:FAST关键点检测
vector<KeyPoint> detectFAST(const Mat& image, int threshold) {
    vector<KeyPoint> keypoints;
    for (int y = EDGE_THRESHOLD; y < image.rows - EDGE_THRESHOLD; ++y) {
        for (int x = EDGE_THRESHOLD; x < image.cols - EDGE_THRESHOLD; ++x) {
            // 比较中心像素与周围16个像素的灰度值
            if (isFASTCorner(image, x, y, threshold)) {
                keypoints.push_back(KeyPoint(x, y, PATCH_SIZE));
            }
        }
    }
    return keypoints;
}
八叉树关键点分配

为避免关键点聚集,ORB_SLAM2采用八叉树(OctTree)算法对关键点进行均匀分布。DistributeOctTree函数将图像区域递归划分为子区域,直到每个子区域包含的关键点数量不超过预期。

vector<KeyPoint> ORBextractor::DistributeOctTree(const vector<KeyPoint>& vToDistributeKeys, 
    const int &minX, const int &maxX, const int &minY, const int &maxY, 
    const int &nFeatures, const int &level) {
    // 初始化根节点
    list<ExtractorNode> lNodes;
    ExtractorNode rootNode;
    rootNode.UL = Point2i(minX, minY);
    rootNode.UR = Point2i(maxX, minY);
    rootNode.BL = Point2i(minX, maxY);
    rootNode.BR = Point2i(maxX, maxY);
    rootNode.vKeys = vToDistributeKeys;
    lNodes.push_back(rootNode);

    // 递归分割节点
    while (lNodes.size() < nFeatures) {
        ExtractorNode node = lNodes.front();
        lNodes.pop_front();
        if (node.vKeys.size() <= 1) {
            lNodes.push_back(node);
            continue;
        }
        // 分割节点为四个子节点
        ExtractorNode n1, n2, n3, n4;
        node.DivideNode(n1, n2, n3, n4);
        if (n1.vKeys.size() > 0) lNodes.push_back(n1);
        if (n2.vKeys.size() > 0) lNodes.push_back(n2);
        if (n3.vKeys.size() > 0) lNodes.push_back(n3);
        if (n4.vKeys.size() > 0) lNodes.push_back(n4);
    }

    // 从每个节点选择一个关键点
    vector<KeyPoint> vResult;
    for (auto &node : lNodes) {
        if (node.vKeys.empty()) continue;
        // 选择响应值最高的关键点
        vResult.push_back(*max_element(node.vKeys.begin(), node.vKeys.end(),
            [](const KeyPoint& a, const KeyPoint& b) { return a.response < b.response; }));
    }
    return vResult;
}

3. 关键点方向赋值

为实现旋转不变性,ORB特征需要为每个关键点分配一个主方向。ORB_SLAM2通过计算关键点邻域内的灰度质心(Intensity Centroid)来确定方向。

IC_Angle函数计算关键点的方向角:

static float IC_Angle(const Mat& image, Point2f pt, const vector<int> & u_max) {
    int m_01 = 0, m_10 = 0;
    const uchar* center = &image.at<uchar>(cvRound(pt.y), cvRound(pt.x));

    // 计算x方向矩和y方向矩
    for (int u = -HALF_PATCH_SIZE; u <= HALF_PATCH_SIZE; ++u)
        m_10 += u * center[u];

    int step = (int)image.step1();
    for (int v = 1; v <= HALF_PATCH_SIZE; ++v) {
        int v_sum = 0;
        int d = u_max[v];
        for (int u = -d; u <= d; ++u) {
            int val_plus = center[u + v*step], val_minus = center[u - v*step];
            v_sum += (val_plus - val_minus);
            m_10 += u * (val_plus + val_minus);
        }
        m_01 += v * v_sum;
    }

    // 返回方向角(弧度)
    return fastAtan2((float)m_01, (float)m_10);
}

方向角计算基于图像矩:$m_{10} = \sum xI(x,y)$,$m_{01} = \sum yI(x,y)$,方向角$\theta = \arctan2(m_{01}, m_{10})$。

4. BRIEF描述子生成

ORB描述子基于BRIEF(Binary Robust Independent Elementary Features)算法改进而来,具有旋转不变性。BRIEF描述子通过比较关键点邻域内一对像素的灰度值生成二进制字符串。

computeOrbDescriptor函数生成ORB描述子:

static void computeOrbDescriptor(const KeyPoint& kpt, const Mat& img, 
    const Point* pattern, uchar* desc) {
    float angle = (float)kpt.angle * factorPI; // 转换为弧度
    float a = (float)cos(angle), b = (float)sin(angle);
    const uchar* center = &img.at<uchar>(cvRound(kpt.pt.y), cvRound(kpt.pt.x));
    const int step = (int)img.step;

    // 对模式点对进行旋转
    #define GET_VALUE(idx) \
        center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + \
               cvRound(pattern[idx].x*a - pattern[idx].y*b)]

    // 生成32字节描述子
    for (int i = 0; i < 32; ++i, pattern += 16) {
        int val = 0;
        val |= (GET_VALUE(0) < GET_VALUE(1)) << 0;
        val |= (GET_VALUE(2) < GET_VALUE(3)) << 1;
        // ... 省略其他14对比较
        val |= (GET_VALUE(14) < GET_VALUE(15)) << 7;
        desc[i] = (uchar)val;
    }
    #undef GET_VALUE
}

ORB_SLAM2使用预定义的31×31像素邻域内的256个点对(bit_pattern_31_数组)生成256位描述子。通过将点对坐标绕关键点中心旋转angle角度,实现旋转不变性。

ORB_SLAM2中的优化策略

ORB_SLAM2在ORB特征提取过程中采用了多种优化策略,以满足实时性要求:

1. 自适应FAST阈值

ORBextractor使用iniThFAST(初始阈值)和minThFAST(最小阈值)控制FAST检测的严格程度。当某一层图像检测到的关键点数量不足时,会降低阈值至minThFAST

2. 关键点均匀分布

通过八叉树分配策略,ORB_SLAM2确保关键点在图像中均匀分布,避免特征聚集,提高匹配稳定性。

3. 预计算模式点对

ORB描述子的点对模式bit_pattern_31_是预定义的,避免了运行时计算开销。该模式通过最大化点对之间的汉明距离进行优化,提高描述子的区分性。

4. 高效内存访问

在描述子生成过程中,通过直接访问图像数据指针(centerstep),减少OpenCV API调用开销,提高内存访问效率。

ORB特征匹配性能分析

ORB特征的二进制描述子使其匹配速度显著优于SIFT等浮点型描述子。ORB_SLAM2中使用汉明距离(Hamming Distance)度量两个描述子的相似度,汉明距离可通过位运算快速计算。

// 汉明距离计算示例
int HammingDistance(const uchar* a, const uchar* b) {
    int dist = 0;
    for (int i = 0; i < 32; ++i) {
        dist += __builtin_popcount(a[i] ^ b[i]);
    }
    return dist;
}

__builtin_popcount指令利用CPU的位计数功能,可在一个时钟周期内计算一个字节中1的个数,显著加速汉明距离计算。

总结与展望

ORB_SLAM2通过图像金字塔、FAST关键点检测、灰度质心方向赋值和旋转BRIEF描述子等技术,实现了高效、稳定的ORB特征提取。其优化策略使其能够在普通CPU上实现实时SLAM,为移动机器人、增强现实等应用提供了强大的视觉前端。

未来,ORB特征的改进方向可能包括:

  1. 深度学习辅助的特征提取,进一步提高特征的区分性。
  2. 动态阈值调整策略,适应复杂光照变化。
  3. 稀疏化描述子,减少存储和匹配开销。

ORB_SLAM2的ORB特征提取模块代码结构清晰,算法优化到位,为理解和实现实时视觉特征提取提供了优秀的参考范例。

【免费下载链接】ORB_SLAM2 Real-Time SLAM for Monocular, Stereo and RGB-D Cameras, with Loop Detection and Relocalization Capabilities 【免费下载链接】ORB_SLAM2 项目地址: https://gitcode.com/gh_mirrors/or/ORB_SLAM2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值