欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ORBSLAM的ORB特征提取

程序员文章站 2022-03-13 23:33:32
...

ORBSLAM中的主要使用了ORB特征,也就是FAST特征+BRIEF描述子的组合,具体这两种方法就不详细介绍了,这里主要说一下每个特征对应的描述子在ORBSLAM中的维护方式;

首先需要说明的是每个frame都有自己对应的找到的feature,在进行特征提取前会先初始化一个Extractor,也就是:

void Frame::ExtractORB(int flag, const cv::Mat &im)
{
    if(flag==0)
        (*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors);
    else
        (*mpORBextractorRight)(im,cv::Mat(),mvKeysRight,mDescriptorsRight);
}

第二步:初始化完成后就是提取,这里的入口函数是:
 

// 计算ORB特征,_keypoints中的坐标与scale已经无关
/* _image: 原图
 * mvImagePyramid:ComputePyramid() 的结果,不同大小的图片
 * allKeypoints:对应mvImagePyramid中每一层图像的特征点,是与金字塔图像坐标对应的,就是在原图像的基础上经过缩放的
 * _keypoints:对allKeypoints中每一个点找到对应的描述子后,再进行scale,
 *             作为后面mvkeys;
 */
void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,OutputArray _descriptors)

_keypoints 和_descriptors也就是我们最后需要的变量;即将一张图片用一个vector的特征点以及这些特征点对应的描述子组成的Mat 来表示;具体由是如何推算的,可以用下面的步骤表示:
 

1.  构建高斯金字塔:

ComputePyramid(image);

 第一层为原图像,往上依次递减,先用高斯函数进行模糊,再缩放,   将图像保存至mvImagePyramid​[]中

   因此这个函数主要输出的变量就是 std::vector<cv::Mat> mvImagePyramid;

2. 计算每层图像的兴趣点

void ORBextractor::ComputeKeyPointsOctTree(vector<vector<KeyPoint> >& allKeypoints)

这个函数主要输出的是vector < vector<KeyPoint> > allKeypoints; 最外层的vector对应金字塔的每张图片,内层vector对应当前图像中的所有特征点;因此这个变量中存储的特征点的坐标是与金字塔图像对应的;

主要步骤为:

1. 对金字塔图像进行遍历,将图像分割成nCols × nRows 个30*30的小块cell
   for (int level = 0; level < nlevels; ++level)
   const int nCols = width/W;
   const int nRows = height/W; // W=30 
2. 对每个小块进行FAST兴趣点能提取,并将提取到的特征点保存在这个cell对应的vKeysCell中
   vector<cv::KeyPoint> vKeysCell;
   FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                     vKeysCell,iniThFAST,true);
3. 将各个小块对应的vKeysCell进行整合,放入到vector<cv::KeyPoint> vToDistributeKeys 中;
4. 对vToDistributeKeys​中的特征点进行四叉树节点分配D;
   vector<cv::KeyPoint> ORBextractor::DistributeOctTree(
            const vector<cv::KeyPoint>& vToDistributeKeys, 
            const int &minX,  const int &maxX, const int &minY, const int &maxY, 
            const int &N, const int &level)

(主要思路是该区域作为均分为n大块,然后作为n个根节点,遍历每一个节点,如果该节点有多于一个的特征点keypoints,则将该节点所处的区域均匀划分成四份,依次类推,直到每个子节点区域只含有一个keypoints,如果该节点区域没有kp则会被erase掉)

ORBSLAM的ORB特征提取

这里详细说一下具体的Keypoint计算过程,也就是特征点是怎么由坐标变成一个关键点类的;

特征点KeyPoint的构造过程:

KeyPoint的构造是首先在FAST中完成的:

FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),vKeysCell,iniThFAST,true);
其中mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX) 是当前金字塔层的某个图像块;
vKeysCell  用于保存提取的特征点 vector<cv::KeyPoint>
iniThFAST  提取特征时用到的阈值;
true为是否使用极大值抑制;

而具体的FAST特征提取时使用的是9_16的fast特征: 

FASTX(_img, keypoints, threshold, nonmax_suppression, FastFeatureDetector::TYPE_9_16);

具体可参考opencv源码中/modules/features2d/src/fast.cpp文件;

在经过一系列的判断,确定当前点为特征点后,对当前坐标进行了KeyPoint类构造:

keypoints.push_back(KeyPoint((float)j, (float)(i-1), 7.f, -1, (float)score));   

构造时,Keypoint的角度初始值,也就是倒数第二个变量设置为-1;详见OpenCV对KeyPOint类的构造:

CV_WRAP KeyPoint() : pt(0,0), size(0), angle(-1), response(0), octave(0), class_id(-1) {}

经过ComputeKeyPointsOctTree​()中的FAST特征提取后,所有特征点的xy坐标,octave都已经确定,接下来就是计算每一个特征点对应的角度,也就是方向值:

for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
//计算当前keypoint的角度
static void computeOrientation(const Mat& image, vector<KeyPoint>& keypoints, const vector<int>& umax)
{
    for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),
         keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)
    {
        keypoint->angle = IC_Angle(image, keypoint->pt, umax);
    }
}

参考: 

How to distribute keypoints on every level image: https://zhuanlan.zhihu.com/p/61738607

https://blog.csdn.net/yang843061497/article/details/38553765

相关标签: SLAM ORB FAST