图像金字塔
# 本部分代码主要位置:src/ORBextractor.cc
在上一讲中,我们大概了解了tracking流程,那么其中最重要的流程,就是ORB特征提取器的建立,其中,给函数传入的参数包括:nFeatures:要提取的特征点数量;fScaleFactor:金字塔层次的尺度因子;nLevels:金字塔的层数;fIniThFAST:FAST角点检测的初始阈值;fMinThFAST:FAST角点检测的最小阈值,这几个值都是在提前设置过的配置文件中设置好的。
mpORBextractorLeft = new ORBextractor(nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);
1. 金字塔参数初始化
我们进入ORB特征提取器的函数中,首先是对金字塔的相关初始化:
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];
}
mvInvScaleFactor.resize(nlevels);
mvInvLevelSigma2.resize(nlevels);
for (int i = 0; i < nlevels; i++) {
mvInvScaleFactor[i] = 1.0f / mvScaleFactor[i];
mvInvLevelSigma2[i] = 1.0f / mvLevelSigma2[i];
}
mvImagePyramid.resize(nlevels);
初始化中,mvImagePyramid是每一层金字塔的图像,mvScaleFactor是尺度因子的数组,对应mvLevelSigma2是尺度的平方数组,设置第0层就是最底层了,那么逐层往上,这个尺度因子逐步变大;为了简便运算,我们进而设置了尺度因子的逆数组,即尺度因子的倒数:mvInvScaleFactor与mvInvLevelSigma2,下面是一个图像金字塔的示意图象:
2. 计算特征点数量:
先附上相应的代码:
mnFeaturesPerLevel.resize(nlevels);
float factor = 1.0f / scaleFactor;
float nDesiredFeaturesPerScale = nfeatures * (1 - factor) / (1 - (float)pow((double)factor, (double)nlevels));
int sumFeatures = 0;
for (int level = 0; level < nlevels - 1; level++) {
mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);
sumFeatures += mnFeaturesPerLevel[level];
nDesiredFeaturesPerScale *= factor;
}
mnFeaturesPerLevel[nlevels - 1] = std::max(nfeatures - sumFeatures, 0);
nDesiredFeaturesPerScale表示在每个尺度上期望分配的特征点数量,这里是通过几何级数分配特征点数量的,想必在这里的代码中最难理解的也是这个部分,下面是一个大致思路:
如何分配每层的特征点数目?
根据金字塔图我们可以知道,金字塔层级越高,图像面积越小,对应的特征点数量也就越少。那么我们就想能否将图像特征点按照面积均摊到金字塔每层的图像上?
假设第0层宽为W,长为L,缩放因子为s^2(这个其实就是之前已经定义过的factor),则金字塔总面积为:
其中,C为最底层面积,则可以算出单位面积特征点数量以及第0层数量:
进而,上一层的特征点数量上下一层的基础上乘缩放因子即可。同时,我们对已有的特征点进行计数,那么最顶层的特征点数量则是总特征点数量减去已有特征点数量。
3. 描述子初始化
对于一个特征点,判断他是否与其他特征点相匹配的关键就是比较他周围的描述子,ORB-SLAM2中ORB特征由角点以及描述子组成,分别使用的是FAST角点(关键点)以及BRIEF描述子。角电的思想大概是如果一个像素与周围领域的像素相差较大,则他可能是角电;而描述子位于关键点周围,一般是二进制数,表示明暗关系。在ORB-SLAM2中,描述子由固定的256个点组成,最终得到128维的由0,1组成的向量,而这256个点又是用512个数表示,这里体现了其初始化过程,big_pattern_31_即是固定的描述子数组,这里将其最终存储到了pattern0中。
const int npoints = 512;
const Point* pattern0 = (const Point*)bit_pattern_31_;
std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));
4. 初始化用于方向计算的辅助数组
这里的初始化主要是为了后续建立灰度质心圆并满足旋转不变性做准备。这里相当于初始化了一个圆,并初始化了其u,v坐标,每一个v有一个对应的u值。
umax.resize(HALF_PATCH_SIZE + 1);
int v, v0, vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1);
int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);
const double hp2 = HALF_PATCH_SIZE * HALF_PATCH_SIZE;
for (v = 0; v <= vmax; ++v)
umax[v] = cvRound(sqrt(hp2 - v * v));
for (v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v) {
while (umax[v0] == umax[v0 + 1])
++v0;
umax[v] = v0;
++v0;
}
上图就是uv对应的圆的大致示意。