ORBSLAM的ORB特征提取
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
上一篇: pickle库的使用详解