orb-slam2 从单目开始的简单学习(4):match

前言

本章不是作为主线,而是作为主线的辅助理解

1. Search

常见函数用途
GetFeaturesInArea返回的以x,y为中心,半径为r的圆形内且金字塔层级在[minLevel, maxLevel]的特征点

可以打开文档之后ctrl+f寻找自己不了解的函数
【参考文档】orb-slam2 从单目开始的简单学习(6)Frame

1.1 SearchByBoW

int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches)
int ORBmatcher::SearchByBoW(KeyFrame *pKF1, KeyFrame *pKF2, vector<MapPoint *> &vpMatches12)

1.1.1数据类型 和 基本函数

1.1.1.1.数据类型
数据名类型
FeatureVectormap
// Vector of nodes with indexes of local features具有局部特征索引的节点向量
class FeatureVector: 
  public std::map<NodeId, std::vector<unsigned int> >

NodeId:节点向量(可以理解为树干)
vector :局部特征索引(可以理解为该树干对应的叶子)

vFeatVecKF,F.mFeatVec本质上都是上面这个map<NodeId, std::vector<unsigned int> >,概括起来就是词袋特征向量

1.1.1.2.基本函数
KFit = vFeatVecKF.lower_bound(Fit->first);

vFeatVecKF底层是一个map,因此lower_bound也是map的一个方法

terator lower_bound(const key_type& _Keyval):返回一个迭代器,指向键值 >= _Keyval 的第一个元素;

1.1.2 对于词袋的理解

进一步了解BOW建议是移步到computeBOW

对于每一个节点应该是两个维度上的观察
竖直维度上:节点之间相互连接
水平维度上:该节点上对应的索引

画了个二维理解图希望能帮助理解

请添加图片描述

1.1.3 完整代码

int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches)
{
    const vector<MapPoint*> vpMapPointsKF = pKF->GetMapPointMatches();
    								//返回mvpMapPoints:与KeyPoint相关联的MapPoints

    vpMapPointMatches = vector<MapPoint*>(F.N,static_cast<MapPoint*>(NULL));
    					// F.N:Number of KeyPoints.

    const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;

    int nmatches=0;
    
//----------------为了检查KeyPoints方向性做准备-----------------
    vector<int> rotHist[HISTO_LENGTH];//const int ORBmatcher::HISTO_LENGTH = 30;
    for(int i=0;i<HISTO_LENGTH;i++)
        rotHist[i].reserve(500);
    const float factor = 1.0f/HISTO_LENGTH;
//----------------为了检查KeyPoints方向性做准备-----------------
//--------------为了遍历KeyPoints做准备----------------
    // 对于同一结点上的orb进行匹配
    DBoW2::FeatureVector::const_iterator KFit = vFeatVecKF.begin();//map类型
    DBoW2::FeatureVector::const_iterator Fit = F.mFeatVec.begin();
    DBoW2::FeatureVector::const_iterator KFend = vFeatVecKF.end();
    DBoW2::FeatureVector::const_iterator Fend = F.mFeatVec.end();
            
        //vFeatVecKF,F.mFeatVec都属于FeatureVector
  //FeatureVector:map<NodeId, std::vector<unsigned int> >
  
//--------------为了遍历KeyPoints做准备-------------
    

//-------------开始遍历-------------------
//FeatureVector:map<node_id,std::vector<feature_id>>                                                 
    while(KFit != KFend && Fit != Fend)
    {
    //具有一定相似性的会集中在同一个结点下
    //为了确保其唯一性,需要找到vector中最好的
        if(KFit->first == Fit->first)
        {
            const vector<unsigned int> vIndicesKF = KFit->second;
            const vector<unsigned int> vIndicesF = Fit->second;

            for(size_t iKF=0; iKF<vIndicesKF.size(); iKF++)
            {
                const unsigned int realIdxKF = vIndicesKF[iKF];

                MapPoint* pMP = vpMapPointsKF[realIdxKF];

                if(!pMP)
                    continue;

                if(pMP->isBad())
                    continue;         
                           
//-------确认KeyPoint的唯一性(最小/次小 bit距离)------------------------
                const cv::Mat &dKF= pKF->mDescriptors.row(realIdxKF);

                int bestDist1=256;
                int bestIdxF =-1 ;
                int bestDist2=256;

                for(size_t iF=0; iF<vIndicesF.size(); iF++)
                {
                    const unsigned int realIdxF = vIndicesF[iF];

                    if(vpMapPointMatches[realIdxF])//已经进入过下面vpMapPointMatches[bestIdxF]=pMP;
                        continue;
                        
					
                    const cv::Mat &dF = F.mDescriptors.row(realIdxF);
                    //orb描述子,每一行与一个关键点相关联

                    const int dist =  DescriptorDistance(dKF,dF);//按位计算距离

                    if(dist<bestDist1)//找最小距离
                    {
                        bestDist2=bestDist1;
                        bestDist1=dist;
                        bestIdxF=realIdxF;
                    }
                    else if(dist<bestDist2)//次小距离  bestDist1<dist<bestDist2
                    {
                        bestDist2=dist;
                    }
                    
                }//最小和次小距离:描述当前结点下的帧中与关键帧中kp【最匹配的】的
			//--------------为检查方向做准备----------------
                if(bestDist1<=TH_LOW)//const int ORBmatcher::TH_LOW = 50;
                {
                    if(static_cast<float>(bestDist1)<mfNNratio*static_cast<float>(bestDist2))
                    {
                        vpMapPointMatches[bestIdxF]=pMP;

                        const cv::KeyPoint &kp = pKF->mvKeysUn[realIdxKF];

                        if(mbCheckOrientation)
                        {
                            float rot = kp.angle-F.mvKeys[bestIdxF].angle;
                            if(rot<0.0)
                                rot+=360.0f;
                            int bin = round(rot*factor);
                            if(bin==HISTO_LENGTH)
                                bin=0;
                            assert(bin>=0 && bin<HISTO_LENGTH);
                            rotHist[bin].push_back(bestIdxF);
                        }
                        nmatches++;
                    }
                }
              //--------------为检查方向做准备----------------
//-------确认KeyPoint的唯一性(最小/次小距离)------------------------
            }

            KFit++;
            Fit++;
        }
        else if(KFit->first < Fit->first)
        {
            KFit = vFeatVecKF.lower_bound(Fit->first);
        }
        else
        {
            Fit = F.mFeatVec.lower_bound(KFit->first);
        }
    }

//-------确认KeyPoint的 有纪念意义(方向)------------------------
    if(mbCheckOrientation)
    {
        int ind1=-1;
        int ind2=-1;
        int ind3=-1;

        ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3);

        for(int i=0; i<HISTO_LENGTH; i++)
        {
            if(i==ind1 || i==ind2 || i==ind3)
                continue;
            for(size_t j=0, jend=rotHist[i].size(); j<jend; j++)
            {
                vpMapPointMatches[rotHist[i][j]]=static_cast<MapPoint*>(NULL);
                nmatches--;
            }
        }
    }
//-------确认KeyPoint的有 纪念意义(方向显著)------------------------
    return nmatches;
}

1.1.4代码分块详解

这个博主注释上算是比较好理解的。理解这一堆代码的核心还是在树和map的映射上。

1)构建旋转直方图

在这里插入图片描述

将两个要匹配的特征点的方向做差得到rot,将rot转换到0-360度,设定一个直方图,直方图的HISTO_LENGTH即条形的个数设置为30,0-360分布在30个bins中,即每个bin代表12度.然后根据角度差将n对匹配特征点分布在直方图中,在直方图中找出三个匹配特征点最多的直方图bin,最后将不属于这三个bin的匹配关系删除
———————————————— 版权声明:本文为CSDN博主「Bobsweetie」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Bobsweetie/article/details/107367464

 vector<int> rotHist[HISTO_LENGTH];//const int ORBmatcher::HISTO_LENGTH = 30;
    for(int i=0;i<HISTO_LENGTH;i++)
        rotHist[i].reserve(500);
    const float factor = 1.0f/HISTO_LENGTH;

可以理解成分成30个基本块,每个块的高度最多500

2)if-elif-else
while(KFit != KFend && Fit != Fend)
    {
        if(KFit->first == Fit->first)
        {
            KFit++;
            Fit++;
        }
        else if(KFit->first < Fit->first)
        {
            KFit = vFeatVecKF.lower_bound(Fit->first);
        }
        else
        {
            Fit = F.mFeatVec.lower_bound(KFit->first);
        }

KFit->first == Fit->first寻找相同结点(基于前面对于词袋的理解)
KFit = vFeatVecKF.lower_bound(Fit->first)可以理解为将相同结点对齐

总结

还是和我在tracking中反复提及的唯一性有纪念意义

  • 唯一性:通过寻找当前帧与关键帧最相似的点
    寻找方法:KP 送进BOW结点中,在节点中进一步寻找最小距离
  • 有纪念意义:记录显著方向的

另一个重载

int ORBmatcher::SearchByBoW(KeyFrame *pKF1, KeyFrame *pKF2, vector<MapPoint *> &vpMatches12)

其实也差不多,节省篇幅就不上全部代码了
只不过就是变成了两个都是关键帧

1.2 SearchByProjection

1.2.1解析

利用重投影在其投影点附近根据描述子距离选取匹配,由此增加当前帧的MapPoints 。在TrackWithMotionModel()中使用
重投影:利用将相机坐标系下(三维)的Local MapPoints投影到图像坐标系(二维)。

共有四种:

int ORBmatcher::SearchByProjection(Frame &F, const vector<MapPoint*> &vpMapPoints, const float th)

int ORBmatcher::SearchByProjection(KeyFrame* pKF, cv::Mat Scw, const vector<MapPoint*> &vpPoints, vector<MapPoint*> &vpMatched, int th)

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, const Frame &LastFrame, const float th, const bool bMono)

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, KeyFrame *pKF, const set<MapPoint*> &sAlreadyFound, const float th , const int ORBdist)

根据TrackWithMotionModel()中代码

 int nmatches = matcher.SearchByProjection(mCurrentFrame,mLastFrame,th,mSensor==System::MONOCULAR);

应该是第三种,因此着重讲解第三种这篇中有对四种进行讲解

1.2.1.1完整代码

1.2.2代码分块解析

int ORBmatcher::SearchByProjection(Frame &CurrentFrame, const Frame &LastFrame, const float th, const bool bMono)

将上一帧跟踪的地图点投影到当前帧,并且搜索匹配点。

th:搜索范围阈值
bMono:是否为单目相机

1) 位姿关系
//当前相机坐标系到世界坐标系的平移向量
    const cv::Mat twc = -Rcw.t()*tcw;

《slam14讲》p44
基于特殊欧式群的性质:求解该矩阵的逆表示一个反向的变换
特殊欧式群:
请添加图片描述

其中矩阵T称为变化矩阵

该矩阵的逆:

请添加图片描述

const cv::Mat tlc = Rlw*twc+tlw;

//截图过来

2)针对双目/RGB-D 相机向前/后
// 仅针对双目或RGB-D相机
    const bool bForward = tlc.at<float>(2)>CurrentFrame.mb && !bMono;
    const bool bBackward = -tlc.at<float>(2)>CurrentFrame.mb && !bMono;
3)小孔成像模型

代码的整体逻辑是

  • 以下是三维世界中的MapPoint点到像素坐标的计算过程
    1.世界坐标–>相机坐标;
    2.相机坐标–>相机归一化平面坐标;
    3.相机归一化平面坐标–>像素坐标;
	  const float invzc = 1.0/x3Dc.at<float>(2)//归一化平面
    float u = CurrentFrame.fx*xc*invzc+CurrentFrame.cx;
    float v = CurrentFrame.fy*yc*invzc+CurrentFrame.cy;

看到uv应该很熟悉,即就是经典的世界坐标系转相机坐标系

vector<size_t> KeyFrame::GetFeaturesInArea(const float &x, const float &y, const float &r) const

vector<size_t> Frame::GetFeaturesInArea(const float &x, const float  &y, const float  &r, const int minLevel, const int maxLevel) const
4) GetFeaturesInArea

【参考文档】orb-slam2 从单目开始的简单学习(6)Frame详见GetFeaturesInArea有完整介绍

根据相机的前后前进方向来判断搜索尺度范围
当相机前进时,原来的特征点需要在更高的尺度下才能找到正确的匹配点
当相机后退时,原来的特征点需要在更低的尺度下才能找到正确的匹配点

金字塔结构图:
在这里插入图片描述
层级越高,所看见的内容在实际场景中越大

if(bForward)
	vIndices2 = CurrentFrame.GetFeaturesInArea(u,v, radius, nLastOctave);
else if(bBackward)
	vIndices2 = CurrentFrame.GetFeaturesInArea(u,v, radius, 0, nLastOctave);
else//非双目情况
	vIndices2 = CurrentFrame.GetFeaturesInArea(u,v, radius, nLastOctave-1, nLastOctave+1);

5)搜索半径限制

双目和rgbd的情况,需要保证右图的点也在搜索半径以内

if(CurrentFrame.mvuRight[i2]>0)
{			//(u,v)
    const float ur = u - CurrentFrame.mbf*invzc;// mbf:Stereo baseline multiplied by fx.
    						//invzc:归一化参数
    const float er = fabs(ur - CurrentFrame.mvuRight[i2]);
    
    if(er>radius)
        continue;
}

1.3. SearchForInitialization

2.Fuse

将地图点与帧中图像的特征点进行匹配,实现地图点融合

// 将地图点投影到KeyFrame并搜索重复的地图点
int Fuse(KeyFrame* pKF, const vector<MapPoint *> &vpMapPoints, const float th=3.0);

// 使用给定的Sim3将MapPoints投影到KeyFrame中,并搜索重复的MapPoints。
int Fuse(KeyFrame* pKF, cv::Mat Scw, const std::vector<MapPoint*> &vpPoints, float th, vector<MapPoint *> &vpReplacePoint);

有两个重载

const float ur = u-bf*invz

基于公式

3. SearchForTriangulation

3.1 极点的理解

在这里插入图片描述
极点可以理解为 :第一帧图像相机光心在第二帧坐标系中的位置

3.2 完整代码和注释

int ORBmatcher::SearchForTriangulation(KeyFrame *pKF1, KeyFrame *pKF2, cv::Mat F12,
                                       vector<pair<size_t, size_t> > &vMatchedPairs, const bool bOnlyStereo)
{    
    const DBoW2::FeatureVector &vFeatVec1 = pKF1->mFeatVec;
    const DBoW2::FeatureVector &vFeatVec2 = pKF2->mFeatVec;

//-------step 1. 计算在第二帧中极点---------------------------------Compute epipole(baseline和平面的连线) in second image
     //极点可以理解为第一帧图像相机光心在第二帧坐标系中的位置
    cv::Mat Cw = pKF1->GetCameraCenter();//pKF1光心在世界坐标系中的坐标
    cv::Mat R2w = pKF2->GetRotation();//世界坐标系到相机坐标系
    cv::Mat t2w = pKF2->GetTranslation();
    cv::Mat C2 = R2w*Cw+t2w;//将pKF1光心从世界坐标系转移到pKF2相机坐标系
    
    const float invz = 1.0f/C2.at<float>(2);
    const float ex =pKF2->fx*C2.at<float>(0)*invz+pKF2->cx;//归一化u
    const float ey =pKF2->fy*C2.at<float>(1)*invz+pKF2->cy;//归一化v

    // 查找未跟踪关键点之间的匹配项Find matches between not tracked keypoints
    // 通过ORB词汇表,加快匹配速度Matching speed-up by ORB Vocabulary
    // 仅比较共享同一节点的ORBCompare only ORB that share the same node

    int nmatches=0;		//先进行预设值
    vector<bool> vbMatched2(pKF2->N,false);
    vector<int> vMatches12(pKF1->N,-1);//记录第二张图中第一张图的对应匹配点序号

    vector<int> rotHist[HISTO_LENGTH];//每一个rotHist[i]都是一个vector<int>类型
    for(int i=0;i<HISTO_LENGTH;i++)
        rotHist[i].reserve(500);//bin的最大高度是500

    const float factor = 1.0f/HISTO_LENGTH;
    
//-------step2 寻找图2中图1的匹配点------------
//FeatureVector本质上是map<NodeId, std::vector<unsigned int> >
//NodeId:代表节点 vector<unsigned int>:映射在同一节点的关键点的索引
    DBoW2::FeatureVector::const_iterator f1it = vFeatVec1.begin();
    DBoW2::FeatureVector::const_iterator f2it = vFeatVec2.begin();
    DBoW2::FeatureVector::const_iterator f1end = vFeatVec1.end();
    DBoW2::FeatureVector::const_iterator f2end = vFeatVec2.end();

//索引对应关系:关键点,地图点,描述子
    while(f1it!=f1end && f2it!=f2end)
    {//-------step2.1对应相同节点通过寻找最小距离来进行匹配---------
        if(f1it->first == f2it->first)
        {//-------step2.1.1遍历第一张图中该节点所有对应的关键点-----
            for(size_t i1=0, iend1=f1it->second.size(); i1<iend1; i1++)
            {
                const size_t idx1 = f1it->second[i1];//取索引
                
                MapPoint* pMP1 = pKF1->GetMapPoint(idx1);//取索引对应的地图点
                
                // 跳过已经存在地图点的 If there is already a MapPoint skip
                if(pMP1)
                    continue;

                const bool bStereo1 = pKF1->mvuRight[idx1]>=0;//判断是否为双目

                if(bOnlyStereo)
                    if(!bStereo1)
                        continue;
                
                const cv::KeyPoint &kp1 = pKF1->mvKeysUn[idx1];//取索引对应的未矫正的关键点
                
                const cv::Mat &d1 = pKF1->mDescriptors.row(idx1);
                
                int bestDist = TH_LOW;
                int bestIdx2 = -1;
                //-------step2.1.2遍历第二张图中该节点所有对应的关键点以寻找最小距离------
                for(size_t i2=0, iend2=f2it->second.size(); i2<iend2; i2++)
                {
                    size_t idx2 = f2it->second[i2];
                    
                    MapPoint* pMP2 = pKF2->GetMapPoint(idx2);
                    
                    // If we have already matched or there is a MapPoint skip
                    if(vbMatched2[idx2] || pMP2)
                        continue;

                    const bool bStereo2 = pKF2->mvuRight[idx2]>=0;

                    if(bOnlyStereo)
                        if(!bStereo2)
                            continue;
                    
                    const cv::Mat &d2 = pKF2->mDescriptors.row(idx2);
                    
                    const int dist = DescriptorDistance(d1,d2);
                    
                    if(dist>TH_LOW || dist>bestDist)
                        continue;

                    const cv::KeyPoint &kp2 = pKF2->mvKeysUn[idx2];

                    if(!bStereo1 && !bStereo2)
                    {// 极点到kp2的像素距离如果小于阈值th,认为kp2对应的MapPoint距离pKF1相机太近,跳过该匹配点对
                        const float distex = ex-kp2.pt.x;
                        const float distey = ey-kp2.pt.y;
                        if(distex*distex+distey*distey<100*pKF2->mvScaleFactors[kp2.octave])
                            continue;
                    }

                    if(CheckDistEpipolarLine(kp1,kp2,F12,pKF2))
                    {
                        bestIdx2 = idx2;
                        bestDist = dist;
                    }
                }
                
                if(bestIdx2>=0)
                {
                    const cv::KeyPoint &kp2 = pKF2->mvKeysUn[bestIdx2];
                    vMatches12[idx1]=bestIdx2;
                    nmatches++;//观测点

                    if(mbCheckOrientation)
                    {
                        float rot = kp1.angle-kp2.angle;
                        if(rot<0.0)
                            rot+=360.0f;
                        int bin = round(rot*factor);
                        if(bin==HISTO_LENGTH)
                            bin=0;
                        assert(bin>=0 && bin<HISTO_LENGTH);
                        rotHist[bin].push_back(idx1);
                    }
                }
            }

            f1it++;
            f2it++;
        }
        else if(f1it->first < f2it->first)
        {
            f1it = vFeatVec1.lower_bound(f2it->first);
        }
        else
        {
            f2it = vFeatVec2.lower_bound(f1it->first);
        }
    }
//-------step3 只记录前三方向变化点--------------
    if(mbCheckOrientation)
    {
        int ind1=-1;
        int ind2=-1;
        int ind3=-1;

        ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3);

        for(int i=0; i<HISTO_LENGTH; i++)
        {
            if(i==ind1 || i==ind2 || i==ind3)
                continue;
            for(size_t j=0, jend=rotHist[i].size(); j<jend; j++)
            {
                vMatches12[rotHist[i][j]]=-1;
                nmatches--;
            }
        }

    }

    vMatchedPairs.clear();
    vMatchedPairs.reserve(nmatches);
//-------step4 将匹配特征点对在帧中序号放入vMatchedPairs--------------
    for(size_t i=0, iend=vMatches12.size(); i<iend; i++)
    {
        if(vMatches12[i]<0)//被初始化为-1,会被过滤
            continue;
        vMatchedPairs.push_back(make_pair(i,vMatches12[i]));
//i代表第一帧中的关键点,vMatches12[i]代表第二帧中对应的匹配点
    }

    return nmatches;
}
  • vMatchedPairs:得到的是两帧图片对应的关键点在对应帧中序号
  • 序号对应关系:关键点,地图点,未矫正的点
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值