ORB-SLAM2算法11之地图点MapPoint

文章目录

  • 0 引言
  • 1 MapPoint类
    • 1.1 构造函数
    • 1.2 成员函数
      • 1.2.1 AddObservation
      • 1.2.2 EraseObservation
      • 1.2.3 SetBadFlag
      • 1.2.4 Replace
      • 1.2.5 ComputeDistinctiveDescriptors
      • 1.2.6 UpdateNormalAndDepth
      • 1.2.7 PredictScale
  • 2 MapPoint类用途

0 引言

ORB-SLAM2算法7详细了解了System主类和多线程、ORB-SLAM2学习笔记8详细了解了图像特征点提取和描述子的生成、ORB-SLAM2算法9详细了解了图像帧及ORB-SLAM2算法10,本文继续学习ORB-SLAM2中的地图点MapPoint类,该类中主要包含增、删、替换地图点及地图点观测关系,计算描述子,更新法向量和深度值等围绕地图点操作的函数。

请添加图片描述

1 MapPoint类

1.1 构造函数

MapPoint类有两个构造函数,分别对应关键帧和普通帧。

  1. 关键帧相关的地图点构造函数:
/**
 * @brief 给定坐标和关键帧构造MapPoint
 * 
 * @param[in] Pos           MapPoint的坐标(世界坐标系)
 * @param[in] pRefKF        关键帧
 * @param[in] pMap          地图
 */
MapPoint::MapPoint(const cv::Mat &Pos,  //地图点的世界坐标
                   KeyFrame *pRefKF,    //生成地图点的关键帧
                   Map* pMap):          //地图点所存在的地图
    mnFirstKFid(pRefKF->mnId),              //第一次观测/生成它的关键帧 id
    mnFirstFrame(pRefKF->mnFrameId),        //创建该地图点的帧ID(因为关键帧也是帧啊)
    nObs(0),                                //被观测次数
    mnTrackReferenceForFrame(0),            //放置被重复添加到局部地图点的标记
    mnLastFrameSeen(0),                     //是否决定判断在某个帧视野中的变量
    mnBALocalForKF(0),                      //
    mnFuseCandidateForKF(0),                //
    mnLoopPointForKF(0),                    //
    mnCorrectedByKF(0),                     //
    mnCorrectedReference(0),                //
    mnBAGlobalForKF(0),                     //
    mpRefKF(pRefKF),                        //
    mnVisible(1),                           //在帧中的可视次数
    mnFound(1),                             //被找到的次数 和上面的相比要求能够匹配上
    mbBad(false),                           //坏点标记
    mpReplaced(static_cast<MapPoint*>(NULL)), //替换掉当前地图点的点
    mfMinDistance(0),                       //当前地图点在某帧下,可信赖的被找到时其到关键帧光心距离的下界
    mfMaxDistance(0),                       //上界
    mpMap(pMap)                             //从属地图
{
    Pos.copyTo(mWorldPos);
    //平均观测方向初始化为0
    mNormalVector = cv::Mat::zeros(3,1,CV_32F);

    // MapPoints can be created from Tracking and Local Mapping. This mutex avoid conflicts with id.
    unique_lock<mutex> lock(mpMap->mMutexPointCreation);
    mnId=nNextId++;
}
  1. 普通帧相关的地图点构造函数:
/*
 * @brief 给定坐标与普通帧构造MapPoint
 *
 * 双目:UpdateLastFrame()
 * @param Pos    MapPoint的坐标(世界坐标系)
 * @param pMap   Map     
 * @param pFrame Frame
 * @param idxF   MapPoint在Frame中的索引,即对应的特征点的编号
 */
MapPoint::MapPoint(const cv::Mat &Pos, Map* pMap, Frame* pFrame, const int &idxF):
    mnFirstKFid(-1), mnFirstFrame(pFrame->mnId), nObs(0), mnTrackReferenceForFrame(0), mnLastFrameSeen(0),
    mnBALocalForKF(0), mnFuseCandidateForKF(0),mnLoopPointForKF(0), mnCorrectedByKF(0),
    mnCorrectedReference(0), mnBAGlobalForKF(0), mpRefKF(static_cast<KeyFrame*>(NULL)), mnVisible(1),
    mnFound(1), mbBad(false), mpReplaced(NULL), mpMap(pMap)
{
    Pos.copyTo(mWorldPos);
    cv::Mat Ow = pFrame->GetCameraCenter();
    mNormalVector = mWorldPos - Ow;// 世界坐标系下相机到3D点的向量 (当前关键帧的观测方向)
    mNormalVector = mNormalVector/cv::norm(mNormalVector);// 单位化

    //这个算重了吧
    cv::Mat PC = Pos - Ow;
    const float dist = cv::norm(PC);    //到相机的距离
    const int level = pFrame->mvKeysUn[idxF].octave;
    const float levelScaleFactor =  pFrame->mvScaleFactors[level];
    const int nLevels = pFrame->mnScaleLevels;

    // 另见 PredictScale 函数前的注释
    /* 因为在提取特征点的时候, 考虑到了图像的尺度问题,因此在不同图层上提取得到的特征点,对应着特征点距离相机的远近
       不同, 所以在这里生成地图点的时候,也要再对其进行确认
       虽然我们拿不到每个图层之间确定的尺度信息,但是我们有缩放比例这个相对的信息哇
    */
    mfMaxDistance = dist*levelScaleFactor;                              //当前图层的"深度"
    mfMinDistance = mfMaxDistance/pFrame->mvScaleFactors[nLevels-1];    //该特征点上一个图层的"深度""

    // 见 mDescriptor 在MapPoint.h中的注释 ==> 其实就是获取这个地图点的描述子
    pFrame->mDescriptors.row(idxF).copyTo(mDescriptor);

    // MapPoints can be created from Tracking and Local Mapping. This mutex avoid conflicts with id.
    // TODO 不太懂,怎么个冲突法? 
    unique_lock<mutex> lock(mpMap->mMutexPointCreation);
    mnId=nNextId++;
}

1.2 成员函数

MapPoint类中的成员函数一览表:

成员函数类型定义
void MapPoint::SetWorldPos(const cv::Mat &Pos)public设置地图点在世界坐标系下的坐标
cv::Mat MapPoint::GetWorldPos()public获取地图点在世界坐标系下的坐标
cv::Mat MapPoint::GetNormal()public世界坐标系下地图点被多个相机观测的平均观测方向
KeyFrame* MapPoint::GetReferenceKeyFrame()public获取地图点的参考关键帧
void MapPoint::AddObservation(KeyFrame* pKF, size_t idx)public给地图点添加观测
void MapPoint::EraseObservation(KeyFrame* pKF)public删除某个关键帧对当前地图点的观测
map<KeyFrame*, size_t> MapPoint::GetObservations()public能够观测到当前地图点的所有关键帧及该地图点在KF中的索引
int MapPoint::Observations()public被观测到的相机数目,单目+1,双目或RGB-D+2
void MapPoint::SetBadFlag()public告知可以观测到该MapPointFrame,该MapPoint已被删除
void MapPoint::Replace(MapPoint* pMP)public替换地图点,更新观测关系
bool MapPoint::isBad()public没有经过 MapPointCulling 检测的MapPoints, 认为是坏掉的点
void MapPoint::IncreaseVisible(int n);void MapPoint::IncreaseFound(int n)public找到地图点和帧的特征点能匹配后+1
float MapPoint::GetFoundRatio()public计算被找到的比例
void MapPoint::ComputeDistinctiveDescriptors()public计算地图点最具代表性的描述子
cv::Mat MapPoint::GetDescriptor()public获取当前地图点的描述子
int MapPoint::GetIndexInKeyFrame(KeyFrame *pKF)public获取当前地图点在某个关键帧的观测中,对应的特征点的ID
bool MapPoint::IsInKeyFrame(KeyFrame *pKF)public检查该地图点是否在关键帧中(有对应的二维特征点)
void MapPoint::UpdateNormalAndDepth()public更新地图点的平均观测方向、观测距离范围
float MapPoint::GetMinDistanceInvariance()public获得最小的平均观测距离
float MapPoint::GetMinDistanceInvariance()public获得最大的平均观测距离
int MapPoint::PredictScale(const float &currentDist, KeyFrame* pKF)public预测地图点对应特征点所在的图像金字塔尺度层数
int MapPoint::PredictScale(const float &currentDist, Frame* pF)public根据地图点到光心的距离来预测一个类似特征金字塔的尺度

以下对一些重点的成员函数进行详细介绍:

1.2.1 AddObservation

该函数主要功能是新增地图点的观测关系,当然,首先判断是否已存在观测关系,如果不存在就新增,而且分成单目和双目两种情况,单目时观测次数加1,双目时观测次数加2

// 新增地图点的观测关系

void MapPoint::AddObservation(KeyFrame* pKF, size_t idx)
{
    unique_lock<mutex> lock(mMutexFeatures);
    // mObservations:观测到该MapPoint的关键帧KF和该MapPoint在KF中的索引
    // 如果已经添加过观测,返回
    if(mObservations.count(pKF)) 
        return;
    // 如果没有添加过观测,记录下能观测到该MapPoint的KF和该MapPoint在KF中的索引
    mObservations[pKF]=idx;

    if(pKF->mvuRight[idx]>=0)
        nObs+=2; // 双目或者rgbd
    else
        nObs++; // 单目
}

1.2.2 EraseObservation

该函数主要功能是删除地图点观测关系,首先判断关键帧是否在观测中,如果在,就先从容器mObservations中移除该关键帧,然后判断该帧是否是参考关键帧,如果是,参考关键帧换成观测的第一帧,删除以后,如果该地图点被观测次数小于2,就删除该地图点。

// 删除某个关键帧对当前地图点的观测
void MapPoint::EraseObservation(KeyFrame* pKF)
{
    bool bBad=false;
    {
        unique_lock<mutex> lock(mMutexFeatures);
        // 查找这个要删除的观测,根据单目和双目类型的不同从其中删除当前地图点的被观测次数
        if(mObservations.count(pKF))
        {
            int idx = mObservations[pKF];
            if(pKF->mvuRight[idx]>=0)
                nObs-=2;
            else
                nObs--;

            mObservations.erase(pKF);

            // 如果该keyFrame是参考帧,该Frame被删除后重新指定RefFrame
            if(mpRefKF==pKF)
                mpRefKF=mObservations.begin()->first;

            // If only 2 observations or less, discard point
            // 当观测到该点的相机数目少于2时,丢弃该点
            if(nObs<=2)
                bBad=true;
        }
    }

    if(bBad)
        // 告知可以观测到该MapPoint的Frame,该MapPoint已被删除
        SetBadFlag();
}

1.2.3 SetBadFlag

该函数的主要功能是删除地图点,并清除关键帧和地图中所有和该地图点对应的关联关系。该函数在1.2.2``EraseObservation函数中的最后一步调用。

// 告知可以观测到该MapPoint的Frame,该MapPoint已被删除

void MapPoint::SetBadFlag()
{
    map<KeyFrame*,size_t> obs;
    {
        unique_lock<mutex> lock1(mMutexFeatures);
        unique_lock<mutex> lock2(mMutexPos);
        mbBad=true;
        // 把mObservations转存到obs,obs和mObservations里存的是指针,赋值过程为浅拷贝
        obs = mObservations;
        // 把mObservations指向的内存释放,obs作为局部变量之后自动删除
        mObservations.clear();
    }
    for(map<KeyFrame*,size_t>::iterator mit=obs.begin(), mend=obs.end(); mit!=mend; mit++)
    {
        KeyFrame* pKF = mit->first;
        // 告诉可以观测到该MapPoint的KeyFrame,该MapPoint被删了
        pKF->EraseMapPointMatch(mit->second);
    }
    // 擦除该MapPoint申请的内存
    mpMap->EraseMapPoint(this);
}

1.2.4 Replace

该函数的主要功能是替换掉当前地图点,函数输入是待替换的地图点,首先判断是否是同一个地图点,如果是直接跳过,然后清除当前地图点的信息,和上一个SetBadFlag函数差不多,然后将当前地图点的观测数据等都替换到新的地图点上,最后将观测到当前地图点的关键帧的信息进行更新。

// 替换地图点,更新观测关系

void MapPoint::Replace(MapPoint* pMP)
{
    // 同一个地图点则跳过
    if(pMP->mnId==this->mnId)
        return;

    //要替换当前地图点,有两个工作:
    // 1. 将当前地图点的观测数据等其他数据都"叠加"到新的地图点上
    // 2. 将观测到当前地图点的关键帧的信息进行更新


    // 清除当前地图点的信息,这一段和SetBadFlag函数相同
    int nvisible, nfound;
    map<KeyFrame*,size_t> obs;
    {
        unique_lock<mutex> lock1(mMutexFeatures);
        unique_lock<mutex> lock2(mMutexPos);
        obs=mObservations;
        //清除当前地图点的原有观测
        mObservations.clear();
        //当前的地图点被删除了
        mbBad=true;
        //暂存当前地图点的可视次数和被找到的次数
        nvisible = mnVisible;
        nfound = mnFound;
        //指明当前地图点已经被指定的地图点替换了
        mpReplaced = pMP;
    }

    // 所有能观测到原地图点的关键帧都要复制到替换的地图点上
    //- 将观测到当前地图的的关键帧的信息进行更新
    for(map<KeyFrame*,size_t>::iterator mit=obs.begin(), mend=obs.end(); mit!=mend; mit++)
    {
        // Replace measurement in keyframe
        KeyFrame* pKF = mit->first;

        if(!pMP->IsInKeyFrame(pKF))
        {   
            // 该关键帧中没有对"要替换本地图点的地图点"的观测
            pKF->ReplaceMapPointMatch(mit->second, pMP);// 让KeyFrame用pMP替换掉原来的MapPoint
            pMP->AddObservation(pKF,mit->second);// 让MapPoint替换掉对应的KeyFrame
        }
        else
        {
            // 这个关键帧对当前的地图点和"要替换本地图点的地图点"都具有观测
            // 产生冲突,即pKF中有两个特征点a,b(这两个特征点的描述子是近似相同的),这两个特征点对应两个 MapPoint 为this,pMP
            // 然而在fuse的过程中pMP的观测更多,需要替换this,因此保留b与pMP的联系,去掉a与this的联系
            //说白了,既然是让对方的那个地图点来代替当前的地图点,就是说明对方更好,所以删除这个关键帧对当前帧的观测
            pKF->EraseMapPointMatch(mit->second);
        }
    }

    //- 将当前地图点的观测数据等其他数据都"叠加"到新的地图点上
    pMP->IncreaseFound(nfound);
    pMP->IncreaseVisible(nvisible);
    //描述子更新
    pMP->ComputeDistinctiveDescriptors();

    //告知地图,删掉我
    mpMap->EraseMapPoint(this);
}

1.2.5 ComputeDistinctiveDescriptors

该函数主要功能是计算最匹配的描述子,由于一个地图点会被多个相机观测到,在插入关键帧后,需判断是否更新当前点的最适合的描述子,最好的描述子与其他描述子应该具有最小的平均距离。

  1. 获取该地图点所有有效的观测关键帧信息;
  2. 遍历观测到该地图点的所有关键帧,对应的orb描述子,放到向量vDescriptors中;
  3. 计算这些描述子两两之间的距离;
  4. 选择最有代表性的描述子,它与其他描述子应该具有最小的距离中值。
// 计算地图点最具代表性的描述子

void MapPoint::ComputeDistinctiveDescriptors()
{
    // Retrieve all observed descriptors
    vector<cv::Mat> vDescriptors;

    map<KeyFrame*,size_t> observations;

    // Step 1 获取该地图点所有有效的观测关键帧信息
    {
        unique_lock<mutex> lock1(mMutexFeatures);
        if(mbBad)
            return;
        observations=mObservations;
    }

    if(observations.empty())
        return;

    vDescriptors.reserve(observations.size());

    // Step 2 遍历观测到该地图点的所有关键帧,对应的orb描述子,放到向量vDescriptors中
    for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
    {
        // mit->first取观测到该地图点的关键帧
        // mit->second取该地图点在关键帧中的索引
        KeyFrame* pKF = mit->first;

        if(!pKF->isBad())        
            // 取对应的描述子向量                                               
            vDescriptors.push_back(pKF->mDescriptors.row(mit->second));     
    }

    if(vDescriptors.empty())
        return;

    // Compute distances between them
    // Step 3 计算这些描述子两两之间的距离
    // N表示为一共多少个描述子
    const size_t N = vDescriptors.size();
	
    // 将Distances表述成一个对称的矩阵
    // float Distances[N][N];
	std::vector<std::vector<float> > Distances;
	Distances.resize(N, vector<float>(N, 0));
	for (size_t i = 0; i<N; i++)
    {
        // 和自己的距离当然是0
        Distances[i][i]=0;
        // 计算并记录不同描述子距离
        for(size_t j=i+1;j<N;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
    // Step 4 选择最有代表性的描述子,它与其他描述子应该具有最小的距离中值
    int BestMedian = INT_MAX;   // 记录最小的中值
    int BestIdx = 0;            // 最小中值对应的索引
    for(size_t i=0;i<N;i++)
    {
        // 第i个描述子到其它所有描述子之间的距离
        // vector<int> vDists(Distances[i],Distances[i]+N);
		vector<int> vDists(Distances[i].begin(), Distances[i].end());
		sort(vDists.begin(), vDists.end());

        // 获得中值
        int median = vDists[0.5*(N-1)];
        
        // 寻找最小的中值
        if(median<BestMedian)
        {
            BestMedian = median;
            BestIdx = i;
        }
    }

    {
        unique_lock<mutex> lock(mMutexFeatures);
        mDescriptor = vDescriptors[BestIdx].clone();       
    }
}

1.2.6 UpdateNormalAndDepth

该函数主要功能是更新法向量和深度值,图像提取描述子是使用金字塔分层提取,那计算法向量和深度后,可知MapPoint在对应的关键帧的金字塔哪一层可以提取到。

法向量:相机光心指向地图点的方向,计算公式是地图点的三维坐标减去相机光心的三维坐标。

// 更新地图点的平均观测方向、观测距离范围

void MapPoint::UpdateNormalAndDepth()
{
    // Step 1 获得观测到该地图点的所有关键帧、坐标等信息
    map<KeyFrame*,size_t> observations;
    KeyFrame* pRefKF;
    cv::Mat Pos;
    {
        unique_lock<mutex> lock1(mMutexFeatures);
        unique_lock<mutex> lock2(mMutexPos);
        if(mbBad)
            return;

        observations=mObservations; // 获得观测到该地图点的所有关键帧
        pRefKF=mpRefKF;             // 观测到该点的参考关键帧(第一次创建时的关键帧)
        Pos = mWorldPos.clone();    // 地图点在世界坐标系中的位置
    }

    if(observations.empty())
        return;

    // Step 2 计算该地图点的平均观测方向
    // 能观测到该地图点的所有关键帧,对该点的观测方向归一化为单位向量,然后进行求和得到该地图点的朝向
    // 初始值为0向量,累加为归一化向量,最后除以总数n
    cv::Mat normal = cv::Mat::zeros(3,1,CV_32F);
    int n=0;
    for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.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];          // 当前金字塔层对应的尺度因子,scale^n,scale=1.2,n为层数
    const int nLevels = pRefKF->mnScaleLevels;                              // 金字塔总层数,默认为8

    {
        unique_lock<mutex> lock3(mMutexPos);
        // 使用方法见PredictScale函数前的注释
        mfMaxDistance = dist*levelScaleFactor;                              // 观测到该点的距离上限
        mfMinDistance = mfMaxDistance/pRefKF->mvScaleFactors[nLevels-1];    // 观测到该点的距离下限
        mNormalVector = normal/n;                                           // 获得地图点平均的观测方向
    }
}

1.2.7 PredictScale

// 下图中横线的大小表示不同图层图像上的一个像素表示的真实物理空间中的大小
//              ____
// Nearer      /____\     level:n-1 --> dmin
//            /______\                       d/dmin = 1.2^(n-1-m)
//           /________\   level:m   --> d
//          /__________\                     dmax/d = 1.2^m
// Farther /____________\ level:0   --> dmax
//
//           log(dmax/d)
// m = ceil(------------)
//            log(1.2)

该函数的主要功能是预测特征点在金字塔哪一层可以找到,如上所示,在进行投影匹配的时候会给定特征点的搜索范围,考虑到处于不同尺度(也就是距离相机远近,位于图像金字塔中不同图层)的特征点受到相机旋转的影响不同,因此会希望距离相机近的点的搜索范围更大一点,距离相机更远的点的搜索范围更小一点,所以要在这里,根据点到关键帧/帧的距离来估计它在当前的关键帧/帧中。

/**
 * @brief 预测地图点对应特征点所在的图像金字塔尺度层数
 * 
 * @param[in] currentDist   相机光心距离地图点距离
 * @param[in] pKF           关键帧
 * @return int              预测的金字塔尺度
 */
int MapPoint::PredictScale(const float &currentDist, KeyFrame* pKF)
{
    float ratio;
    {
        unique_lock<mutex> lock(mMutexPos);
        // mfMaxDistance = ref_dist*levelScaleFactor 为参考帧考虑上尺度后的距离
        // ratio = mfMaxDistance/currentDist = ref_dist/cur_dist
        ratio = mfMaxDistance/currentDist;
    }

    // 取对数
    int nScale = ceil(log(ratio)/pKF->mfLogScaleFactor);
    if(nScale<0)
        nScale = 0;
    else if(nScale>=pKF->mnScaleLevels)
        nScale = pKF->mnScaleLevels-1;

    return nScale;
}

/**
 * @brief 根据地图点到光心的距离来预测一个类似特征金字塔的尺度
 * 
 * @param[in] currentDist       地图点到光心的距离
 * @param[in] pF                当前帧
 * @return int                  尺度
 */
int MapPoint::PredictScale(const float &currentDist, Frame* pF)
{
    float ratio;
    {
        unique_lock<mutex> lock(mMutexPos);
        ratio = mfMaxDistance/currentDist;
    }

    int nScale = ceil(log(ratio)/pF->mfLogScaleFactor);
    if(nScale<0)
        nScale = 0;
    else if(nScale>=pF->mnScaleLevels)
        nScale = pF->mnScaleLevels-1;

    return nScale;
}

2 MapPoint类用途

ORB-SLAM2中,MapPoint类用于表示地图中的一个特征点或地图点。每个MapPoint对象表示场景中的一个3D点,该点由多个帧中的特征描述子观测到。以下是MapPoint类的一些主要作用和用途:

  1. 特征点表示:MapPoint对象保存了一个特征点的位置信息(3D坐标),以及它在多个帧中的观测信息(例如,特征描述子、观测帧等)。

  2. 地图构建:MapPoint对象用于构建ORB-SLAM2的稀疏地图。通过观测到的特征点,ORB-SLAM2可以估计相机的轨迹并构建场景的3D模型。

  3. 相机姿态估计:MapPoint对象与关键帧(KeyFrame)之间的观测关系可用于优化相机的姿态估计。通过三角化算法,ORB-SLAM2可以从多个观测到同一MapPoint的关键帧中估计出相机和地图点的相对姿态。

  4. 重定位和回环检测:MapPoint对象用于重定位和回环检测。在重定位时,ORB-SLAM2可以根据当前帧与地图中的MapPoint的匹配关系来估计相机的姿态。在回环检测中,ORB-SLAM2可以使用地图点的描述子与历史帧进行匹配,以判断是否遇到了先前观测过的场景。

总之,MapPoint类在ORB-SLAM2中扮演着重要的角色,用于表示地图中的3D点以及与之相关的观测信息,为场景恢复、姿态估计、重定位和回环检测等任务提供支持。

请添加图片描述

还要重点关注下地图点MapPoint的三个生命周期:

  1. 创建MapPoint的时机:
  • Tracking线程中初始化过程Tracking::MonocularInitialization()Tracking::StereoInitialization()
  • Tracking线程中创建新的关键帧Tracking::CreateNewKeyFrame()
  • Tracking线程中恒速运动模型跟踪Tracking::TrackWithMotionModel()也会产生临时地图点,但这些临时地图点在跟踪成功后会被马上删除。如果跟踪失败,则不会产生关键帧,这些地图点也不会被注册进地图;
  • LocalMapping线程中创建新地图点的步骤LocalMapping::CreateNewMapPoints()会将当前关键帧与前一关键帧进行匹配,生成新地图点。
  1. 删除MapPoint的时机:
  • LocalMapping线程中删除恶劣地图点的步骤LocalMapping::MapPointCulling()
  • 删除关键帧的函数KeyFrame::SetBadFlag()会调用函数MapPoint::EraseObservation()删除地图点对关键帧的观测,若地图点对关键帧的观测少于2,则地图点无法被三角化,就删除该地图点。
  1. 替换MapPoint的时机:
  • LoopClosing线程中闭环矫正LoopClosing::CorrectLoop()时当前关键帧和闭环关键帧上的地图点发生冲突时,会使用闭环关键帧的地图点替换当前关键帧的地图点;
  • LoopClosing线程中闭环矫正函数LoopClosing::CorrectLoop()会调用LoopClosing::SearchAndFuse()将闭环关键帧的共视关键帧组中所有地图点投影到当前关键帧的共视关键帧组中,发生冲突时就会替换。

Reference:

  • https://github.com/raulmur/ORB_SLAM2
  • https://github.com/electech6/ORB_SLAM2_detailed_comments/tree/master
  • https://blog.csdn.net/ncepu_Chen/article/details/116784652



须知少时凌云志,曾许人间第一流。



⭐️👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍🌔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/88999.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络协议详解之STP

目录 一、STP协议&#xff08;生成树&#xff09; 1.1 生成树协议核心知识点&#xff1a; 1.2 生成树协议与导致问题&#xff1a; 生成树含义&#xff1a; 1.3 802.1D 规则&#xff1a; 802.1D 缺点&#xff1a; 1.4 PVST cisco私有 1.5 PVST 1.6 快速生成树 快速的原…

uniapp 微信小程序:RecorderManager 录音DEMO

uniapp 微信小程序&#xff1a;RecorderManager 录音DEMO 简介index.vue参考资料 简介 使用 RecorderManager 实现录音。及相关的基本操作。&#xff08;获取文件信息&#xff0c;上传文件&#xff09; 此图包含Demo中用于上传测试的服务端程序upload.exe&#xff0c;下载后用…

【Axure原型分享】能统计中英文字数的多行输入框

今天和大家分享能统计中英文字数的多行输入框的原型模板&#xff0c;在输入框里输入内容后&#xff0c;能够动态根据输入框的内容&#xff0c;统计出字符数量&#xff0c;包括总字数、中文字数、英文字数、数字字数、其他标点符号的字数&#xff0c;具体效果可以观看下方视频或…

网络安全(黑客)自学剖析

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

04_Redis与mysql数据双写一致性案例

04——redis与mysql数据双写一致性 一、canal 是什么 canal[ka’nel,中文翻译为水道/管道/沟渠/运河&#xff0c;主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析&#xff0c;是阿里巴巴开发并开源的,采用Java语言开发&#xff1b; 历史背景是早期阿里巴巴因为杭州和…

【Python爬虫】使用代理ip进行网站爬取

前言 使用代理IP进行网站爬取可以有效地隐藏你的真实IP地址&#xff0c;让网站难以追踪你的访问行为。本文将介绍Python如何使用代理IP进行网站爬取的实现&#xff0c;包括代理IP的获取、代理IP的验证、以及如何把代理IP应用到爬虫代码中。 1. 使用代理IP的好处 在进行网站爬…

ROS通信机制之话题(Topics)的发布与订阅以及自定义消息的实现

我们知道在ROS中&#xff0c;由很多互不相干的节点组成了一个复杂的系统&#xff0c;单个的节点看起来是没起什么作用&#xff0c;但是节点之间进行了通信之后&#xff0c;相互之间能够交互信息和数据的时候&#xff0c;就变得很有意思了。 节点之间进行通信的一个常用方法就是…

SpringMVC 反射型跨站点脚本攻击

解决方案&#xff1a; 服务端校验&#xff0c;添加拦截器 配置web,xml <filter><filter-name>xssFilter </filter-name><filter-class>com.fh.filter.XssFilter </filter-class></filter> XssFilter package com.fh.filter;import com…

.NET敏捷开发框架-RDIFramework.NET V6.0发布

1、RDIFramework.NET 敏捷开发框架介绍 RDIFramework.NET敏捷开发框架&#xff0c;是我司重磅推出的基于最新.NET6与.NET Framework的快速信息化系统开发、整合框架&#xff0c;为企业快速构建跨平台、企业级的应用提供了强大支持。 开发人员不需要开发系统的基础功能和公共模…

MCU和MPU你分得清楚吗?

最近有不少同学表示在学习嵌入式的过程中分不清MCU和MPU&#xff0c;这两个确实是长得很像、容易混淆的概念&#xff0c;这里我为大家仔细分辨一下。 从概念上讲&#xff0c;MCU指的是微控制器&#xff0c;优势在于“控制”&#xff0c;MPU指的是微处理器&#xff0c;优势在于“…

微服务基础知识

文章目录 微服务基础知识一、系统架构的演变1、单体应用架构2、垂直应用架构3、分布式SOA架构&#xff08;1&#xff09;什么是SOA&#xff08;2&#xff09;SOA架构 4、微服务架构5、SOA和微服务的关系&#xff08;1&#xff09;SOA&#xff08;2&#xff09;微服务架构 二、分…

idea使用tomcat

1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目&#xff0c;然后在项目上添加框架支持&#xff0c;选择javaee 3. 项目结构 4.执行测试&#xff1a;

运维高级学习--Docker(二)

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 #拉取mysql5.6和owncloud镜像 [rootlocalhost ~]# docker pull mysql:5.6 [rootlocalhost ~]# docker pull owncloud [rootlocalhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED …

【芯片设计封装与测试】芯片测试目的、方法、分类及案例

目录 1.芯片测试概述&#xff08;目的、方法&#xff09; 1.1.测试在芯片产业价值链上的位置 2.测试如何体现在设计的过程中 2.1.半导体测试定义与基本工作机制 2.2.半导体测试环节分类及对应设备 2.3.设计验证 3.测试的各种类型 3.1.抽样测试和生产全测 3.2.测试相关…

【android12-linux-5.1】【ST芯片】HAL移植后配置文件生成报错

根据ST官方源码移植HAL源码后&#xff0c;执行readme指示中的生成配置文件指令时报错ST_HAL_ANDROID_VERSION未定义之类&#xff0c;应该是编译环境参数问题。makefile文件中是自动识别配置的&#xff0c;参数不祥就会报错&#xff0c;这里最快的解决方案是查询确定自己android…

课程项目设计--spring security--认证管理功能--宿舍管理系统--springboot后端

写在前面&#xff1a; 还要实习&#xff0c;每次时间好少呀&#xff0c;进度会比较慢一点 本文主要实现是用户管理相关功能。 前文项目建立 文章目录 验证码功能验证码配置验证码生成工具类添加依赖功能测试编写controller接口启动项目 security配置拦截器配置验证码拦截器 …

Leetcode每日一题:1267. 统计参与通信的服务器

原题 这里有一幅服务器分布图&#xff0c;服务器的位置标识在 m * n 的整数矩阵网格 grid 中&#xff0c;1 表示单元格上有服务器&#xff0c;0 表示没有。 如果两台服务器位于同一行或者同一列&#xff0c;我们就认为它们之间可以进行通信。 请你统计并返回能够与至少一台其…

musl libc ldso 动态加载研究笔记:动态库的加载次序与初始化次序

前言 musl ldso 是按照什么次序加载动态链接的应用程序的共享库的&#xff1f;如果共享库之间有依赖&#xff0c; musl ldso 如何处理先初始化哪个 共享库&#xff1f; musl ldso 的代码可以在 musl 官方代码&#xff1a; ldso\dlstart.c 与 ldso\dynlink.c&#xff0c;其中动…

ETLCloud轻量级数据中台解决方案

引言 随着信息时代的到来&#xff0c;数据已经成为企业的重要资源&#xff0c;如何高效地管理、分析和应用数据变得尤为关键。然而&#xff0c;许多企业在构建数据中台时面临着高昂的成本、复杂的架构和漫长的实施周期等问题。为了解决这些挑战&#xff0c;我们推出了ETLCloud…

使用高斯滤波器进行表面开放轮廓过滤研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…