文章目录
- 简介
-
- ORB 一些概念
- 大体流程:
- Run
-
- 代码流程:
- 代码注解
- 其他函数
-
- ProcessNewKeyFrame 处理新的关键帧
- MapPointCulling 最近添加地图点的消除
- CreateNewMapPoints 创建新的地图点
- SearchInNeighbors 检查并整合当前关键帧和相邻帧(两级相邻)的重复MapPoints
- ComputeF12 计算两帧基本矩阵
- SearchInNeighbors 检查并整合当前关键帧和相邻帧的重复MapPoints
- ScaleRefinement
- InitializeIMU 初始化IMU
- Atlas地图集
-
- 成员变量
- 构造
- 其他函数
-
- 创建地图
- 改变地图
- 设置参考地图点
- 获得所有地图
- 清除地图集
- 获得关键帧数和地图点数
- Map
-
- 成员变量
- 旋转地图
简介
- 在
LocalMapping.cc
主要有两件事:一是局部关键帧和地图点的优化,二是imu的初始化。 - 根据ORB-SLAM论文和代码,imu初始化有三个阶段:
- 。系统运行2s,当地图上大约有10个关键帧和数百个地图点时,纯视觉的局部BA,优化关键帧位置和地图点。
- 。估计imu的速度、bias(在初始化过程中假设不变),重力方向旋转矩阵Rwg(将第一帧相机确定的世界坐标系的负z轴旋转到与重力方向重叠的方向)s。构建预积分imu用图优化的方法估计状态量。
- 。视觉残差和imu结合残差,全局优化,优化相机位置,imu速度、bias、地图点。
ORB 一些概念
- 关键帧是连续几帧普通帧中更具代表性的帧。
- 作用:
- 减少局部相邻关键帧中的信息冗余
- 由于在SLAM该方案将普通帧的深度投影到关键帧上,因此在一定程度上,关键帧是普通帧过滤和优化的结果,防止无效和错误信息进入优化过程
- 后端优化计算能力和精度的妥协,提高计算资源的效率
- 选择:
- :画面清晰,特征点充足,特征点分布均匀等
- :与其他关键帧有一定的共视关系,不能重复太高,即既有约束,又减少冗余信息
- 选择方向:(选择后按上述标准筛选)
- 与上一帧关键帧之间的时间(帧数)间隔是否合适
- 上一帧之间的距离够远吗?
- 跟踪局部地图的质量(共视特征点的数量)
- :
- 任拿一个关键帧,可以观察到与该关键帧相同地图点的关键帧(一级关键帧)。
- 该关键帧的一级关键帧自身的一级关键帧称为该关键帧的二级关键帧。
- 注:由于相同关键帧的数量可以观察到来表征。
- :
- 以任何关键帧为中心,共视关键帧和联系构建的共视网。
- 共视图中的为关键帧;如果两个关键帧共视15个以上的地图点,则使用一个连接两者表示两个关键帧可以共同观察到的点云数量。
- 通常只使用一级相邻层次的共视关系和信息,局部地图优化采用两级相邻共视关系。
- :
- 增加地图点信息,优化地图
- 表示关键帧之间的关系和关系的紧密性
- 具体应用:
- 跟踪局部地图,扩大搜索范围
- 局部建图关键帧之间的新地图点
- 闭环检测、重定位检测
- 优化
- 本质图常用于父子关键帧
- 由关键帧组成的图像。
- 与共视图相比,本质图比较稀疏
- 节点表示所有关键帧,但连接边缘只保留与关键帧密切相关的边缘,使结果更准确
- 图中包含的信息包括:
- 扩大树木的连接关系
- 形成闭环连接关系
- 闭环后,地图点的变化和新的连接关系
- 良好的共视关系(至少有100个共视地图点)
- 作用:
- 闭环矫正时,使用相似变换(
Sim3
)纠正规模漂移,在本质图中均匀分摊闭环误差
- 闭环矫正时,使用相似变换(
- 优势:
- 全局BA 收敛后,局部可能不会收敛,本质图可以保证 局部收敛
- 可显著减少运行时间
- 本质图 局部BA 结合在一定程度上可以提高精度,但需要时间。
大体流程:
- 设置新的关键帧不能接受到系统,即在局部图纸(处理关键帧)过程中不允许新的关键帧集有变化
- 处理新的关键帧。建立新的关键帧及其属性(计算关键帧BOW向量,将关键帧的地图点添加到关键帧中,更新地图点的属性(观察地图点的关键帧、平均观测方向、最佳描述子),并添加到整体情况中map
- 检查当前关键帧对应的地图点是否良好(筛选条件:地图点是否好,地图点的搜索率大于0.25, 地图点的第一个关键帧(第一次观察地图点的帧id)与当前帧id相隔距离, 观察关键点的次数)
- 极限约束检测特征点,建立地图点加入全局map。用对极约束测量当前帧和共视关键帧(当前帧)(匹配当前帧和关键帧,然后通过三角形生成每对匹配3D点,然后检查(检查两帧中地图点的深度,两帧中的重投影误差,尺度连续性),如果满足对极约束,建立当前帧的地图点及其属性(a.观测到该MapPoint的关键帧 b.该MapPoint的描述子 c.该MapPoint平均观测方向和深度范围),将地图点添加到关键帧和全局map.
- 数据融合。每组关键帧(目前没有新的关键帧添加到一组关键帧中)处理完成后,对当前关键帧(每组最后一个关键帧)进行关键帧整合(整合当前关键帧及其共视关键帧),整合地图点(整合当前关键帧的地图点和两级相邻关键帧(关键帧的共视关键帧和所有共视关键帧的共视关键帧)。
- 将关键帧交给回环检测
- 将当前可接受的新关键帧设置到系统中,即局部构图(处理关闭帧)过程中允许新关键帧集有变动
Run
代码流程:
-
while 1 循环
-
SetAcceptKeyFrames(false)。设置不在接收关键帧 ,成员变量控制:
mbAcceptKeyFrames=false
-
如果有新的关键帧 且 imu_ok:
-
ProcessNewKeyFrame() 对新关键帧进行处理。建立新的关键帧及其属性并加入全局map
- 新关键帧队列中 取出第一帧作为当前关键帧,然后从队列中弹出
- 当前关键帧计算词袋
computeBoW
- 将 MapPoints 关联到新的关键帧并更新法线和描述符
- 取出当前帧匹配的地图点,若其在关键帧上则 对该地图点添加关键帧观测+法线和描述符
- 否则,加入:mlpRecentAddMapPoints
- 当前帧更新 图链接
UpdateConnections
- 将该关键帧插入地图
mpAtlas->AddKeyFrame
-
MapPointCulling地图点的剔除。针对 mlpRecentAddMapPoints,地图点坏的条件:
- 地图点本身是坏的
- 地图点的查找率小于0.25
- 第一次观察到该地图点的帧id与当前帧id相隔 ≥2 且 地图观测数小于 阈值
cnThObs
-
CreateNewMapPoints创建新的地图点。针对 mlpRecentAddMapPoints,地图点坏的条件:
-
步骤1:在当前关键帧的共视关键帧中找到共视程度最高的nn帧相邻帧vpNeighKFs
- 在共视图中检索邻域关键帧
vpNeighKFs
,个数10(单目20),该邻域帧按照共视地图点个数从多到少排列 - 若
mbInertial==true
(参数)- 若
vpNeighKFs
个数小于10 且 当前帧存在前关键帧 且 count < 10时: - 找到前关键帧在 邻域的
vpNeighKFs
的下标,若其是最后一个,则将前一关键帧加入邻域 - 前一关键帧 赋值当前关键帧
- 若
- 构建 ORBMatcher 器,
0.6,false
, - 得到当前帧的世界位姿,转换为 4*4矩阵。
- 在共视图中检索邻域关键帧
-
步骤2:遍历相邻关键帧vpNeighKFs
- 邻接的关键帧在世界坐标系中的坐标,并求出基线向量(两关键帧间的相机位移)和基线长度
- 步骤2.1:判断基线是不是足够长:
- 如果传感器不是单目:
- 判断相机运动的基线是不是足够长,如果是立体相机,基线小于关键帧基线时不生成3D点
- 如果传感器是单目:
medianDepthKF2
= 邻接关键帧的场景深度中值- 计算基线与场景深度的比例
ratioBaselineDepth
- 如果特别远(比例特别小),那么不考虑当前邻接的关键帧,不生成3D点
- 如果传感器不是单目:
- 步骤2.2:根据两个关键帧的位姿计算它们之间的基本矩阵 ComputeF12
- inv(K1) * t12x*R12*inv(K2)
- 步骤2.3:通过极线约束限制匹配时的搜索范围,进行特征点匹配 SearchForTriangulation
- Param: 当前关键帧+邻域关键帧+两关键帧基本矩阵+匹配的地图点+匹配成功后的点+false
- 步骤2.4:对每对匹配通过三角化生成3D点,和
Triangulate
函数差不多- 2.4.1 取出各匹配特征点数据(for 循环遍历)
- 当前关键帧和邻域关键帧的索引
idx1
和idx2
- 当前匹配在当前和邻接关键帧中的特征点
pk1
和pk2
- 当前关键帧和邻域关键帧的索引
- 2.4.2 利用匹配点反投影得到视差角
- 投影:
- 正向投影:空间点转为图像上点
- 反向投影:将图像上的点所表示的空间上的点的集合
- 特征点反投影得到
xn1
和xn2
- 由相机坐标系转到世界坐标系,得到视差角余弦值
- 先将两向量投影到世界坐标系,然后点乘除以两条边
- 视差加1是为了让其随便初始化为一个很大的值
- 投影:
- 2.4.3 对于双目,利用双目得到视差角
- 双目,且有深度,
cos(2*atan2(mb/2,depth))
- 双目,且有深度,
- 2.4.4 三角化恢复视差角
- 视差角度小时用三角法恢复3D点,视差角大时用双目恢复3D点(双目以及深度有效)
- 2.4.5 检测生成的3D点是否在相机前方,即
z
大于0,否则 continue - 2.4.6 计算重投影误差:
- 计算3D点在当前关键帧下的重投影误差
x1,y1,invz1
- 计算3D点在另一个关键帧下的重投影误差
x2,y2,invz2
- 两重投影误差:基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
- 计算3D点在当前关键帧下的重投影误差
- 2.4.7 检查尺度连续性
- 世界坐标系下,3D点与相机间的向量,方向由相机指向3D点
x3D-Ow1
ratioDist
是不考虑金字塔尺度下的距离比例ratioOctave
金字塔尺度因子的比例- ratioDist*ratioFactor < ratioOctave 或 ratioDist/ratioOctave > ratioFactor表明尺度变化是连续的
- 世界坐标系下,3D点与相机间的向量,方向由相机指向3D点
- 2.4.8 三角化生成3D点成功,构造成MapPoint
- a.观测到该MapPoint的关键帧
- b.该MapPoint的描述子
- c.该MapPoint的平均观测方向和深度范围
- 2.4.9 将新产生的点放入检测队列
- 这些MapPoints都会经过MapPointCulling函数的检验
- 2.4.1 取出各匹配特征点数据(for 循环遍历)
-
已经处理完队列中的最后的一个关键帧 CheckNewKeyFrames
return(!mlNewKeyFrames.empty());
- 检查并融合当前关键帧与相邻帧(两级相邻)重复的MapPoints SearchInNeighbors
- 步骤1:获得当前关键帧在covisibility图中权重排名前nn的邻接关键帧
- 找到当前帧一级相邻与二级相邻关键帧,存入
vpTargetKFs
- 邻域个数小于20时,扩展到时间邻域 ,当前帧的前一帧、前前一帧加入等加入容器,直到满足20
- 找到当前帧一级相邻与二级相邻关键帧,存入
- 步骤2:将当前帧的MapPoints分别与一级、二级、时间相邻帧(的MapPoints)进行融合
- 取出当前帧匹配的地图点
vpMapPointMatches
,并遍历: - 投影当前帧的MapPoints到相邻关键帧pKFi中,并判断是否有重复的MapPoints
- 1.如果MapPoint能匹配关键帧的特征点,并且该点有对应的MapPoint,那么将两个MapPoint合并(选择观测数多的)
- 2.如果MapPoint能匹配关键帧的特征点,并且该点没有对应的MapPoint,那么为该点添加MapPoint
- 步骤3:将一级二级相邻帧的MapPoints分别与当前帧(的MapPoints)进行融合
- 步骤4:更新当前帧MapPoints的描述子,深度,观测主方向等属性
- 在所有找到pMP的关键帧中,获得最佳的描述子
- 更新平均观测方向和观测距离
- 步骤5:更新当前帧的MapPoints后更新与其它帧的连接关系,更新covisibility图
- 取出当前帧匹配的地图点
- 步骤1:获得当前关键帧在covisibility图中权重排名前nn的邻接关键帧
- 检查并融合当前关键帧与相邻帧(两级相邻)重复的MapPoints SearchInNeighbors
-
mbAbortBA = false; bool b_doneLBA = false;
-
已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
- 若地图集中 关键帧大于2时:
- 使用IMU,且imu已经初始化时:
- 计算当前关键帧到前一关键帧+前一关键帧到前前关键帧相机中心距离之和
- 若距离大于 5cm,则 累计 当前帧与前一帧的时间
- 若当前地图做BA2不成功时,正常都会成功
- 若 时间小于10S 且 距离小于0.02,则打印没足够运动,重置
- 需大范围BA标记,使用单目时需匹配内点大于75,非单目时匹配内点大于100
- 进行内部BA
Optimizer::LocalInertialBA
- 否则 直接进行BA
Optimizer::LocalBundleAdjustment
- 使用IMU,且imu已经初始化时:
- 若IMU 未初始化时,初始化IMU,单目误差更小,imu权重更大
- 初始化IMU InitializeIMU
- 单目:1e2,1e10
- 否则:1e2,1e5
- 初始化IMU InitializeIMU
- 检查冗余本地关键帧 KeyFrameCulling
- 若累计时间小于100s,且 使用imu时
- imu已初始化,且跟踪ok 时:
- BA1 失败时,进行BA2,重新初始化IMU
- 否则进行BA2
- 单目且关键帧小于100时:尺度细化 ScaleRefinement
- 若地图集中 关键帧大于2时:
-
闭环检测插入 当前关键帧 mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame)
-
-
-
如果 Stop 且 imu_ok:
Stop:
if(mbStopRequested && !mbNotStop) return true
,否则返回 false- while 循环,直到完全 stop,while中等待 3000us
- checkFinished检测请求是否结束,
mbFinishRequested==true
,则 break
-
ResetIfRequested
- 如果有重置请求时,清空新关键帧和最近添加的地图点,不进行BA1和BA2
- 如果有重置活跃地图时,清空新关键帧和最近添加的地图点
-
SetAcceptKeyFrames(true)。设置接收关键帧
-
checkFinished检测请求是否结束,
mbFinishRequested==true
,则 break
-
-
SetFinish。两个状态赋值:
mbFinished=true , mbStopped=true
代码注解
void LocalMapping::Run()
{
mbFinished = false;
// while 1 直到标记 mbFinishRequested==true`,则 break
while(1)
{
// Tracking will see that Local Mapping is busy
// 设置不在接收关键帧 ,成员变量控制:`mbAcceptKeyFrames=false`
SetAcceptKeyFrames(false);
// Check if there are keyframes in the queue
// 如果有新的关键帧 且 imu_ok 时:
if(CheckNewKeyFrames() && !mbBadImu)
{
// BoW conversion and insertion in Map
// 对新关键帧进行处理。建立新的关键帧及其属性并加入全局map
ProcessNewKeyFrame();
// Check recent MapPoints
// <font color="blue">地图点的剔除</font>。针对 mlpRecentAddMapPoints,地图点坏的条件:
// - 地图点本身是坏的
// - 地图点的查找率小于0.25
// - 第一次观察到该地图点的帧id与当前帧id相隔 ≥2 且 地图观测数小于 阈值`cnThObs`
MapPointCulling();
// Triangulate new MapPoints
// 针对 mlpRecentAddMapPoints,相机运动过程中与相邻关键帧通过三角化恢复出一些MapPoints
CreateNewMapPoints();
mbAbortBA = false;
// 已经处理完队列中的最后的一个关键帧
if(!CheckNewKeyFrames())
{
// Find more matches in neighbor keyframes and fuse point duplications
//检查并融合当前关键帧与相邻帧(两级相邻)重复的MapPoints
SearchInNeighbors();
}
bool b_doneLBA = false;
int num_FixedKF_BA = 0;
int num_OptKF_BA = 0;
int num_MPs_BA = 0;
int num_edges_BA = 0;
// 已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
if(!CheckNewKeyFrames() && !stopRequested())
{
if(mpAtlas->KeyFramesInMap()>2)
{
// 使用IMU,且imu已经初始化时:
if(mbInertial && mpCurrentKeyFrame->GetMap()->isImuInitialized())
{
// 计算当前关键帧到前一关键帧+前一关键帧到前前关键帧相机中心距离之和
float dist = cv::norm(mpCurrentKeyFrame->mPrevKF->GetCameraCenter() - mpCurrentKeyFrame->GetCameraCenter()) +
cv::norm(mpCurrentKeyFrame->mPrevKF->mPrevKF->GetCameraCenter() - mpCurrentKeyFrame->mPrevKF->GetCameraCenter());
// 若距离大于 5cm,则 累计 当前帧与前一帧的时间
if(dist>0.05)
mTinit += mpCurrentKeyFrame->mTimeStamp - mpCurrentKeyFrame->mPrevKF->mTimeStamp;
// 若当前地图做BA2不成功时,正常都会成功
if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA2())
{
// 若 时间小于10S 且 距离小于0.02,则打印没足够运动,重置
if((mTinit<10.f) && (dist<0.02))
{
cout << "Not enough motion for initializing. Reseting..." << endl;
unique_lock<mutex> lock(mMutexReset);
mbResetRequestedActiveMap = true;
mpMapToReset = mpCurrentKeyFrame->GetMap();
mbBadImu = true;
}
}
// 需大范围BA标记,使用单目时需匹配内点大于75,非单目时匹配点大于100
bool bLarge = ((mpTracker->GetMatchesInliers()>75)&&mbMonocular)||((mpTracker->GetMatchesInliers()>100)&&!mbMonocular);
// 进行内部BA
Optimizer::LocalInertialBA(mpCurrentKeyFrame, &mbAbortBA, mpCurrentKeyFrame->GetMap(),num_FixedKF_BA,num_OptKF_BA,num_MPs_BA,num_edges_BA, bLarge, !mpCurrentKeyFrame->GetMap()->GetIniertialBA2());
b_doneLBA = true;
}
else // 否则直接进行BA
{
// VI-D Local BA
Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpCurrentKeyFrame->GetMap(),num_FixedKF_BA,num_OptKF_BA,num_MPs_BA,num_edges_BA);
b_doneLBA = true;
}
}
// Initialize IMU here
// 若IMU 未初始化时,初始化IMU,单目误差更小,imu权重更大
if(!mpCurrentKeyFrame->GetMap()->isImuInitialized() && mbInertial)
{
if (mbMonocular)
InitializeIMU(1e2, 1e10, true);
else
InitializeIMU(1e2, 1e5, true);
}
// Check redundant local Keyframes 检查冗余本地关键帧
KeyFrameCulling();
// 若累计时间小于100s,且 使用imu时
if ((mTinit<100.0f) && mbInertial)
{
// imu已初始化,且跟踪ok
if(mpCurrentKeyFrame->GetMap()->isImuInitialized() && mpTracker->mState==Tracking::OK)
{
// BA1 失败时,进行BA2,重新初始化IMU
if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA1()){
if (mTinit>5.0f)
{
cout << "start VIBA 1" << endl;
mpCurrentKeyFrame->GetMap()->SetIniertialBA1();
if (mbMonocular)
InitializeIMU(1.f, 1e5, true);
else
InitializeIMU(1.f, 1e5, true);
cout << "end VIBA 1" << endl;
}
}
else if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA2()){
if (mTinit>15.0f){
cout << "start VIBA 2" << endl;
mpCurrentKeyFrame->GetMap()->SetIniertialBA2();
if (mbMonocular)
InitializeIMU(0.f, 0.f, true);
else
InitializeIMU(0.f, 0.f, true);
cout << "end VIBA 2" << endl;
}
}
// scale refinement
if (((mpAtlas->KeyFramesInMap())<=100) &&
((mTinit>25.0f && mTinit<25.5f)||
(mTinit>35.0f && mTinit<35.5f)||
(mTinit>45.0f && mTinit<45.5f)||
(mTinit>55.0f && mTinit<55.5f)||
(mTinit>65.0f && mTinit<65.5f)||
(mTinit>75.0f && mTinit<75.5f))){
cout << "start scale ref" << endl;
if (mbMonocular)
ScaleRefinement();
cout << "end scale ref" << endl;
}
}
}
}
// 闭环检测插入 当前关键帧
mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
}
else if(Stop() && !mbBadImu)
// Stop: `if(mbStopRequested && !mbNotStop) return true`,否则返回 false
{
// Safe area to stop
// while 循环,直到完全 stop,while中等待 3000us
while(isStopped() && !CheckFinish())
{
usleep(3000);
}
// 检测请求是否结束,`mbFinishRequested==true`,则 break
if(CheckFinish())
break;
}
// 如果有请求时,执行:
// 如果有重置请求时,清空新关键帧和最近添加的地图点,不进行BA1和BA2
// 如果有重置活跃地图时,清空新关键帧和最近添加的地图点
ResetIfRequested();
// Tracking will see that Local Mapping is busy
SetAcceptKeyFrames(true); // 设置接收关键帧
// 检测请求是否结束,`mbFinishRequested==true`,则 break
if(CheckFinish())
break;
usleep(3000);
}
// 两个状态赋值:`mbFinished=true , mbStopped=true `
SetFinish();
}
其他函数
ProcessNewKeyFrame 对新关键帧进行处理
- 建立新的关键帧及其属性并加入全局map
void LocalMapping::ProcessNewKeyFrame()
{
/// 1. 新关键帧队列中取出第一帧作为当前关键帧,然后从队列中弹出
{
unique_lock<mutex> lock(mMutexNewKFs);
mpCurrentKeyFrame = mlNewKeyFrames.front();
mlNewKeyFrames.pop_front();
}
// Compute Bags of Words structures
// 当前关键帧计算词袋 `computeBoW`
mpCurrentKeyFrame->ComputeBoW();
/// 2.将 MapPoints 关联到新的关键帧并更新法线和描述符
// Associate MapPoints to the new keyframe and update normal and descriptor
// 取出 当前帧匹配的地图点
const vector<MapPoint*> vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();
// 遍历 地图点
for(size_t i=0; i<vpMapPointMatches.size(); i++)
{
MapPoint* pMP = vpMapPointMatches[i];
if(pMP)
{
// 地图点非空且不是坏的
if(!pMP->isBad())
{
// 若其在关键帧上则 对该地图点添加关键帧观测+法线和描述符
// 否则,加入:mlpRecentAddMapPoints
if(!pMP->IsInKeyFrame(mpCurrentKeyFrame))
{
pMP->AddObservation(mpCurrentKeyFrame, i);
pMP->UpdateNormalAndDepth();
pMP->ComputeDistinctiveDescriptors();
}
else // this can only happen for new stereo points inserted by the Tracking
// 这只会发生在 Tracking 插入的新立体点
{
mlpRecentAddedMapPoints.push_back(pMP);
}
}
}
}
// Update links in the Covisibility Graph
// 当前帧更新 图链接 `UpdateConnections`
mpCurrentKeyFrame->UpdateConnections();
// Insert Keyframe in Map
// 将该关键帧插入地图 `mpAtlas->AddKeyFrame`
mpAtlas->AddKeyFrame(mpCurrentKeyFrame);
}
MapPointCulling 最近添加地图点的剔除
- 针对 mlpRecentAddMapPoints
void LocalMapping::MapPointCulling() { // Check Recent Added MapPoints list<MapPoint*>::iterator lit = mlpRecentAddedMapPoints.begin(); const 标签:
力传感器bk