admin 管理员组

文章数量: 1087677

ORB SLAM2学习笔记之mono

ORB SLAM2学习笔记之mono_kitti(三)

  • 一、Tracking流程
  • 二、实例化mpTracker
    • ORBextractor
  • 三、TrackMonocular
    • GrabImageMonocular
      • CurrentFrame构造
        • ExtractORB函数的说明
          • ComputePyramid
          • ComputeKeyPointsOctTree
      • CurrentFrame构造流程图

一、Tracking流程

大致流程如图所示,下面对细节进行展开说明。

二、实例化mpTracker

在System构造函数中new一个Tracing对象指针mpTracker,方式如下所示:

//Initialize the Tracking thread
//(it will live in the main thread of execution, the one that called this constructor)
mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer,mpMap, mpKeyFrameDatabase, strSettingsFile, mSensor);

构造对象过程中,先进行了传参,如camera焦距,畸变系数,帧率等,然后读取配置文件里的有关提取特征方面的信息,比如每一帧需要提取的特征数(包括整个图像金字塔),金字塔层间尺度,金字塔层数,FAST关键点阈值等,最重要的是最后调用了ORBextractor构造一个ORB特征提取器。

mpIniORBextractor = new ORBextractor(2*nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);


那构造特征提取器时都干了些什么呢,如下所示:

ORBextractor

第一步:算金字塔每层的尺度,然后根据尺度计算每层应该提取多少特征点,这里面涉及了一个等比数列,唤起高中的记忆,还挺有意思的。最后,保证提取总特征点数≥ nfeatures。

//参数:特征点多少,金字塔层与层之间的尺度(用于算每层提取关键点的数量),金字塔层级数量,FAST角点阈值,最小角点阈值
ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels,int _iniThFAST, int _minThFAST):nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),iniThFAST(_iniThFAST), minThFAST(_minThFAST)
{mvScaleFactor.resize(nlevels);mvLevelSigma2.resize(nlevels);mvScaleFactor[0]=1.0f;mvLevelSigma2[0]=1.0f;for(int i=1; i<nlevels; i++){mvScaleFactor[i]=mvScaleFactor[i-1]*scaleFactor;mvLevelSigma2[i]=mvScaleFactor[i]*mvScaleFactor[i];}mvInvScaleFactor.resize(nlevels);mvInvLevelSigma2.resize(nlevels);for(int i=0; i<nlevels; i++){mvInvScaleFactor[i]=1.0f/mvScaleFactor[i];mvInvLevelSigma2[i]=1.0f/mvLevelSigma2[i];}mvImagePyramid.resize(nlevels);//金字塔有几层mnFeaturesPerLevel.resize(nlevels);float factor = 1.0f / scaleFactor;  //梯度q//第一层特征点的数量  nfeatures是总特征点的数量   后面的式子是等比数列求和公式   nfeatures × (1-q)/(1 - q^n)float nDesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)pow((double)factor, (double)nlevels));//依次算每层的特征点数量,加和,保证最后总的特征点数量 ≥ nfeaturesint sumFeatures = 0;for( int level = 0; level < nlevels-1; level++ ){mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);sumFeatures += mnFeaturesPerLevel[level];nDesiredFeaturesPerScale *= factor;}mnFeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);

第二步:准备制作描述子,包括采用bit_pattern_31_模板,计算像素点半径什么的,有点opencv源码的知识,没太看懂,不能钻牛角尖。。。有大神看懂下面的代码能给我讲一哈吗,求带求带

    /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!复制训练用的模板!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/const int npoints = 512;//512个点,256对点,比较完会有一个256位的描述子const Point* pattern0 = (const Point*)bit_pattern_31_;//??? bit_pattern_31_可能的意思是描述子的计算区域直径是 31 的模式//std::copy(start, end, std::back_inserter(container)); 从 start 到 end 的迭代器复制完放入 container 的后面std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));//This is for orientation// pre-compute the end of a row in a circular patchumax.resize(HALF_PATCH_SIZE + 1);int v, v0, vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1);  //cvFloor含义是取不大于参数的最大整数值int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);  //cvCeil含义是取不小于参数的最小整数值//?????????????????????????????????????????????????????????????????????????????????????????????????const double hp2 = HALF_PATCH_SIZE*HALF_PATCH_SIZE;  //半径的平方for (v = 0; v <= vmax; ++v)umax[v] = cvRound(sqrt(hp2 - v * v));// Make sure we are symmetricfor (v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v){while (umax[v0] == umax[v0 + 1])++v0;umax[v] = v0;++v0;}

三、TrackMonocular

如果忽略其他线程的构造,就继续回到main函数里,看得出main函数里很重要的就是这个TrackMonocular了,函数体如下所示:

cv::Mat System::TrackMonocular(const cv::Mat &im, const double &timestamp)
{if(mSensor!=MONOCULAR){cerr << "ERROR: you called TrackMonocular but input sensor was not set to Monocular." << endl;exit(-1);}// Check mode change{unique_lock<mutex> lock(mMutexMode);if(mbActivateLocalizationMode){mpLocalMapper->RequestStop();// Wait until Local Mapping has effectively stoppedwhile(!mpLocalMapper->isStopped()){usleep(1000);}mpTracker->InformOnlyTracking(true);mbActivateLocalizationMode = false;}if(mbDeactivateLocalizationMode){mpTracker->InformOnlyTracking(false);mpLocalMapper->Release();mbDeactivateLocalizationMode = false;}}// Check reset{unique_lock<mutex> lock(mMutexReset);if(mbReset){mpTracker->Reset();mbReset = false;}}cv::Mat Tcw = mpTracker->GrabImageMonocular(im,timestamp);unique_lock<mutex> lock2(mMutexState);mTrackingState = mpTracker->mState;mTrackedMapPoints = mpTracker->mCurrentFrame.mvpMapPoints;mTrackedKeyPointsUn = mpTracker->mCurrentFrame.mvKeysUn;return Tcw;
}

看得出这个函数调用完会返回一个Mat类的东西,就是Tcw位姿。函数刚开始先check了一下系统现在是不是LocalizationMode,有没有Reset,用了mutex大概是为了防止不同线程访问共享内存从而出错,并发编程这里还不是很懂,也没有什么具体资料,等学会以后会更新一下,还请大神走过路过赐教一下鸭…接下来进入GrabImageMonocular函数,计算都是在这里进行的。

GrabImageMonocular

函数需要两个参数:图片和对应的时间戳。先进行图片格式的转换,然后构造了CurrentFrame,最后进行Track。

cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im, const double &timestamp)
{mImGray = im;//根据传入图片的不同情况转换图片格式if(mImGray.channels()==3){if(mbRGB)cvtColor(mImGray,mImGray,CV_RGB2GRAY);elsecvtColor(mImGray,mImGray,CV_BGR2GRAY);}else if(mImGray.channels()==4){if(mbRGB)cvtColor(mImGray,mImGray,CV_RGBA2GRAY);elsecvtColor(mImGray,mImGray,CV_BGRA2GRAY);}if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET) //之前Tracking构造函数的时候,已经将mState初始化为NO_IMAGES_YETmCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);elsemCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);Track();return mCurrentFrame.mTcw.clone();
}

CurrentFrame构造

构造当前帧( CurrentFrame )需要传入几个参数,见下面注释。函数内部干了几件事:
第一个:从ORBextractor中提取一些参数用于后面提取特征点;

Frame::Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth):mpORBvocabulary(voc),mpORBextractorLeft(extractor),mpORBextractorRight(static_cast<ORBextractor*>(NULL)),mTimeStamp(timeStamp), mK(K.clone()),mDistCoef(distCoef.clone()), mbf(bf), mThDepth(thDepth)
{// Frame IDmnId=nNextId++;// Scale Level Info   图像金字塔参数mnScaleLevels = mpORBextractorLeft->GetLevels();mfScaleFactor = mpORBextractorLeft->GetScaleFactor();mfLogScaleFactor = log(mfScaleFactor);mvScaleFactors = mpORBextractorLeft->GetScaleFactors();mvInvScaleFactors = mpORBextractorLeft->GetInverseScaleFactors();mvLevelSigma2 = mpORBextractorLeft->GetScaleSigmaSquares();mvInvLevelSigma2 = mpORBextractorLeft->GetInverseScaleSigmaSquares();

第二个:调用函数ExtractORB提取ORB特征,然后对特征点去畸变;

    // ORB extractionExtractORB(0,imGray);//能得到 mvKeys 与 mDescriptors,关键点和描述子N = mvKeys.size();if(mvKeys.empty())return;UndistortKeyPoints(); //关键点去畸变// Set no stereo informationmvuRight = vector<float>(N,-1);mvDepth = vector<float>(N,-1);mvpMapPoints = vector<MapPoint*>(N,static_cast<MapPoint*>(NULL));mvbOutlier = vector<bool>(N,false);

第三个:如果是对第一帧图像,还会计算图片边界,然后把特征点分配进之前打算分的格子中

    // This is done only for the first Frame (or after a change in the calibration)if(mbInitialComputations){ComputeImageBounds(imGray); // 算出 mnMinX mnMaxX mnMinY mnMaxYmfGridElementWidthInv=static_cast<float>(FRAME_GRID_COLS)/static_cast<float>(mnMaxX-mnMinX);mfGridElementHeightInv=static_cast<float>(FRAME_GRID_ROWS)/static_cast<float>(mnMaxY-mnMinY);fx = K.at<float>(0,0);fy = K.at<float>(1,1);cx = K.at<float>(0,2);cy = K.at<float>(1,2);invfx = 1.0f/fx;invfy = 1.0f/fy;mbInitialComputations=false;}mb = mbf/fx;AssignFeaturesToGrid(); //把特征加入格子中
}
ExtractORB函数的说明

函数里面调用了ORBextractor类里的operator()函数,用于提取关键点和描述子:

void Frame::ExtractORB(int flag, const cv::Mat &im)
{if(flag==0)(*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors);else(*mpORBextractorRight)(im,cv::Mat(),mvKeysRight,mDescriptorsRight);
}

operator()函数需要传入要提取的图像,内部为:

//提取关键点和描述子
void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,OutputArray _descriptors)
{ if(_image.empty())return;Mat image = _image.getMat();assert(image.type() == CV_8UC1 );// Pre-compute the scale pyramidComputePyramid(image); // 计算 mvImagePyramidvector < vector<KeyPoint> > allKeypoints;ComputeKeyPointsOctTree(allKeypoints);//ComputeKeyPointsOld(allKeypoints);Mat descriptors;int nkeypoints = 0;for (int level = 0; level < nlevels; ++level)nkeypoints += (int)allKeypoints[level].size();if( nkeypoints == 0 )_descriptors.release();else{_descriptors.create(nkeypoints, 32, CV_8U);descriptors = _descriptors.getMat();}_keypoints.clear();_keypoints.reserve(nkeypoints);int offset = 0;for (int level = 0; level < nlevels; ++level){vector<KeyPoint>& keypoints = allKeypoints[level];int nkeypointsLevel = (int)keypoints.size();if(nkeypointsLevel==0)continue;// preprocess the resized imageMat workingMat = mvImagePyramid[level].clone();GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101);// Compute the descriptorsMat desc = descriptors.rowRange(offset, offset + nkeypointsLevel);computeDescriptors(workingMat, keypoints, desc, pattern);offset += nkeypointsLevel;// Scale keypoint coordinatesif (level != 0){float scale = mvScaleFactor[level]; //getScale(level, firstLevel, scaleFactor);for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)keypoint->pt *= scale;}// And add the keypoints to the output_keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end());}
}

其中提取FAST关键点有两个很重要的函数是 ComputePyramidComputeKeyPointsOctTree,(其实还有一个computeDescriptors也很重要不过没细看)前者构建了图像金字塔 mvImagePyramid 供给后面 OctTree 的计算。

ComputePyramid

这个函数有个很奇怪的地方,for循环里面有个Mat temp的初始化,也就是说每调用一次就会重新构造temp这个对象,但是这样的话后面copyMakeBorder这个函数就没什么作用了鸭…但我确实就像下面代码中的一样将temp cout 出来发现它并不都是初始化后的状态,C++博大精深…希望路过的大神能帮我解答一下…

void ORBextractor::ComputePyramid(cv::Mat image)
{for (int level = 0; level < nlevels; ++level){float scale = mvInvScaleFactor[level];Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale));//列数是宽,行数是高Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2);Mat temp(wholeSize, image.type()), masktemp;//cout<<"temp"<<temp<<endl;//左上角点坐标,宽,高mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));// Compute the resized imageif( level != 0 ){resize(mvImagePyramid[level-1], mvImagePyramid[level], sz, 0, 0, INTER_LINEAR);//造边界copyMakeBorder(mvImagePyramid[level], temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,BORDER_REFLECT_101+BORDER_ISOLATED);}else{//造边界EDGE_THRESHOLD ,以最外面的像素为对称轴,输出为tempcopyMakeBorder(image, temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,BORDER_REFLECT_101);}}}
ComputeKeyPointsOctTree

函数采用划分格子的形式在格子里挑选关键点,这样可能效率更高些,然后通过函数DistributeOctTree以四叉树形式将关键点分配给每个节点,然后将响应值response最大的点挑出来,这样能保证关键点分布是均匀的,然后通过computeOrientation函数利用灰度质心法IC_Angle计算关键点的方向。过程略复杂,如下面注释所示:

void ORBextractor::ComputeKeyPointsOctTree(vector<vector<KeyPoint> >& allKeypoints)
{allKeypoints.resize(nlevels);// 有 nlevels 层 keypoints// 设置格子大小const float W = 30;for (int level = 0; level < nlevels; ++level){const int minBorderX = EDGE_THRESHOLD-3;const int minBorderY = minBorderX;const int maxBorderX = mvImagePyramid[level].cols-EDGE_THRESHOLD+3;const int maxBorderY = mvImagePyramid[level].rows-EDGE_THRESHOLD+3;vector<cv::KeyPoint> vToDistributeKeys;vToDistributeKeys.reserve(nfeatures*10);const float width = (maxBorderX-minBorderX);const float height = (maxBorderY-minBorderY);const int nCols = width/W;                    //每一行有几个格子const int nRows = height/W;                   //每一列有几个格子const int wCell = ceil(width/nCols);          //每一行每个格子的实际宽度const int hCell = ceil(height/nRows);         //每一列每个格子的实际高度for(int i=0; i<nRows; i++){const float iniY =minBorderY+i*hCell;     //cell的起始y值float maxY = iniY+hCell+6;                //cell的最大y值if(iniY>=maxBorderY-3)continue;if(maxY>maxBorderY)maxY = maxBorderY;for(int j=0; j<nCols; j++){const float iniX =minBorderX+j*wCell; //cell的起始x值float maxX = iniX+wCell+6;            //cell的最大x值if(iniX>=maxBorderX-6)continue;if(maxX>maxBorderX)maxX = maxBorderX;vector<cv::KeyPoint> vKeysCell;FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),vKeysCell,iniThFAST,true);// 如果检测到的fast特征为空,则降低阈值再进行检测if(vKeysCell.empty()){FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),vKeysCell,minThFAST,true);}if(!vKeysCell.empty()){for(vector<cv::KeyPoint>::iterator vit=vKeysCell.begin(); vit!=vKeysCell.end();vit++){(*vit).pt.x+=j*wCell; //每个cell中特征点实际的x坐标,当然,是相对于minBorderX的(*vit).pt.y+=i*hCell; //每个cell中特征点实际的y坐标,当然,是相对于minBorderY的vToDistributeKeys.push_back(*vit); //装满了每一层FAST特征点}}}}//把第level层的keypoints赋值。 keypoints变化,allKeypoints[level]也跟着变化  KeyPoint类里面有pt、angle等信息vector<KeyPoint> & keypoints = allKeypoints[level];keypoints.reserve(nfeatures);//对每个节点里面的特征进行选择最好特征,这样就对检测到的特征进行了均匀化处理keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level]; //每层直径的尺度不一样// Add border to coordinates and scale information// 换算关键点真实位置(添加边界值),添加关键点的尺度信息const int nkps = keypoints.size();for(int i=0; i<nkps ; i++){keypoints[i].pt.x+=minBorderX;keypoints[i].pt.y+=minBorderY;keypoints[i].octave=level; //金字塔第几层keypoints[i].size = scaledPatchSize;}}// compute orientationsfor (int level = 0; level < nlevels; ++level)computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

DistributeOctTree函数功能是把关键点均匀化,就不多说了,代码注释的很详细,直接贴代码了,略长但是逻辑很清晰,父节点生子节点,一生四,四生十六,每层金字塔都这么做:

/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!将图像划分成四叉树形式!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*///参数: 第level层的所有FAST关键点组成的vector,特征检测区域上下两个边界的x与y坐标,第level层的特征点数量,第level层
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
{// Compute how many initial nodes//根据图像宽高比例确定有几个节点,例如图像太长或太扁初始节点可能多于一个const int nIni = round( static_cast<float>(maxX-minX)/(maxY-minY) );//节点在x方向上的长度const float hX = static_cast<float>(maxX-minX)/nIni;list<ExtractorNode> lNodes;vector<ExtractorNode*> vpIniNodes;vpIniNodes.resize(nIni);//UL UR BL BR是节点的四个角的坐标for(int i=0; i<nIni; i++){ExtractorNode ni;ni.UL = cv::Point2i(hX*static_cast<float>(i),0);ni.UR = cv::Point2i(hX*static_cast<float>(i+1),0);ni.BL = cv::Point2i(ni.UL.x,maxY-minY);ni.BR = cv::Point2i(ni.UR.x,maxY-minY);ni.vKeys.reserve(vToDistributeKeys.size());lNodes.push_back(ni);vpIniNodes[i] = &lNodes.back();}//Associate points to childs//把图片里的关键点分配到节点中去for(size_t i=0;i<vToDistributeKeys.size();i++){const cv::KeyPoint &kp = vToDistributeKeys[i];vpIniNodes[kp.pt.x/hX]->vKeys.push_back(kp);}list<ExtractorNode>::iterator lit = lNodes.begin();while(lit!=lNodes.end()){if(lit->vKeys.size()==1){lit->bNoMore=true;lit++;}else if(lit->vKeys.empty())lit = lNodes.erase(lit);elselit++;}bool bFinish = false;int iteration = 0;vector<pair<int,ExtractorNode*> > vSizeAndPointerToNode; //节点 和 它对应包含的 特征点数vSizeAndPointerToNode.reserve(lNodes.size()*4); //一共有几个子节点(从初始节点分叉)while(!bFinish){iteration++;int prevSize = lNodes.size(); //老节点的数量lit = lNodes.begin(); //lit是节点的迭代器int nToExpand = 0;vSizeAndPointerToNode.clear();//清空/*感觉while执行完 要么就是不再划分新节点,lnodes里都是老节点,要么就是分完新节点后老节点被一个个erase掉,新节点从前面一个个添加进来*/while(lit!=lNodes.end()){if(lit->bNoMore){// If node only contains one point do not subdivide and continue// 如果节点中只有一个特征点就不划分了lit++;continue;}else{// If more than one point, subdivideExtractorNode n1,n2,n3,n4;lit->DivideNode(n1,n2,n3,n4);// Add childs if they contain pointsif(!n1.vKeys.empty()){lNodes.push_front(n1);     //把子节点推到lnodes前面 变成lnodes的一部分if(n1.vKeys.size()>1){nToExpand++; //还要再分//把n1与它里面的特征点数量推进去vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin(); //begin()迭代器 赋给 第一个元素的迭代器}}if(!n2.vKeys.empty()){lNodes.push_front(n2);if(n2.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(!n3.vKeys.empty()){lNodes.push_front(n3);if(n3.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(!n4.vKeys.empty()){lNodes.push_front(n4);if(n4.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}lit=lNodes.erase(lit);  //lit指向下一个元素continue;}}       // Finish if there are more nodes than required features// or all nodes contain just one pointif((int)lNodes.size()>=N || (int)lNodes.size()==prevSize){bFinish = true;}//节点展开次数乘以3用于表明下一次的节点分解可能超过特征数,即为最后一次分解else if(((int)lNodes.size()+nToExpand*3)>N){while(!bFinish){prevSize = lNodes.size();vector<pair<int,ExtractorNode*> > vPrevSizeAndPointerToNode = vSizeAndPointerToNode;vSizeAndPointerToNode.clear();sort(vPrevSizeAndPointerToNode.begin(),vPrevSizeAndPointerToNode.end());for(int j=vPrevSizeAndPointerToNode.size()-1;j>=0;j--){ExtractorNode n1,n2,n3,n4;vPrevSizeAndPointerToNode[j].second->DivideNode(n1,n2,n3,n4);// Add childs if they contain pointsif(n1.vKeys.size()>0){lNodes.push_front(n1);if(n1.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n2.vKeys.size()>0){lNodes.push_front(n2);if(n2.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n3.vKeys.size()>0){lNodes.push_front(n3);if(n3.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n4.vKeys.size()>0){lNodes.push_front(n4);if(n4.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}lNodes.erase(vPrevSizeAndPointerToNode[j].second->lit);if((int)lNodes.size()>=N)break;}if((int)lNodes.size()>=N || (int)lNodes.size()==prevSize)bFinish = true;}}}// Retain the best point in each nodevector<cv::KeyPoint> vResultKeys;vResultKeys.reserve(nfeatures);for(list<ExtractorNode>::iterator lit=lNodes.begin(); lit!=lNodes.end(); lit++){vector<cv::KeyPoint> &vNodeKeys = lit->vKeys;//vNodeKeys是一个节点中的特征点cv::KeyPoint* pKP = &vNodeKeys[0];//第一个特征点float maxResponse = pKP->response; //response代表着该关键点how good,更确切的说,是该点角点的程度。for(size_t k=1;k<vNodeKeys.size();k++) //选择一个响应最大的特征点{if(vNodeKeys[k].response>maxResponse){pKP = &vNodeKeys[k];maxResponse = vNodeKeys[k].response;}}vResultKeys.push_back(*pKP); //推进去}return vResultKeys;//把最终的筛选过的每个节点中响应最好的特征点作为结果返回
}

这里面有个函数叫DivideNode,代码如下,功能就像函数名一样,父节点分成四个子节点,别忘了x和y轴的方向:

void ExtractorNode::DivideNode(ExtractorNode &n1, ExtractorNode &n2, ExtractorNode &n3, ExtractorNode &n4)
{const int halfX = ceil(static_cast<float>(UR.x-UL.x)/2);const int halfY = ceil(static_cast<float>(BR.y-UL.y)/2);//Define boundaries of childs/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*                          -------------------------------------------------→*                          |                      |                     |*                          |                      |                     |*                          |          n1          |          n2         |*                          |                      |                     |*                          |                      |                     |*                          |----------------------|---------------------|*                          |                      |                     |*                          |                      |                     |*                          |          n3          |          n4         |*                          |                      |                     |*                          |                      |                     |*                          |---------------------------------------------*                          ↓**!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/n1.UL = UL;n1.UR = cv::Point2i(UL.x+halfX,UL.y);n1.BL = cv::Point2i(UL.x,UL.y+halfY);n1.BR = cv::Point2i(UL.x+halfX,UL.y+halfY);n1.vKeys.reserve(vKeys.size());n2.UL = n1.UR;n2.UR = UR;n2.BL = n1.BR;n2.BR = cv::Point2i(UR.x,UL.y+halfY);n2.vKeys.reserve(vKeys.size());n3.UL = n1.BL;n3.UR = n1.BR;n3.BL = BL;n3.BR = cv::Point2i(n1.BR.x,BL.y);n3.vKeys.reserve(vKeys.size());n4.UL = n3.UR;n4.UR = n2.BR;n4.BL = n3.BR;n4.BR = BR;n4.vKeys.reserve(vKeys.size());//Associate points to childsfor(size_t i=0;i<vKeys.size();i++){const cv::KeyPoint &kp = vKeys[i];if(kp.pt.x<n1.UR.x){if(kp.pt.y<n1.BR.y)n1.vKeys.push_back(kp);elsen3.vKeys.push_back(kp);}else if(kp.pt.y<n1.BR.y)n2.vKeys.push_back(kp);elsen4.vKeys.push_back(kp);}if(n1.vKeys.size()==1)n1.bNoMore = true;if(n2.vKeys.size()==1)n2.bNoMore = true;if(n3.vKeys.size()==1)n3.bNoMore = true;if(n4.vKeys.size()==1)n4.bNoMore = true;}

CurrentFrame构造流程图

Track()函数还没有看,看完下篇博客总结,最后看一下构造当前帧的流程图:

本文标签: ORB SLAM2学习笔记之mono