ORB

您所在的位置:网站首页 ff14g12地图点 ORB

ORB

2022-12-30 06:35| 来源: 网络整理| 查看: 265

pdf版本笔记的下载地址: ORB-SLAM2代码详解03_地图点MapPoint,排版更美观一点,这个网站的默认排版太丑了(访问密码:3834)

ORB-SLAM2代码详解03: 地图点MapPoint 各成员函数/变量地图点的世界坐标: `mWorldPos`与关键帧的观测关系: `mObservations`观测尺度平均观测距离: `mfMinDistance`和`mfMaxDistance`更新平均观测方向和距离: `UpdateNormalAndDepth()` 特征描述子地图点的删除与替换地图点的删除: `SetBadFlag()`地图点的替换: `Replace()` `MapPoint`类的用途`MapPoint`的生命周期

可以看看我录制的视频5小时让你假装大概看懂ORB-SLAM2源码

请添加图片描述

各成员函数/变量 地图点的世界坐标: mWorldPos 成员函数/变量访问控制意义cv::Mat mWorldPosprotected地图点的世界坐标cv::Mat GetWorldPos()publicmWorldPos的get方法void SetWorldPos(const cv::Mat &Pos)publicmWorldPos的set方法std::mutex mMutexPosprotectedmWorldPos的锁 与关键帧的观测关系: mObservations 成员函数/变量访问控制意义std::map mObservationsprotected当前地图点在某KeyFrame中的索引map GetObservations()publicmObservations的get方法void AddObservation(KeyFrame* pKF,size_t idx)public添加当前地图点对某KeyFrame的观测void EraseObservation(KeyFrame* pKF)public删除当前地图点对某KeyFrame的观测bool IsInKeyFrame(KeyFrame* pKF)public查询当前地图点是否在某KeyFrame中int GetIndexInKeyFrame(KeyFrame* pKF)public查询当前地图点在某KeyFrame中的索引int nObspublic记录当前地图点被多少相机观测到单目帧每次观测加1,双目帧每次观测加2int Observations()publicnObs的get方法

成员变量std::map mObservations保存了当前关键点对关键帧KeyFrame的观测关系,std::map是一个key-value结构,其key为某个关键帧,value为当前地图点在该关键帧中的索引(是在该关键帧成员变量std::vector mvpMapPoints中的索引).

成员int nObs记录了当前地图点被多少个关键帧相机观测到了(单目关键帧每次观测算1个相机,双目/RGBD帧每次观测算2个相机).

函数AddObservation()和EraseObservation()同时维护mObservations和nObs

// 向参考帧pKF中添加对本地图点的观测,本地图点在pKF中的编号为idx void MapPoint::AddObservation(KeyFrame* pKF, size_t idx) { unique_lock lock(mMutexFeatures); // 如果已经添加过观测,返回 if(mObservations.count(pKF)) return; // 如果没有添加过观测,记录下能观测到该MapPoint的KF和该MapPoint在KF中的索引 mObservations[pKF]=idx; // 根据观测形式是单目还是双目更新观测计数变量nObs if(pKF->mvuRight[idx]>=0) nObs += 2; else nObs++; } // 从参考帧pKF中移除本地图点 void MapPoint::EraseObservation(KeyFrame* pKF) { bool bBad=false; { unique_lock lock(mMutexFeatures); // 查找这个要删除的观测,根据单目和双目类型的不同从其中删除当前地图点的被观测次数 if(mObservations.count(pKF)) { if(pKF->mvuRight[mObservations[pKF]]>=0) nObs-=2; else nObs--; mObservations.erase(pKF); // 如果该keyFrame是参考帧,该Frame被删除后重新指定RefFrame if(mpRefKF == pKF) mpRefKF = mObservations.begin()->first; // ????参考帧指定得这么草率真的好么? // 当观测到该点的相机数目少于2时,丢弃该点(至少需要两个观测才能三角化) if(nObs unique_lock lock(mMutexFeatures); return (mObservations.count(pKF)); } 观测尺度 成员函数/变量访问控制意义cv::Mat mNormalVectorprotected平均观测方向float mfMinDistanceprotected平均观测距离的下限float mfMaxDistanceprotected平均观测距离的上限cv::Mat GetNormal()publicmNormalVector的get方法float GetMinDistanceInvariance()publicmfMinDistance的get方法float GetMaxDistanceInvariance()publicmNormalVector的get方法void UpdateNormalAndDepth()public更新平均观测距离和方向int PredictScale(const float ¤tDist, KeyFrame* pKF)int PredictScale(const float ¤tDist, Frame* pF)publicpublic估计当前地图点在某Frame中对应特征点的金字塔层级KeyFrame* mpRefKFprotected当前地图点的参考关键帧KeyFrame* GetReferenceKeyFrame()publicmpRefKF的get方法 平均观测距离: mfMinDistance和mfMaxDistance

特征点的观测距离与其在图像金字塔中的图层呈线性关系.直观上理解,如果一个图像区域被放大后才能识别出来,说明该区域的观测深度较深.

特征点的平均观测距离的上下限由成员变量mfMaxDistance和mfMinDistance表示:

mfMaxDistance表示若地图点匹配在某特征提取器图像金字塔第7层上的某特征点,观测距离值mfMinDistance表示若地图点匹配在某特征提取器图像金字塔第0层上的某特征点,观测距离值

这两个变量是基于地图点在其参考关键帧上的观测得到的.

请添加图片描述

// pFrame是当前MapPoint的参考帧 const int level = pFrame->mvKeysUn[idxF].octave; const float levelScaleFactor = pFrame->mvScaleFactors[level]; const int nLevels = pFrame->mnScaleLevels; mfMaxDistance = dist*levelScaleFactor; mfMinDistance = mfMaxDistance/pFrame->mvScaleFactors[nLevels-1];

函数int PredictScale(const float ¤tDist, KeyFrame* pKF)和int PredictScale(const float ¤tDist, Frame* pF)根据某地图点到某帧的观测深度估计其在该帧图片上的层级,是上述过程的逆运算.

请添加图片描述 c u r r e n t D i s t m f M a x D i s t a n c e = 1. 2 l e v e l l e v e l = ⌈ l o g 1.2 ( c u r r e n t D i s t m f M a x D i s t a n c e ) ⌉ \frac{currentDist}{mfMaxDistance} = 1.2 ^ {level}\\ level = \lceil log_{1.2}(\frac{currentDist}{mfMaxDistance}) \rceil mfMaxDistancecurrentDist​=1.2levellevel=⌈log1.2​(mfMaxDistancecurrentDist​)⌉

int MapPoint::PredictScale(const float ¤tDist, KeyFrame* pKF) { float ratio; { unique_lock lock(mMutexPos); ratio = mfMaxDistance/currentDist; } int nScale = ceil(log(ratio)/pKF->mfLogScaleFactor); if(nScale=pKF->mnScaleLevels) nScale = pKF->mnScaleLevels-1; return nScale; } 更新平均观测方向和距离: UpdateNormalAndDepth()

函数UpdateNormalAndDepth()更新当前地图点的平均观测方向和距离,其中平均观测方向是根据mObservations中所有观测到本地图点的关键帧取平均得到的;平均观测距离是根据参考关键帧得到的.

void MapPoint::UpdateNormalAndDepth() { // step1. 获取地图点相关信息 map observations; KeyFrame *pRefKF; cv::Mat Pos; { unique_lock lock1(mMutexFeatures); unique_lock lock2(mMutexPos); observations = mObservations; pRefKF = mpRefKF; Pos = mWorldPos.clone(); } // step2. 根据观测到但钱地图点的关键帧取平均计算平均观测方向 cv::Mat normal = cv::Mat::zeros(3, 1, CV_32F); int n = 0; for (KeyFrame *pKF : observations.begin()) { normal = normal + normali / cv::norm(mWorldPos - pKF->GetCameraCenter()); n++; } // step3. 根据参考帧计算平均观测距离 cv::Mat PC = Pos - pRefKF->GetCameraCenter(); const float dist = cv::norm(PC); const int level = pRefKF->mvKeysUn[observations[pRefKF]].octave; const float levelScaleFactor = pRefKF->mvScaleFactors[level]; const int nLevels = pRefKF->mnScaleLevels; { unique_lock lock3(mMutexPos); mfMaxDistance = dist * levelScaleFactor; mfMinDistance = mfMaxDistance / pRefKF->mvScaleFactors[nLevels - 1]; mNormalVector = normal / n; } }

地图点的平均观测距离是根据其参考关键帧计算的,那么参考关键帧KeyFrame* mpRefKF是如何指定的呢?

构造函数中,创建该地图点的参考帧被设为参考关键帧.

若当前地图点对参考关键帧的观测被删除(EraseObservation(KeyFrame* pKF)),则取第一个观测到当前地图点的关键帧做参考关键帧.

函数MapPoint::UpdateNormalAndDepth()的调用时机:

创建地图点时调用UpdateNormalAndDepth()初始化其观测信息.

pNewMP->AddObservation(pKF, i); pKF->AddMapPoint(pNewMP, i); pNewMP->ComputeDistinctiveDescriptors(); pNewMP->UpdateNormalAndDepth(); // 更新平均观测方向和距离 mpMap->AddMapPoint(pNewMP);

地图点对关键帧的观测mObservations更新时(跟踪局部地图添加或删除对关键帧的观测时、LocalMapping线程删除冗余关键帧时或**LoopClosing线程闭环矫正**时),调用UpdateNormalAndDepth()初始化其观测信息.

pMP->AddObservation(mpCurrentKeyFrame, i); pMP->UpdateNormalAndDepth();

地图点世界坐标mWorldPos发生变化时(BA优化之后),调用UpdateNormalAndDepth()初始化其观测信息.

pMP->SetWorldPos(cvCorrectedP3Dw); pMP->UpdateNormalAndDepth();

总结成一句话: 只要地图点本身或关键帧对该地图点的观测发生变化,就应该调用函数MapPoint::UpdateNormalAndDepth()更新其观测尺度和方向信息.

特征描述子 成员函数/变量访问控制意义cv::Mat mDescriptorprotected当前关键点的特征描述子(所有描述子的中位数)cv::Mat GetDescriptor()publicmDescriptor的get方法void ComputeDistinctiveDescriptors()public计算mDescriptor

一个地图点在不同关键帧中对应不同的特征点和描述子,其特征描述子mDescriptor是其在所有观测关键帧中描述子的中位数(准确地说,该描述子与其他所有描述子的中值距离最小).

特征描述子的更新时机:

一旦某地图点对关键帧的观测mObservations发生改变,就调用函数MapPoint::ComputeDistinctiveDescriptors()更新该地图点的特征描述子.

特征描述子的用途:

在函数ORBmatcher::SearchByProjection()和ORBmatcher::Fuse()中,通过比较地图点的特征描述子与图片特征点描述子,实现将地图点与图像特征点的匹配(3D-2D匹配).

地图点的删除与替换 成员函数/变量访问控制意义bool mbBadprotected坏点标记bool isBad()public查询当前地图点是否被删除(本质上就是查询mbBad)void SetBadFlag()public删除当前地图点MapPoint* mpReplacedprotected用来替换当前地图点的新地图点void Replace(MapPoint *pMP)public使用地图点pMP替换当前地图点 地图点的删除: SetBadFlag()

变量mbBad用来表征当前地图点是否被删除.

删除地图点的各成员变量是一个较耗时的过程,因此函数SetBadFlag()删除关键点时采取先标记再清除的方式,具体的删除过程分为以下两步:

先将坏点标记mbBad置为true,逻辑上删除该地图点.(地图点的社会性死亡)再依次清空当前地图点的各成员变量,物理上删除该地图点.(地图点的肉体死亡)

这样只有在设置坏点标记mbBad时需要加锁,之后的操作就不需要加锁了.

void MapPoint::SetBadFlag() { map obs; { unique_lock lock1(mMutexFeatures); unique_lock lock2(mMutexPos); mbBad = true; // 标记mbBad,逻辑上删除当前地图点 obs = mObservations; mObservations.clear(); } // 删除关键帧对当前地图点的观测 for (KeyFrame *pKF : obs.begin()) { pKF->EraseMapPointMatch(mit->second); } // 在地图类上注册删除当前地图点,这里会发生内存泄漏 mpMap->EraseMapPoint(this); }

成员变量mbBad表示当前地图点逻辑上是否被删除,在后面用到地图点的地方,都要通过isBad()函数确认当前地图点没有被删除,再接着进行其它操作.

int KeyFrame::TrackedMapPoints(const int &minObs) { // ... for (int i = 0; i // 依次检查该地图点物理上和逻辑上是否删除,若删除了就不对其操作 // ... } } // ... } 地图点的替换: Replace()

函数Replace(MapPoint* pMP)将当前地图点的成员变量叠加到新地图点pMP上.

void MapPoint::Replace(MapPoint *pMP) { // 如果是同一地图点则跳过 if (pMP->mnId == this->mnId) return; // step1. 逻辑上删除当前地图点 int nvisible, nfound; map obs; { unique_lock lock1(mMutexFeatures); unique_lock lock2(mMutexPos); obs = mObservations; mObservations.clear(); mbBad = true; nvisible = mnVisible; nfound = mnFound; mpReplaced = pMP; } // step2. 将当地图点的数据叠加到新地图点上 for (map::iterator mit = obs.begin(), mend = obs.end(); mit != mend; mit++) { KeyFrame *pKF = mit->first; if (!pMP->IsInKeyFrame(pKF)) { pKF->ReplaceMapPointMatch(mit->second, pMP); pMP->AddObservation(pKF, mit->second); } else { pKF->EraseMapPointMatch(mit->second); } } pMP->IncreaseFound(nfound); pMP->IncreaseVisible(nvisible); pMP->ComputeDistinctiveDescriptors(); // step3. 删除当前地图点 mpMap->EraseMapPoint(this); } MapPoint类的用途 MapPoint的生命周期

针对MapPoint的生命周期,我们关心以下3个问题:

请添加图片描述

创建MapPoint的时机:

Tracking线程中初始化过程(Tracking::MonocularInitialization()和Tracking::StereoInitialization())Tracking线程中创建新的关键帧(Tracking::CreateNewKeyFrame())Tracking线程中恒速运动模型跟踪(Tracking::TrackWithMotionModel())也会产生临时地图点,但这些临时地图点在跟踪成功后会被马上删除(那跟踪失败怎么办?跟踪失败的话不会产生关键帧,这些地图点也不会被注册进地图).LocalMapping线程中创建新地图点的步骤(LocalMapping::CreateNewMapPoints())会将当前关键帧与前一关键帧进行匹配,生成新地图点.

删除MapPoint的时机:

LocalMapping线程中删除恶劣地图点的步骤(LocalMapping::MapPointCulling()).删除关键帧的函数KeyFrame::SetBadFlag()会调用函数MapPoint::EraseObservation()删除地图点对关键帧的观测,若地图点对关键帧的观测少于2,则地图点无法被三角化,就删除该地图点.

替换MapPoint的时机:

LoopClosing线程中闭环矫正(LoopClosing::CorrectLoop())时当前关键帧和闭环关键帧上的地图点发生冲突时,会使用闭环关键帧的地图点替换当前关键帧的地图点.LoopClosing线程中闭环矫正函数LoopClosing::CorrectLoop()会调用LoopClosing::SearchAndFuse()将闭环关键帧的共视关键帧组中所有地图点投影到当前关键帧的共视关键帧组中,发生冲突时就会替换.

pdf版本笔记的下载地址: ORB-SLAM2代码详解03_地图点MapPoint,排版更美观一点,这个网站的默认排版太丑了(访问密码:3834)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3