【问题标题】:OpenCV Orb not finding matches once rotation/scale invariances are introduced引入旋转/缩放不变性后,OpenCV Orb 找不到匹配项
【发布时间】:2012-03-21 07:42:00
【问题描述】:

我正在开发一个使用 OpenCV 2.3.1 中的 Orb 特征检测器的项目。我在 8 个不同的图像之间找到匹配项,其中 6 个非常相似(相机位置相差 20 厘米,沿着线性滑块,因此没有比例或旋转变化),然后是从大约 45 度角拍摄的 2 个图像边。我的代码在非常相似的图像之间找到了大量准确的匹配,但从更不同的角度拍摄的图像几乎没有。我已经包含了我认为是我的代码的相关部分,如果您需要更多信息,请告诉我。

// set parameters

int numKeyPoints = 1500;
float distThreshold = 15.0;

//instantiate detector, extractor, matcher

detector = new cv::OrbFeatureDetector(numKeyPoints);
extractor = new cv::OrbDescriptorExtractor;
matcher = new cv::BruteForceMatcher<cv::HammingLUT>;

//Load input image detect keypoints

cv::Mat img1;
std::vector<cv::KeyPoint> img1_keypoints;
cv::Mat img1_descriptors;
cv::Mat img2;
std::vector<cv::KeyPoint> img2_keypoints
cv::Mat img2_descriptors;
img1 = cv::imread(fList[0].string(), CV_LOAD_IMAGE_GRAYSCALE);
img2 = cv::imread(fList[1].string(), CV_LOAD_IMAGE_GRAYSCALE);
detector->detect(img1, img1_keypoints);
detector->detect(img2, img2_keypoints);
extractor->compute(img1, img1_keypoints, img1_descriptors);
extractor->compute(img2, img2_keypoints, img2_descriptors);

//Match keypoints using knnMatch to find the single best match for each keypoint
//Then cull results that fall below given distance threshold

std::vector<std::vector<cv::DMatch> > matches;
matcher->knnMatch(img1_descriptors, img2_descriptors, matches, 1);
int matchCount=0;
for (int n=0; n<matches.size(); ++n) {
    if (matches[n].size() > 0){
        if (matches[n][0].distance > distThreshold){
            matches[n].erase(matches[n].begin());
        }else{
            ++matchCount;
        }
    }
}

【问题讨论】:

    标签: c++ opencv computer-vision photogrammetry orb


    【解决方案1】:

    我认为您的代码没有任何问题。根据我的经验,opencv 的 ORB 对尺度变化很敏感。

    您可能可以通过一个小测试来确认这一点,制作一些仅旋转的图像和一些仅具有比例变化的图像。旋转的可能会匹配得很好,但比例的不会(我认为缩小比例是最糟糕的)。

    我还建议您从主干尝试 opencv 版本(有关编译说明,请参阅 opencv 的站点),ORB 自 2.3.1 以来已更新,性能稍好一些,但仍然存在这些规模问题。

    【讨论】:

    • 感谢您的洞察力。我将测试更多的特征检测器。我一直在避免筛选和冲浪,因为据我了解它们都是专利。您还有其他推荐的特征检测器吗?
    • that 和 sift 和 surf 比 orb 慢得多。如果我真的需要 surf 的准确性并且正在为桌面编程(我正在为移动设备编程)我会尝试 surf GPU 版本(opencv 有一个使用 GPU 的 surf 实现,我认为也是 ORB)看看是否我可以足够快地得到它。还有 FAST 检测器,它速度快但不是很准确,还有 BRIEF 检测器。 Brief 不是旋转不变的,但您可以通过向它提供几个旋转的查询图像来破解它(我会阅读此站点及其代码以查看运行中的简要:cvlab.epfl.ch/software/brief/index.php)。
    • 就我的目的而言,我的主要问题在于过滤。我找到了另一个堆栈溢出答案,它引用了《OpenCV 2 Computer Vision Application Programming Cookbook》第 9 章:使用随机样本共识匹配图像。基本上,他们不只是剔除低于给定距离的所有匹配项,而是使用 3 个不同的过滤器,这些过滤器让我得到了更多的匹配项。以前我删除了距离 15.0 以下的所有匹配项,这让我得到了所有好的匹配项,但我在此过程中剔除了很多好的匹配项。
    • 很好,我会看看那个。您能否分享您对代码所做的更正,以便您回答自己的问题? ;)
    【解决方案2】:

    通过更改过滤匹配项的流程,我最终获得了足够多的有用匹配项。我以前的方法是仅根据距离值丢弃很多好的匹配项。我在 OpenCV2 计算机视觉应用程序编程手册 中找到的这个 RobustMatcher 类最终运行良好。现在我的所有匹配都是准确的,通过增加 ORB 检测器正在寻找的关键点的数量,我已经能够获得足够好的结果。将RobustMatcher 与 SIFT 或 SURF 一起使用仍然可以得到更好的结果,但我现在通过 ORB 获得了可用的数据。

    //RobustMatcher class taken from OpenCV2 Computer Vision Application Programming Cookbook Ch 9
    class RobustMatcher {
      private:
         // pointer to the feature point detector object
         cv::Ptr<cv::FeatureDetector> detector;
         // pointer to the feature descriptor extractor object
         cv::Ptr<cv::DescriptorExtractor> extractor;
         // pointer to the matcher object
         cv::Ptr<cv::DescriptorMatcher > matcher;
         float ratio; // max ratio between 1st and 2nd NN
         bool refineF; // if true will refine the F matrix
         double distance; // min distance to epipolar
         double confidence; // confidence level (probability)
      public:
         RobustMatcher() : ratio(0.65f), refineF(true),
                           confidence(0.99), distance(3.0) {
            // ORB is the default feature
            detector= new cv::OrbFeatureDetector();
            extractor= new cv::OrbDescriptorExtractor();
            matcher= new cv::BruteForceMatcher<cv::HammingLUT>;
         }
    
      // Set the feature detector
      void setFeatureDetector(
             cv::Ptr<cv::FeatureDetector>& detect) {
         detector= detect;
      }
      // Set the descriptor extractor
      void setDescriptorExtractor(
             cv::Ptr<cv::DescriptorExtractor>& desc) {
         extractor= desc;
      }
      // Set the matcher
      void setDescriptorMatcher(
             cv::Ptr<cv::DescriptorMatcher>& match) {
         matcher= match;
      }
      // Set confidence level
      void setConfidenceLevel(
             double conf) {
         confidence= conf;
      }
      //Set MinDistanceToEpipolar
      void setMinDistanceToEpipolar(
             double dist) {
         distance= dist;
      }
      //Set ratio
      void setRatio(
             float rat) {
         ratio= rat;
      }
    
      // Clear matches for which NN ratio is > than threshold
      // return the number of removed points
      // (corresponding entries being cleared,
      // i.e. size will be 0)
      int ratioTest(std::vector<std::vector<cv::DMatch> >
                                                   &matches) {
        int removed=0;
          // for all matches
        for (std::vector<std::vector<cv::DMatch> >::iterator
                 matchIterator= matches.begin();
             matchIterator!= matches.end(); ++matchIterator) {
               // if 2 NN has been identified
               if (matchIterator->size() > 1) {
                   // check distance ratio
                   if ((*matchIterator)[0].distance/
                       (*matchIterator)[1].distance > ratio) {
                      matchIterator->clear(); // remove match
                      removed++;
                   }
               } else { // does not have 2 neighbours
                   matchIterator->clear(); // remove match
                   removed++;
               }
        }
        return removed;
      }
    
      // Insert symmetrical matches in symMatches vector
      void symmetryTest(
          const std::vector<std::vector<cv::DMatch> >& matches1,
          const std::vector<std::vector<cv::DMatch> >& matches2,
          std::vector<cv::DMatch>& symMatches) {
        // for all matches image 1 -> image 2
        for (std::vector<std::vector<cv::DMatch> >::
                 const_iterator matchIterator1= matches1.begin();
             matchIterator1!= matches1.end(); ++matchIterator1) {
           // ignore deleted matches
           if (matchIterator1->size() < 2)
               continue;
           // for all matches image 2 -> image 1
           for (std::vector<std::vector<cv::DMatch> >::
              const_iterator matchIterator2= matches2.begin();
               matchIterator2!= matches2.end();
               ++matchIterator2) {
               // ignore deleted matches
               if (matchIterator2->size() < 2)
                  continue;
               // Match symmetry test
               if ((*matchIterator1)[0].queryIdx ==
                   (*matchIterator2)[0].trainIdx &&
                   (*matchIterator2)[0].queryIdx ==
                   (*matchIterator1)[0].trainIdx) {
                   // add symmetrical match
                     symMatches.push_back(
                       cv::DMatch((*matchIterator1)[0].queryIdx,
                                 (*matchIterator1)[0].trainIdx,
                                 (*matchIterator1)[0].distance));
                     break; // next match in image 1 -> image 2
               }
           }
        }
      }
    
      // Identify good matches using RANSAC
      // Return fundemental matrix
      cv::Mat ransacTest(
          const std::vector<cv::DMatch>& matches,
          const std::vector<cv::KeyPoint>& keypoints1,
          const std::vector<cv::KeyPoint>& keypoints2,
          std::vector<cv::DMatch>& outMatches) {
       // Convert keypoints into Point2f
       std::vector<cv::Point2f> points1, points2;
       cv::Mat fundemental;
       for (std::vector<cv::DMatch>::
             const_iterator it= matches.begin();
           it!= matches.end(); ++it) {
           // Get the position of left keypoints
           float x= keypoints1[it->queryIdx].pt.x;
           float y= keypoints1[it->queryIdx].pt.y;
           points1.push_back(cv::Point2f(x,y));
           // Get the position of right keypoints
           x= keypoints2[it->trainIdx].pt.x;
           y= keypoints2[it->trainIdx].pt.y;
           points2.push_back(cv::Point2f(x,y));
        }
       // Compute F matrix using RANSAC
       std::vector<uchar> inliers(points1.size(),0);
       if (points1.size()>0&&points2.size()>0){
          cv::Mat fundemental= cv::findFundamentalMat(
             cv::Mat(points1),cv::Mat(points2), // matching points
              inliers,       // match status (inlier or outlier)
              CV_FM_RANSAC, // RANSAC method
              distance,      // distance to epipolar line
              confidence); // confidence probability
          // extract the surviving (inliers) matches
          std::vector<uchar>::const_iterator
                             itIn= inliers.begin();
          std::vector<cv::DMatch>::const_iterator
                             itM= matches.begin();
          // for all matches
          for ( ;itIn!= inliers.end(); ++itIn, ++itM) {
             if (*itIn) { // it is a valid match
                 outMatches.push_back(*itM);
              }
           }
           if (refineF) {
           // The F matrix will be recomputed with
           // all accepted matches
              // Convert keypoints into Point2f
              // for final F computation
              points1.clear();
              points2.clear();
              for (std::vector<cv::DMatch>::
                     const_iterator it= outMatches.begin();
                  it!= outMatches.end(); ++it) {
                  // Get the position of left keypoints
                  float x= keypoints1[it->queryIdx].pt.x;
                  float y= keypoints1[it->queryIdx].pt.y;
                  points1.push_back(cv::Point2f(x,y));
                  // Get the position of right keypoints
                  x= keypoints2[it->trainIdx].pt.x;
                  y= keypoints2[it->trainIdx].pt.y;
                  points2.push_back(cv::Point2f(x,y));
              }
              // Compute 8-point F from all accepted matches
              if (points1.size()>0&&points2.size()>0){
                 fundemental= cv::findFundamentalMat(
                    cv::Mat(points1),cv::Mat(points2), // matches
                    CV_FM_8POINT); // 8-point method
              }
           }
        }
        return fundemental;
      }
    
      // Match feature points using symmetry test and RANSAC
      // returns fundemental matrix
      cv::Mat match(cv::Mat& image1,
                    cv::Mat& image2, // input images
         // output matches and keypoints
         std::vector<cv::DMatch>& matches,
         std::vector<cv::KeyPoint>& keypoints1,
         std::vector<cv::KeyPoint>& keypoints2) {
       // 1a. Detection of the SURF features
       detector->detect(image1,keypoints1);
       detector->detect(image2,keypoints2);
       // 1b. Extraction of the SURF descriptors
       cv::Mat descriptors1, descriptors2;
       extractor->compute(image1,keypoints1,descriptors1);
       extractor->compute(image2,keypoints2,descriptors2);
       // 2. Match the two image descriptors
       // Construction of the matcher
       //cv::BruteForceMatcher<cv::L2<float>> matcher;
       // from image 1 to image 2
       // based on k nearest neighbours (with k=2)
       std::vector<std::vector<cv::DMatch> > matches1;
       matcher->knnMatch(descriptors1,descriptors2,
           matches1, // vector of matches (up to 2 per entry)
           2);        // return 2 nearest neighbours
        // from image 2 to image 1
        // based on k nearest neighbours (with k=2)
        std::vector<std::vector<cv::DMatch> > matches2;
        matcher->knnMatch(descriptors2,descriptors1,
           matches2, // vector of matches (up to 2 per entry)
           2);        // return 2 nearest neighbours
        // 3. Remove matches for which NN ratio is
        // > than threshold
        // clean image 1 -> image 2 matches
        int removed= ratioTest(matches1);
        // clean image 2 -> image 1 matches
        removed= ratioTest(matches2);
        // 4. Remove non-symmetrical matches
        std::vector<cv::DMatch> symMatches;
        symmetryTest(matches1,matches2,symMatches);
        // 5. Validate matches using RANSAC
        cv::Mat fundemental= ransacTest(symMatches,
                    keypoints1, keypoints2, matches);
        // return the found fundemental matrix
        return fundemental;
      }
    };
    
    
    // set parameters
    
    int numKeyPoints = 1500;
    
    //Instantiate robust matcher
    
    RobustMatcher rmatcher;
    
    //instantiate detector, extractor, matcher
    
    detector = new cv::OrbFeatureDetector(numKeyPoints);
    extractor = new cv::OrbDescriptorExtractor;
    matcher = new cv::BruteForceMatcher<cv::HammingLUT>;
    
    rmatcher.setFeatureDetector(detector);
    rmatcher.setDescriptorExtractor(extractor);
    rmatcher.setDescriptorMatcher(matcher);
    
    //Load input image detect keypoints
    
    cv::Mat img1;
    std::vector<cv::KeyPoint> img1_keypoints;
    cv::Mat img1_descriptors;
    cv::Mat img2;
    std::vector<cv::KeyPoint> img2_keypoints
    cv::Mat img2_descriptors;
    std::vector<std::vector<cv::DMatch> > matches;
    img1 = cv::imread(fList[0].string(), CV_LOAD_IMAGE_GRAYSCALE);
    img2 = cv::imread(fList[1].string(), CV_LOAD_IMAGE_GRAYSCALE);
    
    rmatcher.match(img1, img2, matches, img1_keypoints, img2_keypoints);
    

    【讨论】:

    • 你的姓等于SIFT开发者的姓,你是David Lowe的儿子吗? :) 我也对匹配算法的稳健性感兴趣,我在这里看到的与流行的 knn+ratio 测试的唯一区别是 symmetry test - 它是否具有显着的稳健性?
    • 哈哈,与 David Lowe 无关 :) 我确实发现添加了 SymmetryTest 和 ransacTest 后我得到了明显更好的结果。性能受到了相当大的影响,但我并没有处于对性能非常敏感的环境中,所以这对我来说不是一个问题。
    • 您建议如何给结果打分?我想在整个索引上运行此代码并找到最佳匹配。我应该在过滤匹配后计算关键点的数量还是将所有距离加在一起或获得距离的平均值?我不知道什么是好的标准。
    【解决方案3】:

    我在使用 opencv python 时遇到了类似的问题,并通过谷歌来到这里。

    为了解决我的问题,我编写了基于@KLowes 解决方案的匹配过滤python 代码。我会在这里分享,以防其他人遇到同样的问题:

    """ Clear matches for which NN ratio is > than threshold """
    def filter_distance(matches):
        dist = [m.distance for m in matches]
        thres_dist = (sum(dist) / len(dist)) * ratio
    
        sel_matches = [m for m in matches if m.distance < thres_dist]
        #print '#selected matches:%d (out of %d)' % (len(sel_matches), len(matches))
        return sel_matches
    
    """ keep only symmetric matches """
    def filter_asymmetric(matches, matches2, k_scene, k_ftr):
        sel_matches = []
        for match1 in matches:
            for match2 in matches2:
                if match1.queryIdx < len(k_ftr) and match2.queryIdx < len(k_scene) and \
                    match2.trainIdx < len(k_ftr) and match1.trainIdx < len(k_scene) and \
                                k_ftr[match1.queryIdx] == k_ftr[match2.trainIdx] and \
                                k_scene[match1.trainIdx] == k_scene[match2.queryIdx]:
                    sel_matches.append(match1)
                    break
        return sel_matches
    
    def filter_ransac(matches, kp_scene, kp_ftr, countIterations=2):
        if countIterations < 1 or len(kp_scene) < minimalCountForHomography:
            return matches
    
        p_scene = []
        p_ftr = []
        for m in matches:
            p_scene.append(kp_scene[m.queryIdx].pt)
            p_ftr.append(kp_ftr[m.trainIdx].pt)
    
        if len(p_scene) < minimalCountForHomography:
            return None
    
        F, mask = cv2.findFundamentalMat(np.float32(p_ftr), np.float32(p_scene), cv2.FM_RANSAC)
        sel_matches = []
        for m, status in zip(matches, mask):
            if status:
                sel_matches.append(m)
    
        #print '#ransac selected matches:%d (out of %d)' % (len(sel_matches), len(matches))
    
        return filter_ransac(sel_matches, kp_scene, kp_ftr, countIterations-1)
    
    
    
    def filter_matches(matches, matches2, k_scene, k_ftr):
        matches = filter_distance(matches)
        matches2 = filter_distance(matches2)
        matchesSym = filter_asymmetric(matches, matches2, k_scene, k_ftr)
        if len(k_scene) >= minimalCountForHomography:
            return filter_ransac(matchesSym, k_scene, k_ftr)
    

    要过滤匹配,必须调用filter_matches(matches, matches2, k_scene, k_ftr),其中matches, matches2 表示由orb-matcher 获得的匹配,k_scene, k_ftr 是对应的关键点。

    【讨论】:

    • 谢谢,太好了!我正要写一个使用特征匹配的小型 python opencv 脚本,你省去了我移植它的麻烦!
    猜你喜欢
    • 2021-03-29
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 2020-03-14
    • 2018-04-05
    • 2012-09-18
    相关资源
    最近更新 更多