ORBSLAM的ORB特征提取

故事扮演 提交于 2020-01-19 01:39:55

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掉)

这里详细说一下具体的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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!