当前位置: 首页 > article >正文

ORB-SLAM2 从理论到代码实现(十三):MapPoint 类

MapPoint是地图中的特征点它自身的参数是三维坐标和描述子在这个类中它需要完成的主要工作有以下方面(1) 维护关键帧之间的共视关系(2) 通过计算描述向量之间的距离在多个关键帧的特征点中找最匹配的特征点(3) 在闭环完成修正后需要根据修正的主帧位姿修正特征点(4) 对于非关键帧也产生MapPoint只不过是给Tracking功能临时使用它的主要功能梳理完以后我们就可以看它对应的函数了先通过一张图梳理它的主要函数1. 成员变量// Position in absolute coordinates cv::Mat mWorldPos; // Keyframes observing the point and associated index in keyframe std::mapKeyFrame*,size_t mObservations; // Mean viewing direction cv::Mat mNormalVector; // Best descriptor to fast matching cv::Mat mDescriptor; // Reference KeyFrame KeyFrame* mpRefKF; // Tracking counters记录观测到的帧数每被观测到一次普通帧不要求是关键帧则计数加1 int mnVisible; // 只要在一帧中匹配成功计数就加1 int mnFound; // 不仅要匹配成功经过优化之后还不能被判定为外点 // Bad flag (we do not currently erase MapPoint from memory) bool mbBad; MapPoint* mpReplaced; // Scale invariance distances float mfMinDistance; float mfMaxDistance;2. 构造函数它的构造函数一共有两个分别对应关键帧和普通帧。2.1. 关键帧相关的地图点构造函数MapPoint(const cv::Mat Pos, KeyFrame* pRefKF, Map* pMap)其中Pos指的是该点的3D位置pRefKF是参考关键帧pMap是地图。和关键帧相关的地图点构造函数主要是突出地图点和关键帧之间的观测关系参考关键帧是哪一帧该地图点被哪些关键帧观测到。一个地图点会被多个关键帧观测到多个关键帧之间通过共同观测到地图点而发生的关系叫共视关系在orb slam中就是通过MapPoint类来维护共视关系的。在进行局部BA优化时只优化具有共视关系的这些关键帧其他关键帧的位姿不参与优化。2.2. 普通帧相关的地图点构造函数MapPoint(const cv::Mat Pos, Map* pMap, Frame* pFrame, const int idxF)其中Pos指的是该点的3D位置pMap是地图pFrame是对应的普通帧idxF是地图点在该帧特征点中的索引号。3. AddObservation(KeyFrame* pKF, size_t idx)增加地图点的观测关系它包含两个参数KeyFrame* pKF是对应的关键帧size_t idx是该地图点在关键帧中对应的索引值它的作用是判断此关键帧是否已经在观测关系中了如果是这里就不会添加如果不是往下记录下此关键帧以及此MapPoint的索引就算是记录下观测信息了void MapPoint::AddObservation(KeyFrame* pKF, size_t idx) { unique_lockmutex lock(mMutexFeatures); //如果已经存在观测关系就返回 if(mObservations.count(pKF)) return; //如果不存在就添加 mObservations[pKF]idx; //分成单目和双目两种清空添加观测单目时观测次数加1双目时观测次数加2 if(pKF-mvuRight[idx]0) nObs2; else nObs; }此处涉及到两个重要变量std::mapKeyFrame *, size_t mObservations它是用来存放观测关系的容器把能够观测到该MapPoint的关键帧以及MapPoint在该关键帧中对应的索引值关联并存储起来int nObs它用来记录被观测的次数4. EraseObservation(KeyFrame* pKF)删除观测关系它的参数KeyFrame* pKF指的是关键帧这个函数首先判断该关键帧是否在观测中如果在就从存放观测关系的容器mObservations中移除该关键帧接着判断该帧是否是参考关键帧如果是参考关键帧换成观测的第一帧因为不能没有参考关键帧呀。删除以后如果该MapPoint被观测的次数小于2那么这个MapPoint就没有存在的必要了需要删除。void MapPoint::EraseObservation(KeyFrame* pKF) { bool bBadfalse; { unique_lockmutex lock(mMutexFeatures); //判断该关键帧是否在观测关系中即该关键帧是否看到了这个MapPoint if(mObservations.count(pKF)) { int idx mObservations[pKF]; //这里同样要判断单目和双目单目时观测次数减1双目时减2 if(pKF-mvuRight[idx]0) nObs-2; else nObs--; //删除该关键帧对应的观测关系 mObservations.erase(pKF); //如果关键帧是参考帧则重新指定 if(mpRefKFpKF) mpRefKFmObservations.begin()-first; // 当被观测次数小于等于2时该地图点需要剔除 if(nObs2) bBadtrue; } } //即删除地图点 if(bBad) SetBadFlag(); }5. SetBadFlag删除地图点它的作用就是删除地图点并清除关键帧和地图中所有和该地图点对应的关联关系void MapPoint::SetBadFlag() { mapKeyFrame*,size_t obs; { unique_lockmutex lock1(mMutexFeatures); unique_lockmutex lock2(mMutexPos); mbBadtrue; obs mObservations; //清除该地图点所有的观测关系 mObservations.clear(); } for(mapKeyFrame*,size_t::iterator mitobs.begin(), mendobs.end(); mit!mend; mit) { KeyFrame* pKF mit-first; //删除关键帧中和该MapPoint对应的匹配关系 pKF-EraseMapPointMatch(mit-second); } //从地图中删除MapPoint mpMap-EraseMapPoint(this); }6. Replace(MapPoint* pMP)替换地图点MapPoint* pMP就是用来替换的地图点该函数的作用是将当前地图点 (this)替换成pMp这主要是因为在使用闭环时完成闭环优化以后需要调整地图点和关键帧建立新的关系。具体流程是循环遍历所有的观测信息判断此MapPoint是否在该关键帧中如果在那么只要移除原来MapPoint的匹配信息最后增加这个MapPoint找到的数量以及可见的次数另外地图中要移除原来的那个MapPoint。最后需要计算这个点独有的描述子。void MapPoint::Replace(MapPoint* pMP) { //如果传入的该MapPoint就是当前的MapPoint直接跳出 if(pMP-mnIdthis-mnId) return; int nvisible, nfound; mapKeyFrame*,size_t obs; { unique_lockmutex lock1(mMutexFeatures); unique_lockmutex lock2(mMutexPos); obsmObservations; mObservations.clear(); mbBadtrue; nvisible mnVisible; nfound mnFound; mpReplaced pMP; } for(mapKeyFrame*,size_t::iterator mitobs.begin(), mendobs.end(); mit!mend; mit) { // Replace measurement in keyframe KeyFrame* pKF mit-first; // 如果该MapPoint不在关键帧的观测关系中就添加观测关系 if(!pMP-IsInKeyFrame(pKF)) { pKF-ReplaceMapPointMatch(mit-second, pMP); pMP-AddObservation(pKF,mit-second); } //如果在就删除关键帧和老的MapPoint之间的对应关系 else { pKF-EraseMapPointMatch(mit-second); } } pMP-IncreaseFound(nfound); pMP-IncreaseVisible(nvisible); pMP-ComputeDistinctiveDescriptors(); //删掉Map中该地图点 mpMap-EraseMapPoint(this); }7. ComputeDistinctiveDescriptors计算最匹配的描述子由于一个MapPoint会被许多相机观测到因此在插入关键帧后需要判断是否更新当前点的最适合的描述子。最好的描述子与其他描述子应该具有最小的中值距离因此先获得当前点的所有描述子然后计算描述子之间的两两距离选择每个描述子相对于其它描述子的距离的中值即中值距离这个中值距离最小的描述子作为地图点的描述子。void MapPoint::ComputeDistinctiveDescriptors() { // Retrieve all observed descriptors vectorcv::Mat vDescriptors; mapKeyFrame*,size_t observations; { unique_lockmutex lock1(mMutexFeatures); //如果地图点标记为不好直接返回 if(mbBad) return; observationsmObservations; } //如果观测为空则返回 if(observations.empty()) return; //保留的描述子数最多和观测数一致 vDescriptors.reserve(observations.size()); for(mapKeyFrame*,size_t::iterator mitobservations.begin(), mendobservations.end(); mit!mend; mit) { KeyFrame* pKF mit-first; if(!pKF-isBad()) //针对每帧的对应的都提取其描述子 vDescriptors.push_back(pKF-mDescriptors.row(mit-second)); } if(vDescriptors.empty()) return; // Compute distances between them const size_t N vDescriptors.size(); float Distances[N][N]; for(size_t i0;iN;i) { Distances[i][i]0; for(size_t ji1;jN;j) { int distij ORBmatcher::DescriptorDistance(vDescriptors[i],vDescriptors[j]); Distances[i][j]distij; Distances[j][i]distij; } } // Take the descriptor with least median distance to the rest // 选择距离其他描述子中值距离最小的描述子作为地图点的描述子基本上类似于取了个均值 int BestMedian INT_MAX; int BestIdx 0; for(size_t i0;iN;i) { vectorint vDists(Distances[i],Distances[i]N); sort(vDists.begin(),vDists.end()); int median vDists[0.5*(N-1)]; if(medianBestMedian) { BestMedian median; BestIdx i; } } { unique_lockmutex lock(mMutexFeatures); mDescriptor vDescriptors[BestIdx].clone(); } }8. UpdateNormalAndDepth更新法向量和深度值由于图像提取描述子是使用金字塔分层提取所以计算法向量和深度可以知道该MapPoint在对应的关键帧的金字塔哪一层可以提取到。明确了目的下一步就是方法问题所谓的法向量就是也就是说相机光心指向地图点的方向计算这个方向方法很简单只需要用地图点的三维坐标减去相机光心的三维坐标就可以。void MapPoint::UpdateNormalAndDepth() { mapKeyFrame*,size_t observations; KeyFrame* pRefKF; cv::Mat Pos; { unique_lockmutex lock1(mMutexFeatures); unique_lockmutex lock2(mMutexPos); if(mbBad) return; observationsmObservations; pRefKFmpRefKF; Pos mWorldPos.clone(); } if(observations.empty()) return; cv::Mat normal cv::Mat::zeros(3,1,CV_32F); int n0; for(mapKeyFrame*,size_t::iterator mitobservations.begin(), mendobservations.end(); mit!mend; mit) { KeyFrame* pKF mit-first; cv::Mat Owi pKF-GetCameraCenter(); //观测点坐标减去关键帧中相机光心的坐标就是观测方向 //也就是说相机光心指向地图点 cv::Mat normali mWorldPos - Owi; //对其进行归一化后相加 normal normal normali/cv::norm(normali); n; } 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; //深度范围地图点到参考帧只有一帧相机中心距离乘上参考帧中描述子获取金字塔放大尺度 //得到最大距离mfMaxDistance;最大距离除以整个金字塔最高层的放大尺度得到最小距离mfMinDistance. //通常说来距离较近的地图点将在金字塔较高的地方提出 //距离较远的地图点在金字塔层数较低的地方提取出金字塔层数越低分辨率越高才能识别出远点 //因此通过地图点的信息主要对应描述子我们可以获得该地图点对应的金字塔层级 //从而预测该地图点在什么范围内能够被观测到 { unique_lockmutex lock3(mMutexPos); mfMaxDistance dist*levelScaleFactor; mfMinDistance mfMaxDistance/pRefKF-mvScaleFactors[nLevels-1]; mNormalVector normal/n; } }9. PredictScale(const float currentDist, KeyFrame* pKF)预测尺度其中currentDist是当前距离pKF是关键帧该函数的作用是预测特征点在金字塔哪一层可以找到。示意图如下注意金字塔ScaleFactor和距离的关系当特征点对应ScaleFactor为1.2的意思是图片分辨率下降1.2倍后可以提取出该特征点分辨率更高的时候肯定也可以提出这里取金字塔中能够提取出该特征点最高层级作为该特征点的层级同时由当前特征点的距离推测所在的层级。int MapPoint::PredictScale(const float currentDist, KeyFrame* pKF) { float ratio; { unique_lockmutex lock(mMutexPos); ratio mfMaxDistance/currentDist; } int nScale ceil(log(ratio)/pKF-mfLogScaleFactor); if(nScale0) nScale 0; else if(nScalepKF-mnScaleLevels) nScale pKF-mnScaleLevels-1; return nScale; }参考文献主要内容来自下文重写了一些描述增加了一些注释ORB SLAM2源码解读(二)MapPoint类 - 知乎

相关文章:

ORB-SLAM2 从理论到代码实现(十三):MapPoint 类

MapPoint是地图中的特征点,它自身的参数是三维坐标和描述子,在这个类中它需要完成的主要工作有以下方面: (1) 维护关键帧之间的共视关系 (2) 通过计算描述向量之间的距离,在多个关键帧的特征点中找最匹配的特征点 (3) 在闭环完…...

天龙八部单机版GM工具:从手动修改到一键管理的革命

天龙八部单机版GM工具:从手动修改到一键管理的革命 【免费下载链接】TlbbGmTool 某网络游戏的单机版本GM工具 项目地址: https://gitcode.com/gh_mirrors/tl/TlbbGmTool 还在为《天龙八部》单机版的数据管理而头疼吗?每次修改角色属性都要手动编辑…...

如何在Windows上快速安装安卓应用:APK Installer完整实战指南

如何在Windows上快速安装安卓应用:APK Installer完整实战指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 你是否厌倦了笨重的安卓模拟器?是…...

探索 MCP 协议:连接 AI 模型与外部工具的新标准

探索 MCP 协议:连接 AI 模型与外部工具的新标准 引言 在大型语言模型(LLM)快速发展的今天,如何让模型安全、高效地访问外部数据源和工具,成为了 AI Agent 落地应用中的关键挑战。Model Context Protocol (MCP) 的出现&…...

通达信缠论插件快速入门:3步实现自动化技术分析,告别手动画线烦恼

通达信缠论插件快速入门:3步实现自动化技术分析,告别手动画线烦恼 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX 缠论技术分析是股票交易中极具价值的理论体系,但传统…...

怎样用Stretchly打造你的专属健康办公节奏:5分钟快速上手指南

怎样用Stretchly打造你的专属健康办公节奏:5分钟快速上手指南 【免费下载链接】stretchly The break time reminder app 项目地址: https://gitcode.com/gh_mirrors/st/stretchly 在数字办公时代,健康屏幕时间管理已成为现代职场人士的必备技能。…...

yolov5实现火焰识别/检测步骤记录

1.克隆yolov5仓库 git clone https://github.com/ultralytics/yolov5 2.安装python3.7、Pytorch1.7.0环境 3.安装yolov5环境 pip install -r requirements.txt 4.数据集与配置文件 #数据集来源 https://universe.roboflow.com/dataset-9xayt/fire-data-annotations-lwfou 在…/…...

GetQzonehistory:三步轻松备份你的QQ空间完整历史说说

GetQzonehistory:三步轻松备份你的QQ空间完整历史说说 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾经担心QQ空间里那些记录青春岁月的说说会随着时间流逝而消失&…...

ubuntu中添加用户并赋予root权限

1. 添加用户 useradd [-d homepath] [-s shell] -m username useradd -d /home/test -s /bin/bash -m test -d:指定用户的家目录 -s:用户的登录shell -m:创建用户家目录2. 给用户添加root权限 usermod -aG sudo username #测试用户是否有ro…...

中小企业IT治理困局破局之道(AISMM轻量化实施框架首次公开)

更多请点击: https://intelliparadigm.com 第一章:中小企业IT治理困局的本质解构 中小企业IT治理常被简化为“买几台服务器、装个OA、找人修电脑”,但其深层矛盾实为战略意图、组织能力与技术现实之间的三重断裂。当业务部门抱怨系统响应慢&…...

为AI助手集成BigDataCloud MCP Server:实现IP定位与数据验证

1. 项目概述:当AI助手学会“看地图”与“查户口” 如果你经常和Claude、Cursor或者GitHub Copilot这类AI助手打交道,有没有想过让它们变得更“接地气”?比如,你正在写一个用户注册表单,想让AI帮你验证用户输入的手机号…...

如何在老旧Android电视上免费观看4K直播?终极电视直播应用指南

如何在老旧Android电视上免费观看4K直播?终极电视直播应用指南 【免费下载链接】mytv-android 使用Android原生开发的电视直播软件 项目地址: https://gitcode.com/gh_mirrors/myt/mytv-android 如果你正在寻找一款能在老旧Android电视上流畅播放4K直播的免费…...

GetQzonehistory终极指南:3分钟永久备份你的QQ空间所有历史记录

GetQzonehistory终极指南:3分钟永久备份你的QQ空间所有历史记录 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还在担心QQ空间里那些承载着青春回忆的说说会随着时间流逝而…...

基于Azure Cosmos DB与OpenAI构建企业级RAG智能问答应用实战

1. 项目概述:构建一个基于向量数据库的智能对话应用最近在折腾一个挺有意思的项目,想和大家分享一下如何用 Azure Cosmos DB 和 Azure OpenAI Service 来搭建一个真正能用的“副驾驶”应用。这个项目的核心思路,就是把你的数据变成 AI 能理解…...

基于 Taotoken 构建支持多模型切换的智能内容创作平台

基于 Taotoken 构建支持多模型切换的智能内容创作平台 1. 多模型内容创作场景需求分析 在智能内容创作领域,不同创作类型对生成模型的需求存在显著差异。小说创作可能需要更强的叙事连贯性和角色塑造能力,商业文案需要精准的品牌调性把控,而…...

告别手动拷贝!用cwRsync在Windows和Linux间自动同步文件(附详细配置步骤)

跨平台文件同步利器:cwRsync在Windows与Linux间的自动化实践 对于需要在Windows与Linux系统间频繁传输文件的运维工程师和开发者来说,手动复制粘贴或使用FTP工具不仅效率低下,还容易出错。想象一下凌晨三点被叫醒处理生产环境文件同步失败的场…...

Cherry MX键帽3D模型库:解锁机械键盘个性化定制新维度

Cherry MX键帽3D模型库:解锁机械键盘个性化定制新维度 【免费下载链接】cherry-mx-keycaps 3D models of Chery MX keycaps 项目地址: https://gitcode.com/gh_mirrors/ch/cherry-mx-keycaps 还在为寻找独特键帽而烦恼吗?cherry-mx-keycaps项目为…...

BthPS3蓝牙驱动:Windows上完美连接PS3控制器的终极解决方案

BthPS3蓝牙驱动:Windows上完美连接PS3控制器的终极解决方案 【免费下载链接】BthPS3 Windows kernel-mode Bluetooth Profile & Filter Drivers for PS3 peripherals 项目地址: https://gitcode.com/gh_mirrors/bt/BthPS3 还在为PS3控制器在Windows电脑上…...

Emby.CustomCssJS:深度定制你的媒体服务器界面架构

Emby.CustomCssJS:深度定制你的媒体服务器界面架构 【免费下载链接】Emby.CustomCssJS Easy to manage your Custom JavaScript and Css to modify Emby 项目地址: https://gitcode.com/gh_mirrors/em/Emby.CustomCssJS Emby.CustomCssJS是一个专为Emby媒体服…...

Windows安卓应用安装终极指南:APK-Installer完整使用教程

Windows安卓应用安装终极指南:APK-Installer完整使用教程 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 想在Windows电脑上轻松安装安卓应用吗&#xff1f…...

Cloud Commander测试策略:确保文件管理器稳定性的完整方案

Cloud Commander测试策略:确保文件管理器稳定性的完整方案 【免费下载链接】cloudcmd ✨☁️📁✨ Cloud Commander file manager for the web with console and editor. 项目地址: https://gitcode.com/gh_mirrors/cl/cloudcmd Cloud Commander是…...

Spring Boot项目里,除了velocity-engine-core,你还需要Velocity-Tools吗?一个工具包的选择指南

Spring Boot项目中Velocity工具包的深度选型指南:何时需要Velocity-Tools? 在Java生态中,模板引擎的选择往往让开发者陷入"功能过剩"与"能力不足"的两难境地。Velocity作为老牌模板引擎,其轻量级设计哲学至今…...

Windows 10 下 Qt 5.15 组件选择避坑指南:从MSVC到MinGW,32G空间怎么装最合理?

Windows 10下Qt 5.15组件选择避坑指南:从MSVC到MinGW的32G空间优化方案 Qt作为跨平台开发框架,其组件选择直接影响开发效率和磁盘空间占用。面对Qt在线安装器中庞大的组件列表,开发者常陷入两难:既希望功能完备,又担心…...

Linux下部署MySQL5.7.35

1.MySQL下载 (1)登录到以下网站 https://downloads.mysql.com/archives/community/ (2)选择需要的版本 ,以及操作系统 ,这里是Red Hat Enterprise Linux / Oracle Linux 5.7.35 版本。 (3&…...

OpenMV的PWM控制舵机,从调参到避坑的全流程记录(基于Timer和pyb库)

OpenMV的PWM控制舵机:从调参到避坑的全流程实战指南 引言:为什么选择OpenMV控制舵机? 在嵌入式视觉项目中,我们常常需要同时处理图像识别和机械控制两个任务。传统方案通常采用主控视觉模块的架构,但这种设计存在通信延…...

为什么选择vue-markdown?与其他Markdown渲染器的全面对比分析

为什么选择vue-markdown?与其他Markdown渲染器的全面对比分析 【免费下载链接】vue-markdown vue-markdown: 是一个用于Vue.js的Markdown渲染器组件,允许在Vue应用中轻松展示Markdown格式的内容。 项目地址: https://gitcode.com/gh_mirrors/vu/vue-ma…...

芯片流片前夜,后端工程师的‘救火’指南:手把手教你搞定那些顽固的DRV和时序违例

芯片流片前夜:数字后端工程师的DRV与时序违例实战指南 凌晨三点的办公室,咖啡杯早已见底,屏幕上PrimeTime的违例报告像一张密密麻麻的通缉令——这是每个数字后端工程师在流片前夜都熟悉的场景。当项目进入最后冲刺阶段,那些顽固的…...

Cursor AI开发助手功能体验优化技术探索指南

Cursor AI开发助手功能体验优化技术探索指南 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached your trial request limit. / …...

GoPro视频GPS数据终极提取指南:三分钟搞定轨迹可视化

GoPro视频GPS数据终极提取指南:三分钟搞定轨迹可视化 【免费下载链接】gopro2gpx Parse the gpmd stream for GOPRO moov track (MP4) and extract the GPS info into a GPX (and kml) file. 项目地址: https://gitcode.com/gh_mirrors/go/gopro2gpx 还在为G…...

PBR-White-Paper抗锯齿技术:TAA时域抗锯齿在PBR渲染中的优化应用

PBR-White-Paper抗锯齿技术:TAA时域抗锯齿在PBR渲染中的优化应用 【免费下载链接】PBR-White-Paper ⚡️基于物理的渲染(PBR)白皮书 | White Paper of Physically Based Rendering(PBR) 项目地址: https://gitcode.com/gh_mirrors/pb/PBR-…...