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),              //第一次观测/生成它的关键帧 idmnFirstFrame(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);//平均观测方向初始化为0mNormalVector = 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; // 双目或者rgbdelsenObs++; // 单目
}

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;elsenObs--;mObservations.erase(pKF);// 如果该keyFrame是参考帧,该Frame被删除后重新指定RefFrameif(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 keyframeKeyFrame* pKF = mit->first;if(!pMP->IsInKeyFrame(pKF)){   // 该关键帧中没有对"要替换本地图点的地图点"的观测pKF->ReplaceMapPointMatch(mit->second, pMP);// 让KeyFrame用pMP替换掉原来的MapPointpMP->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 descriptorsvector<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++){// 和自己的距离当然是0Distances[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向量,累加为归一化向量,最后除以总数ncv::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_distratio = 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.rhkb.cn/news/109181.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flask狼书笔记 | 04_表单

文章目录 4 表单4.1 HTML表单4.2 使用Flask-WTF4.3 处理表单数据4.4 表单进阶实践小记 4 表单 表单是和用户交互最常见的方式之一&#xff0c;本章涉及的Python包由WTForms、Flask-WTF、Flask-CKEditor。&#xff08;p104&#xff09; 4.1 HTML表单 通过<form>标签创建…

淘宝API技术解析,实现按图搜索淘宝商品

淘宝提供了开放平台接口&#xff08;API&#xff09;来实现按图搜索淘宝商品的功能。您可以通过以下步骤来实现&#xff1a; 1. 获取开放平台的访问权限&#xff1a;首先&#xff0c;您需要在淘宝开放平台创建一个应用&#xff0c;获取访问淘宝API的权限。具体的申请步骤和要求…

[QT]设置程序仅打开一个,再打开就唤醒已打开程序的窗口

需求&#xff1a;speedcrunch 这个软件是开源的计算器软件。配合launch类软件使用时&#xff0c;忘记关闭就经常很多窗口&#xff0c;强迫症&#xff0c;从网上搜索对版本进行了修改。 #include "gui/mainwindow.h"#include <QCoreApplication> #include <…

ubuntu学习(六)----文件编程实现cp指令

1 思路 Linux要想复制一份文件通常指令为&#xff1a; cp src.c des.c 其中src.c为源文件&#xff0c;des.c为目标文件。 要想通过文件编程实现cp效果&#xff0c;思路如下 1 首先打开源文件 src.c 2 读src到buf 3 创建des.c 4 将buf写入到des.c 5 close两个文件 2 实现 vi …

2023高教社杯数学建模思路 - 案例:FPTree-频繁模式树算法

文章目录 算法介绍FP树表示法构建FP树实现代码 建模资料 ## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c…

【德哥说库系列】-Oracle 19C RAC 应用RU19补丁

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

gPRC与SpringBoot整合教程

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Nginx配置文件详解

Nginx配置文件详解 1、Nginx配置文件1.1主配置文件详解1.2子配置文件 2、全局配置部分2.1修改启动的工作进程数&#xff08;worker process) 优化2.2cpu与worker process绑定2.3 PID 路径修改2.4 修改工作进程的优先级2.5调试工作进程打开的文件的个数2.6关闭master-worker工作…

智慧工厂解决方案:推动制造业转型升级的新引擎

随着信息技术的迅猛发展和制造业竞争的加剧&#xff0c;智慧工厂成为了推动制造业转型升级的重要引擎。智慧工厂解决方案通过整合物联网、人工智能、大数据分析等先进技术&#xff0c;实现生产过程的智能化、自动化和高效化&#xff0c;为企业提供了更加灵活、智能的生产模式和…

基于亚马逊云科技服务,构建大语言模型问答知识库

随着大语言模型效果明显提升&#xff0c;其相关的应用不断涌现呈现出越来越火爆的趋势。其中一种比较被广泛关注的技术路线是大语言模型&#xff08;LLM&#xff09;知识召回&#xff08;Knowledge Retrieval&#xff09;的方式&#xff0c;在私域知识问答方面可以很好的弥补通…

Kotlin 高阶函数详解

高阶函数 在 Kotlin 中&#xff0c;函数是一等公民&#xff0c;高阶函数是 Kotlin 的一大难点&#xff0c;如果高阶函数不懂的话&#xff0c;那么要学习 Kotlin 中的协程、阅读 Kotlin 的源码是非常难的&#xff0c;因为源码中有太多高阶函数了。 高阶函数的定义 高阶函数的…

vue中form、table和input标签过长

form标签过长 效果&#xff1a; 代码&#xff1a; <el-form-item v-for"(item,index) in ticketEditTable1" :label"item.fieldNameCn" :propitem.fieldName :key"item.fieldNameCn" overflow"":rules"form[item.fieldName…

8年测试经验之谈 —— 什么是全链路压测?

随着互联网技术的发展和普及&#xff0c;越来越多的互联网公司开始重视性能压测&#xff0c;并将其纳入软件开发和测试的流程中。 阿里巴巴在2014 年双11 大促活动保障背景下提出了全链路压测技术&#xff0c;能更好的保障系统可用性和稳定性。 什么是全链路压测&#xff1f; …

【AutoLayout案例04-游戏图片-按钮适配 Objective-C语言】

一、好,我们再看一个案例, 刚才,这个案例, 这么一个案例 这个案例,是什么意思呢, 这里给大家做一个3.5英寸、4.0英寸的屏幕适配, 因为我们这里图片,只有一个,就是4英寸的这么一个图片 什么意思呢,要求我们在3.5英寸的屏幕、和4英寸的屏幕的时候,都能正常显示这个图…

基于大数据+django+mysql的学习资源推送系统的设计与实现(含报告+源码+指导)

本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库&#xff0c;而Python技术&#xff0c; B/S架构则保证了较高的平台适应性。文中主要是讲解了该系统的开发环境、要实现的基本功能和开发步骤&#xff0c;并主要讲述了系统设计方案的关键点、设计思想。 由于篇幅限制…

2.神经网络的实现

创建神经网络类 import numpy # scipy.special包含S函数expit(x) import scipy.special # 打包模块 import pickle# 激活函数 def activation_func(x):return scipy.special.expit(x)# 用于创建、 训练和查询3层神经网络 class neuralNetwork:# 初始化神经网络def __init__(se…

【数据分析】统计量

1. 均值、众数描述数据的集中趋势度量&#xff0c;四分位差、极差描述数据的离散程度。 2. 标准差、四分位差、异众比率度量离散程度&#xff0c;协方差是度量相关性。 期望值分别为E[X]与E[Y]的两个实随机变量X与Y之间的协方差Cov(X,Y)定义为&#xff1a; 从直观上来看&…

Vue2向Vue3过度核心技术自定义指令

目录 1 自定义指令1.指令介绍2.自定义指令3.自定义指令语法4.指令中的配置项介绍5.代码示例6.总结 2 自定义指令-指令的值1.需求2.语法3.代码示例 3 自定义指令-v-loading指令的封装1.场景2.需求3.分析4.实现5.准备代码 1 自定义指令 1.指令介绍 内置指令&#xff1a;v-html、v…

channel并发编程

不要通过共享内存通信&#xff0c;要通过通信共享内存。 channel是golang并发编程中一种重要的数据结构&#xff0c;用于多个goroutine之间进行通信。 我们通常可以把channel想象成一个传送带&#xff0c;将goroutine想象成传送带周边的人&#xff0c;一个传送带的上游放上物品…

四信重磅推出5G RedCap AIoT摄像机 RedCap轻量级5G终端新品首发!

6月6日&#xff0c;四信受邀出席移动物联网高质量发展论坛&#xff0c;并在移动物联网新产品发布环节隆重推出5G RedCap AIoT摄像机&#xff0c;再次抓紧需求先机&#xff0c;为行业用户创造无限可能&#xff01; 两大应用场景 助推RedCap走深向实 火遍全网络的RedCap应用场景可…