该类负责特征点与特征点之间,地图点与特征点之间通过投影关系、词袋模型或者Sim3位姿匹配。用来辅助完成单目初始化,三角化恢复新的地图点,tracking,relocalization以及loop closing,因此比较重要。

该类提供的API是:

. 几个重载的SearchByProjection函数(第一个形参代表需要在其中寻找匹配点的当前图像帧/query;第二个形参则包含待匹配特征/train),用于

  a. 跟踪局部地图(在局部地图中寻找与当前帧特征点匹配的)。因为在TrackReferenceKeyFrame和TrackWithMotionModel中,仅仅是两帧之间跟踪,会跟丢地图点,这里通过跟踪局部地图,在当前帧中恢复出一些当前帧的地图点。  其中的阈值th一般根据单目还是双目,或者最近有没有进行过重定位来确定,代表在投影点的这个平面阈值范围内寻找匹配特征点。匹配点不仅需要满足对极几何,初始位姿的约束;还需要满足描述子之间距离较小。

int ORBmatcher::SearchByProjection(Frame &F, const vector<MapPoint*> &vpMapPoints, const float th);

  b. 匹配上一帧的地图点,即前后两帧匹配,用于TrackWithMotionModel。

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, const Frame &LastFrame, const float th, const bool bMono);

  c. 在当前帧中匹配所有关键帧中的地图点,用于Relocalization。

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, KeyFrame *pKF, const set<MapPoint*> &sAlreadyFound, const float th , const int ORBdist);

  d. 在当前关键帧中匹配所有关键帧中的地图点,需要计算sim3,用于Loop Closing。

int ORBmatcher::SearchByProjection(KeyFrame* pKF, cv::Mat Scw, const vector<MapPoint*> &vpPoints, vector<MapPoint*> &vpMatched, int th);

. 两个重载的SearchByBow函数(注意这里形参表示的匹配的主被动关系和SearchByProjection是反的),用于

  a. 在当前帧中匹配关键帧中的地图点,用于TrackReferenceKeyFrame和Relocalization。

int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches);

  b. 在当前关键帧中匹配所有关键帧中的地图点,用于Loop Closing。

int ORBmatcher::SearchByBoW(KeyFrame *pKF1, KeyFrame *pKF2, vector<MapPoint *> &vpMatches12);

. 用于单目初始化的SearchForInitialization,以及利用三角化,在两个关键帧之间恢复出一些地图点SearchForTriangulation。

int ORBmatcher::SearchForInitialization(Frame &F1, Frame &F2, vector<cv::Point2f> &vbPrevMatched, vector<int> &vnMatches12, int windowSize);
int ORBmatcher::SearchForTriangulation(KeyFrame *pKF1, KeyFrame *pKF2, cv::Mat F12,
vector<pair<size_t, size_t> > &vMatchedPairs, const bool bOnlyStereo);

. 两个重载的Fuse函数,用于地图点的融合:

  地图点能匹配上当前关键帧的地图点,也就是地图点重合了,选择观测数多的地图点替换;地图点能匹配上当前帧的特征点,但是该特征点还没有生成地图点,则生成新的地图点)。

  重载的函数是为了减小尺度漂移的影响,需要知道当前关键帧的sim3位姿。

int ORBmatcher::Fuse(KeyFrame *pKF, const vector<MapPoint *> &vpMapPoints, const float th);
int ORBmatcher::Fuse(KeyFrame *pKF, cv::Mat Scw, const vector<MapPoint *> &vpPoints, float th, vector<MapPoint *> &vpReplacePoint);

. 计算描述子之间的hanmming距离

int ORBmatcher::DescriptorDistance(const cv::Mat &a, const cv::Mat &b);

选取其中一个用于Relocalization的投影匹配着重理解。疑问是,何时用投影匹配,何时用DBow2进行匹配?在Relocalization和LoopClosing中进行匹配的是在很多帧关键帧集合中匹配,属于Place Recognition,因此需要用DBow,而投影匹配适用于两帧之间,或者投影范围内(局部地图,前一个关键帧对应地图点)的MapPoints与当前帧之间。

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, KeyFrame *pKF, const set<MapPoint*> &sAlreadyFound, const float th , const int ORBdist);

用关键帧pKF的地图点投影匹配当前帧的特征点:

// For Relocalization
//
// 1. 获取pKF对应的地图点vpMPs,遍历
// (1). 若该点为NULL、isBad或者在SearchByBow中已经匹配上(Relocalization中首先会通过SearchByBow匹配一次),抛弃;
// 2. 通过当前帧的位姿,将世界坐标系下的地图点坐标转换为当前帧坐标系(相机坐标系)下的坐标
// (2). 投影点(u,v)不在畸变矫正过的图像范围内,地图点的距离dist3D不在地图点的可观测距离内(根据地图点对应的金字塔层数,
// 也就是提取特征的neighbourhood尺寸),抛弃
// 3. 通过地图点的距离dist3D,预测特征对应金字塔层nPredictedLevel,并获取搜索window大小(th*scale),在以上约束的范围内,
// 搜索得到候选匹配点集合向量vIndices2
// const vector<size_t> vIndices2 = CurrentFrame.GetFeaturesInArea(u, v, radius, nPredictedLevel-1, nPredictedLevel+1);
// 4. 计算地图点的描述子和候选匹配点描述子距离,获得最近距离的最佳匹配,但是也要满足距离<ORBdist。
// 5. 最后,还需要通过直方图验证描述子的方向是否匹配
int ORBmatcher::SearchByProjection(Frame &CurrentFrame, KeyFrame *pKF, const set<MapPoint*> &sAlreadyFound, const float th , const int ORBdist)
{
int nmatches = ; const cv::Mat Rcw = CurrentFrame.mTcw.rowRange(,).colRange(,);
const cv::Mat tcw = CurrentFrame.mTcw.rowRange(,).col();
const cv::Mat Ow = -Rcw.t()*tcw; // Rotation Histogram (to check rotation consistency)
vector<int> rotHist[HISTO_LENGTH];
for(int i=;i<HISTO_LENGTH;i++)
rotHist[i].reserve();
const float factor = 1.0f/HISTO_LENGTH; const vector<MapPoint*> vpMPs = pKF->GetMapPointMatches(); for(size_t i=, iend=vpMPs.size(); i<iend; i++)
{
MapPoint* pMP = vpMPs[i]; if(pMP)
{
// before this, Relocalization has already execute SearchByBoW, those matched was inserted into sAlreadyFound
if(!pMP->isBad() && !sAlreadyFound.count(pMP))
{
//Project
cv::Mat x3Dw = pMP->GetWorldPos();
cv::Mat x3Dc = Rcw*x3Dw+tcw; const float xc = x3Dc.at<float>();
const float yc = x3Dc.at<float>();
const float invzc = 1.0/x3Dc.at<float>(); const float u = CurrentFrame.fx*xc*invzc+CurrentFrame.cx;
const float v = CurrentFrame.fy*yc*invzc+CurrentFrame.cy;
// u,v是关键帧中地图点在当前帧上的投影点
if(u<CurrentFrame.mnMinX || u>CurrentFrame.mnMaxX)
continue;
if(v<CurrentFrame.mnMinY || v>CurrentFrame.mnMaxY)
continue; // Compute predicted scale level
cv::Mat PO = x3Dw-Ow;
float dist3D = cv::norm(PO); const float maxDistance = pMP->GetMaxDistanceInvariance();
const float minDistance = pMP->GetMinDistanceInvariance(); // Depth must be inside the scale pyramid of the image
if(dist3D<minDistance || dist3D>maxDistance)
continue; int nPredictedLevel = pMP->PredictScale(dist3D,&CurrentFrame); // Search in a window
const float radius = th*CurrentFrame.mvScaleFactors[nPredictedLevel]; const vector<size_t> vIndices2 = CurrentFrame.GetFeaturesInArea(u, v, radius, nPredictedLevel-, nPredictedLevel+); if(vIndices2.empty())
continue; const cv::Mat dMP = pMP->GetDescriptor(); int bestDist = ;
int bestIdx2 = -; for(vector<size_t>::const_iterator vit=vIndices2.begin(); vit!=vIndices2.end(); vit++)
{
const size_t i2 = *vit;
if(CurrentFrame.mvpMapPoints[i2])
continue; const cv::Mat &d = CurrentFrame.mDescriptors.row(i2); const int dist = DescriptorDistance(dMP,d); if(dist<bestDist)
{
bestDist=dist;
bestIdx2=i2;
}
} if(bestDist<=ORBdist)
{
CurrentFrame.mvpMapPoints[bestIdx2]=pMP;
nmatches++; if(mbCheckOrientation)
{
float rot = pKF->mvKeysUn[i].angle-CurrentFrame.mvKeysUn[bestIdx2].angle;
if(rot<0.0)
rot+=360.0f;
int bin = round(rot*factor);
if(bin==HISTO_LENGTH)
bin=;
assert(bin>= && bin<HISTO_LENGTH);
rotHist[bin].push_back(bestIdx2);
}
} }
}
} if(mbCheckOrientation)
{
int ind1=-;
int ind2=-;
int ind3=-; ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3); for(int i=; i<HISTO_LENGTH; i++)
{
if(i!=ind1 && i!=ind2 && i!=ind3)
{
for(size_t j=, jend=rotHist[i].size(); j<jend; j++)
{
CurrentFrame.mvpMapPoints[rotHist[i][j]]=NULL;
nmatches--;
}
}
}
} return nmatches;
}

其中角度直方图是用来剔除不满足两帧之间角度旋转的外点的,也就是所谓的旋转一致性检测

1. 将关键帧与当前帧匹配点的angle相减,得到rot(0<=rot<360),放入一个直方图中,对于每一对匹配点的角度差,均可以放入一个bin的范围内(360/HISTO_LENGTH)。

2. 统计直方图最高的三个bin保留,其他范围内的匹配点剔除。另外,若最高的比第二高的高10倍以上,则只保留最高的bin中的匹配点。

最后该函数会

1. 为当前帧生成和关键帧匹配上的地图点

2. 统计通过投影匹配上的点

CurrentFrame.mvpMapPoints[bestIdx2]=pMP;
nmatches++;

ORB-SLAM(八)ORBmatcher 特征匹配的更多相关文章

  1. 第十六节、基于ORB的特征检测和特征匹配

    之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因.人们提出了使用ORB来替代SIFT和SURF.与前两者相比,ORB有更快的速度.ORB在2011年才首次发布.在前面小节中 ...

  2. 【特征匹配】SIFT原理之KD树+BBF算法解析

    转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/47606159 继上一篇中已经介绍了SIFT原理与C源代码剖析,最后得到了一系列 ...

  3. (三)ORB特征匹配

    ORBSLAM2匹配方法流程 在基于特征点的视觉SLAM系统中,特征匹配是数据关联最重要的方法.特征匹配为后端优化提供初值信息,也为前端提供较好的里程计信息,可见,若特征匹配出现问题,则整个视觉SLA ...

  4. 特征提取(Detect)、特征描述(Descriptor)、特征匹配(Match)的通俗解释

    特征匹配(Feature Match)是计算机视觉中很多应用的基础,比如说图像配准,摄像机跟踪,三维重建,物体识别,人脸识别,所以花一些时间去深入理解这个概念是不为过的.本文希望通过一种通俗易懂的方式 ...

  5. OpenCV探索之路(二十三):特征检测和特征匹配方法汇总

    一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要. ...

  6. 利用SIFT进行特征匹配

    SIFT算法是一种基于尺度空间的算法.利用SIFT提取出的特征点对旋转.尺度变化.亮度变化具有不变性,对视角变化.仿射变换.噪声也有一定的稳定性. SIFT实现特征的匹配主要包括四个步骤: 提取特征点 ...

  7. OpenCV-Python 特征匹配 | 四十四

    目标 在本章中, 我们将看到如何将一个图像中的特征与其他图像进行匹配. 我们将在OpenCV中使用Brute-Force匹配器和FLANN匹配器 Brute-Force匹配器的基础 蛮力匹配器很简单. ...

  8. OpenCV 之 特征匹配

    OpenCV 中有两种特征匹配方法:暴力匹配 (Brute force matching) 和 最近邻匹配 (Nearest Neighbors matching) 它们都继承自 Descriptor ...

  9. [OpenCV]基于特征匹配的实时平面目标检测算法

    一直想基于传统图像匹配方式做一个融合Demo,也算是对上个阶段学习的一个总结. 由此,便采购了一个摄像头,在此基础上做了实时检测平面目标的特征匹配算法. 代码如下: # coding: utf-8 ' ...

随机推荐

  1. Using the @synchronized Directive

    https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/ThreadSafe ...

  2. Eclipse_java项目中导入外部jar文件

    非常多时候,在java项目中须要导入外部 .jar文件.比方:须要导入数据库连接驱动等等一些包.不熟悉eclipse的人可能会犯愁,事实上非常easy. ...过程例如以下:  在须要加入外部文件的项 ...

  3. UVa 11181 - Probability|Given(条件概率)

    链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  4. php函数:call_user_func

    前段时间浏览文档发现一个有意思的PHP函数:call_user_func [文档地址] 函数作用:该函数主要用于通过函数名去调用该函数 例如: function test(){ echo " ...

  5. R语言学习笔记1——R语言中的基本对象

    R语言,一种自由软件编程语言与操作环境,主要用于统计分析.绘图.数据挖掘.R本来是由来自新西兰奥克兰大学的Ross Ihaka和Robert Gentleman开发(也因此称为R),现在由“R开发核心 ...

  6. Advanced Plugin Concepts

    Provide Public Access to Default Plugin Settings An improvement we can, and should, make to the code ...

  7. Oracle 体系结构一 概述

    Oracle服务器由两个实体组成:实例和数据库. 实例由内存结构和进程组成. 它暂时存在于RAM和CPU中.当关闭运行的实例时,实例将消失的无影无踪. 数据库由磁盘上的文件组成.不管在运行状态还是停止 ...

  8. js中两个日期大小比较,获取当前日期,日期加减一天

    一.两个日期大小比较 1.日期参数格式:yyyy-mm-dd // a: 日期a, b: 日期b, flag: 返回的结果 function duibi(a, b,flag) { var arr = ...

  9. TCP/IP协议族之链路层(二)

    TCP/IP学习记录,如有错误请指正,谢谢!!! TCP/IP协议族之链路层(二) 链路层是最底层协议,主要有三个目的: 1. 为IP模块发送和接收IP数据报 2. 为ARP模块发送ARP请求和接收A ...

  10. php与java

    作者:eechen链接:https://www.zhihu.com/question/20377398/answer/141328982来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转 ...