ORBextractor部分的学习
在ORBextractor中包括定义的两个类
- ExtractorNode
- ORBextractor
在ExtractorNode中,维护的是四叉树的数据结构,后面在关键点均匀化时,会用到其中的DvideNode函数。
今天的主角ORBextractor类
- 枚举类包括harris角点和fast角点的得分
- 类本身的构造函数和析构函数
- 重载的()运算符,进行关键点的提取和描述子的计算
#########这下面几个函数都是内联函数,运行时是直接return某个值################
- 获取关键点所在金字塔的层数
- 获取尺度因子,在整个程序内,每一层之间的尺度因子为1.2
- 获取一个vector的尺度因子
- 获取一个vector的尺度因子的逆
- 获取一个vector的尺度sigma平方
- 获取一个vector的尺度sigma平方的逆
##########下面是protected权限的函数#########
- 计算建立图像金字塔
- 计算关键点是在八叉树的位置
- 将关键点分配到八叉树的每一个节点
- 计算原来的关键点???
在ORBextractor中维护的变量主要有:
节点类中主要是关键点;一个节点中的左上、右上、左下和右下的point;该节点类型的迭代列表;变量bNoMore
ORBextractor中维护的变量主要有:
图像金字塔(mat类型的容器)
##########下面是protected权限的变量#########
图像块(point类的容器)
提取出的关键点的数量
尺度因子
金字塔的层数
初始化的Fast??得分吗?
最小的Fast???
每一层上的关键点的数量
umax 最大u??
一系列的尺度银子
一系列尺度因子的逆
一系列层数的sigma的平方
一系列层数的sigma平方的逆
下面我们来看一下其中的每一个函数是怎么实现的?
在cpp的文件中,首先是定义了三个常整型的变量:
图像块的尺寸为31
图像块一半尺寸为15
边的阈值为19
IC_Angle(const Mat& image, Point2f pt, const vector & u_max)
参数列表:一帧图像、图像上的一个关键点、u_max(int 类型的容器)
将该点作为一个图像块的中心,位于不同列的像素值的权值是不一样的,然后进行行的累加,这样中心点这一行的像素值完成了累加。
const uchar* center = &image.at (cvRound(pt.y), cvRound(pt.x));这里在进行像素值的访问时利用到了指针的地址偏移。
这边利用了u_max中存储了行号,正负v*step可以同时处理两行像素值。
这个函数最后得到的是关键点在这个图像块中的正切角度。
利用了的是u、v方向的镜像对应位置的像素差与行权重的乘积的累加。具体的角度计算涉及到opencv中的fastAtan2()函数。最后返回该点的角度描述。
computeOrbDescriptor()
在函数外定义了PI和角度之间的转换因子
计算ORB的描述子
利用定义的转换因子把关键点的角度制转成弧度制
计算该角度的正弦和余弦值
在函数里宏定义了GET_VALUE()函数
对于特征点的描述,由于传入参数中包含了Point类型的指针,通过pattern中点的像素值来描述这一关键点,每一个pattern就包含了16个点,整个描述子一共有32维,具体如何描述的后面再补充理解,这里的pattern后面还要在调用该函数时看一下代表的具体是???
最后的描述子数组在desc中
ORBextractor()
ORB构造函数
主要包括特征点的数量、尺度因子、金字塔的层数、初始fast和最小fast
金字塔第一层的尺度因子和sigma平方都是1.0
尺度因子随着金字塔的层数越来越大
而sigma平方是当前层数的尺度因子的平方
这两者的逆就是取他们自己的倒数
在程序中,它定义了某一种尺度下理想的关键点的数量
float nDesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)pow((double)factor, (double)nlevels));
计算前几层每一种尺度下理想的关键点数量,放进一个数组中,并进行累加,前几层理想关键点数目累加和原本设置的关键点数量进行比较来确定最上层的关键点数量。
定义的pattern0指针指向的是程序中自带的bit_pattern_31_
且将从头到512位置的内容,拷贝到了pattern中
umax容器的长度是窗口大小一半+1
v, v0 , vmax = 12
vmin = 11
hp2 = 225
在umax中索引越小u越大,最后通过一个循环使umax对称。
computeOrientation()
计算方向
参数列表:一帧图像、关键点和umax
调用IC_Angle()函数计算每一个关键点的angle,angle放在关键点中进行维护
DivideNode()
参数列表:4个检测节点
计算上方两个角点的x的中间位置
计算下方右边y和上方左边y的中间位置
这边计算x和y方向上的一半位置的方法还有其他,只要满足它的要求就可以。
下面是相当于是把一个大节点分成了4个孩子节点
原始维护的左上、左上、右上和右下的4个节点,组合起来就是一个大节点。下面的话就是要把这每一个节点继续往下分割成4个子节点。
对于节点1来讲,扮演的角色是原来的左上的节点,所以在他的左上就是原来的左上节点,见下图所示,画得有点丑,能偶帮助理解就可以啦。
子节点分解了之后,需要将关键点与这些新的子节点进行关联,每一新节点中都包含了关键点的容器。当分解之后子节点中只有一个关键点,那个该节点之后将不被分解,否则继续往下分解,直至满足条件。那么这里面就可以回答一下bNoMore这个变量的作用了,它是用来标价这个节点是否需要继续往下分解的。
DistributeOctTree()
看这函数名,是要用八叉树的数据结构进行组织
参数列表里包括了被分布的关键点、图像的上下左右边界、关键点的数目和层数。
计算有多少初始节点,获取X方向上的高度
定义了提取节点类型的链表list
定义了提取节点类型的指针容器
如上图可以得到初始的几个节点,将这一块区域内的关键点根据x坐标关联到每个初始化的节点。
往下开始遍历每个初始节点
- 判断该节点内包含的关键点数目,如果是1,bNoMore变量标记为true;如果该节点没有包含关键点,那么这个节点将会被移除;其他情况的话就迭代器自增,循环下一个节点。
定义bFinish为false,iteration为0
使用了pair来将int类型和提取节点类型关联在一起
长成vector< pair >这样的容器,大小为节点数的4倍
从整幅图像出发,一开始整个大节点,利用DivideNode()对节点进行分解,分解是否停止的标记是每个节点内的bNoMore的状态,上面pair类型的容器存放的每一个节点及其对应的关键点数目。一旦一个节点被分解完,他也将从列表里移除。分解的次数,通过nToExpand来记录。
- 当节点的数量大于等于N或者是等于原来节点的数量时,就表示节点分解任务就完成了。
- 如果节点的数量与3倍的分解次数之和大于N,将继续分解,这时的分解,先将vector< pair >按照关键点的数量进行从小到大的排序,从关键点数量最大的节点开始分解,分解的过程和上面的讲解差不多,然后迭代器一直指向链表的第一个元素。当分解的节点数大于N时,结束分解。bFinish标记位置为true
- 确认每个节点内最好的关键点,这里用到了响应值。取节点内响应值最大的那个关键点。
其实看完这个函数,有一点纳闷的是,既然保证了分解时判断的依据有关键点的数量等于1,当节点个数大于等于N时,还是不能保证在一个节点内只有1个关键点????
ComputeKeyPointsOctTree()
参数列表就是所有的关键点
金字塔内的所有关键点分布在金字塔的每一层上
图像边界值,由阈值来控制,去掉了边缘的一圈,这一圈的长度为EDGE_THRESHOLD-3
- 提取小区域内的Fast角点,之前的iniThFAST和minThFAST在这找到了答案,这边是阈值的设置。如果初始的阈值,提取不出角点,就换用最小的阈值进行提取。
- 如果能提取出角点,要将该角点的像素坐标恢复到缩原始的图像上。
- 提取出金字塔上每一层的关键点
调用DistributeOctTree()函数将提取出的关键点进行分解
窗口大小也会因为尺度因子的不同为不同
给关键点纠正坐标信息,补充提取该关键点的层数和尺度窗口大小
最后计算不同层上的关键的角度,这边就调用上文的computeOrientation()函数实现。
ComputeKeyPointsOld()
该函数在该cpp中已被隐去,从它的名字上也可以看出来,毕竟old了!
computeDescriptors()
计算描述子这边就调用了 computeOrbDescriptor()函数实现的,只不过在调用此函数之前,定义了以关键点数量为行,32为宽的8位单通道的矩阵,每一行放一个关键点的描述子
operator()()
重载括号运算符,实现了关键点的提取和描述子的计算
这边就单纯叙述它的整个流程,因为主要的算法,上面都已经展开过了。
- 判断输入的图像是否为空
- 保证是单通道的图像
- 建立图像金字塔
- 对关键点进行分解,保证一个节点内只有一个关键点
- 对每一层图像进行高斯滤波,进行描述子的计算
- 最后将提取出的特征点恢复尺度,都转换到原始图上的像素坐标
ComputePyramid()
建立图像金字塔,讲白了就是将原始图像每一层都按照相同的缩小。这里ORB中都考虑了边界的问题,所以他在取图像区域时,都是外面留一些边界再取图像的。
终于看完了,真的没想到,虽然自己的眼睛都已经花了,但是看完这1000多行的代码,不得不感叹别人代码的优美,反正所有的函数都已经理了一遍,今天就到这吧,已经晚上10.30了呀,理东西回宿舍睡觉,明天见!
时间:2019年08月08日
作者:hhuchern
机构:河海大学机电工程学院