前言:受高博的启发,想自己写一个完整的slam,不知如何动手,想到将orbslam2拆分,按照自己的理解组装一个slam(先整出个视觉里程计吧),所以开始研究orbslam2前端的代码,在slam14讲13章的学习中发现,前端主要有四个点(特征点法)分别为点提取、点匹配(数据关联)、位姿估计和三角测量,所以在学习的过程中抓住这个四个点,深入理解,下面是第一个点-点提取:
结构图如下:
构造函数
ORBextractor ::ORBextractor(int nfeatures, float scaleFactor, int nlevels, int iniThFAST, int minThFAST)
理解:主要是给一些属性赋值,为后续操作做准备。
输入参数:
1.该帧提取的总特征点数(int)
2.图像金字塔缩放的尺度因子(float))
3.构建图像金字塔的层数(int)
4.fast角点的初始响应阈值(int)
5.fast角点更小的响应阈值(int)
包含的工作:构建图像金字塔,计算每层需提取的特征点数,为计算特征点的方向做准备(计算1/4个圆的边界值)
仿函数
Void ORBextractor::operator()(InputArray _image, InputArray _mask, vector<keyPoint>& _keypoints, OutputArray _descriptors)
理解:前俩个参数提供原始图像和掩膜作为处理的原材料,而我们需要的的结果则存放在后两个参数。 _keyPoints里面存放了该图片所提取的所有特征点(原先是根据图像金字塔分 层存放在allKeyPoints里面,在最后将除0层以外的所有特征点乘上尺度因子映射到了第0层上),_descriptors里面存放了所有特征点对应的描述子,行数对应特征点数,列数32(一个元素8位,所以对应的二进制描述位32*8位)。
输入参数:
1. 原始图像(InputArray)
2. 掩膜 (InputArray)
3. 存储特征点的关键点vector容器(vector<keyPoint>)
4. 存储特征点的描述子的矩阵(OutputArray)
包含的工作:
判断图像是否存在,计算图像金字塔,使用四叉树的方式计算每层图像的特征点并进行分配,遍历金字塔每层图像进行高斯模糊,计算高斯模糊后的描述子,将非0层的特征点恢复到第0层(原图像上)。每帧图片的描述子用矩阵来存储,行数表示特征点数,列数表述描述子的位数(32*8)。
构建图像金字塔
Void ORBextractor::ComputePyramid(cv::Mat image)
理解:只要输入需要构建图像金字塔的图像就行(灰度图),工作的成果保存再公共属性 mvImagePyramid 中,它对应着一张图片的图像金字塔。
输入参数:
1. 原始图像
包含的共工作:分层创建放图片的容器,图片根据尺度因子进行缩放在补边(确定图片的大小,但里面并没有内容);然后将原始图像放在图像中间,对补边区域进行填充(放进去内容)
提取特征点、进行均匀分配、剔除冗余点保留下最具代表性的特征点并计算特征点方向
Void ORBextractor::ComputeKeyPointsOctree(vector<vector<keyPoint>& allKeyPoints>)
allKeypoints 是二维数组,第一维放的是金字塔的层数,第二维存储的是该层所有的特征点。(每次处理一帧,所有一个allkeypoints对应一帧所有的特征点)
输入参数:allKeypoints 是用来存放处理结果的
理解:处理的成果保存再allkeypoints中。遍历每层金字塔图像,以网格为单位提取fast角点即用函数FAST(提取角点区域,角点存放的容器,检测的阈值,true为使用非极大值抑制)(注意,一个网格可以提取多个角点,只要超过阈值就会判断为角点),按网格提取完该层图像金字塔图像之后,使用函数DistributeOctTree(保存该层图片所有特征点的容器,图片四个角的位置(四个参数),该层图片希望保留下来特征点的数目,在第几层)去除特征点直到和我们设置的特征点数相等。然后遍历保存下来的每一个特征点恢复它在扩了边缘图像中的真实位置、记录它所在金字塔的层数、计算特征点方向的半径。利用函数computeOrientation(待处理的图片,待处理图对应存储所有特征点的容器,PATH的横坐标边界)
进行均匀分配、剔除冗余点保留下最具代表性的特征点
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
输入参数:
1. 该图像提取的所有特征点
2. 被处理图像的四个角的位置
3. 希望提取出来的特征点数
4. 该图像所在的金字塔层数
处理的成果全保存在vResultKeys这个容器里面,该容器存储的是某层图像金字塔图像对应响应值最大的特征点,特征点数为我们输入的特征点数。
处理过程:按宽高比设置初始提取器的个数,当该提取器对应区域内特征点数大于一就一个分裂四个提取器,同时删掉初始提取器,重复上述步骤,直到提取器区域内只有一个点或者提取器数目和我们预先设定好提取的特征点数相同,不再分裂。然后选取所有提取器区域内响应值最大的那个特征点作为代表保留下来存入vResultKey容器内返回。
【Inode容器内放的是分裂后特征点数大于0的ExtractorNode为方便插入删除用链表实现;而vSizeAndPointerToNode容器则是记录的特征点数大于1的ExtractorNode,为下次分裂做准备;当Inode里面元素的个数大于或等于我们要求的特征点数或者分裂之前Inode容器内元素个数和分裂后元素个数相同((int)Inodes.size()==prevSize),则将bFinish的状态改为true;nToExpand累计能再次分裂ExtractorNode的个数;DivideNode()这个函数,所做的工作是确定这四个对象管辖的图像区域,将该区域内的的特征点数存入各自的vkeys内,判断vkey容器内特征点数为1则对应属性bNoMore状态改为true】
计算特征点方向
computeOrientation(mvImagePyramid[level], //对应的图层的图像
allKeypoints[level], //这个图层中提取并保留下来的特征点容器
umax); //以及PATCH的横坐标边界
计算的成果全保存再每个特征点angle这个属性里面。 (keypoint->angle = IC_Angle())
用下列函数
IC_Angle(image, //特征点所在的图层的图像
keypoint->pt, //特征点在这张图像中的坐标
umax); //每个特征点所在图像区块的每行的边界 u_max 组成的vector
来计算。
函数过程:计算m_10和m_01。首先将这个特征点计算方向的图像块的像素值用uchar* center 表示,先计算m_10的第0行(第0行则m_01的这块区域为0),然后同时计算1行2行依次类推到15行区域内m_10和m_01.求出m_01和m_10之后,就利用函数fastAtan2((float)m_01, (float)m_10)来计算结果,输出为[0,360)角度,精度为0.3.
高斯模糊
GaussianBlur(workingMat, //源图像
workingMat, //输出图像
Size(7, 7), //高斯滤波器kernel大小,必须为正的奇数
2, //高斯滤波在x方向的标准差
2, //高斯滤波在y方向的标准差
BORDER_REFLECT_101);//边缘拓展点插值类型
理解:就是对图像金字塔每层图像进行高斯模糊,输出结果保存在workingMat中。提取特征点的时候,使用的是清晰的原图像,而计算描述子是在高斯模糊之后。高斯模糊是为了避免图像噪声的影响。
计算描述子
computeDescriptors(workingMat, //高斯模糊之后的图层图像
keypoints, //当前图层中的特征点集合
desc, //存储计算之后的描述子
pattern); //随机采样模板
理解:处理结果全保存在desc这个矩阵内。注意这里desc这个矩阵存储每层图像金字塔的描述子,而特征点是分层保存的。desc的每一行对应一个特征点的描述,列数256位。该函数对应一层的特征点,而描述子计算是逐个进行的。用下列函数计算
computeOrbDescriptor(keypoints[i], //要计算描述子的特征点
image, //以及其图像
&pattern[0], //随机点集的首地址
descriptors.ptr((int)i)); //提取出来的描述子的保存位置
理解:处理结果保存在第四个参数内,对应desc的一行。
处理过程:将特征点像素值对应的地址放在center变量里面,根据特征点方向的旋转角度(顺时针旋转)确定好pattern后的x和y,并转换位一维(y*每行字节数+x)数值,根据一维数值找到对应的特征点,每次取两个特征点的灰度值进行比较,值为0或1,将值按位赋给val,16个点为一组即能确定八位,将转为uchar型,保存到desc里面,循环32次即该特征点的描述子有8*32位进行描述。
【pattern里装了512个点;一个像素灰度值是由8位整数记录即取值范围位0~255内】
ps:后续还会有点匹配、位姿估计、三角测量的理解。本人为学生,理解可能有些偏差,大佬如果能提出并指正,不甚感激(如果能说些跟论文相关的意见,万分感谢,正在为此恼!)。