1. 程式人生 > >opencv之SURF影象匹配

opencv之SURF影象匹配

1.概述

前面介紹模板匹配的時候已經提到模板匹配時一種基於灰度的匹配方法,而基於特徵的匹配方法有FAST、SIFT、SURF等,上面兩篇文章已經介紹過使用Surf演算法進行特徵點檢測以及使用暴力匹配(BruteForceMatcher)和最近鄰匹配(FLANN)兩種匹配方法。接下來將更深一步介紹利用Surf檢測到的特徵點以及匹配對進行影象匹配.
利用Surf演算法進行影象匹配其一般流程為:檢測物體特徵點->計算特徵點描述子->使用BurteForceMatcher或FLANN進行特徵點匹配->匹配到的特徵點進行透視變換findHomography()->透視矩陣變換perspectiveTransform()->繪製匹配物體輪廓

2. OpenCV API

透視變換
findHomography()
這個函式的作用是在影象原平面和目標影象平面之間尋找並返回一個透視變換矩陣H,如下:

所以反向投影誤差

最小化。如果引數metchod被設定為預設值0,改函式使用所有的點以簡單的最小二乘法計算一個初始的單應估計。可以簡單理解為透視變換矩陣就是把一幅影象從一個空間變換到另一個空間所需要進行旋轉平移而進行加權的矩陣。
但是並不是所有的點匹配對(srcPoints_i, dstPoints_i)都適合使用剛性的透視變換(有些異常值),這樣變換得到的透視矩陣誤差較大。在這種情況下可以使用兩種魯棒的方法RANSAC和LMeDS,嘗試使用很多對應匹配點的隨機子集,使用該子集和最簡單的最小二乘法估計單應性矩陣,然後計算得到的透視變換矩陣的質量/好壞(quality/goodness),然後使用最佳子集來計算單應性矩陣的初始估計矩陣和內在值/異常值(inliers/outliers)的掩碼。
不管方法是否具有魯棒性,使用Levenberg-Marquardt方法進一步精確計算單應性矩陣(如果方法具有魯棒性僅使用內在值(inline))來減少再投影誤差。
RANSAC幾乎可以處理任意比例的異常值,但是它需要一個閾值來區分內在值還是異常值。LMeDS不需要任何閾值,但是隻有在內在值比例大於50%的情況下才能準確計算。如果沒有太多異常值,噪聲有比較小的情況下使用預設方法即可
函式用來查詢初始的內在和外在矩陣,透視變換矩陣確定了一個比例.無論何時如果不能估計H矩陣則函式將返回一個空矩陣

Mat cv::findHomography  (   InputArray  srcPoints,
                            InputArray  dstPoints,
                            int     method = 0,
                            double  ransacReprojThreshold = 3,
                            OutputArray     mask = noArray(),
                            const int   maxIters = 2000,
                            const double    confidence = 0.995 
                        )

srcPoints:原平面對應點,可以是CV_32FC2或vector型別的矩陣。
dstPoints:目標平面對應點,可以是CV_32FC2或vector型別的矩陣
method:用於計算矩陣的方法,可選方法上面已經介紹過有預設值0,CV_RANSAC和CV_LMEDS
ransacReprojThreshold:有預設值3,區分內在值還是異常值的閾值點,只在RANSAC方法有用,當||dstPoints-convertPointsHomogeneous(H*srcPoints)||>ransacReprojThreshold,這個點就會被認為是異常值(outlier).如果srcPoints和dstPoints是以畫素為單位,則引數的取值範圍一般在1-10之間。
mask:可選引數,有預設值noArray(),通過魯棒性方法(RANSAC或LMEDS)設定輸出掩碼。
函式另一種定義形式如下:

Mat cv::findHomography  (   InputArray  srcPoints,
                            InputArray  dstPoints,
                            OutputArray     mask,
                            int     method = 0,
                            double  ransacReprojThreshold = 3 
                        )   

求得的透視矩陣是一個3x3的變換矩陣。

perspectiveTransform()
函式能夠進行向量透視矩陣變換。

void cv::perspectiveTransform   (   InputArray  src,
                                    OutputArray     dst,
                                    InputArray  m 
                                )

src:雙通道或三通道浮點型原影象或陣列,每個元素都是二維或三維可被轉換的向量。
dst:目標陣列或影象,與原影象有相同的尺寸和型別
m:變換矩陣,為3x3或4x4的浮點型矩陣

3.示例程式碼

#include <iostream>
#include <stdio.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\nonfree\features2d.hpp>
#include <opencv2\calib3d\calib3d.hpp>

using namespace std;
using namespace cv;

int main()
{
    Mat image_object = imread("match_src.jpg", IMREAD_GRAYSCALE);
    Mat image_scene = imread("match_dst.jpg", IMREAD_GRAYSCALE);

    //判斷影象是否載入成功
    if (image_object.empty() || image_scene.empty())
    {
        cout << "影象載入失敗";
        return -1;
    }
    else
        cout << "影象載入成功..." << endl << endl;

    //檢測特徵點
    const int minHessian = 700;
    SurfFeatureDetector detector(minHessian);
    vector<KeyPoint>keypoints_object, keypoints_scene;
    detector.detect(image_object, keypoints_object);
    detector.detect(image_scene, keypoints_scene);

    //計算特徵點描述子
    SurfDescriptorExtractor extractor;
    Mat descriptors_object, descriptors_scene;
    extractor.compute(image_object, keypoints_object, descriptors_object);
    extractor.compute(image_scene, keypoints_scene, descriptors_scene);

    //使用FLANN進行特徵點匹配
    FlannBasedMatcher matcher;
    vector<DMatch>matches;
    matcher.match(descriptors_object, descriptors_scene, matches);

    //計算匹配點之間最大和最小距離
    double max_dist = 0;
    double min_dist = 100;
    for (int i = 0; i < descriptors_object.rows; i++)
    {
        double dist = matches[i].distance;
        if (dist < min_dist)
        {
            min_dist = dist;
        }
        else if (dist > max_dist)
        {
            max_dist = dist;
        }
    }
    printf("Max dist: %f \n", max_dist);
    printf("Min dist: %f \n", min_dist);

    //繪製“好”的匹配點
    vector<DMatch>good_matches;
    for (int i = 0; i < descriptors_object.rows; i++)
    {
        if (matches[i].distance<2*min_dist)
        {
            good_matches.push_back(matches[i]);
        }
    }
    Mat image_matches;
    drawMatches(image_object, keypoints_object, image_scene, keypoints_scene, good_matches, image_matches,
        Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    //定位“好”的匹配點
    vector<Point2f> obj;
    vector<Point2f> scene;
    for (int i = 0; i < good_matches.size(); i++)
    {
        //DMathch型別中queryIdx是指match中第一個陣列的索引,keyPoint型別中pt指的是當前點座標
        obj.push_back(keypoints_object[good_matches[i].queryIdx].pt);
        scene.push_back(keypoints_scene[good_matches[i].trainIdx].pt);
    }

    Mat H = findHomography(obj, scene, CV_RANSAC);
    vector<Point2f> obj_corners(4), scene_corners(4);
    obj_corners[0] = cvPoint(0, 0);
    obj_corners[1] = cvPoint(image_object.cols, 0);
    obj_corners[2] = cvPoint(image_object.cols, image_object.rows);
    obj_corners[3] = cvPoint(0, image_object.rows);

    perspectiveTransform(obj_corners, scene_corners, H);

    //繪製角點之間的直線
    line(image_matches, scene_corners[0] + Point2f(image_object.cols, 0),
        scene_corners[1] + Point2f(image_object.cols, 0), Scalar(0, 0, 255), 2);
    line(image_matches, scene_corners[1] + Point2f(image_object.cols, 0),
        scene_corners[2] + Point2f(image_object.cols, 0), Scalar(0, 0, 255), 2);
    line(image_matches, scene_corners[2] + Point2f(image_object.cols, 0),
        scene_corners[3] + Point2f(image_object.cols, 0), Scalar(0, 0, 255), 2);
    line(image_matches, scene_corners[3] + Point2f(image_object.cols, 0),
        scene_corners[0] + Point2f(image_object.cols, 0), Scalar(0, 0, 255), 2);

    //輸出影象
    namedWindow("匹配影象", WINDOW_AUTOSIZE);
    imshow("匹配影象", image_matches);
    waitKey(0);

    return 0;
}

程式說明
在定位匹配點中用到了DMatch的queryIdx、trainIdx成員變數和keyPoint的成員變數pt,做個說明:
DMatch有三個建構函式,其中一組如下:

cv::DMatch::DMatch  (   int     _queryIdx,  //在對描述子匹配時,第一組特徵點的索引
                        int     _trainIdx,  //在對描述子匹配時,第二組特徵點的索引
                        int     _imgIdx,    //多個影象中影象的索引
                        float   _distance   //兩個特徵向量間的歐氏距離,越小表明匹配度越高
                    )

對於keyPoint類有兩種構造形式如下:

cv::KeyPoint::KeyPoint  (   Point2f     _pt,
                            float   _size,
                            float   _angle = -1,
                            float   _response = 0,
                            int     _octave = 0,
                            int     _class_id = -1 
                        )
cv::KeyPoint::KeyPoint  (   float   x,
                            float   y,
                            float   _size,
                            float   _angle = -1,
                            float   _response = 0,
                            int     _octave = 0,
                            int     _class_id = -1 
                        )

兩種形式在本質上是一樣的,只是第一種形式中的特徵點座標pt在第二種形式中以x和y的形式給出。
pt關鍵點座標
size是關鍵點鄰域直徑
angle特徵點方向,範圍為[0,360),負值表示不使用
response關鍵點檢測器對於關鍵點的響應程度
octave關鍵點位於影象金字塔的層
class_id用於聚類的id

執行結果