Relocalization
Relocalization
主要的作用是在跟踪失败时,通过词袋在关键帧数据库KeyFrameDatabase
中寻找和当前帧相似的关键帧作为匹配帧,进而恢复当前帧的位姿
- 计算当前帧的
Bow
,参考ORB_SLAM3 TrackReferenceKeyFrame中计算当前帧的描述子的Bow向量
mCurrentFrame.ComputeBoW();
- 检测当前帧的重定位候选帧
vpCandidateKFs
vector<KeyFrame*> vpCandidateKFs = mpKeyFrameDB->DetectRelocalizationCandidates(&mCurrentFrame, mpAtlas->GetCurrentMap());
- 对于所有的候选关键帧,通过
Bow
搜索与当前帧的匹配,并初始化对应的MLPnPsolver
const int nKFs = vpCandidateKFs.size();
// We perform first an ORB matching with each candidate
// If enough matches are found we setup a PnP solver
ORBmatcher matcher(0.75,true);
// 每个关键帧的解算器
vector<MLPnPsolver*> vpMLPnPsolvers;
vpMLPnPsolvers.resize(nKFs);
// 每个关键帧和当前帧中特征点的匹配关系
vector<vector<MapPoint*> > vvpMapPointMatches;
vvpMapPointMatches.resize(nKFs);
// 放弃某个关键帧的标记
vector<bool> vbDiscarded;
vbDiscarded.resize(nKFs);
// 有效的候选关键帧数目
int nCandidates=0;
// Step 3:遍历所有的候选关键帧,通过BoW进行快速匹配,用匹配结果初始化PnP Solver
for(int i=0; i<nKFs; i++)
{
KeyFrame* pKF = vpCandidateKFs[i];
if(pKF->isBad())
vbDiscarded[i] = true;
else
{
// 当前帧和候选关键帧用BoW进行快速匹配,匹配结果记录在vvpMapPointMatches,nmatches表示匹配的数目
int nmatches = matcher.SearchByBoW(pKF,mCurrentFrame,vvpMapPointMatches[i]);
// 如果和当前帧的匹配数小于15,那么只能放弃这个关键帧
if(nmatches<15)
{
vbDiscarded[i] = true;
continue;
}
else
{
// 如果匹配数目够用,用匹配结果初始化MLPnPsolver
// ? 为什么用MLPnP? 因为考虑了鱼眼相机模型,解耦某些关系?
// 参考论文《MLPNP-A REAL-TIME MAXIMUM LIKELIHOOD SOLUTION TO THE PERSPECTIVE-N-POINT PROBLEM》
MLPnPsolver* pSolver = new MLPnPsolver(mCurrentFrame,vvpMapPointMatches[i]);
// 构造函数调用了一遍,这里重新设置参数
pSolver->SetRansacParameters(
0.99, // 模型最大概率值,默认0.9
10, // 内点的最小阈值,默认8
300, // 最大迭代次数,默认300
6, // 最小集,每次采样六个点,即最小集应该设置为6,论文里面写着I > 5
0.5, // 理论最少内点个数,这里是按照总数的比例计算,所以epsilon是比例,默认是0.4
5.991); // 卡方检验阈值 //This solver needs at least 6 points
vpMLPnPsolvers[i] = pSolver;
nCandidates++; // 1.0版本新加的
}
}
}
- 从候选关键帧中找到能够匹配上的关键帧,当位姿优化
PoseOptimization
的内点数到达50
则成功
PnP+Opt:
- 通过
MLPnPsolver
计算当前帧的位姿eigTcw
,并设置当前帧位姿的初值 - 根据
PnP
的内外点vbInliers
更新下当前帧的地图点,然后用PoseOptimization
优化当前帧的Pose - 根据优化后的内外点
mCurrentFrame.mvbOutlier
更新当前帧的地图点 - 如果内点太少
10
,直接跳过;如果内点大于50
,匹配成功;如果内点在[10, 50]
之间,可以再尝试下
vector<bool> vbInliers;
int nInliers;
bool bNoMore;
//PnP
MLPnPsolver* pSolver = vpMLPnPsolvers[i];
Eigen::Matrix4f eigTcw;
bool bTcw = pSolver->iterate(5,bNoMore,vbInliers,nInliers, eigTcw);
// If Ransac reachs max. iterations discard keyframe
if(bNoMore)
{
vbDiscarded[i]=true;
nCandidates--;
}
// If a Camera Pose is computed, optimize
if(bTcw)
{
//设置当前帧的位姿的初值
Sophus::SE3f Tcw(eigTcw);
mCurrentFrame.SetPose(Tcw);
set<MapPoint*> sFound;
const int np = vbInliers.size();
//更新当前帧的地图点
for(int j=0; j<np; j++)
{
if(vbInliers[j])
{
mCurrentFrame.mvpMapPoints[j]=vvpMapPointMatches[i][j];
sFound.insert(vvpMapPointMatches[i][j]);
}
else
mCurrentFrame.mvpMapPoints[j]=NULL;
}
//位姿优化
int nGood = Optimizer::PoseOptimization(&mCurrentFrame);
//内点太少了,直接退出
if(nGood<10)
continue;
//根据位姿优化后的内点,更新当前帧的地图点
for(int io =0; io<mCurrentFrame.N; io++)
if(mCurrentFrame.mvbOutlier[io])
mCurrentFrame.mvpMapPoints[io]=static_cast<MapPoint*>(NULL);
.....
}
注意:sFound
赋值是在PnP后,而不在优化后,说明sFound
为包含了PnP的所有内点的集合。为啥不在优化后赋值,主要是为了防止通过SearchByProjection
匹配搜索到新的匹配为优化后的外点,那么再位姿优化,内点可能无法达标。
第一次尝试:
- 因为内点数不够
50
,那么尝试通过SearchByProjection
将关键帧中不属于当前帧的地图点投影到当前帧中获得一些新的匹配,新的匹配数为nadditional
。 - 如果新的匹配+内点数大于50,那么可以再用
PoseOptimization
优化当前帧的位姿试试 - 这时候判断下是否成功,如果优化后的内点数仍不足
50
而在[30, 50]
之间,那么说明离匹配不远可以再试试
if(nGood<50)
{
int nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,10,100);
if(nadditional+nGood>=50)
{
nGood = Optimizer::PoseOptimization(&mCurrentFrame);
}
}
第二次尝试
- 因为当前帧的位姿已经优化了很多次,而内点离
50
很近了,说明当前帧的位姿更准,所以缩小搜索窗口,通过SearchByProjection
在更狭窄的窗口上进行搜索得到额外的匹配点数。 - 如果新的匹配+内点数大于50,那么可以再用
PoseOptimization
优化当前帧的位姿试试 - 如果内点大于
50
,那么匹配成功
if(nGood>30 && nGood<50)
{
// 用更小窗口、更严格的描述子阈值,重新进行投影搜索匹配
sFound.clear();
for(int ip =0; ip<mCurrentFrame.N; ip++)
if(mCurrentFrame.mvpMapPoints[ip])
sFound.insert(mCurrentFrame.mvpMapPoints[ip]);
nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,3,64);
// Final optimization
if(nGood+nadditional>=50)
{
nGood = Optimizer::PoseOptimization(&mCurrentFrame);
for(int io =0; io<mCurrentFrame.N; io++)
if(mCurrentFrame.mvbOutlier[io])
mCurrentFrame.mvpMapPoints[io]=NULL;
}
}
.....
注意:
sFound
包含了第一次尝试时当前帧的内点以及额外搜索到的新的匹配- 第一尝试优化后没有利用内点更新当前帧的地图点,即使匹配成功,而第二次则有(不太合理)
DetectRelocalizationCandidates
DetectRelocalizationCandidates
的作用是通过词袋模糊搜索在已有的关键帧中查找和当前帧最接近的候选帧,其函数接口如下:
vector<KeyFrame *> KeyFrameDatabase::DetectRelocalizationCandidates(Frame *F, Map *pMap)
- 遍历当前帧中所有的
mBowVec
,每个word由WordId
和WordValue
组成,利用倒排索引mvInvertedFile
得到拥有相同Bow
的关键帧,并统计相同单词的数量
list<KeyFrame *> lKFsSharingWords;
// Search all keyframes that share a word with current frame
{
unique_lock<mutex> lock(mMutex);
for (DBoW2::BowVector::const_iterator vit = F->mBowVec.begin(), vend = F->mBowVec.end(); vit != vend; vit++)
{
list<KeyFrame *> &lKFs = mvInvertedFile[vit->first];
for (list<KeyFrame *>::iterator lit = lKFs.begin(), lend = lKFs.end(); lit != lend; lit++)
{
KeyFrame *pKFi = *lit;
if (pKFi->mnRelocQuery != F->mnId)
{
pKFi->mnRelocWords = 0;
pKFi->mnRelocQuery = F->mnId;
lKFsSharingWords.push_back(pKFi);
}
pKFi->mnRelocWords++;
}
}
}
if (lKFsSharingWords.empty())
return vector<KeyFrame *>();
- 获得最大最小公共单词数,得到: m i n C o m m o n W o r d s = m a x C o m m o n W o r d s ⋅ 0.8 f minCommonWords = maxCommonWords \cdot 0.8f minCommonWords=maxCommonWords⋅0.8f
int maxCommonWords = 0;
for (list<KeyFrame *>::iterator lit = lKFsSharingWords.begin(), lend = lKFsSharingWords.end(); lit != lend; lit++)
{
if ((*lit)->mnRelocWords > maxCommonWords)
maxCommonWords = (*lit)->mnRelocWords;
}
int minCommonWords = maxCommonWords * 0.8f;
- 遍历所有具有公共单词的关键帧, 过滤掉公共单词数小于
minCommonWords
的关键帧,并计算关键帧与当前帧mBowVec
的相似性score
for (list<KeyFrame *>::iterator lit = lKFsSharingWords.begin(), lend = lKFsSharingWords.end(); lit != lend; lit++)
{
KeyFrame *pKFi = *lit;
if (pKFi->mnRelocWords > minCommonWords)
{
nscores++;
float si = mpVoc->score(F->mBowVec, pKFi->mBowVec);
pKFi->mRelocScore = si;
lScoreAndMatch.push_back(make_pair(si, pKFi));
}
}
- 每个关键帧和最佳的10个共视关键帧组队,得到每组中分数最大的关键帧和组的总分数
list<pair<float, KeyFrame *>> lAccScoreAndMatch;
float bestAccScore = 0;
// Lets now accumulate score by covisibility
for (list<pair<float, KeyFrame *>>::iterator it = lScoreAndMatch.begin(), itend = lScoreAndMatch.end(); it != itend; it++)
{
KeyFrame *pKFi = it->second;
vector<KeyFrame *> vpNeighs = pKFi->GetBestCovisibilityKeyFrames(10);
float bestScore = it->first;
float accScore = bestScore;
KeyFrame *pBestKF = pKFi;
for (vector<KeyFrame *>::iterator vit = vpNeighs.begin(), vend = vpNeighs.end(); vit != vend; vit++)
{
KeyFrame *pKF2 = *vit;
if (pKF2->mnRelocQuery != F->mnId)
continue;
accScore += pKF2->mRelocScore;
if (pKF2->mRelocScore > bestScore)
{
pBestKF = pKF2;
bestScore = pKF2->mRelocScore;
}
}
lAccScoreAndMatch.push_back(make_pair(accScore, pBestKF));
if (accScore > bestAccScore)
bestAccScore = accScore;
}
- 过滤掉组的总分数不合格的关键帧,得到最终的结果
float minScoreToRetain = 0.75f * bestAccScore;
set<KeyFrame *> spAlreadyAddedKF;
vector<KeyFrame *> vpRelocCandidates;
vpRelocCandidates.reserve(lAccScoreAndMatch.size());
for (list<pair<float, KeyFrame *>>::iterator it = lAccScoreAndMatch.begin(), itend = lAccScoreAndMatch.end(); it != itend; it++)
{
const float &si = it->first;
if (si > minScoreToRetain)
{
KeyFrame *pKFi = it->second;
if (pKFi->GetMap() != pMap)
continue;
if (!spAlreadyAddedKF.count(pKFi))
{
vpRelocCandidates.push_back(pKFi);
spAlreadyAddedKF.insert(pKFi);
}
}
}
SearchByBoW
SearchByBoW
主要是通过词袋搜索当前帧与候选关键帧的匹配,参考通过SearchByBoW加速当前帧与参考帧之间的特征匹配,其接口为:
int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches)
- 构造旋转直方图
// 特征点角度旋转差统计用的直方图
vector<int> rotHist[HISTO_LENGTH];
for(int i=0;i<HISTO_LENGTH;i++)
rotHist[i].reserve(500);
// 将0~360的数转换到0~HISTO_LENGTH的系数
//! 原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码
// const float factor = HISTO_LENGTH/360.0f;
const float factor = 1.0f/HISTO_LENGTH;
- 对于
pKF
与F
的FeatureVector
,对属于同一节点的ORB特征进行匹配
为什么用FeatureVector
能加快搜索?从词汇树中可以看出,Node
相对于Word
为更加抽象的簇,一个Node
下包含了许多的相似的Word
。如果想比较两个东西,那么先用抽象特征进行粗筛,然后再逐步到具体的特征。两帧图像特征匹配类似,先对比FeatureVector
中的Node
,如果Node
为同一节点,再用节点下features
进行匹配,这样避免了所有特征点之间的两两匹配
// 取出关键帧的词袋特征向量
const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;
// We perform the matching over ORB that belong to the same vocabulary node (at a certain level)
// 将属于同一节点的ORB特征进行匹配
DBoW2::FeatureVector::const_iterator KFit = vFeatVecKF.begin();
DBoW2::FeatureVector::const_iterator Fit = F.mFeatVec.begin();
DBoW2::FeatureVector::const_iterator KFend = vFeatVecKF.end();
DBoW2::FeatureVector::const_iterator Fend = F.mFeatVec.end();
while(KFit != KFend && Fit != Fend)
{
// Step 1:分别取出属于同一node的ORB特征点
if(KFit->first == Fit->first)
{
...
KFit++;
Fit++;
}
else if(KFit->first < Fit->first)
{
// 对齐
KFit = vFeatVecKF.lower_bound(Fit->first);
}
else
{
// 对齐
Fit = F.mFeatVec.lower_bound(KFit->first);
}
}
- 对同一node,用
KF
中地图点对应的ORB特征点与F
中的ORB特征点两两匹配,其条件:- 不能重复匹配
vpMapPointMatches
:如果vpMapPointMatches[realIdxF]
非NULL
,说明已有匹配了,则不能再匹配 - 最佳匹配距离小于阈值
TH_LOW
- 最佳匹配距离与次佳匹配距离的比值小于阈值
mfNNratio
- 不能重复匹配
// second 是该node内存储的feature index
const vector<unsigned int> vIndicesKF = KFit->second;
const vector<unsigned int> vIndicesF = Fit->second;
// Step 2:遍历KF中属于该node的特征点
for(size_t iKF=0; iKF<vIndicesKF.size(); iKF++)
{
// 关键帧该节点中特征点的索引
const unsigned int realIdxKF = vIndicesKF[iKF];
// 取出KF中该特征对应的地图点
MapPoint* pMP = vpMapPointsKF[realIdxKF];
if(!pMP)
continue;
if(pMP->isBad())
continue;
// 取出关键帧KF中该特征对应的描述子
const cv::Mat &dKF= pKF->mDescriptors.row(realIdxKF);
int bestDist1=256; // 最好的距离(最小距离)
int bestIdxF =-1 ;
int bestDist2=256; // 次好距离(倒数第二小距离)
int bestDist1R=256;
int bestIdxFR =-1 ;
int bestDist2R=256;
// Step 3:遍历F中属于该node的特征点,寻找最佳匹配点
for(size_t iF=0; iF<vIndicesF.size(); iF++)
{
if(F.Nleft == -1){
// 这里的realIdxF是指普通帧该节点中特征点的索引
const unsigned int realIdxF = vIndicesF[iF];
// 如果地图点存在,说明这个点已经被匹配过了,不再匹配,加快速度
if(vpMapPointMatches[realIdxF])
continue;
// 取出普通帧F中该特征对应的描述子
const cv::Mat &dF = F.mDescriptors.row(realIdxF);
// 计算描述子的距离
const int dist = DescriptorDistance(dKF,dF);
// 遍历,记录最佳距离、最佳距离对应的索引、次佳距离等
// 如果 dist < bestDist1 < bestDist2,更新bestDist1 bestDist2
if(dist<bestDist1)
{
bestDist2=bestDist1;
bestDist1=dist;
bestIdxF=realIdxF;
}
// 如果bestDist1 < dist < bestDist2,更新bestDist2
else if(dist<bestDist2)
{
bestDist2=dist;
}
}
else{
const unsigned int realIdxF = vIndicesF[iF];
if(vpMapPointMatches[realIdxF])
continue;
const cv::Mat &dF = F.mDescriptors.row(realIdxF);
const int dist = DescriptorDistance(dKF,dF);
if(realIdxF < F.Nleft && dist<bestDist1){
bestDist2=bestDist1;
bestDist1=dist;
bestIdxF=realIdxF;
}
else if(realIdxF < F.Nleft && dist<bestDist2){
bestDist2=dist;
}
if(realIdxF >= F.Nleft && dist<bestDist1R){
bestDist2R=bestDist1R;
bestDist1R=dist;
bestIdxFR=realIdxF;
}
else if(realIdxF >= F.Nleft && dist<bestDist2R){
bestDist2R=dist;
}
}
}
// Step 4:根据阈值 和 角度投票剔除误匹配
// Step 4.1:第一关筛选:匹配距离必须小于设定阈值
if(bestDist1<=TH_LOW)
{
// Step 4.2:第二关筛选:最佳匹配比次佳匹配明显要好,那么最佳匹配才真正靠谱
if(static_cast<float>(bestDist1)<mfNNratio*static_cast<float>(bestDist2))
{
// Step 4.3:记录成功匹配特征点的对应的地图点(来自关键帧)
vpMapPointMatches[bestIdxF]=pMP;
// 这里的realIdxKF是当前遍历到的关键帧的特征点id
const cv::KeyPoint &kp =
(!pKF->mpCamera2) ? pKF->mvKeysUn[realIdxKF] :
(realIdxKF >= pKF -> NLeft) ? pKF -> mvKeysRight[realIdxKF - pKF -> NLeft]
: pKF -> mvKeys[realIdxKF];
// Step 4.4:计算匹配点旋转角度差所在的直方图
if(mbCheckOrientation)
{
cv::KeyPoint &Fkp =
(!pKF->mpCamera2 || F.Nleft == -1) ? F.mvKeys[bestIdxF] :
(bestIdxF >= F.Nleft) ? F.mvKeysRight[bestIdxF - F.Nleft]
: F.mvKeys[bestIdxF];
// 所有的特征点的角度变化应该是一致的,通过直方图统计得到最准确的角度变化值
float rot = kp.angle-Fkp.angle;
if(rot<0.0)
rot+=360.0f;
int bin = round(rot*factor);// 将rot分配到bin组, 四舍五入, 其实就是离散到对应的直方图组中
if(bin==HISTO_LENGTH)
bin=0;
assert(bin>=0 && bin<HISTO_LENGTH);
rotHist[bin].push_back(bestIdxF);
}
nmatches++;
}
if(bestDist1R<=TH_LOW)
{
if(static_cast<float>(bestDist1R)<mfNNratio*static_cast<float>(bestDist2R) || true)
{
vpMapPointMatches[bestIdxFR]=pMP;
const cv::KeyPoint &kp =
(!pKF->mpCamera2) ? pKF->mvKeysUn[realIdxKF] :
(realIdxKF >= pKF -> NLeft) ? pKF -> mvKeysRight[realIdxKF - pKF -> NLeft]
: pKF -> mvKeys[realIdxKF];
if(mbCheckOrientation)
{
cv::KeyPoint &Fkp =
(!F.mpCamera2) ? F.mvKeys[bestIdxFR] :
(bestIdxFR >= F.Nleft) ? F.mvKeysRight[bestIdxFR - F.Nleft]
: F.mvKeys[bestIdxFR];
float rot = kp.angle-Fkp.angle;
if(rot<0.0)
rot+=360.0f;
int bin = round(rot*factor);
if(bin==HISTO_LENGTH)
bin=0;
assert(bin>=0 && bin<HISTO_LENGTH);
rotHist[bin].push_back(bestIdxFR);
}
nmatches++;
}
}
}
}
- 旋转一致检测,剔除不一致的匹配
// Step 5 根据方向剔除误匹配的点
if(mbCheckOrientation)
{
// index
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++)
{
vpMapPointMatches[rotHist[i][j]]=static_cast<MapPoint*>(NULL);
nmatches--;
}
}
}
SearchByProjection
SearchByProjection
的主要作用是通过将关键帧中的地图点投影到当前帧寻找两帧之间新的匹配,关键帧中的地图点不在sAlreadyFound
中
- 构造旋转直方图
vector<int> rotHist[HISTO_LENGTH];
for(int i=0;i<HISTO_LENGTH;i++)
rotHist[i].reserve(500);
const float factor = 1.0f/HISTO_LENGTH;
- 将关键帧中的地图点投影到当前帧寻找匹配,需要满足如下要求:
- 该地图点不在
sAlreadyFound
中 - 深度 d i s t 3 D = n o r m ( X w − O w ) \mathbf{dist3D}=\mathbf{norm}\left(X_w-O_w\right) dist3D=norm(Xw−Ow) 必须在 [ m i n D i s t a n c e , m a x D i s t a n c e ] [\mathbf{minDistance}, \mathbf{maxDistance}] [minDistance,maxDistance]之内
- 根据地图点在当前帧预测的尺度搜索候选匹配点
- 最佳匹配距离小于阈值
ORBdist
- 计算旋转直方图
- 该地图点不在
for(size_t i=0, iend=vpMPs.size(); i<iend; i++)
{
MapPoint* pMP = vpMPs[i];
if(pMP)
{
if(!pMP->isBad() && !sAlreadyFound.count(pMP))
{
//Project
Eigen::Vector3f x3Dw = pMP->GetWorldPos();
Eigen::Vector3f x3Dc = Tcw * x3Dw;
const Eigen::Vector2f uv = CurrentFrame.mpCamera->project(x3Dc);
if(uv(0)<CurrentFrame.mnMinX || uv(0)>CurrentFrame.mnMaxX)
continue;
if(uv(1)<CurrentFrame.mnMinY || uv(1)>CurrentFrame.mnMaxY)
continue;
// Compute predicted scale level
Eigen::Vector3f PO = x3Dw-Ow;
float dist3D = PO.norm();
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];
// Step 3 搜索候选匹配点
const vector<size_t> vIndices2 = CurrentFrame.GetFeaturesInArea(uv(0), uv(1), radius, nPredictedLevel-1, nPredictedLevel+1);
if(vIndices2.empty())
continue;
const cv::Mat dMP = pMP->GetDescriptor();
int bestDist = 256;
int bestIdx2 = -1;
// Step 4 遍历候选匹配点,寻找距离最小的最佳匹配点
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++;
// Step 5 计算匹配点旋转角度差所在的直方图
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=0;
assert(bin>=0 && bin<HISTO_LENGTH);
rotHist[bin].push_back(bestIdx2);
}
}
}
}
}
- 旋转一致检测,剔除不一致的匹配
if(mbCheckOrientation)
{
int ind1=-1;
int ind2=-1;
int ind3=-1;
ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3);
for(int i=0; i<HISTO_LENGTH; i++)
{
if(i!=ind1 && i!=ind2 && i!=ind3)
{
for(size_t j=0, jend=rotHist[i].size(); j<jend; j++)
{
CurrentFrame.mvpMapPoints[rotHist[i][j]]=NULL;
nmatches--;
}
}
}
}