• ORB-SLAM(八)ORBmatcher 特征匹配


    该类负责特征点与特征点之间,地图点与特征点之间通过投影关系、词袋模型或者Sim3位姿匹配。用来辅助完成单目初始化,三角化恢复新的地图点,tracking,relocalization以及loop closing,因此比较重要。

    该类提供的API是:

    1. 几个重载的SearchByProjection函数(第一个形参代表需要在其中寻找匹配点的当前图像帧/query;第二个形参则包含待匹配特征/train),用于

      a. 跟踪局部地图(在局部地图中寻找与当前帧特征点匹配的)。因为在TrackReferenceKeyFrame和TrackWithMotionModel中,仅仅是两帧之间跟踪,会跟丢地图点,这里通过跟踪局部地图,在当前帧中恢复出一些当前帧的地图点。  其中的阈值th一般根据单目还是双目,或者最近有没有进行过重定位来确定,代表在投影点的这个平面阈值范围内寻找匹配特征点。匹配点不仅需要满足对极几何,初始位姿的约束;还需要满足描述子之间距离较小。

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

      b. 匹配上一帧的地图点,即前后两帧匹配,用于TrackWithMotionModel。

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

      c. 在当前帧中匹配所有关键帧中的地图点,用于Relocalization。

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

      d. 在当前关键帧中匹配所有关键帧中的地图点,需要计算sim3,用于Loop Closing。

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

    2. 两个重载的SearchByBow函数(注意这里形参表示的匹配的主被动关系和SearchByProjection是反的),用于

      a. 在当前帧中匹配关键帧中的地图点,用于TrackReferenceKeyFrame和Relocalization。

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

      b. 在当前关键帧中匹配所有关键帧中的地图点,用于Loop Closing。

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

    3. 用于单目初始化的SearchForInitialization,以及利用三角化,在两个关键帧之间恢复出一些地图点SearchForTriangulation。

    int ORBmatcher::SearchForInitialization(Frame &F1, Frame &F2, vector<cv::Point2f> &vbPrevMatched, vector<int> &vnMatches12, int windowSize);
    int ORBmatcher::SearchForTriangulation(KeyFrame *pKF1, KeyFrame *pKF2, cv::Mat F12,
                                           vector<pair<size_t, size_t> > &vMatchedPairs, const bool bOnlyStereo);

    4. 两个重载的Fuse函数,用于地图点的融合:

      地图点能匹配上当前关键帧的地图点,也就是地图点重合了,选择观测数多的地图点替换;地图点能匹配上当前帧的特征点,但是该特征点还没有生成地图点,则生成新的地图点)。

      重载的函数是为了减小尺度漂移的影响,需要知道当前关键帧的sim3位姿。

    int ORBmatcher::Fuse(KeyFrame *pKF, const vector<MapPoint *> &vpMapPoints, const float th);
    int ORBmatcher::Fuse(KeyFrame *pKF, cv::Mat Scw, const vector<MapPoint *> &vpPoints, float th, vector<MapPoint *> &vpReplacePoint);

    5. 计算描述子之间的hanmming距离

    int ORBmatcher::DescriptorDistance(const cv::Mat &a, const cv::Mat &b);

    选取其中一个用于Relocalization的投影匹配着重理解。疑问是,何时用投影匹配,何时用DBow2进行匹配?在Relocalization和LoopClosing中进行匹配的是在很多帧关键帧集合中匹配,属于Place Recognition,因此需要用DBow,而投影匹配适用于两帧之间,或者投影范围内(局部地图,前一个关键帧对应地图点)的MapPoints与当前帧之间。

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

    用关键帧pKF的地图点投影匹配当前帧的特征点:

    // For Relocalization
    //
    // 1. 获取pKF对应的地图点vpMPs,遍历
    //     (1). 若该点为NULL、isBad或者在SearchByBow中已经匹配上(Relocalization中首先会通过SearchByBow匹配一次),抛弃;
    // 2. 通过当前帧的位姿,将世界坐标系下的地图点坐标转换为当前帧坐标系(相机坐标系)下的坐标
    //     (2). 投影点(u,v)不在畸变矫正过的图像范围内,地图点的距离dist3D不在地图点的可观测距离内(根据地图点对应的金字塔层数,
    //           也就是提取特征的neighbourhood尺寸),抛弃
    // 3. 通过地图点的距离dist3D,预测特征对应金字塔层nPredictedLevel,并获取搜索window大小(th*scale),在以上约束的范围内,
    //    搜索得到候选匹配点集合向量vIndices2
    //     const vector<size_t> vIndices2 = CurrentFrame.GetFeaturesInArea(u, v, radius, nPredictedLevel-1, nPredictedLevel+1);
    // 4. 计算地图点的描述子和候选匹配点描述子距离,获得最近距离的最佳匹配,但是也要满足距离<ORBdist。
    // 5. 最后,还需要通过直方图验证描述子的方向是否匹配
    int ORBmatcher::SearchByProjection(Frame &CurrentFrame, KeyFrame *pKF, const set<MapPoint*> &sAlreadyFound, const float th , const int ORBdist)
    {
        int nmatches = 0;
    
        const cv::Mat Rcw = CurrentFrame.mTcw.rowRange(0,3).colRange(0,3);
        const cv::Mat tcw = CurrentFrame.mTcw.rowRange(0,3).col(3);
        const cv::Mat Ow = -Rcw.t()*tcw;
    
        // Rotation Histogram (to check rotation consistency)
        vector<int> rotHist[HISTO_LENGTH];
        for(int i=0;i<HISTO_LENGTH;i++)
            rotHist[i].reserve(500);
        const float factor = 1.0f/HISTO_LENGTH;
    
        const vector<MapPoint*> vpMPs = pKF->GetMapPointMatches();
    
        for(size_t i=0, iend=vpMPs.size(); i<iend; i++)
        {
            MapPoint* pMP = vpMPs[i];
    
            if(pMP)
            {
                // before this, Relocalization has already execute SearchByBoW, those matched was inserted into sAlreadyFound
                if(!pMP->isBad() && !sAlreadyFound.count(pMP))
                {
                    //Project
                    cv::Mat x3Dw = pMP->GetWorldPos();
                    cv::Mat x3Dc = Rcw*x3Dw+tcw;
    
                    const float xc = x3Dc.at<float>(0);
                    const float yc = x3Dc.at<float>(1);
                    const float invzc = 1.0/x3Dc.at<float>(2);
    
                    const float u = CurrentFrame.fx*xc*invzc+CurrentFrame.cx;
                    const float v = CurrentFrame.fy*yc*invzc+CurrentFrame.cy;
                    // u,v是关键帧中地图点在当前帧上的投影点
                    if(u<CurrentFrame.mnMinX || u>CurrentFrame.mnMaxX)
                        continue;
                    if(v<CurrentFrame.mnMinY || v>CurrentFrame.mnMaxY)
                        continue;
    
                    // Compute predicted scale level
                    cv::Mat PO = x3Dw-Ow;
                    float dist3D = cv::norm(PO);
    
                    const float maxDistance = pMP->GetMaxDistanceInvariance();
                    const float minDistance = pMP->GetMinDistanceInvariance();
    
                    // Depth must be inside the scale pyramid of the image
                    if(dist3D<minDistance || dist3D>maxDistance)
                        continue;
    
                    int nPredictedLevel = pMP->PredictScale(dist3D,&CurrentFrame);
    
                    // Search in a window
                    const float radius = th*CurrentFrame.mvScaleFactors[nPredictedLevel];
    
                    const vector<size_t> vIndices2 = CurrentFrame.GetFeaturesInArea(u, v, radius, nPredictedLevel-1, nPredictedLevel+1);
    
                    if(vIndices2.empty())
                        continue;
    
                    const cv::Mat dMP = pMP->GetDescriptor();
    
                    int bestDist = 256;
                    int bestIdx2 = -1;
    
                    for(vector<size_t>::const_iterator vit=vIndices2.begin(); vit!=vIndices2.end(); vit++)
                    {
                        const size_t i2 = *vit;
                        if(CurrentFrame.mvpMapPoints[i2])
                            continue;
    
                        const cv::Mat &d = CurrentFrame.mDescriptors.row(i2);
    
                        const int dist = DescriptorDistance(dMP,d);
    
                        if(dist<bestDist)
                        {
                            bestDist=dist;
                            bestIdx2=i2;
                        }
                    }
    
                    if(bestDist<=ORBdist)
                    {
                        CurrentFrame.mvpMapPoints[bestIdx2]=pMP;
                        nmatches++;
    
                        if(mbCheckOrientation)
                        {
                            float rot = pKF->mvKeysUn[i].angle-CurrentFrame.mvKeysUn[bestIdx2].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(bestIdx2);
                        }
                    }
    
                }
            }
        }
    
        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)
                {
                    for(size_t j=0, jend=rotHist[i].size(); j<jend; j++)
                    {
                        CurrentFrame.mvpMapPoints[rotHist[i][j]]=NULL;
                        nmatches--;
                    }
                }
            }
        }
    
        return nmatches;
    }

    其中角度直方图是用来剔除不满足两帧之间角度旋转的外点的,也就是所谓的旋转一致性检测

    1. 将关键帧与当前帧匹配点的angle相减,得到rot(0<=rot<360),放入一个直方图中,对于每一对匹配点的角度差,均可以放入一个bin的范围内(360/HISTO_LENGTH)。

    2. 统计直方图最高的三个bin保留,其他范围内的匹配点剔除。另外,若最高的比第二高的高10倍以上,则只保留最高的bin中的匹配点。

    最后该函数会

    1. 为当前帧生成和关键帧匹配上的地图点

    2. 统计通过投影匹配上的点

    CurrentFrame.mvpMapPoints[bestIdx2]=pMP;
    nmatches++;
  • 相关阅读:
    BZOJ 3506 机械排序臂 splay
    BZOJ 2843 LCT
    BZOJ 3669 魔法森林
    BZOJ 2049 LCT
    BZOJ 3223 文艺平衡树 splay
    BZOJ 1433 假期的宿舍 二分图匹配
    BZOJ 1051 受欢迎的牛 强连通块
    BZOJ 1503 郁闷的出纳员 treap
    BZOJ 1096 ZJOI2007 仓库设计 斜率优化dp
    BZOJ 1396: 识别子串( 后缀数组 + 线段树 )
  • 原文地址:https://www.cnblogs.com/shang-slam/p/6431017.html
Copyright © 2020-2023  润新知