ORB-SLAM2算法11之地图点MapPoint

news2025/1/24 8:50:06

文章目录

  • 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.coloradmin.cn/o/924522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Webstorm 入门级玩转uni-app 项目-微信小程序+移动端项目方案

1. Webstorm uni-app语法插件 &#xff1a; Uniapp Support Uniapp Support - IntelliJ IDEs Plugin | Marketplace 第一个是不收费&#xff0c;第二个收费 我选择了第二个Uniapp Support &#xff0c;有试用30天&#xff0c;安装重启webstorm之后&#xff0c;可以提高生产率…

排序链表-归并排序

给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5] 示例 3&#xff1a; 输…

vue 展开和收起

效果图 代码块 <div><span v-for"(item,index) in showHandleList" :key"item.index"><span>{{item.emailFrom}}</span></span><span v-if"this.list.length > 4" click"showAll !showAll">{…

Ceph入门到精通-大流量10GB/s LVS+OSPF 高性能架构

LVS 和 LVSkeepalived 这两种架构在平时听得多了&#xff0c;最近才接触到另外一个架构LVSOSPF。这个架构实际上是LVSKeepalived 的升级版本&#xff0c;我们所知道LVSKeepalived 架构是这样子的&#xff1a; 随着业务的扩展&#xff0c;我们可以对web服务器做水平扩展&#xf…

聚观早报 | 云鲸扫拖机器人J4体验;芯科科技第三代无线开发平台

【聚观365】8月24日消息 云鲸扫拖机器人J4体验 芯科科技推出第三代无线开发平台 英伟达与VMWare宣布扩大合作 万物新生&#xff08;爱回收&#xff09;2023年二季度财报 充电桩需求增长带动汽车后服务市场 云鲸扫拖机器人J4体验 家庭卫生清洁是每个人都无法回避的事情&am…

实训笔记8.24

实训笔记8.24 8.24笔记一、Sqoop数据迁移工具1.1 Sqoop的基本概念1.2 Sqoop的基本操作1.2.1 命令语法1.2.2 list-databases1.2.3 list-tables1.2.3 eval1.2.4 import1.2.5 export1.2.6 导入 二、Flume日志采集工具2.1 数据采集的问题2.2 数据采集一般使用的技术2.3 扩展&#x…

Tokenview再度升级:全新Web3开发者APIs数据服务体验!

Tokenview发布全新版本的区块链APIs和数据服务平台&#xff0c;为开发者打造更强大、更便捷的开发体验&#xff01; 此次升级&#xff0c;我们整合了开发者使用习惯以及Tokenview产品优势。我们深知对于开发者来说&#xff0c;时间是非常宝贵的&#xff0c;因此我们努力提供一…

联合注入步骤

使用场景&#xff1a; 有回显&#xff0c;可以看到某些字段的回显信息 像下面的有具体的回显信息 一、判断注入位点 在原始的id&#xff08;参数&#xff09;的输入后面添加额外的条件 如果and 11 有结果&#xff0c;and10没有结果输出&#xff0c; 就说明我们添加的额外条件…

sqlmap安装以及运用

目录 一、sqlmap简介 linux系统安装 windows系统安装 二.sqlmap确定目标 (1) sqlmap直连数据库 (2) sqlmap的URL探测 (3) Sqlmap文件读取目标 (4) Sqlmap Google批量扫注入 一、sqlmap简介 sqlmap是一个开源的渗透测试工具&#xff0c;它可以自动化检测sql注入漏洞利用…

opencv 文档识别+UI界面识别系统

目录 一、实现和完整UI视频效果展示 主界面&#xff1a; 识别结果界面&#xff1a; 查看处理图片过程&#xff1a; 查看历史记录界面&#xff1a; 二、原理介绍&#xff1a; 将图像变换大小->灰度化->高斯滤波->边缘检测 轮廓提取 筛选第三步中的轮廓&#xf…

Unity OnDrawGizmos的简单应用 绘制圆形

编辑器和配置表各有各的好。 卡牌游戏即使再复杂&#xff0c;哪怕是梦幻西游&#xff0c;大话西游那种&#xff0c;甚至wow那种&#xff0c;用配表都完全没问题。但是崩坏3&#xff0c;或者鬼泣&#xff0c;格斗游戏&#xff0c;可视化编辑器是唯一的选择。 开发初期刚开始配技…

2.redis数据结构之Hash

Hash-散列类型:H 为什么选择Hash? 假设有User对象以JSON序列化的形式存储到Redis中&#xff0c;User对象有id&#xff0c;username、password、age、name等属性&#xff0c;存储的过程如下&#xff1a; 保存、更新&#xff1a; User对象 -> json(string) -> redis 如果在…

基于mha+mycat2+gtid的半同步主从复制双vip高可用MySQL集群

目录 项目名称 项目架构图 项目概述 项目准备 项目步骤 一、使用ansible编写palybook实现4台二进制安装MySQL环境的部署&#xff0c;并把master上的基础数据下发到所有slave服务器上 1. 建立免密通道 2.安装ansible在ansible服务器上&#xff0c;并写好主机清单 3.将…

Ubuntu20.04安装软件报错:The following packages have unmet dependencies

Ubuntu20.04更换阿里云源后安装软件都会报错&#xff1a;The following packages have unmet dependencies 查看资料&#xff0c;大概是ubuntu本身的源比较版本较老&#xff0c;而阿里云的源比较新&#xff0c;因此版本不匹配造成依赖的库不匹配&#xff0c;所以只要将阿里云的…

【微服务】03-HttpClientFactory与gRpc

文章目录 1.HttpClientFactory &#xff1a;管理外向请求的最佳实践1.1 核心能力1.2 核心对象1.3 HttpClient创建模式 2.gRPC&#xff1a;内部服务间通讯利器2.1 什么是gRPC2.2 特点gRPC特点2.3.NET生态对gRPC的支持情况2.4 服务端核心包2.5 客户端核心包2.5 .proto文件2.6 gRP…

opencv 水果识别+UI界面识别系统,可训练自定义的水果数据集

目录 一、实现和完整UI视频效果展示 主界面&#xff1a; 测试图片结果界面&#xff1a; 自定义图片结果界面&#xff1a; 二、原理介绍&#xff1a; 图像预处理 HOG特征提取算法 数据准备 SVM支持向量机算法 预测和评估 完整演示视频&#xff1a; 完整代码链接 一、…

redis--集群

redis集群 Redis 集群是一种用于分布式存储和管理数据的解决方案&#xff0c;它允许将多个 Redis 实例组合成一个单一的逻辑数据库&#xff0c;提供更高的性能、容量和可用性。 redis集群的优点 高可用性&#xff1a; Redis集群使用主从复制和分片技术&#xff0c;使得数据可…

提高nodejs中promise的性能

提高nodejs中promise的性能 我们先来看一个常见问题&#xff0c;假设我们有 N 条记录需要处理&#xff0c;或者例如&#xff0c;为每条记录发出 API 请求以获取数据。 通常情况下我们都是使用promise.all方法来实现这一需求&#xff1a; // 记录 const data [{}, {}, {}];/…

Linux网络编程:线程池并发服务器 _UDP客户端和服务器_本地和网络套接字

文章目录&#xff1a; 一&#xff1a;线程池模块分析 threadpool.c 二&#xff1a;UDP通信 1.TCP通信和UDP通信各自的优缺点 2.UDP实现的C/S模型 server.c client.c 三&#xff1a;套接字 1.本地套接字 2.本地套 和 网络套对比 server.c client.c 一&#xff1a;线…

如何使用CSS实现一个3D旋转效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 3D效果实现⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域…