void Tracking::CheckReplacedInLastFrame()
{
for(int i =0; iGetReplaced();
if(pRep)
{
//然后替换一下
mLastFrame.mvpMapPoints[i] = pRep;
}
}
}
}
由于LocalMapping线程可能会更换关键中的地图点 ,而Tracking会用到上一帧中的地图点,因此需要检查更新上一帧被替换的地图点。
流程为: 遍历上一帧的关键帧,如果上一帧有对应的地图点,获取这个地图点是否被替代,如果被替代则更新关键点对应的地图点。
bool Tracking::TrackReferenceKeyFrame()
{
// Compute Bag of Words vector
// Step 1:将当前帧的描述子转化为BoW向量
mCurrentFrame.ComputeBoW();
// We perform first an ORB matching with the reference keyframe
// If enough matches are found we setup a PnP solver
ORBmatcher matcher(0.7,true);
vector vpMapPointMatches;
// Step 2:通过词袋BoW加速当前帧与参考帧之间的特征点匹配
int nmatches = matcher.SearchByBoW(
mpReferenceKF, //参考关键帧
mCurrentFrame, //当前帧
vpMapPointMatches); //存储匹配关系
// 匹配数目小于15,认为跟踪失败
if(nmatches<15)
return false;
// Step 3:将上一帧的位姿态作为当前帧位姿的初始值
mCurrentFrame.mvpMapPoints = vpMapPointMatches;
mCurrentFrame.SetPose(mLastFrame.mTcw); // 用上一次的Tcw设置初值,在PoseOptimization可以收敛快一些
// Step 4:通过优化3D-2D的重投影误差来获得位姿
Optimizer::PoseOptimization(&mCurrentFrame);
// Discard outliers
// Step 5:剔除优化后的匹配点中的外点
//之所以在优化之后才剔除外点,是因为在优化的过程中就有了对这些外点的标记
int nmatchesMap = 0;
for(int i =0; i(NULL);
mCurrentFrame.mvbOutlier[i]=false;
pMP->mbTrackInView = false;
pMP->mnLastFrameSeen = mCurrentFrame.mnId;
nmatches--;
}
else if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)
//匹配的内点计数++
nmatchesMap++;
}
}
// 跟踪成功的数目超过10才认为跟踪成功,否则跟踪失败
return nmatchesMap>=10;
}
该方法的流程为:
1.将当前帧的描述子转换为词袋。
2.构造ORB匹配器。
3. 通过词袋获取参考关键帧和当前帧之间的匹配关系。
4.如果匹配个数少于15个则认为匹配失败。
5.将当前帧特征点和地图点之间的匹配关系用ORBmatcher获得的匹配关系替换。
6.并且用当前帧的位置用上一帧的位置赋值,运动良好的情况下号的初值能加快优化器的迭代次数。
7.通过3D-2D之间的重投影误差来优化当前帧的位置,优化器这部分后面单独讲。
8.遍历当前帧的所有特征点,如果该特征点和地图点存在匹配关系但这个特征点是外点,就清除匹配关系,否则内点匹配计数器自增,如果最后匹配到的内点个数大于等于10个,则认为跟踪成功,否则跟踪失败。
bool Tracking::TrackLocalMap()
{
// We have an estimation of the camera pose and some map points tracked in the frame.
// We retrieve the local map and try to find matches to points in the local map.
// Update Local KeyFrames and Local Points
// Step 1:更新局部关键帧 mvpLocalKeyFrames 和局部地图点 mvpLocalMapPoints
UpdateLocalMap();
// Step 2:筛选局部地图中新增的在视野范围内的地图点,投影到当前帧搜索匹配,得到更多的匹配关系
SearchLocalPoints();
// Optimize Pose
// 在这个函数之前,在 Relocalization、TrackReferenceKeyFrame、TrackWithMotionModel 中都有位姿优化,
// Step 3:前面新增了更多的匹配关系,BA优化得到更准确的位姿
Optimizer::PoseOptimization(&mCurrentFrame);
mnMatchesInliers = 0;
// Update MapPoints Statistics
// Step 4:更新当前帧的地图点被观测程度,并统计跟踪局部地图后匹配数目
for(int i=0; iIncreaseFound();
//查看当前是否是在纯定位过程
if(!mbOnlyTracking)
{
// 如果该地图点被相机观测数目nObs大于0,匹配内点计数+1
// nObs: 被观测到的相机数目,单目+1,双目或RGB-D则+2
if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)
mnMatchesInliers++;
}
else
// 记录当前帧跟踪到的地图点数目,用于统计跟踪效果
mnMatchesInliers++;
}
// 如果这个地图点是外点,并且当前相机输入还是双目的时候,就删除这个点
// ?单目就不管吗
else if(mSensor==System::STEREO)
mCurrentFrame.mvpMapPoints[i] = static_cast(NULL);
}
}
// Decide if the tracking was succesful
// More restrictive if there was a relocalization recently
// Step 5:根据跟踪匹配数目及重定位情况决定是否跟踪成功
// 如果最近刚刚发生了重定位,那么至少成功匹配50个点才认为是成功跟踪
if(mCurrentFrame.mnId
该方法的流程为:
1. 更新局部关键帧 mvpLocalKeyFrames 和局部地图点 mvpLocalMapPoints 这里会在下面讲到
2. 筛选局部地图中新增的在视野范围内的地图点,投影到当前帧搜索匹配,得到更多的匹配关系 ,这里也是在下面单独讲
3.通过位姿优化器对当前帧进行优化
4.遍历当前帧的所有特征点,遍历当前帧的所有匹配点对(也就是上面说的特征点和地图点的匹配关系,用这个术语说更专业),如果不是外点则匹配点对的Found数+1,如果在Slam模式下,当前帧的匹配点对被观测的次数大于0,则匹配到的内点数+1,否则如果是在纯定位下,只要是内点就将匹配到的内点数+1 ,如果是双目相机,但是这个匹配点对的为外点,就直接删除这个匹配点对。
5.正常状态下,只要匹配到的内点数大于30个就认为匹配成功,但如果刚刚进行了重定位且匹配到的内点数少于50个,则返回 false
void Tracking::SearchLocalPoints()
{
// Do not search map points already matched
// Step 1:遍历当前帧的地图点,标记这些地图点不参与之后的投影搜索匹配
for(vector::iterator vit=mCurrentFrame.mvpMapPoints.begin(), vend=mCurrentFrame.mvpMapPoints.end(); vit!=vend; vit++)
{
MapPoint* pMP = *vit;
if(pMP)
{
if(pMP->isBad())
{
*vit = static_cast(NULL);
}
else
{
// 更新能观测到该点的帧数加1(被当前帧观测了)
pMP->IncreaseVisible();
// 标记该点被当前帧观测到
pMP->mnLastFrameSeen = mCurrentFrame.mnId;
// 标记该点在后面搜索匹配时不被投影,因为已经有匹配了
pMP->mbTrackInView = false;
}
}
}
// 准备进行投影匹配的点的数目
int nToMatch=0;
// Project points in frame and check its visibility
// Step 2:判断所有局部地图点中除当前帧地图点外的点,是否在当前帧视野范围内
for(vector::iterator vit=mvpLocalMapPoints.begin(), vend=mvpLocalMapPoints.end(); vit!=vend; vit++)
{
MapPoint* pMP = *vit;
// 已经被当前帧观测到的地图点肯定在视野范围内,跳过
if(pMP->mnLastFrameSeen == mCurrentFrame.mnId)
continue;
// 跳过坏点
if(pMP->isBad())
continue;
// Project (this fills MapPoint variables for matching)
// 判断地图点是否在在当前帧视野内
if(mCurrentFrame.isInFrustum(pMP,0.5))
{
// 观测到该点的帧数加1
pMP->IncreaseVisible();
// 只有在视野范围内的地图点才参与之后的投影匹配
nToMatch++;
}
}
// Step 3:如果需要进行投影匹配的点的数目大于0,就进行投影匹配,增加更多的匹配关系
if(nToMatch>0)
{
ORBmatcher matcher(0.8);
int th = 1;
if(mSensor==System::RGBD) //RGBD相机输入的时候,搜索的阈值会变得稍微大一些
th=3;
// If the camera has been relocalised recently, perform a coarser search
// 如果不久前进行过重定位,那么进行一个更加宽泛的搜索,阈值需要增大
if(mCurrentFrame.mnId
该方法的流程为:
1.遍历当前帧的匹配点对,如果匹配点对有值并且不是坏点,则p匹配点对的能观测到次数+1,将上一帧被看到的ID号写为当前帧的ID号,标记这个点在之后的操作后不重复被投影。
2.定义准备进行投影匹配点的数目写0
3.遍历局部地图中的匹配点对,如果是前面已经做过投影的点或坏点就退出本次循环,如果地图点在当前帧视野范围内,则点该匹配点对能观测到的次数+1,准备进行投影匹配点的数目+1
4.如果存在需要进行投影匹配的点,对当前帧和局部匹配点对再进行一次ORBmatcher匹配。
void Tracking::UpdateLocalMap()
{
// This is for visualization
// 设置参考地图点用于绘图显示局部地图点(红色)
mpMap->SetReferenceMapPoints(mvpLocalMapPoints);
// Update
// 用共视图来更新局部关键帧和局部地图点
UpdateLocalKeyFrames();
UpdateLocalPoints();
}
该方法的流程为:
1.将局部匹配点对加入到图像参考点对。
2.更新局部关键点和局部关键点。
void Tracking::UpdateLocalPoints()
{
// Step 1:清空局部地图点
mvpLocalMapPoints.clear();
// Step 2:遍历局部关键帧 mvpLocalKeyFrames
for(vector::const_iterator itKF=mvpLocalKeyFrames.begin(), itEndKF=mvpLocalKeyFrames.end(); itKF!=itEndKF; itKF++)
{
KeyFrame* pKF = *itKF;
const vector vpMPs = pKF->GetMapPointMatches();
// step 2:将局部关键帧的地图点添加到mvpLocalMapPoints
for(vector::const_iterator itMP=vpMPs.begin(), itEndMP=vpMPs.end(); itMP!=itEndMP; itMP++)
{
MapPoint* pMP = *itMP;
if(!pMP)
continue;
// 用该地图点的成员变量mnTrackReferenceForFrame 记录当前帧的id
// 表示它已经是当前帧的局部地图点了,可以防止重复添加局部地图点
if(pMP->mnTrackReferenceForFrame==mCurrentFrame.mnId)
continue;
if(!pMP->isBad())
{
mvpLocalMapPoints.push_back(pMP);
pMP->mnTrackReferenceForFrame=mCurrentFrame.mnId;
}
}
}
}
该方法的流程为:
1.清空局部地图点
2.遍历局部关键帧,获取关键帧的地图点,如果存在地图点,如果是这个地图点已经是当前帧的局部地图点,退出本次循环,如果是不是坏点,就把这个点加到局部地图点,并且将跟踪参考帧的ID写成当前帧的ID。
bool Tracking::Relocalization()
{
// Compute Bag of Words Vector
// Step 1:计算当前帧特征点的词袋向量
mCurrentFrame.ComputeBoW();
// Relocalization is performed when tracking is lost
// Track Lost: Query KeyFrame Database for keyframe candidates for relocalisation
// Step 2:用词袋找到与当前帧相似的候选关键帧
vector vpCandidateKFs = mpKeyFrameDB->DetectRelocalizationCandidates(&mCurrentFrame);
// 如果没有候选关键帧,则退出
if(vpCandidateKFs.empty())
return false;
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 vpPnPsolvers;
vpPnPsolvers.resize(nKFs);
//每个关键帧和当前帧中特征点的匹配关系
vector > vvpMapPointMatches;
vvpMapPointMatches.resize(nKFs);
//放弃某个关键帧的标记
vector vbDiscarded;
vbDiscarded.resize(nKFs);
//有效的候选关键帧数目
int nCandidates=0;
// Step 3:遍历所有的候选关键帧,通过词袋进行快速匹配,用匹配结果初始化PnP Solver
for(int i=0; iisBad())
vbDiscarded[i] = true;
else
{
// 当前帧和候选关键帧用BoW进行快速匹配,匹配结果记录在vvpMapPointMatches,nmatches表示匹配的数目
int nmatches = matcher.SearchByBoW(pKF,mCurrentFrame,vvpMapPointMatches[i]);
// 如果和当前帧的匹配数小于15,那么只能放弃这个关键帧
if(nmatches<15)
{
vbDiscarded[i] = true;
continue;
}
else
{
// 如果匹配数目够用,用匹配结果初始化EPnPsolver
// 为什么用EPnP? 因为计算复杂度低,精度高
PnPsolver* pSolver = new PnPsolver(mCurrentFrame,vvpMapPointMatches[i]);
pSolver->SetRansacParameters(
0.99, //用于计算RANSAC迭代次数理论值的概率
10, //最小内点数, 但是要注意在程序中实际上是min(给定最小内点数,最小集,内点数理论值),不一定使用这个
300, //最大迭代次数
4, //最小集(求解这个问题在一次采样中所需要采样的最少的点的个数,对于Sim3是3,EPnP是4),参与到最小内点数的确定过程中
0.5, //这个是表示(最小内点数/样本总数);实际上的RANSAC正常退出的时候所需要的最小内点数其实是根据这个量来计算得到的
5.991); // 自由度为2的卡方检验的阈值,程序中还会根据特征点所在的图层对这个阈值进行缩放
vpPnPsolvers[i] = pSolver;
nCandidates++;
}
}
}
// Alternatively perform some iterations of P4P RANSAC
// Until we found a camera pose supported by enough inliers
// 这里的 P4P RANSAC是Epnp,每次迭代需要4个点
// 是否已经找到相匹配的关键帧的标志
bool bMatch = false;
ORBmatcher matcher2(0.9,true);
// Step 4: 通过一系列操作,直到找到能够匹配上的关键帧
// 为什么搞这么复杂?答:是担心误闭环
while(nCandidates>0 && !bMatch)
{
//遍历当前所有的候选关键帧
for(int i=0; i vbInliers;
//内点数
int nInliers;
// 表示RANSAC已经没有更多的迭代次数可用 -- 也就是说数据不够好,RANSAC也已经尽力了。。。
bool bNoMore;
// Step 4.1:通过EPnP算法估计姿态,迭代5次
PnPsolver* pSolver = vpPnPsolvers[i];
cv::Mat Tcw = pSolver->iterate(5,bNoMore,vbInliers,nInliers);
// If Ransac reachs max. iterations discard keyframe
// bNoMore 为true 表示已经超过了RANSAC最大迭代次数,就放弃当前关键帧
if(bNoMore)
{
vbDiscarded[i]=true;
nCandidates--;
}
// If a Camera Pose is computed, optimize
if(!Tcw.empty())
{
// Step 4.2:如果EPnP 计算出了位姿,对内点进行BA优化
Tcw.copyTo(mCurrentFrame.mTcw);
// EPnP 里RANSAC后的内点的集合
set sFound;
const int np = vbInliers.size();
//遍历所有内点
for(int j=0; j(NULL);
// If few inliers, search by projection in a coarse window and optimize again
// Step 4.3:如果内点较少,则通过投影的方式对之前未匹配的点进行匹配,再进行优化求解
// 前面的匹配关系是用词袋匹配过程得到的
if(nGood<50)
{
// 通过投影的方式将关键帧中未匹配的地图点投影到当前帧中, 生成新的匹配
int nadditional = matcher2.SearchByProjection(
mCurrentFrame, //当前帧
vpCandidateKFs[i], //关键帧
sFound, //已经找到的地图点集合,不会用于PNP
10, //窗口阈值,会乘以金字塔尺度
100); //匹配的ORB描述子距离应该小于这个阈值
// 如果通过投影过程新增了比较多的匹配特征点对
if(nadditional+nGood>=50)
{
// 根据投影匹配的结果,再次采用3D-2D pnp BA优化位姿
nGood = Optimizer::PoseOptimization(&mCurrentFrame);
// If many inliers but still not enough, search by projection again in a narrower window
// the camera has been already optimized with many points
// Step 4.4:如果BA后内点数还是比较少(<50)但是还不至于太少(>30),可以挽救一下, 最后垂死挣扎
// 重新执行上一步 4.3的过程,只不过使用更小的搜索窗口
// 这里的位姿已经使用了更多的点进行了优化,应该更准,所以使用更小的窗口搜索
if(nGood>30 && nGood<50)
{
// 用更小窗口、更严格的描述子阈值,重新进行投影搜索匹配
sFound.clear();
for(int ip =0; ip=50)
{
nGood = Optimizer::PoseOptimization(&mCurrentFrame);
//更新地图点
for(int io =0; io=50)
{
bMatch = true;
// 只要有一个候选关键帧重定位成功,就退出循环,不考虑其他候选关键帧了
break;
}
}
}//一直运行,知道已经没有足够的关键帧,或者是已经有成功匹配上的关键帧
}
// 折腾了这么久还是没有匹配上,重定位失败
if(!bMatch)
{
return false;
}
else
{
// 如果匹配上了,说明当前帧重定位成功了(当前帧已经有了自己的位姿)
// 记录成功重定位帧的id,防止短时间多次重定位
mnLastRelocFrameId = mCurrentFrame.mnId;
return true;
}
}
该方法的流程为:
1.将当前帧的描述子转换为词袋。
2.用词袋找到与当前帧相似的候选关键帧
3.没有候选关键帧,则退出
4.构造ORB匹配器,遍历所有关键帧,通过词袋快速匹配,将匹配结果作为PNP的初值,具体实现在PnPsolver.cc内,到时候单独讲
5.如果有有效关键帧并且能匹配上,则遍历所有候选关键帧,再通过EPNP算法进行进行迭代,如果能够算出位置,再做位姿优化,如果内点数量足够多则退出循环,不够多则再使用ORBmatcher进行搜索,如果效果不理想再使用更小的搜索窗口重新进行投影搜索匹配,再做位姿优化。
6.最后将上一帧重定位的帧ID设置成当前帧ID。
void Tracking::Reset()
{
//基本上是挨个请求各个线程终止
if(mpViewer)
{
mpViewer->RequestStop();
while(!mpViewer->isStopped())
std::this_thread::sleep_for(std::chrono::milliseconds(3));
}
cout << "System Reseting" << endl;
// Reset Local Mapping
cout << "Reseting Local Mapper...";
mpLocalMapper->RequestReset();
cout << " done" << endl;
// Reset Loop Closing
cout << "Reseting Loop Closing...";
mpLoopClosing->RequestReset();
cout << " done" << endl;
// Clear BoW Database
cout << "Reseting Database...";
mpKeyFrameDB->clear();
cout << " done" << endl;
// Clear Map (this erase MapPoints and KeyFrames)
mpMap->clear();
//然后复位各种变量
KeyFrame::nNextId = 0;
Frame::nNextId = 0;
mState = NO_IMAGES_YET;
if(mpInitializer)
{
delete mpInitializer;
mpInitializer = static_cast(NULL);
}
mlRelativeFramePoses.clear();
mlpReferences.clear();
mlFrameTimes.clear();
mlbLost.clear();
if(mpViewer)
mpViewer->Release();
}
该方法流程为:
1.结束显示线程
2.复位局部建图线程
3.复位闭环线程
4.清空词袋数据
5.清除地图
6.清除下一个关键帧ID,下一帧ID,状态为无图,删除初始器,情除所有参考帧位姿,清除所有参考帧,清除所有帧时间戳,清除跟丢标志
void Tracking::ChangeCalibration(const string &strSettingPath)
{
cv::FileStorage fSettings(strSettingPath, cv::FileStorage::READ);
float fx = fSettings["Camera.fx"];
float fy = fSettings["Camera.fy"];
float cx = fSettings["Camera.cx"];
float cy = fSettings["Camera.cy"];
cv::Mat K = cv::Mat::eye(3,3,CV_32F);
K.at(0,0) = fx;
K.at(1,1) = fy;
K.at(0,2) = cx;
K.at(1,2) = cy;
K.copyTo(mK);
cv::Mat DistCoef(4,1,CV_32F);
DistCoef.at(0) = fSettings["Camera.k1"];
DistCoef.at(1) = fSettings["Camera.k2"];
DistCoef.at(2) = fSettings["Camera.p1"];
DistCoef.at(3) = fSettings["Camera.p2"];
const float k3 = fSettings["Camera.k3"];
if(k3!=0)
{
DistCoef.resize(5);
DistCoef.at(4) = k3;
}
DistCoef.copyTo(mDistCoef);
mbf = fSettings["Camera.bf"];
//做标记,表示在初始化帧的时候将会是第一个帧,要对它进行一些特殊的初始化操作
Frame::mbInitialComputations = true;
}
主要更新内参和畸变参数,这个简单不讲了。
void Tracking::InformOnlyTracking(const bool &flag)
{
mbOnlyTracking = flag;
}