通俗易懂理解ORBSLAM2建图模块
一、通俗易懂理解LocalMapping类==建图(抓要点 切题 Done)
1.参考资料:
ORB SLAM2源码解读(十):LocalMapping类
[1] 深蓝学院 视觉SLAM开源代码论文带读(ORB_SLAM2)local mapping部分
2.主要函数:
void LocalMapping::ProcessNewKeyFrame()
MapPointCulling();
CreateNewMapPoints()
SearchInNeighbors();
Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame, &mbAbortBA,
mpMap);
KeyFrameCulling();
二、[w1]通俗易懂理解插入关键帧LocalMapping::ProcessNewKeyFrame
1.插入关键帧做了什么,具体流程是什么(W1)
mpCurrentKeyFrame = mlNewKeyFrames.front();
mlNewKeyFrames.pop_front();
(1)从队列中取出一个关键帧
(2)计算当前关键帧的词袋信息
(只要词袋向量是空的或者特征点向量是空的,将一帧的描述子转成词袋向量和特征向量,具体怎么转的skip)
(3)获取关键帧对应的地图点
对于每个地图点,如果该地图点不是isBad的话
如果地图点不包含当前关键帧的观测,那么就1)添加地图点观测,包含当前帧和当前帧对应的特征点id,2)更新地图点的平均观测方向和最大最小距离,这里的参考关键帧是什么?3)更新地图点的最佳描述子
(4)更新当前关键帧的共视图和生成树
mpCurrentKeyFrame->UpdateConnections();
(5)将关键帧插入到地图中
=========================================================================
题目:一个地图点的参考关键帧是什么?一个地图点的最大最小距离怎么算的。平均观测方向是什么?
参考:源码
思路:
1.思路:
参考关键帧:第一次观测到该地图点的关键帧。
平均观测方向:地图点的位置减去观测到该地图点的关键帧的位置的单位向量求和再求平均。
2.图解(请用纸):
3.公式推导(请用纸):
见上
要点程序:
其他:
=========================================================================
题目:什么叫共视图
参考:
思路:
1.思路:
通俗易懂理解KeyFrame类问题 要点(要点切题 Done)
2.图解(请用纸):
3.公式推导(请用纸):
=========================================================================
题目:什么叫地图点的最佳描述子
参考:
void MapPoint::ComputeDistinctiveDescriptors()
思路:
1.思路:
首先一个地图被多帧观测,一个地图点对应每帧有一个特征点,他的描述子是怎样的,获取多帧观测的全部描述子集合。然后计算一个距离矩阵,每一行每一列都是对应的描述子。然后取出每一行,得到这一行的距离中值(第i行就是第i个描述子和其他描述子的距离),比较全部行,得到最小的距离中值,对应的第i行的描述子就是最佳的描述子。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
void function() {
vector<vector<float>> vDistances;
vDistances.resize(N, vector<float>(N, 0));
for (int i = 0;i < N; i++) {
vDistances[i][i] = 0;
for (int j = i + 1; j < N; j++) {
distanceij = DescriptorDistance(vDescriptor[i], vDescriptor[j]);
vDistances[i][j] = distanceij;
vDistances[j][i] = distanceij;
}
}
int best_dist = INT_MAX;
int best_idx;
for (int i = 0; i < N; i++) {
vector<int> vDistance(vDistances[i].begin(), vDistances[i].end());
sort(vDistance.begin(), vDistance.end());
int median_distance = vDistance[0.5 * (N - 1)];
if (median_distance < best_dist) {
best_dist = median_distance;
best_idx = i;
}
}
}
其他:
三、通俗易懂理解地图点删除LocalMapping::MapPointCulling()
2.删除地图点做了什么,具体流程是什么
RGBD或者双目情况下ProcessNewKeyFrame产生的地图点对观测不够的进行setBagFlag,另一种情况下是CreateNewMapPoints函数也有mlpRecentAddedMapPoints,产生的地图点对观测不够的进行setBagFlag
setBagFlag就是删除地图点的观测
=========================================================================
题目:ProcessNewKeyFrame中mlpRecentAddedMapPoints的push的条件
参考:
思路:
1.思路:
(1)RGBD或者双目情况下,当前关键帧生成的地图点.这个地图点会被当前关键帧观测到,
这个会push到mlpRecentAddedMapPoints。
(2)单目情况下,关键帧的地图点基本是NULL的情况,因为还没3角化,会不会有匹配产生的地图点呢?
会,但是也不会进入这个mlpRecentAddedMapPoints。
单目情况下,地图点没有被当前帧观测到,需要添加当前帧的观测。
=========================================================================
题目:地图点删除函数void LocalMapping::MapPointCulling(地图点删除是对双目和RGBD而言的)。erase到底第什么意思?和setBadFlag有什么区别?
参考:
思路:
1.思路:
setBadFlag到底干了啥?把mappoint所有观测给剔除掉。得有观测!!!
(1)pMP->GetFoundRatio() < 0.25f这是什么意思呢??? Found和Visible有什么区别呢???
found:
对单目来说,在TrackLocalMap的Optimization后,如果当前帧的地图点不是outlier的话,那就增加1。由于当前帧的MapPoints可以被当前帧观测到,所以其被观测统计量+1
单目情况下,当前帧的地图点应该是NULL的情况,如果匹配上了,该关键帧对应的地图点就不为NULL了, 他就有深度了。对的。
Visible:
在TrackLocalMap搜索局部地图点Tracking::SearchLocalPoints()进行匹配的时候,
如果地图点(局部地图点和当前帧的地图点)在当前帧的视野范围内,那这个地图点可见次数就+1.
(2)(((int)nCurrentKFid - (int)pMP->mnFirstKFid) >= 3) 这个是一个质量很好的点。
却要 erase掉,这是什么回事?可以不erase,不影响。
因为这个函数void LocalMapping::MapPointCulling是为双目和RGB-D服务的,如果是坏点的话,
(怎么判断是否是外点,外点的话就没有观测了么?)
有观测质量不好是要剔除,所以要setBadFlag
好的点直接erase,这是什么意思呢?
mlpRecentAddedMapPoints push完,然后进行选择,然后它后续用来干啥呢?后续没有用到,只是用来管理地图点。
所以他的功能就是对观测次数不满足条件以及found/visible比例小的进行setbadflag
结论:双目和RGB-D情况下,对观测次数不满足条件以及found/visible比例小的进行setbadflag。或者单目情况下CreateNewMapPoints产生的地图点进行setbadflag。
=========================================================================
题目:CreateNewMapPoints函数也有mlpRecentAddedMapPoints,那么什么时候再次调用MapPointCulling呢?
参考:
思路:
1.思路:
这个会在下一次中调用MapPointCulling把观测不合格的地图点setbagflag.
=========================================================================
题目:删除的地图点是在哪里引入的。
参考:
思路:
1.思路:
双目和RGB-D下ProcessNewKeyFrame中mlpRecentAddedMapPoints
单目只有在CreateNewMapPoints中的mlpRecentAddedMapPoints
2.图解(请用纸):
3.公式推导(请用纸):
=========================================================================
题目:地图点找到(mnFound)和可见(mnVisible)是什么意思?
参考:
思路:
1.思路:
mnFound:
void MapPoint::IncreaseFound(int n) {
unique_lock<mutex> lock(mMutexFeatures);
mnFound += n;
}
[1]在不考虑闭环的情况下,MapPoint_Replace函数不起作用。
void MapPoint::Replace(MapPoint* pMP)
[2]IncreaseFound函数在Tracking中是什么情况下被调用的呢?
<1>定位模式下。TODO暂不讨论
<2>在TrackLocalMap的Optimization后,如果当前帧的地图点不是outlier的话,那就增加1。由于当前帧的MapPoints可以被当前帧观测到,其被观测统计量加1。单目情况下,当前帧的地图点应该是NULL的情况,如果匹配上了,该关键帧对应的地图点就不为NULL了, 他就有深度了。对的。
[3]结论:在TrackLocalMap的Optimization后,如果当前帧的地图点不是outlier的话,那就增加1。由于当前帧的MapPoints可以被当前帧观测到,所以其被观测统计量+1
单目情况下,当前帧的地图点应该是NULL的情况,如果匹配上了,该关键帧对应的地图点就不为NULL了, 他就有深度了。对的。那么相应的这个地图点就被当前帧观测到。
SearchByProjection
//保存结果: 为Frame中的特征点增加对应的MapPoint
F.mvpMapPoints[bestIdx] = pMP;
mnVisible:
在TrackLocalMap搜索局部地图点Tracking::SearchLocalPoints()进行匹配的时候,如果地图点(局部地图点和当前帧的地图点)在当前帧的视野范围内,那这个地图点可见次数就+1.
2.图解(请用纸):
3.公式推导(请用纸):
=========================================================================
题目:关键帧id和帧id是不一样的吧
参考:
思路:
是的,他们没有继承的关系,各自有自己的id,都是连续的
KeyFrame构造函数中:
// 获取id
mnId = nNextId++;
1.思路:
2.图解(请用纸):
3.公式推导(请用纸):
=========================================================================
题目:从建立该地图点开始,到目前为止经过了3个关键帧还在,为什么要放弃对该mappoint的检测?
参考:
思路:
无所谓的。
1.思路:
2.图解(请用纸):
3.公式推导(请用纸):
四、[射线][w1]通俗易懂理解地图点创建LocalMapping::CreateNewMapPoints()
3.创建地图点做了什么,具体流程是什么
[1]找到当前关键帧共视程度最强的20帧关键帧
[2]计算当前关键帧和1个共视关键帧的基本矩阵
[3]通过极线约束限制匹配的搜索范围,进行特征点的匹配[特征点对应的地图点是NULL]
[4]三角化生成地图点
=========================================================================
题目:F21和F12各是什么意思,为什么是相反的。
参考:
思路:
1.思路:
F12:当前帧1,另一个共视帧2,正向解算F12,
// Epipolar line in second image l = x1'F12 = [a b c]
// 求出kp1在pKF2上对应的极线
bool ORBmatcher::CheckDistEpipolarLine(const cv::KeyPoint &kp1,
const cv::KeyPoint &kp2,
const cv::Mat &F12,
const KeyFrame *pKF2)
初始化部分:
F21是从帧1到帧2,将帧1上的点投到帧2上
// Step 2.2 计算 img1 上的点在 img2 上投影得到的极线 l2 = F21 * p1 =
// (a2,b2,c2)
float Initializer::CheckFundamental(
const cv::Mat &F21, //当前帧和参考帧之间的基础矩阵
vector<bool> &vbMatchesInliers, //匹配的特征点对属于inliers的标记
float sigma)
2.图解(请用纸):
3.公式推导(请用纸):
// 需要好好理解!!!
/*
* 这里可以这样理解:世界坐标系原点记为W,关键帧1和关键帧2的相机光心分别记为O1
* O2. 根据Frame类中的有关说明,在世界坐标系下 这两帧相机光心的坐标可以记为:
* O1=-Rw1*t1w
* O2=-RW2*t2w
* 那么 t12 本质上描述的是从O2 ->
* O1相机光心所产生的位移,当在世界坐标系下的时候可以写成: t12(w) = O2-O1 =
* -Rw2*t2w+Rw1*t1w
* 要放在O1坐标系下表示的话,需要进行一个旋转变换(注意不要有平移变换,这个是对点才用的,如果对向量也应用的话很明显t12的长度都变了)
* t12(1) = R1w*t12(w)
* = -R1w*Rw2*t2w+Rw1*R1w*t1w
* = -R1w*Rw2+t2w+t1w
* 就是下面这行代码中写的:
*/
要点程序:
基本矩阵的构造
// 根据两关键帧的姿态计算两个关键帧之间的基本矩阵
cv::Mat LocalMapping::ComputeF12(KeyFrame *&pKF1, KeyFrame *&pKF2) {
// 先构造两帧之间的R12,t12
cv::Mat R1w = pKF1->GetRotation();
cv::Mat t1w = pKF1->GetTranslation();
cv::Mat R2w = pKF2->GetRotation();
cv::Mat t2w = pKF2->GetTranslation();
cv::Mat R12 = R1w * R2w.t();
/*
* 这里可以这样理解:世界坐标系原点记为W,关键帧1和关键帧2的相机光心分别记为O1
* O2. 根据Frame类中的有关说明,在世界坐标系下 这两帧相机光心的坐标可以记为:
* O1=-Rw1*t1w
* O2=-RW2*t2w
* 那么 t12 本质上描述的是从O2 ->
* O1相机光心所产生的位移,当在世界坐标系下的时候可以写成: t12(w) = O2-O1 =
* -Rw2*t2w+Rw1*t1w
* 要放在O1坐标系下表示的话,需要进行一个旋转变换(注意不要有平移变换,这个是对点才用的,如果对向量也应用的话很明显t12的长度都变了)
* t12(1) = R1w*t12(w)
* = -R1w*Rw2*t2w+Rw1*R1w*t1w
* = -R1w*Rw2+t2w+t1w
* 就是下面这行代码中写的:
*/
cv::Mat t12 = -R1w * R2w.t() * t2w + t1w;
// 得到 t12 的反对称矩阵
cv::Mat t12x = SkewSymmetricMatrix(t12);
const cv::Mat &K1 = pKF1->mK;
const cv::Mat &K2 = pKF2->mK;
// Essential Matrix: t12叉乘R12
// Fundamental Matrix: inv(K1)*E*inv(K2)
return K1.t().inv() * t12x * R12 * K2.inv();
}
void Initializer::FindFundamental(vector<bool> &vbMatchesInliers, float &score,
cv::Mat &F21)
//F21:从参考帧到当前帧的基础矩阵
//从F恢复R t
bool Initializer::ReconstructF(
vector<bool> &vbMatchesInliers, //匹配好的特征点对的Inliers标记
cv::Mat &F21, //从参考帧到当前帧的基础矩阵
cv::Mat &K, //相机的内参数矩阵
cv::Mat &R21, //计算好的相机从参考帧到当前帧的旋转
cv::Mat &t21, //计算好的相机从参考帧到当前帧的平移
vector<cv::Point3f> &vP3D, //三角化测量之后的特征点的空间坐标
vector<bool> &vbTriangulated, //某个特征点是否被三角化了的标记
float minParallax, //认为三角化测量有效的最小视差角
int minTriangulated) //认为使用三角化测量进行数据判断的最小测量点数量
=========================================================================
题目:如何利用极线约束进行特征点匹配
参考:
int ORBmatcher::SearchForTriangulation(
KeyFrame *pKF1, KeyFrame *pKF2, cv::Mat F12,
vector<pair<size_t, size_t> > &vMatchedPairs, const bool bOnlyStereo)
思路:
1.思路:
PS:我认为是像素。
// 检查极线距离是否符合要求,用在初始化时根据两个初始关键帧计算地图点的时候
bool ORBmatcher::CheckDistEpipolarLine(const cv::KeyPoint &kp1,
const cv::KeyPoint &kp2,
const cv::Mat &F12,
const KeyFrame *pKF2)
越上层,分辨率越低,不确定性范围越大
越上层,分辨率越低,不确定性范围越大。
总结:将pKF1图像的每个特征点与pKF2图像同一node节点的所有特征点依次检测,判断是否满足对极几何约束(点到极线的距离,小于设定的阈值),满足约束就是匹配的特征点,找到满足对极约束并且描述子之间的距离最小的建立匹配对。然后利用旋转直方图去除误匹配。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
=========================================================================
题目: 如何三角化建立地图点呢?
参考:
第6讲 视觉前端(切题 Done) TODO
思路:
1.思路:
射线公式:
两条射线的夹角大于3.6度
三角化知识,参考第6讲 视觉前端(切题 Done) TODO
一个用的是投影矩阵,一个是用变换矩阵,一模一样。
[1]生成的3D点是否在2帧前方,不在的话就放弃这个点
[2]计算3D点在当前关键帧下的重投影误差
[3]计算3D点在另一个关键帧下的重投影误差
[4]计算距离的连续性,(没有太明白,经验吧,TODO)
[5]增加地图点,计算地图点最佳描述子,计算地图点平均方向和最大最小距离。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
五、[背][w1]通俗易懂理解地图点融合LocalMapping::SearchInNeighbors()
关键是:地图点和特征点匹配上了,地图点添加观测和关键帧添加地图点。
=========================================================================
题目:创建地图点之后,如何进行地图点融合?
参考:
思路:
1.思路:
[1]找到当前关键帧共视程度最强的20帧相邻帧
// Extend to some second neighbors
const vector<KeyFrame *> vpSecondNeighKFs =
pKFi->GetBestCovisibilityKeyFrames(5);
[2]Extend to some second neighbors
存入一级和对应的二级相邻帧(就是一级的共视关键帧5帧)。
[3]对于当前帧的地图点和每一个关键帧。
将MapPoints往当前帧投影,
投影的步骤:
地图点是不是在该关键帧前面,地图点是不是在该关键帧视野范围内,是否在地图点的最大最小范围内,是否和平均观测方向夹角小于60度
匹配的步骤:
根据地图点的尺度确定在关键帧上的搜索范围,获取地图点的匹配集合,距离小于阈值的前提下描述子距离最小得到匹配点。
如果地图点和当前帧的特征点匹配上了:
<1>如果地图点和当前关键帧的特征点匹配上的话,那就那就先看这个特征点对应的地图点是不是为空
<2>如果关键帧的特征点有地图点的话,那么看那个地图点的观测次数较多,多的替换少的。
<3>如果关键帧的特征点对应的地图点为NULL的话,那么地图点添加观测,关键帧添加地图点。
[4]上面是当前帧的地图点和每一个关键帧进行融合。
现在是每一个关键帧的地图点和当前帧进行融合。
[5]更新当前帧MapPoints的描述子,深度,观测方向和最大最小距离。
[6]更新当前帧的covisibility图。更新共视的关键帧和对应的权重。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
其他:
=========================================================================
题目:地图点进行替换,替换的信息包含什么呢?
参考:
int ORBmatcher::Fuse(KeyFrame *pKF, const vector<MapPoint *> &vpMapPoints,
const float th)
思路:
1.思路:
[1]如果地图点和当前关键帧的特征点匹配上的话,那就那就先看这个特征点对应的地图点是不是为空
[2]如果关键帧的特征点有地图点的话,那么看那个地图点的观测次数较多,多的替换少的。
<1>如果要的地图点不包含该帧的观测,那么该帧替换地图点,地图点添加该帧观测
<2>如果要的地图点包含该帧的观测,那么就有冲突了,那么把该帧的地图点置为NULL。
[3]如果关键帧的特征点对应的地图点为NULL的话,那么地图点添加观测,关键帧添加地图点。
=========================================================================
题目:local BA做了什么,具体流程是什么
参考:
思路:
1.思路:
当前帧相连的局部关键帧和对应局部地图点的LocalBA
lLocalKeyFrames:当前关键帧和当前关键帧的共视关键帧。
lLocalMapPoints:当前关键帧和当前关键帧的共视关键帧对应的地图点。
对于有一些关键帧,能观测到该地图点lLocalMapPoints,但是不属于lLocalKeyFrames,把它设置为lFixedCameras。
[1]添加位姿顶点,其中第一关键帧不优化
[2]添加不优化的位姿顶点
[3]添加地图点,对地图点进行边缘化,对于每一个地图点的观测,建立边
<1>设置信息矩阵,权重为地图点对应金字塔层尺度因子权重的倒数,进行去量纲
<2>使用鲁棒核函数去除外点
<3>添加边
[4]第一阶段优化,迭代5次
[5]对于误差太大,不优化。第二阶段属于精求解,所以不使用鲁棒核函数。
[6]第二阶段,优化10次。
[7]对于误差太大的,去除关键帧和地图点的边。关键帧移除地图点,地图点移除关键帧的观测。
[8]更新关键帧的位姿,更新地图点的位置和平均观测方向、最大最小距离。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
其他:
=========================================================================
题目:初始关键帧,要锁住位姿不优化,为什么?
参考:第 5 讲 后端优化实践:逐行手写求解器 要点(切题 Done)
思路:
1.思路:
为了限定优化值不乱飘,那么代码如何实现,添加超强先验,H矩阵巨大,使得为0,或者让雅克比矩阵为0,那么,所以为0。
六、[背][w1]通俗易懂理解LocalMapping::LocalBundleAdjustment
=========================================================================
题目:local BA做了什么,具体流程是什么
参考:
思路:
1.思路:
当前帧相连的局部关键帧和对应局部地图点的LocalBA
lLocalKeyFrames:当前关键帧和当前关键帧的共视关键帧。
lLocalMapPoints:当前关键帧和当前关键帧的共视关键帧对应的地图点。
对于有一些关键帧,能观测到该地图点lLocalMapPoints,但是不属于lLocalKeyFrames,把它设置为lFixedCameras。
[1]添加位姿顶点,其中第一关键帧不优化
[2]添加不优化的位姿顶点
[3]添加地图点,对地图点进行边缘化,对于每一个地图点的观测,建立边
<1>设置信息矩阵,权重为地图点对应金字塔层尺度因子权重的倒数,进行去量纲
<2>使用鲁棒核函数去除外点
<3>添加边
[4]第一阶段优化,迭代5次
[5]对于误差太大,不优化。第二阶段属于精求解,所以不使用鲁棒核函数。
[6]第二阶段,优化10次。
[7]对于误差太大的,去除关键帧和地图点的边。关键帧移除地图点,地图点移除关键帧的观测。
[8]更新关键帧的位姿,更新地图点的位置和平均观测方向、最大最小距离。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
其他:
=========================================================================
题目:初始关键帧,要锁住位姿不优化,为什么?
参考:第 5 讲 后端优化实践:逐行手写求解器 要点(切题 Done)
思路:
1.思路:
为了限定优化值不乱飘,那么代码如何实现,添加超强先验,H矩阵巨大,使得为0,或者让雅克比矩阵为0,那么,所以为0。
七、[背诵][w1]通俗易懂理解关键帧删除LocalMapping::KeyFrameCulling
=========================================================================
题目:关键帧删除的原则
参考:
思路:
1.思路:
进来很容易,要留下来就很难。[宽进严留]
[1]得到当前关键帧对应的共视关键帧。
[2]对于每一个共视关键帧。某个关键帧对应的地图点,该地图点的观测次数大于3次,这样的地图点,它的数量若大于该帧对应的地图点数量的90%,那么这个关键帧就是冗余的,可以删除该关键帧。
PS:ORBSLAM中加了一些策略,但是整体是这样的。
策略是:当前关键帧对应的特征点有一个层数,要求地图点的其他观测,它对应的特征点的金字塔层数应该小于当前关键帧对应的特征点的层数,也就是说要远一点。
2.图解(请用纸):
3.公式推导(请用纸):
=========================================================================
题目:如何删除关键帧
参考:
通俗易懂理解KeyFrame类问题 要点(切题 Done)
思路:
1.思路:
2.图解(请用纸):
3.公式推导(请用纸):
八、[w1]通俗易懂理解KeyFrame类问题 要点(要点切题 Done)
参考
主要有3大类函数:
管理关键帧和MapPoint之间关系的函数
管理共视关系的函数
管理生成树的函数
=========================================================================
题目:什么叫共视图
参考:void KeyFrame::UpdateConnections()
思路:
1.思路:
KeyFrame为关键帧,关键帧之所以存在是因为优化需要,所以关键帧所有的内容都是为优化服务的。
图优化需要构建节点和边,节点有关键帧的位姿,边,第一种边是关键帧和MapPoint之间的,第二种边是和其他关键帧之间的,他们之间需要通过MapPoint产生联系,两帧能共同观测到一定数量的MapPoint可以在他俩之间建立边,这种通过共视关系建立的模型叫 covisibility graph。
covisibility graph:每个关键帧是一个节点,如果两个关键帧之间的共视地图点数量大于15,则在这两个节点之间建立边,边的权重是共视地图点的数量。
2.图解(请用纸):
KeyFrame_UpdateConnections3.公式推导(请用纸):
要点程序:
map<KeyFrame*, int> KFCounter;
=========================================================================
题目:什么叫本质图,为什么存在,怎么用?
参考:
思路:
1.思路:
大范围的话,计算量会很大,简版就是essential graph,通过spanning tree来管理关键帧之间的关系。每个帧都有一个父节点和多个子节点,节点为其他关键帧,在构建优化模型的时候,只有具有父子关系的关键帧之间才建立边,换句话说,essential graph就是covisibility graph的子集,大大减小边的数量,从而减小计算量。
covisibility graph:每个关键帧是一个节点,如果两个关键帧之间的共视地图点数量大于15,则在这两个节点之间建立边,边的权重是共视地图点的数量
spanning tree:保留所有的关键帧,给每一个关键帧找了1个父节点和多个子节点,每帧只跟自己的父节点和子节点相连,与其他帧不连接
essential graph:根据spanning tree建立的图模型,就是简版的covisibility graph
spanning tree、essential graph
其中子节点可能有多个,因为有一个容器mspChildrens,父节点只能有一个,因为有一个变量mpParent
当前关键帧的父节点就是和当前关键帧共视强度最强的。
本质图存在的意义是为全局优化服务的。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
mpParent = mvpOrderedConnectedKeyFrames.front();
// 建立双向连接关系
mpParent->AddChild(this);
=========================================================================
题目:local mapping中如何删除关键帧
参考:void KeyFrame::SetBadFlag()
// 删除和该帧相关的所有连接关系
void KeyFrame::SetBadFlag()
// 删除当前关键帧和指定关键帧之间的共视关系
void KeyFrame::EraseConnection(KeyFrame *pKF)
思路:
1.思路:
[1]当前关键帧有很多共视的关键帧,这些共视关键帧中的每一帧,从它的共视关键帧中,erase当前帧的联系。
[2]当前帧对应的地图点,这些地图点把当前帧从观测中erase。如果当前帧是地图点的参考关键帧(即观测到该地图点的第一个关键帧),重新给地图点指定参考关键帧。如果地图点观测的数量太少,那么把地图点给删除[详细见通俗易懂理解MapPoint类 要点(切题 Done)]。
本质图相关
[3]重新给每个children指定父亲,应为自己要死了,这个父亲是从children的共视关键帧中选择,找孩子和共视关键帧中强度最高的作为children的父亲。见下图。
[4]如果有的孩子找不到父亲,那么孩子的父亲等于我的父亲。
2.图解(请用纸):
KeyFrame_SetBadFlag3.公式推导(请用纸):
要点程序:
=========================================================================
题目:评估当前关键帧场景深度,在哪用呢?
参考:
float KeyFrame::ComputeSceneMedianDepth(const int q)
思路:
1.思路:
相机坐标系下地图点深度的中间值作为场景的平均深度。
单目地图点初始化用到。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
其他:
=========================================================================
题目:共视图、本质图建立的边如何在Optimizer中得到体现呢?有什么关系呢?
参考:
思路:
void Optimizer::LocalBundleAdjustment(KeyFrame* pKF, bool* pbStopFlag,
Map* pMap)
1.思路:
通过一个关键帧,我可以找到他的共视关键帧,组成lLocalKeyFrames,由lLocalKeyFrames得到lLocalMapPoints, 能够观测到lLocalMapPoints但是不属于lLocalKeyFrames进行fix,不优化。
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
九、通俗易懂理解MapPoint类 要点(切题 Done)
参考
题目:如何删除地图点?
参考:
// 把这个地图点给删掉
SetBadFlag()
思路:
1.思路:
[1]删除地图点的观测
[2]因为地图点被很多关键帧观测到,每个观测包含关键帧和对应的特征点id,把关键帧对应的地图点置空
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
问题
MapPoint的id是怎样的。
这个地图点被很多关键帧观测到,地图点的每一个观测包含关键帧和对应在关键帧中的索引。
主要函数
void MapPoint::AddObservation(KeyFrame* pKF, size_t idx)
void MapPoint::EraseObservation(KeyFrame* pKF)
void MapPoint::Replace(MapPoint* pMP)
// 把这个地图点给删掉
SetBadFlag()
void MapPoint::ComputeDistinctiveDescriptors()
void MapPoint::UpdateNormalAndDepth()
int MapPoint::PredictScale(const float& currentDist, Frame* pF)