一直都在基于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); //现将这块的内容初始化为0for (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

详细解读ORBSLAM中的描述子提取过程相关推荐

  1. python方法_详细解读Python中的__init__()方法

    __init__()方法意义重大的原因有两个.第一个原因是在对象生命周期中初始化是最重要的一步:每个对象必须正确初始化后才能正常工作.第二个原因是__init__()参数值可以有多种形式. 因为有很多 ...

  2. 详细解读Maven中pom.xml

    详细解读Maven中pom.xml 一.POM是什么 二.存放位置 三.基本设置 3.1.头信息 3.2.maven的基本信息 3.3.POM之间的关系 3.3.1.依赖关系 3.3.2 继承关系:继 ...

  3. 点云配准新方案!SuperLine3D:激光雷达点云中的自监督线分割和描述子提取(ECCV2022)...

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨汽车人 来源丨自动驾驶之心 论文链接:https://arxiv.org/pdf/2208.01 ...

  4. 特征描述子提取公用接口

    OpenCV封装了一些特征描述子提取算法,使得用户能够解决该问题时候方便使用各种算法.这章用来计算的描述子提取被表达成一个高维空间的向量 vector.所有实现 vector 特征描述子子提取的部分继 ...

  5. 你真的了解大数定律吗?(详细解读机器学习中的大数定律)

    你真的了解大数定律吗?(详细解读机器学习中的大数定律) 前言 大数定律 生活中的预言家们 小数定律的骗局 大数定律的含义 中心极限定理 概率故事-捉羊问题 前言 大数定律也是伯努利定律,是历史上第一个 ...

  6. 超详细讲解ArcGIS中地形指标的提取(附练习数据下载)

    图片 地形指标是最基本的自然地理要素(包括坡度变率.坡向变率.地形起伏度和地面粗糙度这4个基本的地形指标),也是对人类的生产和生活影响最大的自然要素.地形特征制广泛应用于诸多研究和应用领域.地形指标的 ...

  7. python中def _init_是什么意思_详细解读Python中的__init__()方法

    __init__()方法意义重大的原因有两个.第一个原因是在对象生命周期中初始化是最重要的一步:每个对象必须正确初始化后才能正常工作.第二个原因是__init__()参数值可以有多种形式. 因为有很多 ...

  8. FCGF-基于稀疏全卷积网络的点云特征描述子提取(ICCV2019)

    概要 论文: Fully Convolutional Geometric Features 标签: ICCV 2019; feature, match, registration 作者: Christ ...

  9. dubbo 消费者也要暴露端口吗_一文详细解读 Dubbo 中的 http 协议

    (给ImportNew加星标,提高Java技能) 转自:Kirito的技术分享,作者:kiritomoe 太阳红彤彤,花儿五颜六色,各位读者朋友好,又来到了分享 Dubbo 知识点的时候了.说到 Du ...

最新文章

  1. 更轻量的 View Controllers
  2. js取一定范围内的随机整数
  3. Kaggle第一人 | 详细解读2021Google地标识别第一名
  4. ==和equals的简单比较
  5. mysql 语音_MySQL 在各种程序语音的连接字符串(转)
  6. 如何理解subplot绘制不规则子图的参数设置
  7. 腾讯web前端招聘条件汇总
  8. Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
  9. numpy 删除所有为_用Numpy和Pandas工具分析销售数据
  10. Linux(CentOS)下安装Elasticsearch5.0.0
  11. 【perl】LWP module
  12. selinux denied: u:r:untrusted_app:s0:c512,c768报错解决
  13. Python 二次开发 AutoCAD 简介
  14. leetcode 904 滑动窗口
  15. Quartz定时任务自学
  16. KDD2015,Accepted Papers
  17. int和integer
  18. python画位势高度图_气候变化位势高度
  19. 潘多拉无线打印服务器设置,潘多拉(PandoraBox)系统的路由设置
  20. Shopee开店前必看:精品店铺运营五步法则

热门文章

  1. opencv cv.waitKey(60) 0xff 含义和作用
  2. python opencv 利用分水岭算法实现对物体的分割 图文详细注释版 以分割官网提供的硬币为例
  3. 一个demo学会css
  4. python基础系列教程——Python中的编码问题,中文乱码问题
  5. vivado和modelsim联合仿真实现占空比1:15的分频
  6. springmvc中Date类型转换
  7. Libevent 事件循环(1)
  8. Java —— Reflect反射机制
  9. C#之重定向输入输出
  10. 使用zabbix发送邮件的简易设置流程(存档用)