特征匹配(三):根据词典进行匹配

本次主要讲解ORBSLAM2中使用词袋来加速度特征点匹配并使用对极约束剔除外点的函数SearchForTriangulation,这个函数中主要进行了以下工作:

  • 计算KF2中的极点坐标并预分配内存

    // Step 1 计算KF1的相机中心在KF2图像平面的二维像素坐标
    // KF1相机光心在世界坐标系坐标Cw
    cv::Mat Cw = pKF1->GetCameraCenter(); 
    // KF2相机位姿R2w,t2w,是世界坐标系到相机坐标系
    cv::Mat R2w = pKF2->GetRotation();    
    cv::Mat t2w = pKF2->GetTranslation(); 
    // KF1的相机光心转化到KF2坐标系中的坐标
    cv::Mat C2 = R2w*Cw+t2w; 
    const float invz = 1.0f/C2.at<float>(2);
    // 得到KF1的相机光心在KF2中的坐标,也叫极点,这里是像素坐标
    const float ex =pKF2->fx*C2.at<float>(0)*invz+pKF2->cx;
    const float ey =pKF2->fy*C2.at<float>(1)*invz+pKF2->cy;
    
    
    int nmatches=0;
    // 记录匹配是否成功,避免重复匹配
    vector<bool> vbMatched2(pKF2->N,false);        
    vector<int> vMatches12(pKF1->N,-1);
    // 用于统计匹配点对旋转差的直方图
    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 float factor = HISTO_LENGTH/360.0f;
    
  • 利用词袋加速匹配,只有属于同一个Node下的特征点才有可能相互匹配

    // Step 2.1:遍历pKF1和pKF2中的node节点
    while(f1it!=f1end && f2it!=f2end)
    {
        // 如果f1it和f2it属于同一个node节点才会进行匹配,这就是BoW加速匹配原理
        if(f1it->first == f2it->first)
        {
            // Step 2.2:遍历属于同一node节点(id:f1it->first)下的所有特征点
            for(size_t i1=0, iend1=f1it->second.size(); i1<iend1; i1++)
            {
                // 获取pKF1中属于该node节点的所有特征点索引
                const size_t idx1 = f1it->second[i1];
                
                // Step 2.3:通过特征点索引idx1在pKF1中取出对应的MapPoint
                MapPoint* pMP1 = pKF1->GetMapPoint(idx1);
                
                // If there is already a MapPoint skip
                // 由于寻找的是未匹配的特征点,所以pMP1应该为NULL
                if(pMP1)
                    continue;
    
                // 如果mvuRight中的值大于0,表示是双目,且该特征点有深度值
                const bool bStereo1 = pKF1->mvuRight[idx1]>=0;
    
                if(bOnlyStereo)
                    if(!bStereo1)
                        continue;
                
                // Step 2.4:通过特征点索引idx1在pKF1中取出对应的特征点
                const cv::KeyPoint &kp1 = pKF1->mvKeysUn[idx1];
                
                // 通过特征点索引idx1在pKF1中取出对应的特征点的描述子
                const cv::Mat &d1 = pKF1->mDescriptors.row(idx1);
                
                int bestDist = TH_LOW;
                int bestIdx2 = -1;
                
                // Step 2.5:遍历该node节点下(f2it->first)对应KF2中的所有特征点
                for(size_t i2=0, iend2=f2it->second.size(); i2<iend2; i2++)
                {
                    // 获取pKF2中属于该node节点的所有特征点索引
                    size_t idx2 = f2it->second[i2];
                    
                    // 通过特征点索引idx2在pKF2中取出对应的MapPoint
                    MapPoint* pMP2 = pKF2->GetMapPoint(idx2);
                    
    
                    // 如果pKF2当前特征点索引idx2已经被匹配过或者对应的3d点非空,那么跳过这个索引idx2
                    if(vbMatched2[idx2] || pMP2)
                        continue;
    
                    const bool bStereo2 = pKF2->mvuRight[idx2]>=0;
    
                    if(bOnlyStereo)
                        if(!bStereo2)
                            continue;
                    
                    // 通过特征点索引idx2在pKF2中取出对应的特征点的描述子
                    const cv::Mat &d2 = pKF2->mDescriptors.row(idx2);
                    
                    // Step 2.6 计算idx1与idx2在两个关键帧中对应特征点的描述子距离
                    const int dist = DescriptorDistance(d1,d2);
                    
                    if(dist>TH_LOW || dist>bestDist)
                        continue;
    
                    // 通过特征点索引idx2在pKF2中取出对应的特征点
                    const cv::KeyPoint &kp2 = pKF2->mvKeysUn[idx2];
    
                    //? 为什么双目就不需要判断像素点到极点的距离的判断?
                    // 因为双目模式下可以左右互匹配恢复三维点
                    if(!bStereo1 && !bStereo2)
                    {
                        const float distex = ex-kp2.pt.x;
                        const float distey = ey-kp2.pt.y;
                        // Step 2.7 极点e2到kp2的像素距离如果小于阈值th,认为kp2对应的MapPoint距离pKF1相机太近,跳过该匹配点对
                        // 作者根据kp2金字塔尺度因子(scale^n,scale=1.2,n为层数)定义阈值th
                        // 金字塔层数从0到7,对应距离 sqrt(100*pKF2->mvScaleFactors[kp2.octave]) 是10-20个像素
                        //? 对这个阈值的有效性持怀疑态度
                        if(distex*distex+distey*distey<100*pKF2->mvScaleFactors[kp2.octave])
                            continue;
                    }
    
                    // Step 2.8 计算特征点kp2到kp1对应极线的距离是否小于阈值
                    if(CheckDistEpipolarLine(kp1,kp2,F12,pKF2))
                    {
                        // bestIdx2,bestDist 是 kp1 对应 KF2中的最佳匹配点 index及匹配距离
                        bestIdx2 = idx2;
                        bestDist = dist;
                    }
                }
    
                
                if(bestIdx2>=0)
                {
                    const cv::KeyPoint &kp2 = pKF2->mvKeysUn[bestIdx2];
                    // 记录匹配结果
                    vMatches12[idx1]=bestIdx2;      
                    
                    vbMatched2[bestIdx2]=true;  // !记录已经匹配,避免重复匹配。原作者漏掉!
                    nmatches++;
    
                    // 记录旋转差直方图信息
                    if(mbCheckOrientation)
                    {
                        // angle:角度,表示匹配点对的方向差。
                        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++;
        }
        // 这两步的目的是使用泛型算法使得两个Nodeid尽可能接近,不要去一个一个遍历
        else if(f1it->first < f2it->first)
        {
            f1it = vFeatVec1.lower_bound(f2it->first);
        }
        else
        {
            f2it = vFeatVec2.lower_bound(f1it->first);
        }
    }
    

    这里用到了两次对极几何,一次用来判断匹配点对对应的3d点距离KF1光心的距离,一次用本质矩阵来验证特征点匹配的误差是否在给定阈值内

  • 使用旋转直方图来剔除掉错误的匹配对

    // Step 3 用旋转差直方图来筛掉错误匹配对
    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++)
            {              
                vbMatched2[vMatches12[rotHist[i][j]]] = false;  // !清除匹配关系。原作者漏掉!
                vMatches12[rotHist[i][j]]=-1;
                nmatches--;
            }
        }
    
    }
    
  • 储存匹配关系

    // Step 4 存储匹配关系,下标是关键帧1的特征点id,存储的是关键帧2的特征点id
    vMatchedPairs.clear();
    vMatchedPairs.reserve(nmatches);
    
    for(size_t i=0, iend=vMatches12.size(); i<iend; i++)
    {
        if(vMatches12[i]<0)
            continue;
        vMatchedPairs.push_back(make_pair(i,vMatches12[i]));
    }
    
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值