详细解读ORBSLAM中的描述子提取过程

一直都在基于ORBSLAM做一些相关的开发,只知道进来的图片会直接提取出BRIEF描述子,但是都没有详细地看过它具体的提取过程,今天仔细研究了一下代码和相关理论,弄清楚之后感觉神清气爽,部分内容查找有些费劲,所以特此整理出来,希望对需要的人有所帮助。

1. 前言

ORBSLAM中使用的ORB特征是FAST特征和BRIEF描述子的集合,详细的FAST特征的提取过程这里大概说一下,方便后面对描述子的理解;

FAST特征的提取过程:

1.  构建高斯金字塔:ComputePyramid()​

 第一层为原图像,往上依次递减

 先用高斯函数进行模糊,再缩放

    将图像保存至mvImagePyramid​[]中

2. 计算每层图像的兴趣点ComputeKeyPointsOctTree​()

    对金字塔图像进行遍历,将图像分割成cCols X nRows 个30*30的小块

     对每个小块进行FAST兴趣点能提取,并将提取到的特征点保存在vToDistributeKeys​ vector中

     对vToDistributeKeys​中的特征点进行四叉树节点分配DistributeOctTree​()

     这一步将提取出来的所有特征点都保存在变量allKeypoints中,但是这里注意allKeypoints的形式是vector < vector<KeyPoint> >,也就是说并不是所有的特征点都混在一起,外层的vector表示不同的金字塔层,也就是level;

2.  描述子变量的层层嵌套

    2.1 提取描述子的第一步先建立用于保存所有描述子的变量 cv::Mat descriptors; 这里应用了形参_descriptors,为其开辟了一块内存,然后将地址给descriptors,后面对于descriptors的修改其实最终都这里会保存到形参_descriptors所对应的内存中

    int nkeypoints = 0;
    for (int level = 0; level < nlevels; ++level)
        nkeypoints += (int)allKeypoints[level].size();
    if( nkeypoints == 0 )
        _descriptors.release();
    else
    {
     //为当前图像的描述子矩阵开辟了一块 n*32的区域
        _descriptors.create(nkeypoints, 32, CV_8U);
        descriptors = _descriptors.getMat();
    }

这里注意,假如有800个特征点, 那这里的Mat就是800*32, 同时又因为Mat的类型是CV_8U, 也就对应了Mat的每一行其实是32*8 = 256bit; 256这个数字很重要,后面会对应到pattern等;

通过新建测Mat大小也可以看到,descriptors的ROW数量与关键点个数nkieypoints一样,其实就是一行对应一个特征点

   2.2 接下来就是具体的提取过程,不是一下子提取,而是按照图像金字塔来提取,从第0层开始提取,一直到最高层nlevels;

         具体每一层金字塔图像要提取多少个描述子,就要看其对应到这一层有多少个特征点nkeypointsLevel

        a. 第一步先对这一层的金字塔图像做高斯模糊;

        b. 将这一层所要占用的在descriptors中的内存地址放进来,进行描述子的计算;

        Mat desc = descriptors.rowRange(offset, offset + nkeypointsLevel);
        //offset为其他层已经占用的行数,轮到这一层时只能从offset行开始,共提取nkeypointsLevel个
        computeDescriptors(workingMat, keypoints, desc, pattern);

        c. 接下来进入到computeDescriptors()函数中,就开始对单个的特征点进行提取:

descriptors = Mat::zeros((int)keypoints.size(), 32, CV_8UC1); //现将这块的内容初始化为0

for (size_t i = 0; i < keypoints.size(); i++)
        computeOrbDescriptor(keypoints[i], image, &pattern[0], descriptors.ptr((int)i));

       d.  而具体每个特征点所对应的描述子的提取过程其实在computeOrbDescriptor()中,这里一层层的嵌套,看起来有些啰嗦,其实是很整洁的,从一个完整的Mat,到基于Level的块儿,再到基于每个特征点的row,所以,computeOrbDescriptor()中传入的地址是descriptors.ptr((int)i), 也就是当前块描述子的第i行;

       f. 接下来是详细的提取过程,这里传入的几个参数分别为 

          keypoints -- 关键点坐标

          img -- 当前level的被高斯后的图像

          pattern 就是BRIEF的提取模板,保存的是一组一组的坐标值

          desc 用于保存最后提取出的描述子;

3. BRIEF描述子的模板:

这里要先说一下BRIEF的提取步骤:

a. 在特征点周围选择一个patch,在ORBSLAM中patch的size为31*31

b. 在这个patch内通过某种方法挑选出nd个点对, ORBSLAM中为256个;

     这里的某种方法,就是某个pattern,不同的pattern表示在这个patch中选择点的方式不同,ORBSLAM中nd为256, 也就是我们上面说的选择256个点对儿,后面每一个点对儿对应一个0或1的值

c. 对于每一个点对,比如上面提到的q1(8,-3) 和点 q2(9,5), 比较这两点在patch中所对应的坐标的像素值

d. 如果 I(p1) > I(p2) , 则该点对应的值为1, 反之为0;

最后得到了nd×1的描述子;

对应到ORBSLAM中的代码就是 computeOrbDescriptor() 中;

4. pattern的构建

要提取BRIEF描述子,这里需要先明白的一个变量就是pattern,它里面具体保存的内容,以及他的作用,个人觉得与BRIEF相关的其实就是这里(貌似也没有其他地方了[捂脸])

理解pattern之前需要先看一个变量,也就是bit_pattern_31_,也就是那个256*4的变量,看过无数遍,一直假装它并不重要,这里只摘抄两行出来:

static int bit_pattern_31_[256*4] =
{
    8,-3, 9,5/*mean (0), correlation (0)*/,
    4,2, 7,-12/*mean (1.12461e-05), correlation (0.0437584)*/,
   ...
}

这个变量里的数字,在ORBSLAM的代码中总共是256行,代表了256个点对儿,也就是每一个都代表了一对点的坐标,如第一行表示点q1(8,-3) 和点 q2(9,5), 接下来就是要对比这两个坐标对应的像素值的大小

好了,明白了bit_pattern_31_里面保存的点对就可以,接下来在ORBextractor的构造函数中,将这个数组转换成了std::vector<cv::Point> pattern; 也就是一个包含512个Point的变量

const int npoints = 512;
    const Point* pattern0 = (const Point*)bit_pattern_31_;
    // copy [pattern0,pattern0+npoints] 到std::vector<cv::Point> pattern 
    std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));

至此,BRIEF描述子的模板已经保存成功,将要在下面的描述子成型中使用;

5. 描述子成型


center为中心,因为点对的数量为256,也就是512个点,这里将其分成32组,每一组包含16个点,也就是8个点对;

 for (int i = 0; i < 32; ++i, pattern += 16)
{
        int t0, t1, val;
        t0 = GET_VALUE(0); t1 = GET_VALUE(1);  //GET_VALUE用于获取该id对应的坐标出的像素值
        val = t0 < t1;
        t0 = GET_VALUE(2); t1 = GET_VALUE(3);
        val |= (t0 < t1) << 1;
 }

GET_VALUE(idx)的主要作用就是获取坐标点的像素值,这里做的转换就是以特征点的坐标位置为0,0, 其他依次为正负{-15,15},组成31*31的patch;

将上面的for循环完成,也就的到了该特征点对应的描述子,1*256的一个Mat,其中第一个点对q1(8,-3) 和点 q2(9,5) 所计算出的二值放在最前面,然后依次,第二个点对儿,第三个....

最后的结果再一层一层的“传出去”,最后保存到每一个Frame对应的类成员变量mDescriptors 中;

这个变量与保存地图点的keyPOint是对应的,这样就可以保证后面进行匹配是能根据mappoint直接找到对应的描述子,用于后面计算距离;

如有疑问,欢迎交流: wx: baobaohaha_ 欢迎对SLAM有兴趣的小伙伴一起交流学习~~

参考:

BRIEF描述子:https://www.cnblogs.com/ronny/p/4081362.html

ORB算法原理: https://www.cnblogs.com/ronny/p/4083537.html

back_inserter: https://www.jianshu.com/p/6862a79eba0a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值