orbslam源码理论分析1—特征点提取

1.读取待处理图片并灰度化

orbslam源码理论分析1—特征点提取  orbslam源码理论分析1—特征点提取
       原图            灰度化图像(第0层)

2.利用图像金字塔将灰度图尺度化为若干层

  程序中为8层金字塔,这里只展示前五层,原灰度图为第0层,则
orbslam源码理论分析1—特征点提取    orbslam源码理论分析1—特征点提取
      第1层              第2层
orbslam源码理论分析1—特征点提取     orbslam源码理论分析1—特征点提取
     第3层              第4层
  具体实现过程,以从第0层到第1层为例讲解。先根据第1层的尺度因数确定该层图像的size。然后将第0层图像缩放到这个size大小,缩放方式采用的是双线性差值法。然后扩充该缩放图像的边缘,程序中将图像四周各扩充了16个像素点,扩充方式为对称法(以图像扩充前最边缘像素为轴,对称扩充)。
  程序中的图像金字塔为8层,对每层都进行如是操作,最后将8层金字塔图像矩阵存储到一个vector<cv::Mat>容器mvMaskPyramid中。

3.特征点提取

  程序中将图像尺度化为8层图像金字塔,首先确定“期望提取到的特征点总数量(nfeatures)”是多少,然后以一定的规则确定“各层期望提取到的特征点数量(mnFeaturesPerLevel [level])”是多少,Level取值范围为0-7,最后对每层分别进行特征点提取。以第0层为例(nDesiredFeatures = mnFeaturesPerLevel [0]),讲解此过程。
  将第0层图像分割成若干图像网格区域,对每个网格区域分别进行特征点提取。那么问题来了,若干个区域是多少区域?怎么划分呢?下面给出划分依据。
  首先,你自己要确定“你期望每个网格区域可以提取出多少(nfeaturesCell)个特征点”,然后根据公式nCells = nDesiredFeatures / nfeaturesCell,确定出网格区域数量。然后根据图像的行列之比,确定出nCells个网格区域分成了多少行(levelRows)、多少列(levelCols)。最后你可以遍历每个网格区域了。
  下面假设网格区域为7行7列(7 ×\times 7),讲解一下程序具体遍历过程,7 ×\times 7网格区域如下图所示。
           orbslam源码理论分析1—特征点提取
               7 ×\times 7网格区域
  中间的点就是此网格区域提取到的特征点,红色的框框为遍历每个网格时实际遍历的图像像素点区域的大小。为什么实际遍历的区域比每个网格区域要大呢?大多少合适呢?下面给出解释。
  ORB特征点提取时,会用到FAST角点提取算法,此算法在判断一个像素点是否为FAST角点时,会选取一个以此像素点为圆心,半径为3的圆。试想一下如果网格区域的边缘上有FAST角点,我们要提取到此点,就必须扩充遍历的区域大小。因为FAST角点圆的半径为3,所以将遍历区域的每个边缘都扩充3个像素最为合适。 
  遍历完所有的网格区域后,得到了“本层图像上实际提取到的所有关键点”。那么问题来了,“本层图像上实际提取到的所有关键点数量(nrealFeatures)”不一定等于“期望提取到的特征点总数量(nDesiredFeatures)”。怎么办?如何解决呢?不慌,下面讲解程序是如何避免这个问题的。
  设每个网格区域实际提取出的特征点数量为nKeys,每个网格区域实际保留的特征点数量为nToRetain[i][j]0 < i < levelRows, 0 < j < levelCols。
      如果nKeys > nfeaturesCell,则 nToRetain[i][j] = nfeaturesCell
      如果nKeys ⩽ nfeaturesCell,则 nToRetain[i][j] = nKeys
                      nToDistribute += nfeaturesCell-nKeys;
  显然,nToDistribute代表了当前保留的特征点总量与期望nDesiredFeatures的差值,这个差值如何填补呢,这就要看那些nKeys > nfeaturesCell的网格区域了。 
  设nKeys > nfeaturesCell的网格区域数量为nbigtofeatures=nCells-nNoMore,设每个nKeys > nfeaturesCell的网格区域的 “新的期望可以提取出的特征点数量”为nNewFeaturesCell(nfeaturesCell+nToDistribute/(nCells-nNoMore)),则
      如果nKeys > nNewFeaturesCell,则 nToRetain[i][j] = nNewFeaturesCell
      如果nKeys ⩽ nNewFeaturesCell,则 nToRetain[i][j] = nKeys
                       nToDistribute += nNewFeaturesCell-nKeys;
如此循环,直到nToDistribute为0或者nbigtofeatures为0。
  好了,到了这里,每个网格区域实际保留的特征点数量nToRetain[i][j],算是拿到手了,但是要在nKeys个特征点中保留哪nToRetain[i][j]个特征点呢?留谁?丢谁?规则怎么制定呢?且看下面分析。 
  根据特征点响应值排序进行筛选。现在得到的特征点像素坐标是在网格区域下的局部像素坐标,将此坐标恢复到该层图像上。注意,这次恢复与下面讲的恢复不是一回事儿。
  对金字塔的8层图像都进行上述操作,提取出所有特征点nfeatures。这时候,我们得到的特征点像素坐标为特征点在各自层面上的像素坐标。我们需要根据各层金字塔尺度因数将特征点像素坐标恢复到原始图像层面上去。这个过程可用下图表示。  
orbslam源码理论分析1—特征点提取
  从图中可以发现少了几个特征点,是因为各层特征点恢复到原图后,有些特征点像素会重合。所以实际上特征点并没有少。只是看起来好像是少了。
  目前为止,我们得到了所有特征点的像素坐标(pt)尺寸(size)层数(octave)信息。但方向角(angle)信息没有得到,所以接下来轮到它了。计算特征点角度会用到灰度质心法,请参考《orbslam源码理论分析0—灰度质心法》这篇文章。 
  特征点会存储在vector<KeyPoint>容器_keypoints中,并且特征点是按照所在层数依次存储的。

4.高斯模糊化

  将8层金字塔图像模糊化,这里以第4层图像为例,展示一下此过程。程序中采用的是7 ×\times 7的高斯核,高斯核在x,y方向上的标准差为2,像素外插策略为对称法。模糊化后的图像为计算描述子所采用的图像。
orbslam源码理论分析1—特征点提取    orbslam源码理论分析1—特征点提取
     第4层          模糊化后第4层

5.计算描述子

  描述子由256个二进制位组成。每一个二进制位由一对像素点像素值比较大小后得到。所以需要256对像素点。程序中选取的是一块31 ×\times 31的方形区域,并在方形区域中作内切圆,特征点在圆心位置,256对像素点在内切圆中选取。如何选取256对像素点呢?根据高斯采样选取256对像素点
  我们以11 ×\times 11的图像块区域为例,展示一下这个过程,中间红色像素代表特征点,其它五种颜色的像素点为高斯采样获取。相同颜色的两个点为一个点对。通过比较这一点对的像素值,可得到描述子的一个二进制位。一共可以得到5个二进制位。那这个描述子就是5位的,比如结果为10110。
           orbslam源码理论分析1—特征点提取
              11 ×\times 11图像块
  为什么在圆形区域内选取描述子点对呢?笔者认为是因为只有选取圆形块,才能保证此特征点的旋转不变性。我们可以想象一下,一个矩形块绕中心旋转任意角度,不能保证所有角度下,旋转前后两个矩形块完全重合。而圆形块绕圆心无论怎样旋转,前后圆形块一定完全重合。这就可以保证,同一特征点在图片发生旋转后,参与计算特征点描述子的那些点对与旋转前完全一样(找到旋转前的那些点对还需要特征点方向角的配合)。
  描述子会存储在cv::Mat矩阵中,并且此矩阵的行(row)与特征点是一一对应的。