ORB-SLAM2源码学习:ORBmatcher.cc:ORBmatcher::SearchForInitialization 单目初始化中用于参考帧和当前帧的特征点匹配

前言

特征匹配函数众多,但是它们的基本思想类似,下面的函数是用于单目初始化中的特征匹配。

1.函数声明

/* 单目初始化中用于参考帧和当前帧的特征点匹配 步骤 Step 1 构建旋转直方图 Step 2 在半径窗口内搜索当前帧F2中所有的候选匹配特征点 Step 3 遍历搜索搜索窗口中的所有潜在的匹配候选点,找到最优的和次优的 Step 4 对最优次优结果进行检查,满足阈值、最优/次优比例,删除重复匹配 Step 5 计算匹配点旋转角度差所在的直方图 Step 6 筛除旋转直方图中“非主流”部分 Step 7 将最后通过筛选的匹配好的特征点保存 F1 初始化参考帧 F2 当前帧 vbPrevMatched 本来存储的是参考帧的所有特征点坐标,该函数更新为匹配好的当前帧的特征点坐标 vnMatches12 保存参考帧F1中特征点是否匹配上,index保存是F1对应特征点索引,值保存的是匹配好的F2特征点索引 windowSize 搜索窗口 return int 返回成功匹配的特征点数目 */ int ORBmatcher::SearchForInitialization(Frame &F1, Frame &F2, vector<cv::Point2f> &vbPrevMatched, vector<int> &vnMatches12, int windowSize) { .... }

2.函数定义

1.确定匹配方法

单目初始化时,没有任何的先验信息,可以通过暴力匹配来实现匹配,但这种匹配效率低,在ORB-SLAM2中单目初始化时提取的特征点是跟踪特征点的2倍,导致计算量大大增加并且会出现大量的误匹配。

ORB-SLAM2采用了另一种单目初始化时的特征匹配方法,思路如下:

1、参与初始化的两帧认为是非常接近的。(实际上在单目初始化时就采用的连续的两帧因此是符合非常接近的

2、第一帧提取的特征点坐标对应到第二帧的位置,我们在第二帧的对应位置画一个圆,匹配的特征点就在这个圆内搜索,圆内待匹配的特征点称为候选特征点。

3、遍历圆内所有的候选特征点,找到最优和次优的候选特征点,计算其最优特征点的描述子的距离与次优特征点的描述子距离的比值判断其是否小于设定的比例。

4、接着判断最优特征点的描述子的距离是否小于设定的阈值。

5、最后经过方向一致性检验。

2.加速确定候选匹配特征点

在特征点提取中,我们已经将提取的特征点划分到了不同的网格中了,我们在搜索时可以以网格为单位进行,这样可以通过遍历搜索圆域中的网格来代替圆域内的像素,大大的增加了匹配特征点的效率。

ORB-SLAM2源码学习:Frame.cc:GetFeaturesInArea快速寻找候选匹配特征点

3.方向一致性检验

统计两帧之间的所有匹配特征点对的两个特征点主方向的差,构建一个直方图,将360划分为固定的份数(ORB-SLAM2中将直方图分为30份,HISTO_LENGTH

30),统计所有的主方向差的角度落在直方图的哪个角度范围内,取出现频率最大的三个直方图的格子, 剔除其余格子的中的对象。

 { int nmatches=0; // F1中特征点和F2中匹配关系,注意是按照F1特征点数目分配空间 vnMatches12 = vector<int>(F1.mvKeysUn.size(),-1);//用-1来初始化容器中的每一个元素。 // Step 1 构建旋转直方图,HISTO_LENGTH = 30 vector<int> rotHist[HISTO_LENGTH]; for(int i=0;i<HISTO_LENGTH;i++) // 每个bin里预分配500个,因为使用的是vector不够的话可以自动扩展容量 rotHist[i].reserve(500); //! 原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码 const float factor = HISTO_LENGTH/360.0f; // 匹配点对距离,注意是按照F2特征点数目分配空间 vector<int> vMatchedDistance(F2.mvKeysUn.size(),INT_MAX); // 从帧2到帧1的反向匹配,注意是按照F2特征点数目分配空间 vector<int> vnMatches21(F2.mvKeysUn.size(),-1); // 遍历帧1中的所有特征点 for(size_t i1=0, iend1=F1.mvKeysUn.size(); i1<iend1; i1++) { cv::KeyPoint kp1 = F1.mvKeysUn[i1]; int level1 = kp1.octave; // 只使用原始图像上提取的特征点 if(level1>0)//只要不是金字塔的第0层都要进行去除。 continue; // Step 2 在半径窗口内搜索当前帧F2中所有的候选匹配特征点 // vbPrevMatched 输入的是参考帧 F1的特征点 // windowSize = 100,输入最大最小金字塔层级 均为0 // 获取 vector<size_t> vIndices2 = F2.GetFeaturesInArea(vbPrevMatched[i1].x,vbPrevMatched[i1].y, windowSize,level1,level1); // 没有候选特征点,跳过 if(vIndices2.empty())//?GetFeaturesInArea这个函数不是已经排除掉了吗? continue; // 取出参考帧F1中当前遍历特征点对应的描述子 cv::Mat d1 = F1.mDescriptors.row(i1); int bestDist = INT_MAX; //最佳描述子匹配距离,越小越好 int bestDist2 = INT_MAX; //次佳描述子匹配距离 int bestIdx2 = -1; //最佳候选特征点在F2中的index // Step 3 遍历搜索搜索窗口中的所有潜在的匹配候选点,找到最优的和次优的 for(vector<size_t>::iterator vit=vIndices2.begin(); vit!=vIndices2.end(); vit++) { size_t i2 = *vit; // 取出候选特征点对应的描述子 cv::Mat d2 = F2.mDescriptors.row(i2);//当前遍历到F2帧上的候选匹配特征点所对应描述子。 // 计算两个特征点描述子距离 int dist = DescriptorDistance(d1,d2); if(vMatchedDistance[i2]<=dist) continue; // 如果当前匹配距离更小,更新最佳次佳距离。 //先将计算后的距离与最近距离先比较,如果符合则依次替换最近和次近的距离值并记录候选特征点的ID。(最近和次近距离在最开始时设置的一样) if(dist<bestDist) { bestDist2=bestDist; bestDist=dist; bestIdx2=i2; } else if(dist<bestDist2) { bestDist2=dist; } } // Step 4 对最优次优结果进行检查,满足阈值、最优/次优比例,删除重复匹配 // 即使算出了最佳描述子匹配距离,也不一定保证配对成功。要小于设定阈值 if(bestDist<=TH_LOW) { // 最佳距离比次佳距离要小于设定的比例,这样特征点辨识度更高 if(bestDist<(float)bestDist2*mfNNratio) { // 如果找到的候选特征点对应F1中特征点已经匹配过了,说明发生了重复匹配,将原来的匹配也删掉 if(vnMatches21[bestIdx2]>=0) { vnMatches12[vnMatches21[bestIdx2]]=-1; nmatches--; } // 次优的匹配关系,双向建立 // vnMatches12保存参考帧F1和F2匹配关系,index保存是F1对应特征点索引,值保存的是匹配好的F2特征点索引 vnMatches12[i1]=bestIdx2; vnMatches21[bestIdx2]=i1; vMatchedDistance[bestIdx2]=bestDist; nmatches++; // Step 5 计算匹配点旋转角度差所在的直方图 if(mbCheckOrientation) { // 计算匹配特征点的角度差,这里单位是角度°,不是弧度 float rot = F1.mvKeysUn[i1].angle-F2.mvKeysUn[bestIdx2].angle; if(rot<0.0) rot+=360.0f; // 前面factor = HISTO_LENGTH/360.0f // bin = rot / 360.of * HISTO_LENGTH 表示当前rot被分配在第几个直方图bin int bin = round(rot*factor); // 如果bin 满了又是一个轮回 if(bin==HISTO_LENGTH) bin=0; assert(bin>=0 && bin<HISTO_LENGTH); // assert 是 C++ 中的一个宏,用于在运行时检查某个条件是否为真。如果条件为假(即表达式的值为 false),程序会终止执行,并打印错误信息。 rotHist[bin].push_back(i1); } } } } // Step 6 筛除旋转直方图中“非主流”部分 if(mbCheckOrientation) { int ind1=-1; int ind2=-1; int ind3=-1; // 筛选出在旋转角度差落在在直方图区间内数量最多的前三个bin的索引 ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3); for(int i=0; i<HISTO_LENGTH; i++) { if(i==ind1 || i==ind2 || i==ind3) continue; // 剔除掉不在前三的匹配对,因为他们不符合“主流旋转方向” for(size_t j=0, jend=rotHist[i].size(); j<jend; j++) { int idx1 = rotHist[i][j]; if(vnMatches12[idx1]>=0) { vnMatches12[idx1]=-1; nmatches--; } } } } //Update prev matched // Step 7 将最后通过筛选的匹配好的特征点保存到vbPrevMatched for(size_t i1=0, iend1=vnMatches12.size(); i1<iend1; i1++) if(vnMatches12[i1]>=0) vbPrevMatched[i1]=F2.mvKeysUn[vnMatches12[i1]].pt; return nmatches; }

结束语

以上就是我学习到的内容,如果对您有帮助请多多支持我,如果哪里有问题欢迎大家在评论区积极讨论,我看到会及时回复。

本文是转载文章,点击查看原文
如有侵权,请联系 lx@jishuguiji.net 删除。