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

ORB-SLAM3源码的学习:Atlas.cc②: Atlas:: CreateNewMap创建新地图

前言

简单总结一下地图是何时创建的:

构建slam系统时还没有地图就需要创建,当时间戳不对劲时影响数据的同步时需要创建,当跟踪的第一和第二阶段都为失败时都要分别创建,且满足一定要求的地图会保留作为非活跃地图。

1.创建新地图

1.存在当前活跃地图,则先将当前活跃地图标记为非活跃地图。 

2.新建地图,将新地图标记为活跃地图。 

3.将新的活跃地图插入地图集中。 

函数 

注意:将当前地图存储起来,在代码中并未真的进行存储而是把mIsInUse标记为false即将地图设置为非活跃地图,此时的这个非活跃地图仍然在地图集中。

/*** @brief 创建新地图,如果当前活跃地图有效,先存储当前地图为不活跃地图,然后新建地图;否则,可以直接新建地图。* */
void Atlas::CreateNewMap()
{// 锁住地图集unique_lock<mutex> lock(mMutexAtlas);//使用互斥锁来保护地图集的线程安全。cout << "Creation of new map with id: " << Map::nNextId << endl;//打印新地图创建信息,显示了新地图的ID。// 如果当前活跃地图有效,先存储当前地图为不活跃地图后退出if (mpCurrentMap){// mnLastInitKFidMap为当前地图创建时第1个关键帧的id,它是在上一个地图最大关键帧id的基础上增加1if (!mspMaps.empty() && mnLastInitKFidMap < mpCurrentMap->GetMaxKFid())mnLastInitKFidMap = mpCurrentMap->GetMaxKFid() + 1; // 初始化关键帧ID为当前最大ID的下一个// 将当前地图储存起来,其实就是把mIsInUse标记为falsempCurrentMap->SetStoredMap();cout << "Stored map with ID: " << mpCurrentMap->GetId() << endl;// if(mHasViewer)//     mpViewer->AddMapToCreateThumbnail(mpCurrentMap);}cout << "Creation of new map with last KF id: " << mnLastInitKFidMap << endl;mpCurrentMap = new Map(mnLastInitKFidMap);  //新建地图mpCurrentMap->SetCurrentMap();              //设置为活跃地图mspMaps.insert(mpCurrentMap);               //插入地图集
}

2.创建地图的时机

创建太频繁会影响效率,创建太保守影响效果。 

1.构建SLAM系统时 

在构建地图集时,地图集是空的,这时候需要创建第一个地图。 在system.cc中:

mpAtlas = new Atlas(0);

Atlas类的构造函数则是在Atlas.cc中:

Atlas::Atlas(int initKFid) : mnLastInitKFidMap(initKFid), mHasViewer(false)
{mpCurrentMap = static_cast<Map *>(NULL);CreateNewMap();
}

2.跟踪线程中时间戳异常时 

ORB-SLAM2 不需要像 ORB-SLAM3 那样专门处理时间戳异常问题,主要是因为它面向的是较为简单的单传感器或双目传感器配置,时间戳同步的问题相对较少。而 ORB-SLAM3 由于支持更复杂的传感器配置(如 IMU、多相机等),因此需要更加精确地处理时间戳同步,以确保不同传感器数据的协调和正确的融合。

异常的两种情况: 

1.时间戳颠倒。当前帧的时间戳比上一帧的时间戳小。 

2.时间戳跳变。当前帧的时间戳与上一帧的时间戳间隔时间比较久(大于1s) 

// Step 2 处理时间戳异常的情况if(mState!=NO_IMAGES_YET){if(mLastFrame.mTimeStamp>mCurrentFrame.mTimeStamp){// 如果当前图像时间戳比前一帧图像时间戳小,说明出错了,清除imu数据,创建新的子地图cerr << "ERROR: Frame with a timestamp older than previous frame detected!" << endl;unique_lock<mutex> lock(mMutexImuQueue);// mlQueueImuData.clear();// 创建新地图CreateMapInAtlas();return;}else if(mCurrentFrame.mTimeStamp>mLastFrame.mTimeStamp+1.0){// cout << mCurrentFrame.mTimeStamp << ", " << mLastFrame.mTimeStamp << endl;// cout << "id last: " << mLastFrame.mnId << "    id curr: " << mCurrentFrame.mnId << endl;// 如果当前图像时间戳和前一帧图像时间戳大于1s,说明时间戳明显跳变了,重置地图后直接返回//根据是否是imu模式,进行imu的补偿if(mpAtlas->isInertial()){// 如果当前地图imu成功初始化if(mpAtlas->isImuInitialized()){cout << "Timestamp jump detected. State set to LOST. Reseting IMU integration..." << endl;// IMU完成第3次初始化(在localmapping线程里)if(!pCurrentMap->GetIniertialBA2()){// 如果当前子图中imu没有经过BA2,重置active地图,也就是之前的数据不要了mpSystem->ResetActiveMap();}else{// 如果当前子图中imu进行了BA2,重新创建新的子图,保存当前地图CreateMapInAtlas();}}else{// 如果当前子图中imu还没有初始化,重置active地图cout << "Timestamp jump detected, before IMU initialization. Reseting..." << endl;mpSystem->ResetActiveMap();}return;}}}

其中调用了CreateMapInAtlas函数不仅创建了新地图还重置了系统状态以便重新获取新数据。

具体代码分析:

1.重置初始化帧 ID

mnLastInitFrameId = mCurrentFrame.mnId;

 将当前帧的 ID 记录到mnLastInitFrameId中,标记当前帧作为新地图的起始帧。

2. 创建新地图

调用CreateNewMap函数创建新地图,此时该地图为活跃地图,并将其插入到地图集中。

mpAtlas->CreateNewMap();

3.设置惯性传感器标志

if (mSensor == System::IMU_STEREO || mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_RGBD)mpAtlas->SetInertialSensor();  // mpAtlas中map的mbIsInertial=true

如果是IMU的模式是需要包含惯性数据的。

4. 重置初始化标志

mbSetInit = false;  // 好像没什么用

避免后续的重复初始化

5. 设置新地图的初始化帧 ID

mnInitialFrameId = mCurrentFrame.mnId + 1;

6.重置系统状态

mState = NO_IMAGES_YET;

7.重置其他相关标志

mbVelocity = false; 

8.打印信息

Verbose::PrintMess("First frame id in map: " + to_string(mnLastInitFrameId + 1), Verbose::VERBOSITY_NORMAL);

 打印日志信息,告知当前新地图中的第一帧 ID。这有助于调试和查看系统的状态。

9.初始化 VO 标志

mbVO = false; // Init value for know if there are enough MapPoints in the last KF

mbVO(Visual Odometry)标志设置为 false,表示视觉里程计的状态尚未建立。mbVO 可能用于判断当前的地图中是否有足够的地图点(MapPoints)来支持视觉里程计的估计。

10. 重置单目初始化标志

if (mSensor == System::MONOCULAR || mSensor == System::IMU_MONOCULAR)
{mbReadyToInitializate = false;
}

如果当前系统使用的是单目传感器或单目 IMU,mbReadyToInitializate 将被重置为 false。这是因为在单目系统中,初始化过程需要根据多帧图像来恢复 3D 位姿,当前的帧在重新创建地图时可能还没有足够的信息来初始化系统。

11. 重新初始化 IMU 数据

if ((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO || mSensor == System::IMU_RGBD) && mpImuPreintegratedFromLastKF)
{delete mpImuPreintegratedFromLastKF;mpImuPreintegratedFromLastKF = new IMU::Preintegrated(IMU::Bias(), *mpImuCalib);
}

12.重置关键帧指针

if (mpLastKeyFrame)mpLastKeyFrame = static_cast<KeyFrame*>(NULL);if (mpReferenceKF)mpReferenceKF = static_cast<KeyFrame*>(NULL);

13. 重置当前和上一帧

mLastFrame = Frame();
mCurrentFrame = Frame();

14.清空匹配点和 IMU 数据队列

mvIniMatches.clear();
mlQueueImuData.clear();

15. 标记地图已创建

mbCreatedMap = true;

完整的代码: 


/*在Atlas中保存当前地图,创建新地图,所有跟状态相关的变量全部重置1. 前后两帧对应的时间戳反了2. imu模式下前后帧超过1s3. 上一帧为最近丢失且重定位失败时4. 重定位成功,局部地图跟踪失败
*/
void Tracking::CreateMapInAtlas()
{mnLastInitFrameId = mCurrentFrame.mnId;mpAtlas->CreateNewMap();if (mSensor==System::IMU_STEREO || mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_RGBD)mpAtlas->SetInertialSensor();  // mpAtlas中map的mbIsInertial=truembSetInit=false;  // 好像没什么用mnInitialFrameId = mCurrentFrame.mnId+1;mState = NO_IMAGES_YET;// Restart the variable with information about the last KFmbVelocity = false;//mnLastRelocFrameId = mnLastInitFrameId; // The last relocation KF_id is the current id, because it is the new starting point for new mapVerbose::PrintMess("First frame id in map: " + to_string(mnLastInitFrameId+1), Verbose::VERBOSITY_NORMAL);mbVO = false; // Init value for know if there are enough MapPoints in the last KFif(mSensor == System::MONOCULAR || mSensor == System::IMU_MONOCULAR){mbReadyToInitializate = false;}if((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO || mSensor == System::IMU_RGBD) && mpImuPreintegratedFromLastKF){delete mpImuPreintegratedFromLastKF;mpImuPreintegratedFromLastKF = new IMU::Preintegrated(IMU::Bias(),*mpImuCalib);}if(mpLastKeyFrame)mpLastKeyFrame = static_cast<KeyFrame*>(NULL);if(mpReferenceKF)mpReferenceKF = static_cast<KeyFrame*>(NULL);mLastFrame = Frame();mCurrentFrame = Frame();mvIniMatches.clear();mlQueueImuData.clear();mbCreatedMap = true;
}

3.跟踪线程中确定跟踪丢失后

根据跟踪的阶段不同,判断条件也不同。如果在跟踪的第一阶段就确定跟踪丢失,则进行如下处理:

1.如果当前活跃地图中关键帧的数量小于10 个,则认为该地图中有效信息太少, 直接重置, 丢弃当前地图。

2.如果当前活跃地图中关键帧的数量超过10 个,则认为该地图仍有一定价值,存储起来作为非活跃地图, 然后新建一个地图。

else if (mState == LOST)  // 上一帧为最近丢失且重定位失败时{// Step 6.6 如果是LOST状态// 开启一个新地图Verbose::PrintMess("A new map is started...", Verbose::VERBOSITY_NORMAL);if (pCurrentMap->KeyFramesInMap()<10){// 当前地图中关键帧数目小于10,重置当前地图mpSystem->ResetActiveMap();Verbose::PrintMess("Reseting current map...", Verbose::VERBOSITY_NORMAL);}elseCreateMapInAtlas();  // 当前地图中关键帧数目超过10,创建新地图// 干掉上一个关键帧if(mpLastKeyFrame)mpLastKeyFrame = static_cast<KeyFrame*>(NULL);Verbose::PrintMess("done", Verbose::VERBOSITY_NORMAL);return;}

如果在跟踪的第二阶段确定跟踪丢失,则进行如下处理:

1. 如果当前是纯视觉模式且地图中关键帧的数量超过10个或者在惯性模式下已经完成IMU 第一阶段初始化,则认为该地图仍有一定价值, 存储起来作为非活跃地图,然后新建一个地图。

2. 否则重置, 丢弃当前地图。

// Reset if the camera get lost soon after initialization// Step 10 如果第二阶段跟踪失败,跟踪状态为LOSTif(mState==LOST){// 如果地图中关键帧小于10,重置当前地图,退出当前跟踪if(pCurrentMap->KeyFramesInMap()<=10)  // 上一个版本这里是5{mpSystem->ResetActiveMap();return;}if (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO || mSensor == System::IMU_RGBD)if (!pCurrentMap->isImuInitialized()){// 如果是IMU模式并且还未进行IMU初始化,重置当前地图,退出当前跟踪Verbose::PrintMess("Track lost before IMU initialisation, reseting...", Verbose::VERBOSITY_QUIET);mpSystem->ResetActiveMap();return;}// 如果地图中关键帧超过10 并且 纯视觉模式 或 虽然是IMU模式但是已经完成IMU初始化了,保存当前地图,创建新的地图CreateMapInAtlas();// 新增加了个returnreturn;}

 以上就是需要创建地图的几种情况。

结束语

以上就是我学习到的内容,如果对您有帮助请多多支持我,如果哪里有问题欢迎大家在评论区积极讨论,我看到会及时回复。

 

 

相关文章:

ORB-SLAM3源码的学习:Atlas.cc②: Atlas:: CreateNewMap创建新地图

前言 简单总结一下地图是何时创建的&#xff1a; 构建slam系统时还没有地图就需要创建&#xff0c;当时间戳不对劲时影响数据的同步时需要创建&#xff0c;当跟踪的第一和第二阶段都为失败时都要分别创建&#xff0c;且满足一定要求的地图会保留作为非活跃地图。 1.创建新地…...

多头自注意力中的多头作用及相关思考

文章目录 1. num_heads2. pytorch源码演算 1. num_heads 将矩阵的最后一维度进行按照num_heads的方式进行切割矩阵&#xff0c;具体表示如下&#xff1a; 2. pytorch源码演算 pytorch 代码 import torch import torch.nn as nn import torch.nn.functional as Ftorch.set…...

常用的python库-安装与使用

常用的python库函数 yield关键字openslide库openslide库的安装-linuxopenslide的使用openslide对象的常用属性 cv2库numpy库ASAP库-multiresolutionimageinterface库ASAP库的安装ASAP库的使用 concurrent.futures.ThreadPoolExecutorxml.etree.ElementTree库skimage库PIL.Image…...

对接DeepSeek

其实&#xff0c;整个对接过程很简单&#xff0c;就四步&#xff0c;获取key&#xff0c;找到接口文档&#xff0c;接口测试&#xff0c;代码对接。 获取 KEY https://platform.deepseek.com/transactions 直接付款就是了&#xff08;现在官网暂停充值2025年2月7日&#xff0…...

DevOps工具链概述

1. DevOps工具链概述 1.1 DevOps工具链的定义 DevOps工具链是支持DevOps实践的一系列工具的集合&#xff0c;这些工具覆盖了软件开发的整个生命周期&#xff0c;包括需求管理、开发、测试、部署和运维等各个环节。它旨在通过工具的集成和自动化&#xff0c;打破开发与运维之间…...

ChatGPT提问技巧:行业热门应用提示词案例-文案写作

ChatGPT 作为强大的 AI 语言模型&#xff0c;已经成为文案写作的得力助手。但要让它写出真正符合你需求的文案&#xff0c;关键在于如何与它“沟通”&#xff0c;也就是如何设计提示词&#xff08;Prompt&#xff09;。以下是一些实用的提示词案例&#xff0c;帮助你解锁 ChatG…...

分享如何通过Mq、Redis、XxlJob实现算法任务的异步解耦调度

一、背景 1.1 产品简介 基于大模型塔斯&#xff0c;整合传统的多项能力&#xff08;NLP、OCR、CV等&#xff09;&#xff0c;构建以场景为中心的新型智能文档平台。通过文档审阅&#xff0c;实现结构化、半结构化和非结构化文档的信息获取、处理及审核&#xff0c;同时基于大…...

力扣-栈与队列-239 滑动窗口的最大值

双指针思路 每移动一次&#xff0c;可以比较上一次窗口的最大值和被移除的值&#xff0c;如果被移除的值小于最大值&#xff0c;则说明最大值仍在新的区间&#xff0c;但是最后超时了 双指针超时代码 class Solution { public:vector<int> maxSlidingWindow(vector<…...

在 MySQL 中,通过存储过程结合条件判断来实现添加表字段时,如果字段已存在则不再重复添加

-- 创建存储过程 DELIMITER $$ CREATE PROCEDURE add_column(IN db_name VARCHAR(255),IN table_name VARCHAR(255),IN column_name VARCHAR(255),IN column_definition VARCHAR(255),IN column_comment VARCHAR(255) ) BEGINDECLARE column_exists INT;-- 检查字段是否存在SEL…...

8.flask+websocket

http是短连接&#xff0c;无状态的。 websocket是长连接&#xff0c;有状态的。 flask中使用websocket from flask import Flask, request import asyncio import json import time import websockets from threading import Thread from urllib.parse import urlparse, pars…...

【大模型实战】使用Ollama+Chatbox实现本地Deepseek R1模型搭建

下载安装Ollama Ollama官方链接:https://ollama.com/,打开链接后就可以看到大大的下载按钮,如下图: 我选择用Win的安装。将Ollama的安装包下载到本地,如果下载慢可以复制链接到迅雷里面,提高下载速度,如下图: 双击之后,就可以开始安装了,如下图: 默认安装到C盘,…...

VMware 虚拟机 ubuntu 20.04 扩容工作硬盘

一、关闭虚拟机 关闭虚拟机参考下图&#xff0c;在vmware 调整磁盘容量 二、借助工具fdisk testubuntu ~ $ df -h Filesystem Size Used Avail Use% Mounted on udev 1.9G 0 1.9G 0% /dev tmpfs 388M 3.1M 385M 1% /run /dev/sda5 …...

ZooKeeper 和 Dubbo 的关系:技术体系与实际应用

引言 在现代微服务架构中&#xff0c;服务治理和协调是至关重要的环节。ZooKeeper 和 Dubbo 是两个在分布式系统中常用的技术工具&#xff0c;它们之间有着紧密的联系。本文将详细探讨 ZooKeeper 和 Dubbo 的关系&#xff0c;从基础概念、技术架构、具体实现到实际应用场景&am…...

【LeetCode 热题100】74:搜索二维矩阵(二分、线性两种方式 详细解析)(Go 语言实现)

&#x1f680; 力扣热题 74&#xff1a;搜索二维矩阵&#xff08;详细解析&#xff09; &#x1f4cc; 题目描述 力扣 74. 搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵 matrix &#xff1a; 每行中的整数从左到右按非递减顺序排列。每行的第一个整数大于前一行的…...

《Peephole LSTM:窥视孔连接如何开启性能提升之门》

在深度学习的领域中&#xff0c;长短期记忆网络&#xff08;LSTM&#xff09;以其出色的序列数据处理能力而备受瞩目。而Peephole LSTM作为LSTM的一种重要变体&#xff0c;通过引入窥视孔连接&#xff0c;进一步提升了模型的性能。那么&#xff0c;窥视孔连接究竟是如何发挥作用…...

HTML之JavaScript变量和数据类型

HTML之JavaScript变量和数据类型 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…...

(少儿编程)关于讲解C++函数(认识,了解)的思考与总结

前言&#xff1a; 在少儿编程中&#xff0c;讲解函数的概念时&#xff0c;需要将复杂的概念简化&#xff0c;并通过生动有趣的例子和互动方式来帮助孩子理解。以下是一个适合少儿的函数讲解思路和示例&#xff1a; 用生活中的例子引入函数的概念&#xff1a; 目标&#xff1a…...

【漫话机器学习系列】082.岭回归(或脊回归)中的α值(alpha in ridge regression)

岭回归&#xff08;Ridge Regression&#xff09;中的 α 值 岭回归&#xff08;Ridge Regression&#xff09;是一种 带有 L2​ 正则化 的线性回归方法&#xff0c;用于处理多重共线性&#xff08;Multicollinearity&#xff09;问题&#xff0c;提高模型的泛化能力。其中&am…...

Node.js怎么调用到打包的python文件呢

在 Node.js 中调用打包后的 Python 可执行文件&#xff08;如 PyInstaller 生成的 .exe 或二进制文件&#xff09;&#xff0c;可以通过以下步骤实现&#xff1a; 一、Python 打包准备 假设已有打包好的 Python 文件 your_script.exe&#xff08;以 Windows 为例&#xff09;&…...

9 Pydantic复杂数据结构的处理

在构建现代 Web 应用时&#xff0c;我们往往需要处理复杂的输入和输出数据结构。例如&#xff0c;响应数据可能包含嵌套字典、列表、元组&#xff0c;甚至是多个嵌套对象。Pydantic 是一个强大的数据验证和序列化库&#xff0c;可以帮助我们轻松地处理这些复杂的数据结构&#…...

C++ decltype 规则推导

C decltype 规则推导 文章目录 C decltype 规则推导**1. 基本规则****(1) 如果 decltype 的参数是变量名&#xff08;无括号的标识符&#xff09;****(2) 如果 decltype 的参数是表达式&#xff08;带括号或操作符&#xff09;** **2. 与 auto 的区别****3. 特殊场景****(1) 函…...

Rust 测试组织指南:单元测试与集成测试

一、为什么要同时使用单元测试与集成测试 单元测试&#xff1a;更为精细、聚焦某一逻辑单元&#xff1b;可以调用到私有函数&#xff0c;快速定位错误根源。集成测试&#xff1a;作为“外部代码”来使用库的公开接口&#xff0c;测试多个模块间的交互&#xff0c;确保整体功能…...

Day62_补20250210_图论part6_108冗余连接|109.冗余连接II

Day62_20250210_图论part6_108冗余连接|109.冗余连接II 108冗余连接 【把题意转化为并查集问题】 题目 有一个图&#xff0c;它是一棵树&#xff0c;他是拥有 n 个节点&#xff08;节点编号1到n&#xff09;和 n - 1 条边的连通无环无向图&#xff08;其实就是一个线形图&am…...

kafka消费端之消费者协调器和组协调器

文章目录 概述回顾历史老版本获取消费者变更老版本存在的问题 消费者协调器和组协调器新版如何解决老版本问题再均衡过程**第一阶段CFIND COORDINATOR****第二阶段&#xff08;JOINGROUP&#xff09;**选举消费组的lcader选举分区分配策略 第三阶段&#xff08;SYNC GROUP&…...

语法备忘04:将 事件处理函数 绑定到 组件 的事件上

示例1&#xff1a;<Table OnQueryAsync"OnQueryAsync" /> 示例2&#xff1a;<Table OnQueryAsync"OnQueryAsync" /> 说明&#xff1a;这两种写法在功能上是‌完全相同的‌&#xff0c;都是在将 OnQueryAsync 事件处理函数绑定到 Table 组件的 …...

C++20中的std::atomic_ref

一、std::atomic_ref 我们在学习C11后的原子操作时&#xff0c;都需要提前定义好std::atomic变量&#xff0c;然后才可以在后续的应用程序中进行使用。原子操作的优势在很多场合下优势非常明显&#xff0c;所以这也使得很多开发者越来习惯使用原子变量。 但是&#xff0c;在实…...

CSS 相关知识

1、高度已知&#xff0c;三栏布局&#xff0c;左右宽度 200&#xff0c;中间自适应&#xff0c;如何实现&#xff1f; <body><div class"box"><div class"box1">高度已知</div><div class"box2">左右宽度 200&…...

RocketMQ、RabbitMQ、Kafka 的底层实现、功能异同、应用场景及技术选型分析

1️⃣ 引言 在现代分布式系统架构中&#xff0c;&#x1f4e9;消息队列&#xff08;MQ&#xff09;是不可或缺的组件。它在系统&#x1f517;解耦、&#x1f4c9;流量削峰、⏳异步处理等方面发挥着重要作用。目前&#xff0c;主流的消息队列系统包括 &#x1f680;RocketMQ、&…...

IDEA升级出现问题Failed to prepare an update Temp directory inside installation

IDEA升级出现问题"Failed to prepare an update Temp directory inside installation…" 问题来源&#xff1a; 之前修改了IDEA的默认配置文件路径&#xff0c;然后升级新版本时就无法升级&#xff0c;提示"Failed to prepare an update Temp directory insid…...

DeepSeek提示词手册

一、核心原则&#xff1a;基于DeepSeek的推理特性 自然语言优先undefinedDeepSeek擅长理解自然表达&#xff0c;无需复杂模板。例如&#xff1a; ❌旧模板&#xff1a;"你是专业分析师&#xff0c;需分三步回答&#xff0c;第一步…" ✅高效提问&#xff1a;"…...