OpenCV 之 特徵匹配

    OpenCV 中有兩種特徵匹配方法:暴力匹配 (Brute force matching) 和 最近鄰匹配 (Nearest Neighbors matching)html

    它們都繼承自 DescriptorMatcher,是基於特徵描述符距離的匹配,根據描述符的不一樣,距離能夠是 歐氏距離,也能夠是 漢明距算法

      

 

1  暴力匹配

    首先,任取圖像 A 的一個特徵描述符,計算它到圖像 B 中全部特徵描述符的距離;而後,將所獲得的距離進行排序;最後,選擇距離最短的特徵,做爲 A-B 的匹配點app

1.1  BFMatcher

    BFMatcher 屬於 features2d 模塊,繼承自 DescriptorMatcher,其 create() 函數以下:    函數

    static Ptr<BFMatcher> create(
        int normType = NORM_L2,    // normType, One of NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2.
        bool crossCheck = false    // crossCheck
    );

    1) normType 距離類型ui

        SIFT和SURF 的 HOG 描述符,對應歐氏距離 L1 和 L2;ORB 和 BRISK 的 BRIEF 描述符,對應漢明距 HAMMING;HAMMING2 則對應當 WTA_K = 3或4 時的 ORB 算法this

        - 歐氏距離:最經常使用的一種距離定義,指的是 n 維空間中,兩點之間的實際距離spa

                            $L1 = \sum_I | \texttt{src1} (I) - \texttt{src2}|$3d

                            $L2 = \sqrt{\sum_I (\texttt{src1}(I) - \texttt{src2}(I))^2}$   rest

        - 漢明距離:實際是計算機的異或操做,適用於二進制串描述符,如 BRIEF 描述符,定義以下:code

                            $ Hamming \left ( a,b \right ) = \sum\limits_{i=0}^{n-1} \left ( a_i \oplus b_i \right ) $

    2) crossCheck 交叉覈對

        - 若是在圖像 B 中,特徵 $f_{b}$ 是特徵 $f_{a}$ 的最佳匹配,而且在圖像 A 中,特徵 $f_{a}$ 也是特徵 $f_{b}$ 的最佳匹配,則稱 $(f_{a}, f_{b})$ 爲 "good match"

1.2  代碼示例

     特徵匹配步驟以下:讀圖 -> 提取特徵 -> 計算特徵描述符 -> 暴力匹配 -> 顯示匹配結果 

#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp"

using namespace cv;

int main()
{
    // 1) read
    Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
    Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty())
        return -1;

    // 2) detect and compute
    Ptr<SIFT> sift = SIFT::create();
    std::vector<KeyPoint>  kps1, kps2;
    Mat desc1, desc2;
    sift->detectAndCompute(img1, Mat(), kps1, desc1);
    sift->detectAndCompute(img2, Mat(), kps2, desc2);

    // 3) match
    Ptr<BFMatcher> bfmatcher = BFMatcher::create(NORM_L2, true);
    std::vector<DMatch> matches;
    bfmatcher->match(desc1, desc2, matches);

    // 4) draw and show
    Mat img_matches;
    drawMatches(img1, kps1, img2, kps2, matches, img_matches);
    imshow("BFMatcher", img_matches);

    waitKey();
}

    crosscheck 分別爲 true 和 false:

          

   

2  最近鄰匹配

    FLANN 是一個開源庫,全稱 Fast Library for Approximate Nearest Neighbors,它實現了一系列高維向量的近似最近鄰搜索算法

    基於 FLANN 庫的最近鄰匹配算子 FlannBasedMatcher,在特徵數據集較大或一些實時處理領域,其運行效率要遠高於 BFMatcher

    OpenCV 中 FlannBasedMatcher 的定義以下:   

// This matcher trains cv::flann::Index on a train descriptor collection and calls its nearest search methods to find the best matches. 
// So, this matcher may be faster when matching a large train collection than the brute force matcher.
class FlannBasedMatcher : public DescriptorMatcher { public: FlannBasedMatcher( const Ptr<flann::IndexParams>& indexParams=makePtr<flann::KDTreeIndexParams>(), const Ptr<flann::SearchParams>& searchParams=makePtr<flann::SearchParams>() ); static Ptr<FlannBasedMatcher> create();

 2.1  距離比

    爲了進一步提升特徵匹配精度,David Lowe 提出了一種最近鄰次近鄰距離比的方法:

     - 取圖像 A 的一個特徵,搜索它到圖像 B 距離最近的兩個特徵,距離分別記爲 $d_{1}$ 和 $d_{2}$,只有當 $\displaystyle{\frac{d_{1}}{d_{2}}}$ 小於某個閾值時,才認爲是 "good match"

    "good match" 的機率密度函數 PDF (Probability Density Function) 與最近鄰次近鄰距離比的關係,以下:

         

2.2  代碼示例

     取 distance ratio = 0.7,對比使用和不使用距離比濾波的匹配效果,代碼以下:

#include "opencv2/highgui.hpp"
#include "opencv2/features2d.hpp"

using namespace cv;

const float kRatioThresh = 0.7f;

int main()
{
    // 1) read
    Mat img1 = imread("box.png", IMREAD_GRAYSCALE);
    Mat img2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty())
        return -1;

    // 2) detect feature and compute descriptor
    Ptr<SIFT> sift = SIFT::create();
    std::vector<KeyPoint>  kps1, kps2;
    Mat desc1, desc2;
    sift->detectAndCompute(img1, Mat(), kps1, desc1);
    sift->detectAndCompute(img2, Mat(), kps2, desc2);

    // 3) FLANN based matcher
    Ptr<FlannBasedMatcher> knnmatcher = FlannBasedMatcher::create();
    std::vector<std::vector<DMatch> > matches;
    knnmatcher->knnMatch(desc1, desc2, matches, 2);

    // 4) filter matches using Lowe's distance ratio test
    std::vector<DMatch> good_matches;
    for (size_t i = 0; i < matches.size(); i++)
    {
        if (matches[i][0].distance < kRatioThresh*matches[i][1].distance)
        {
            good_matches.push_back(matches[i][0]);
        }
    }
    // 5) draw and show matches
    Mat img_matches;
    drawMatches(img1, kps1, img2, kps2, good_matches, img_matches);
    imshow("Good Matches", img_matches);
waitKey(); }

    匹配效果對好比下:

            

 

3  應用示例

    特徵匹配 + 平面單應性,在計算機視覺中有不少應用,如:透視校訂,目標定位等

3.1  透視校訂

    OpenCV 之 平面單應性 4.1 中的示例,並非標準的透視校訂,由於是人拿着標定板旋轉不一樣角度,使相機和標定板產生了相對的視角變換,而不是相機和整個場景之間

    多視圖幾何中,嚴格意思的透視校訂,是指相機在不一樣的視角下,對同一場景成不一樣的像而進行的視角校訂,以下圖:

          

    在獲得匹配點對 good_matches 以後,再執行以下代碼,即可用於透視校訂

    // Localize the object
    std::vector<Point2f> obj;
    std::vector<Point2f> scene;
    for (size_t i = 0; i < good_matches.size(); i++)
    {
        // Get the keypoints from the good matches
        obj.push_back(kps1[good_matches[i].queryIdx].pt);
        scene.push_back(kps2[good_matches[i].trainIdx].pt);
    }

    // estimate H
    Mat H = findHomography(scene, obj, RANSAC);

    // warp scene 
    Mat scene_warp;
    warpPerspective(img2, scene_warp, H, Size(1.35*img2.cols, img2.rows)); 

    // show
    imshow("scene_warp", scene_warp);   

    校訂先後的結果以下:

          

3.2  目標定位

     獲得匹配點對 good_matches 後,再執行以下代碼,即可用於目標定位

    // Localize the object
    std::vector<Point2f> obj;
    std::vector<Point2f> scene;
    for (size_t i = 0; i < good_matches.size(); i++)
    {
        // Get the keypoints from the good matches
        obj.push_back(kps1[good_matches[i].queryIdx].pt);
        scene.push_back(kps2[good_matches[i].trainIdx].pt);
    }
    // estimate H
    Mat H = findHomography(obj,scene, RANSAC);

    // get the corners from the image_1 ( the object to be "detected" )
    std::vector<Point2f> obj_corners(4);
    obj_corners[0] = Point2f(0, 0);
    obj_corners[1] = Point2f((float)img1.cols, 0);
    obj_corners[2] = Point2f((float)img1.cols, (float)img1.rows);
    obj_corners[3] = Point2f(0, (float)img1.rows);

    std::vector<Point2f> scene_corners(4);
    perspectiveTransform(obj_corners, scene_corners, H);

    // draw lines between the corners (the mapped object in the scene - image_2 )
    line(img_matches, scene_corners[0] + Point2f((float)img1.cols, 0), scene_corners[1] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
    line(img_matches, scene_corners[1] + Point2f((float)img1.cols, 0), scene_corners[2] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
    line(img_matches, scene_corners[2] + Point2f((float)img1.cols, 0), scene_corners[3] + Point2f((float)img1.cols, 0), Scalar(0,255,0));
    line(img_matches, scene_corners[3] + Point2f((float)img1.cols, 0), scene_corners[0] + Point2f((float)img1.cols, 0), Scalar(0,255,0));

    // show detected matches
    imshow("Object detection", img_matches);

    目標定位結果以下:

       

 

參考資料

  OpenCV-Python Tutorials / Feature Detection and Description / Feature Matching 

  OpenCV Tutorials / 2D Features framework (feature2d module) / Feature Matching with FLANN

  OpenCV Tutorials / 2D Features framework (feature2d module) / Features2D + Homography to find a known object

相關文章
相關標籤/搜索