整体架构

构造函数进行初始化,传入设定几个重要的成员变量。nfeatures(特征点的个数)、nlevels(构造金字塔的层数)、scaleFactor(金字塔中相邻层图像的比例系数)、iniThFAST(检测 FAST 角点的阈值)、minThFAST(在 iniThFAST 没有检测到角点的前提下,降低的阈值)。

括号运算符对输入的图像进行角点检测。1. ComputePyramid 函数构造金字塔。2. ComputeKeyPointsOctTree 对金字塔图像进行角点检测。3. 计算角点的描述子,输出。

括号运算符 operator()

括号运算符输入图像,并且传入引用参数 _keypoints, _descriptors 用于存储计算得到的特征点及其描述子。

这种设计使得只需要构造一次 ORBextractor 就可以为所有图像生成特征点。

ComputePyramid 函数

我认为 ComputePyramid 函数中调用的 OpenCV 函数 copyMakeBorder 是没有意义的。

当 level == 0 时,或许有用,mvImagePyramid[level] 指向 temp 图像的中间,将 image 复制了进去。

当 level != 0 是,resize 函数就已经完成了工作。

copyMakeBorder 只是对 temp 图像的外围进行了修改,而 temp 图像的外围并不存在于影像金字塔中。

ComputeKeyPointsOctTree 函数

对影像金字塔中的每一层图像进行特征点的计算。具体的计算过程是将影像格网分割为小区域,每一个小区域独立使用 FAST 角点检测。检测完成之后使用 DistributeOctTree 函数对检测得到的所有角点进行筛选,使得角点分布均匀。

划分格网计算

I 确定计算角点的范围:

对影像“裁边”周围 EDGE_THRESHOLD-3 的像素不进行角点检测。

const int minBorderX = EDGE_THRESHOLD-3;
const int minBorderY = minBorderX;
const int maxBorderX = mvImagePyramid[level].cols-EDGE_THRESHOLD+3;
const int maxBorderY = mvImagePyramid[level].rows-EDGE_THRESHOLD+3;

II 确定确定格网的数量:

nCols, nRows 是宽高方向上格网的的数量,wCell, hCell 是每个格网的宽和高。

使用这种方式计算能够使得最后不完整的 Cell 尽可能不会太小。W 是预估 Cell 的宽高。

const float W = 30;

const int nCols = width/W;
const int nRows = height/W;
const int wCell = ceil(width/nCols);
const int hCell = ceil(height/nRows);

如 640*480 的影像,进行裁边之后是 608*448。计算得到:

nCols == 20, wCell == 31;
nRows == 14, hCell == 32;
nCols * wCell == 620;
620 % 31 == 19; // constrast to 608 % 30 == 8
nRows * hCell == 448;

III 循环生成每个 Cell 的角点:

最后计算的 Cell 的宽或高不会小于7。因为 FAST 计算的邻域是直径为7的 BressenHam 圆。

数字7与代码中出现的数字6对应。

最后生成的 FAST 角点存放在 vToDistributeKeys 中,坐标是以 (minBorderX, minBorderY) 为原点(左上)的坐标。

八叉树分配

DistributeOctTree 这个是八叉树分配的意思,但是函数中每次对节点进行分裂都是分裂成4个。

// If more than one point, subdivide
ExtractorNode n1,n2,n3,n4;
lit->DivideNode(n1,n2,n3,n4);

Excuse me,这东西叫 DistributeQuadTree 好不好?毕竟是在平面上划分。

I 水平划分:

将影像进行水平划分,nIni 是水平划分的数量,hX 是水平划分格子的宽度。

const int nIni = round(static_cast<float>(maxX-minX)/(maxY-minY));
const float hX = static_cast<float>(maxX-minX)/nIni;

注:这里有 bug,如果输入的是一张 宽高比 小于 0.5 的图像,nIni 计算得到 0,计算 hX 会报错。

将所有的特征点分配到 lNodes 的 node 中,vpIniNodes 存在的意义只是分配特征点。

对于每个 node 而言,若其只有一个特征点,bNoMore 为 true,表明其不用再继续划分。

II Node 四叉分裂:

进入循环

while(!bFinish)
{
...
}

i. 循环首先遍历 lNodes 中的每一个节点,对节点进行分裂:

ExtractorNode n1,n2,n3,n4;
lit->DivideNode(n1,n2,n3,n4);

分裂成 n1, n2, n3, n4 四个节点,将这四个节点中含有特征点的节点存入 lNodes 的最前面,因为是向后遍历的,不希望一次遍历下来会遍历到前面加进来的节点。如果分裂出的节点中特征点的个数大于1,说明还可以分裂,将特征点数、节点的指针存入 vSizeAndPointerToNode 中,nToExpand 计数器加1,后面会用于判断可分裂的能力。

ii. 在分裂成功之后进行判断

判断如果当前节点数量大于等于需要的特征点数量(N),或者分裂过程并没有增加节点的数量,说明不需要再进行分裂了,bFinish 设置为 true,可以跳出循环。

这个判断好像不是很充分,因为前面的分裂是整体的,只用一个数量参数来判断,有点牵强,有些分裂出的节点可以继续分裂。

如果上面的条件不满足,判断条件

((int)lNodes.size()+nToExpand*3)>N

是否满足。满足表明了再如果所有的节点再进行一次完全分裂(所有节点都能分裂成4个节点),可以满足特征点数量的要求。

vSizeAndPointerToNode 是前面分裂出来的子节点(n1, n2, n3, n4)中可以分裂的节点。按照它们特征点的排序,先从特征点多的开始分裂,分裂的结果继续存储在 lNodes 中。每分裂一个节点都会进行一次判断,如果 lNodes 中的节点数量大于所需要的特征点数量,退出整个 while(!bFinish) 循环。

如果进行了一次分裂,并没有增加节点数量,不玩了,退出整个 while(!bFinish) 循环。

III 取最大响应点:

在前面的工作完成时 lNodes 中节点的数量应该大于所需要的特征点数量 N,如果不是大于,那么很抱歉,也只能这样子了。

取出每一个节点中最大响应的特征点,存储进 vResultKeys 中。

函数返回。

总结

剩下的代码是在 ComputeKeyPointsOctTree 函数中对过滤出的特征点坐标进行调整,调整到整个图像的坐标,计算特征点的方向。

在 operator() 函数中计算特征点的描述子。

ORB_SLAM2 源码阅读 ORB_SLAM2::ORBextractor的更多相关文章

  1. ORB_SLAM2 源码阅读 ORB_SLAM2::Initializer::ComputeF21 (OpenCV 细节)

    ORB_SLAM2 计算 F21 的代码是这样的. cv::Mat Initializer::ComputeF21(const vector<cv::Point2f> &vP1,c ...

  2. ORB_SLAM2 源码阅读 ORB_SLAM2::Initializer

    ORB_SLAM2::Initializer 用于单目情况下的初始化. Initializer 的构造函数中传入第一张影像,这张影像被称作 reference frame(rFrame).在获得第二张 ...

  3. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  4. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  5. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  6. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  7. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  8. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  9. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

随机推荐

  1. 自平衡二叉(查找树/搜索树/排序树) binary search tree

    在计算机科学中,AVL树是最先发明的自平衡二叉查找树.AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An alg ...

  2. PowerDesigner在生成SQL时报错Generation aborted due to errors detected during the verification of the mod

    一.本章节要用到  ODBC连接数据库直接创建表,请先创建连接库的ODBC 请参考  新建  http://www.cnblogs.com/wdw31210/p/7580286.html 二.生成 去 ...

  3. 【模板】ISAP最大流

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...

  4. 安装elasticsearch5.4.1集群和head插件

    这里用的系统版本是CentOS6.6. 192.168.3.56 ES01 192.168.3.49 ES02 192.168.3.57 ES03 1.为三个节点安装java环境 # yum inst ...

  5. 用Gradle命令行编译Android工程

    在Android sdk 目录下的samples/android-21/ 文件夹下,任找一个工程,如果在命令行直接编译 可能会报这种错误:gradle buile.gradle FAILURE: Bu ...

  6. 解决SurfaceView调用setZOrderOnTop(true)遮挡其他控件

    解决SurfaceView调用setZOrderOnTop(true)遮挡其他控件的问题 http://marller.blog.51cto.com/8699646/1762028 FAQ: Surf ...

  7. MVP, MVVM In Android

    MVP, MVVM In Android(新手必看)安卓MVP的理解,看完你就会用mvp了 - zq019的博客 - 博客频道 - CSDN.NEThttp://blog.csdn.net/zq019 ...

  8. android:onClick="xxx"

    一般监听OnClickListener事件,我们都是通过Button button = (Button)findViewById(....); button.setOClickLisener....这 ...

  9. 【整体二分】【P3527】 [POI2011]MET-Meteors

    Description 有 n 个国家,总共占有一个环,环被分成了 m 段,已知有 k 次流星雨会落在这个环上的一些位置.再给出每个国家目标收集多少流星,对每个国家求出第几次流星雨后可以满足这个国家的 ...

  10. HAOI2018游记

    前言 很懒. 太懒了. 不仅懒得写题..连游记都懒得写.. 花点时间填一下坑吧..不过话说我去年的NOI/APIO/CTSC游记也没写.. 省选前 板子好像一早就打完了,没什么可干的. 也不愿意开新题 ...