zoukankan      html  css  js  c++  java
  • OpenCV2:特征匹配及其优化

    OpenCV2简单的特征匹配中对使用OpenCV2进行特征匹配的步骤做了一个简单的介绍,其匹配出的结果是非常粗糙的,在这篇文章中对使用OpenCV2进行匹配的细化做一个简单的总结。主要包括以下几个内容:

    • DescriptorMatcher
    • DMatcher
    • KNN匹配
    • 计算两视图的基础矩阵F,并细化匹配结果
    • 计算两视图的单应矩阵H,并细化匹配结果

    DescriptorMatcher 和 DMatcher

    DescriptorMatcher是匹配特征向量的抽象类,在OpenCV2中的特征匹配方法都继承自该类(例如:BFmatcher,FlannBasedMatcher)。该类主要包含了两组匹配方法:图像对之间的匹配以及图像和一个图像集之间的匹配。

    用于图像对之间匹配的方法的声明

    // Find one best match for each query descriptor (if mask is empty).
        CV_WRAP void match( const Mat& queryDescriptors, const Mat& trainDescriptors,
                    CV_OUT vector<DMatch>& matches, const Mat& mask=Mat() ) const;
        // Find k best matches for each query descriptor (in increasing order of distances).
        // compactResult is used when mask is not empty. If compactResult is false matches
        // vector will have the same size as queryDescriptors rows. If compactResult is true
        // matches vector will not contain matches for fully masked out query descriptors.
        CV_WRAP void knnMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
                       CV_OUT vector<vector<DMatch> >& matches, int k,
                       const Mat& mask=Mat(), bool compactResult=false ) const;
        // Find best matches for each query descriptor which have distance less than
        // maxDistance (in increasing order of distances).
        void radiusMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
                          vector<vector<DMatch> >& matches, float maxDistance,
                          const Mat& mask=Mat(), bool compactResult=false ) const;

    方法重载,用于图像和图像集匹配的方法声明

    CV_WRAP void match( const Mat& queryDescriptors, CV_OUT vector<DMatch>& matches,
                    const vector<Mat>& masks=vector<Mat>() );
        CV_WRAP void knnMatch( const Mat& queryDescriptors, CV_OUT vector<vector<DMatch> >& matches, int k,
               const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
        void radiusMatch( const Mat& queryDescriptors, vector<vector<DMatch> >& matches, float maxDistance,
                       const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );

    DMatcher 是用来保存匹配结果的,主要有以下几个属性

    CV_PROP_RW int queryIdx; // query descriptor index
        CV_PROP_RW int trainIdx; // train descriptor index
        CV_PROP_RW int imgIdx;   // train image index
    
        CV_PROP_RW float distance;

    在图像匹配时有两种图像的集合,查找集(Query Set)和训练集(Train Set),对于每个Query descriptor,DMatch中保存了和其最好匹配的Train descriptor。另外,每个train image会生成多个train descriptor。

    如果是图像对之间的匹配的话,由于所有的train descriptor都是由一个train image生成的,所以在匹配结果DMatch中所有的imgIdx是一样的,都为0.

    KNNMatch

    匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点事错误的,图像上的特征点无法匹配。常用的删除错误的匹配有

    • 交叉过滤

      如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。

      OpenCV中的BFMatcher已经包含了这种过滤   BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。

    • 比率测试
      KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。

    在抽象基类DescriptorMatcher中封装了knnMatch方法,具体使用方法如下:

    void FeatureMatchTest::knnMatch(vector<DMatch>& matches) {
    
        const float minRatio = 1.f / 1.5f;
        const int k = 2;
    
        vector<vector<DMatch>> knnMatches;
        matcher->knnMatch(leftPattern->descriptors, rightPattern->descriptors, knnMatches, k);
    
        for (size_t i = 0; i < knnMatches.size(); i++) {
            const DMatch& bestMatch = knnMatches[i][0];
            const DMatch& betterMatch = knnMatches[i][1];
    
            float  distanceRatio = bestMatch.distance / betterMatch.distance;
            if (distanceRatio < minRatio)
                matches.push_back(bestMatch);
        }
    }

    RASIC方法计算基础矩阵,并细化匹配结果

    如果已经知道了两视图(图像)间的多个点的匹配,就可以进行基础矩阵F的计算了。OpenCV2中可以使用findFundamentalMat方法,其声明如下:

    //! finds fundamental matrix from a set of corresponding 2D points
    CV_EXPORTS_W Mat findFundamentalMat( InputArray points1, InputArray points2,
                                         int method=FM_RANSAC,
                                         double param1=3., double param2=0.99,
                                         OutputArray mask=noArray());

    参数说明:

    points1,points2 两幅图像间相匹配的点,点的坐标要是浮点数(float或者double)

    第三个参数method是用来计算基础矩阵的具体方法,是一个枚举值。

    param1,param2保持默认值即可。

    主要来说下mask参数,有N个匹配点用来计算基础矩阵,则该值有N个元素,每个元素的值为0或者1.值为0时,代表该匹配点事错误的匹配(离群值),只在使用RANSAC和LMeds方法时该值有效,

    可以使用该值来删除错误的匹配。

    另外,在匹配完成后使用得到的匹配点来计算基础矩阵时,首先需要将特征点对齐,并且将特征点转换为2D点,具体实现如下:

    //Align all points
        vector<KeyPoint> alignedKps1, alignedKps2;
        for (size_t i = 0; i < matches.size(); i++) {
            alignedKps1.push_back(leftPattern->keypoints[matches[i].queryIdx]);
            alignedKps2.push_back(rightPattern->keypoints[matches[i].trainIdx]);
        }
    
        //Keypoints to points
        vector<Point2f> ps1, ps2;
        for (unsigned i = 0; i < alignedKps1.size(); i++)
            ps1.push_back(alignedKps1[i].pt);
    
        for (unsigned i = 0; i < alignedKps2.size(); i++)
            ps2.push_back(alignedKps2[i].pt);

    使用RANSAC方法计算基础矩阵后可以得到一个status向量,用来删除错误的匹配

    //优化匹配结果
        vector<KeyPoint> leftInlier;
        vector<KeyPoint> rightInlier;
        vector<DMatch> inlierMatch;
    
        int index = 0;
        for (unsigned i = 0; i < matches.size(); i++) {
            if (status[i] != 0){
                leftInlier.push_back(alignedKps1[i]);
                rightInlier.push_back(alignedKps2[i]);
                matches[i].trainIdx = index;
                matches[i].queryIdx = index;
                inlierMatch.push_back(matches[i]);
                index++;
            }
        }
        leftPattern->keypoints = leftInlier;
        rightPattern->keypoints = rightInlier;
        matches = inlierMatch;

    计算单应矩阵H,并细化匹配结果

    同基础矩阵类似,得到匹配的特征点后也可以计算单应矩阵。

    //! computes the best-fit perspective transformation mapping srcPoints to dstPoints.
    CV_EXPORTS_W Mat findHomography( InputArray srcPoints, InputArray dstPoints,
                                     int method=0, double ransacReprojThreshold=3,
                                     OutputArray mask=noArray());

    参数说明:

    srcPoints,dstPoints是两视图中匹配的点

    method 是计算单应矩阵所使用的方法,是一个枚举值。

    ransacReprojThreshold 是允许的最大反投影错误,只在使用RANSAC方法时有效。

    mask 同findFundamentalMat 类似,指出匹配的点是不是离群值,用来优化匹配结果。

    void FeatureMatchTest::refineMatcheswithHomography(vector<DMatch>& matches, double reprojectionThreshold, Mat& homography){
        const int minNumbermatchesAllowed = 8;
        if (matches.size() < minNumbermatchesAllowed)
            return;
    
        //Prepare data for findHomography
        vector<Point2f> srcPoints(matches.size());
        vector<Point2f> dstPoints(matches.size());
    
        for (size_t i = 0; i < matches.size(); i++) {
            srcPoints[i] = rightPattern->keypoints[matches[i].trainIdx].pt;
            dstPoints[i] = leftPattern->keypoints[matches[i].queryIdx].pt;
        }
    
        //find homography matrix and get inliers mask
        vector<uchar> inliersMask(srcPoints.size());
        homography = findHomography(srcPoints, dstPoints, CV_FM_RANSAC, reprojectionThreshold, inliersMask);
    
        vector<DMatch> inliers;
        for (size_t i = 0; i < inliersMask.size(); i++){
            if (inliersMask[i])
                inliers.push_back(matches[i]);
        }
        matches.swap(inliers);
    }

    匹配结果对比

    基础矩阵后的过滤 单应矩阵后的过滤
    Homography Homography
    交叉过滤 KNNMatch
    crossfilter knn

    代码说明

    定义了Pattern结构用来保存匹配过程中需要用到的数据

    struct Pattern
    {
        cv::Mat image;
        std::vector<cv::KeyPoint>  keypoints;
        cv::Mat descriptors;
    
        Pattern(cv::Mat& img) :
            image(img) {}
    };

    将各种匹配方法封装到了一个类中,在该类的构造函数中填充Pattern取得匹配所需的数据

    FeatureMatchTest::FeatureMatchTest(std::shared_ptr<Pattern> left, std::shared_ptr<Pattern> right, std::shared_ptr<cv::DescriptorMatcher> matcher) :
    leftPattern(left), rightPattern(right), matcher(matcher) {
    
        //step1:Create detector
        int minHessian = 400;
        SurfFeatureDetector detector(minHessian);
    
        //step2:Detecte keypoint
        detector.detect(leftPattern->image, leftPattern->keypoints);
        detector.detect(rightPattern->image, rightPattern->keypoints);
    
        //step3:Compute descriptor
        detector.compute(leftPattern->image, leftPattern->keypoints, leftPattern->descriptors);
        detector.compute(rightPattern->image, rightPattern->keypoints, rightPattern->descriptors);
    }


    看评论好多想参看下这部分代码的,终于从硬盘上扒拉出来了,上传到了CSDN上。 下载地址: http://download.csdn.net/detail/brookicv/9729163

  • 相关阅读:
    Nginx+Lua系列:Nginx api for lua
    Twemproxy 代理Redis集群
    Nginx+Lua系列:安装配置Nginx+Lua环境
    Nginx配置http2
    双杀 0day 漏洞(CVE-2018-8174)复现
    Restful 和 Rpc
    合作共赢
    package.json 配置发布到私有仓库
    Ant-design-vue form 表单错误提示在 输入框为 large 时 会撑开 排版样式
    献血 组织
  • 原文地址:https://www.cnblogs.com/wangguchangqing/p/4333873.html
Copyright © 2011-2022 走看看