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
執行結果
opencv之SURF圖像匹配