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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM