ORB-SLAM(八)ORBmatcher 特征匹配
该类负责特征点与特征点之间,地图点与特征点之间通过投影关系、词袋模型或者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 特征匹配的更多相关文章
- 第十六节、基于ORB的特征检测和特征匹配
之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因.人们提出了使用ORB来替代SIFT和SURF.与前两者相比,ORB有更快的速度.ORB在2011年才首次发布.在前面小节中 ...
- 【特征匹配】SIFT原理之KD树+BBF算法解析
转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/47606159 继上一篇中已经介绍了SIFT原理与C源代码剖析,最后得到了一系列 ...
- (三)ORB特征匹配
ORBSLAM2匹配方法流程 在基于特征点的视觉SLAM系统中,特征匹配是数据关联最重要的方法.特征匹配为后端优化提供初值信息,也为前端提供较好的里程计信息,可见,若特征匹配出现问题,则整个视觉SLA ...
- 特征提取(Detect)、特征描述(Descriptor)、特征匹配(Match)的通俗解释
特征匹配(Feature Match)是计算机视觉中很多应用的基础,比如说图像配准,摄像机跟踪,三维重建,物体识别,人脸识别,所以花一些时间去深入理解这个概念是不为过的.本文希望通过一种通俗易懂的方式 ...
- OpenCV探索之路(二十三):特征检测和特征匹配方法汇总
一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要. ...
- 利用SIFT进行特征匹配
SIFT算法是一种基于尺度空间的算法.利用SIFT提取出的特征点对旋转.尺度变化.亮度变化具有不变性,对视角变化.仿射变换.噪声也有一定的稳定性. SIFT实现特征的匹配主要包括四个步骤: 提取特征点 ...
- OpenCV-Python 特征匹配 | 四十四
目标 在本章中, 我们将看到如何将一个图像中的特征与其他图像进行匹配. 我们将在OpenCV中使用Brute-Force匹配器和FLANN匹配器 Brute-Force匹配器的基础 蛮力匹配器很简单. ...
- OpenCV 之 特征匹配
OpenCV 中有两种特征匹配方法:暴力匹配 (Brute force matching) 和 最近邻匹配 (Nearest Neighbors matching) 它们都继承自 Descriptor ...
- [OpenCV]基于特征匹配的实时平面目标检测算法
一直想基于传统图像匹配方式做一个融合Demo,也算是对上个阶段学习的一个总结. 由此,便采购了一个摄像头,在此基础上做了实时检测平面目标的特征匹配算法. 代码如下: # coding: utf-8 ' ...
随机推荐
- IBM MQ介绍
转自:http://hi.baidu.com/lubezhang/blog/item/bd308b3b7ecce3ec14cecb4f.html IBM MQ(IBM Message Queue)是I ...
- BIND简易教程(0):在Ubuntu下源码安装BIND(其实跟前面的教程没太大关系)
之前介绍过BIND的基本使用啦.关于BIND的入门级使用方法见:http://www.cnblogs.com/anpengapple/p/5877661.html简易教程系列,本篇只讲BIND安装. ...
- App上线流程全攻略(史上最具体步骤)
@转载请保留:iOS界一迷糊小书童--->专注于iOS开发!!谢谢合作 /*****************************************1****************** ...
- 汇编试验十四:访问CMOS RAM
CMOS RAM 芯片的特征: 包含一个时钟和一个有128个存储单元的RAM存储器. 该芯片靠电池供电.所以,关机后其内部的时钟仍可正常工作,RAM中的信息不丢失. 128个字节的RAM中,内部时钟占 ...
- php 处理微信账单
最近要做支付对账,即检查第三方支付与数据库中账单是否一一对应,涉及到微信对账单的处理,微信账单接口返回为一个字符串类似如下结果: 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单 ...
- shiro简单入门介绍
shiro是apache的一个java安全框架 可以完成认证,授权,加密,会话管理,基于web继承,缓存等 功能简介: 从外部来看: shiro架构 Subject:主体,代表了当前“用户”,这个用 ...
- JQuery 学习总结及实例 !! (转载)
出自 new:http://www.jianshu.com/users/1967b163cb61/latest_articles 1.JQuery简介 普通JavaScript的缺点:每种控件的操作方 ...
- mybatis动态sql语句学习(一)
动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦.拼接的时候要确保不能忘了必要的空格 ...
- C#通过指针读取文件
// readfile.cs // 编译时使用:/unsafe // 参数:readfile.txt // C#通过指针读取文件.使用该程序读并显示文本文件. using System; using ...
- 【题解】洛谷P1032 [NOIP2002TG]字串变换(BFS+字符串)
洛谷P1032:https://www.luogu.org/problemnew/show/P1032 思路 初看题目觉得挺简单的一道题 但是仔细想了一下发现实现代码挺麻烦的 而且2002年的毒瘤输入 ...