OpenCV實現(xiàn)特征檢測和特征匹配方法匯總
一幅圖像中總存在著其獨特的像素點,這些點我們可以認為就是這幅圖像的特征,成為特征點。計算機視覺領(lǐng)域中的很重要的圖像特征匹配就是一特征點為基礎(chǔ)而進行的,所以,如何定義和找出一幅圖像中的特征點就非常重要。這篇文章我總結(jié)了視覺領(lǐng)域最常用的幾種特征點以及特征匹配的方法。
在計算機視覺領(lǐng)域,興趣點(也稱關(guān)鍵點或特征點)的概念已經(jīng)得到了廣泛的應(yīng)用, 包括目標識別、 圖像配準、 視覺跟蹤、 三維重建等。 這個概念的原理是, 從圖像中選取某些特征點并對圖像進行局部分析,而非觀察整幅圖像。 只要圖像中有足夠多可檢測的興趣點,并且這些興趣點各不相同且特征穩(wěn)定, 能被精確地定位,上述方法就十分有效。
以下是實驗用的圖像:第一幅是手機抓拍的風景圖,第二幅是遙感圖像。


1.SURF
特征檢測的視覺不變性是一個非常重要的概念。 但是要解決尺度不變性問題,難度相當大。 為解決這一問題,計算機視覺界引入了尺度不變特征的概念。 它的理念是, 不僅在任何尺度下拍攝的物體都能檢測到一致的關(guān)鍵點,而且每個被檢測的特征點都對應(yīng)一個尺度因子。 理想情況下,對于兩幅圖像中不同尺度的的同一個物體點, 計算得到的兩個尺度因子之間的比率應(yīng)該等于圖像尺度的比率。近幾年, 人們提出了多種尺度不變特征,本節(jié)介紹其中的一種:SURF特征。 SURF全稱為“加速穩(wěn)健特征”(Speeded Up Robust Feature),我們將會看到,它們不僅是尺度不變特征,而且是具有較高計算效率的特征。
我們首先進行常規(guī)的特征提取和特征點匹配,看看效果如何。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("2.jpg", 1); //右圖
Mat image02 = imread("1.jpg", 1); //左圖
namedWindow("p2", 0);
namedWindow("p1", 0);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
SurfFeatureDetector surfDetector(800); // 海塞矩陣閾值,在這里調(diào)整精度,值越大點越少,越精準
vector<KeyPoint> keyPoint1, keyPoint2;
surfDetector.detect(image1, keyPoint1);
surfDetector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SurfDescriptorExtractor SurfDescriptor;
Mat imageDesc1, imageDesc2;
SurfDescriptor.compute(image1, keyPoint1, imageDesc1);
SurfDescriptor.compute(image2, keyPoint2, imageDesc2);
//獲得匹配特征點,并提取最優(yōu)配對
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());
cout << "total match points: " << matchePoints.size() << endl;
Mat img_match;
drawMatches(image01, keyPoint1, image02, keyPoint2, matchePoints, img_match);
namedWindow("match", 0);
imshow("match",img_match);
imwrite("match.jpg", img_match);
waitKey();
return 0;
}


由上面的特征點匹配的效果來看,匹配的效果還是相當糟糕的,如果我們拿著這樣子的匹配結(jié)果去實現(xiàn)圖像拼接或者物體追蹤,效果肯定是極差的。所以我們需要進一步篩選匹配點,來獲取優(yōu)秀的匹配點,這就是所謂的“去粗取精”。這里我們采用了Lowe's算法來進一步獲取優(yōu)秀匹配點。
為了排除因為圖像遮擋和背景混亂而產(chǎn)生的無匹配關(guān)系的關(guān)鍵點,SIFT的作者Lowe提出了比較最近鄰距離與次近鄰距離的SIFT匹配方式:取一幅圖像中的一個SIFT關(guān)鍵點,并找出其與另一幅圖像中歐式距離最近的前兩個關(guān)鍵點,在這兩個關(guān)鍵點中,如果最近的距離除以次近的距離得到的比率ratio少于某個閾值T,則接受這一對匹配點。因為對于錯誤匹配,由于特征空間的高維性,相似的距離可能有大量其他的錯誤匹配,從而它的ratio值比較高。顯然降低這個比例閾值T,SIFT匹配點數(shù)目會減少,但更加穩(wěn)定,反之亦然。
Lowe推薦ratio的閾值為0.8,但作者對大量任意存在尺度、旋轉(zhuǎn)和亮度變化的兩幅圖片進行匹配,結(jié)果表明ratio取值在0. 4~0. 6 之間最佳,小于0. 4的很少有匹配點,大于0. 6的則存在大量錯誤匹配點,所以建議ratio的取值原則如下:
ratio=0. 4:對于準確度要求高的匹配;
ratio=0. 6:對于匹配點數(shù)目要求比較多的匹配;
ratio=0. 5:一般情況下。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("g2.jpg", 1);
Mat image02 = imread("g4.jpg", 1);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
SurfFeatureDetector surfDetector(2000); // 海塞矩陣閾值,在這里調(diào)整精度,值越大點越少,越精準
vector<KeyPoint> keyPoint1, keyPoint2;
surfDetector.detect(image1, keyPoint1);
surfDetector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SurfDescriptorExtractor SurfDescriptor;
Mat imageDesc1, imageDesc2;
SurfDescriptor.compute(image1, keyPoint1, imageDesc1);
SurfDescriptor.compute(image2, keyPoint2, imageDesc2);
FlannBasedMatcher matcher;
vector<vector<DMatch> > matchePoints;
vector<DMatch> GoodMatchePoints;
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
// Lowe's algorithm,獲取優(yōu)秀匹配點
for (int i = 0; i < matchePoints.size(); i++)
{
if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance)
{
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
waitKey();
return 0;
}


為了體現(xiàn)所謂的尺度不變形,我特意加入了額外一組圖片來測試

由特征點匹配的效果來看,現(xiàn)在的特征點匹配應(yīng)該是非常精準了,因為我們已經(jīng)把不合格的匹配點統(tǒng)統(tǒng)移除出去了。
2.SIFT
SURF算法是SIFT算法的加速版, 而SIFT(尺度不變特征轉(zhuǎn)換, ScaleInvariant Feature Transform) 是另一種著名的尺度不變特征檢測法。我們知道,SURF相對于SIFT而言,特征點檢測的速度有著極大的提升,所以在一些實時視頻流物體匹配上有著很強的應(yīng)用。而SIFT因為其巨大的特征計算量而使得特征點提取的過程異?;ㄙM時間,所以在一些注重速度的場合難有應(yīng)用場景。但是SIFT相對于SURF的優(yōu)點就是,由于SIFT基于浮點內(nèi)核計算特征點,因此通常認為, SIFT算法檢測的特征在空間和尺度上定位更加精確,所以在要求匹配極度精準且不考慮匹配速度的場合可以考慮使用SIFT算法。
SIFT特征檢測的代碼我們僅需要對上面的SURF代碼作出一丁點修改即可。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("1.jpg", 1); //右圖
Mat image02 = imread("2.jpg", 1); //左圖
namedWindow("p2", 0);
namedWindow("p1", 0);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
SiftFeatureDetector siftDetector(2000); // 海塞矩陣閾值,在這里調(diào)整精度,值越大點越少,越精準
vector<KeyPoint> keyPoint1, keyPoint2;
siftDetector.detect(image1, keyPoint1);
siftDetector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SiftDescriptorExtractor SiftDescriptor;
Mat imageDesc1, imageDesc2;
SiftDescriptor.compute(image1, keyPoint1, imageDesc1);
SiftDescriptor.compute(image2, keyPoint2, imageDesc2);
//獲得匹配特征點,并提取最優(yōu)配對
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());
cout << "total match points: " << matchePoints.size() << endl;
Mat img_match;
drawMatches(image01, keyPoint1, image02, keyPoint2, matchePoints, img_match);
imshow("match",img_match);
imwrite("match.jpg", img_match);
waitKey();
return 0;
}


沒有經(jīng)過點篩選的匹配效果同樣糟糕。下面繼續(xù)采用Lowe‘s的算法選出優(yōu)秀匹配點。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("1.jpg", 1);
Mat image02 = imread("2.jpg", 1);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
SiftFeatureDetector siftDetector(800); // 海塞矩陣閾值,在這里調(diào)整精度,值越大點越少,越精準
vector<KeyPoint> keyPoint1, keyPoint2;
siftDetector.detect(image1, keyPoint1);
siftDetector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SiftDescriptorExtractor SiftDescriptor;
Mat imageDesc1, imageDesc2;
SiftDescriptor.compute(image1, keyPoint1, imageDesc1);
SiftDescriptor.compute(image2, keyPoint2, imageDesc2);
FlannBasedMatcher matcher;
vector<vector<DMatch> > matchePoints;
vector<DMatch> GoodMatchePoints;
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
// Lowe's algorithm,獲取優(yōu)秀匹配點
for (int i = 0; i < matchePoints.size(); i++)
{
if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance)
{
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
imwrite("first_match.jpg", first_match);
waitKey();
return 0;
}



3.ORB
ORB是ORiented Brief的簡稱,是brief算法的改進版。ORB算法比SIFT算法快100倍,比SURF算法快10倍。在計算機視覺領(lǐng)域有種說法,ORB算法的綜合性能在各種測評里較其他特征提取算法是最好的。
ORB算法是brief算法的改進,那么我們先說一下brief算法有什么去缺點。
BRIEF的優(yōu)點在于其速度,其缺點是:
- 不具備旋轉(zhuǎn)不變性
- 對噪聲敏感
- 不具備尺度不變性
而ORB算法就是試圖解決上述缺點中1和2提出的一種新概念。值得注意的是,ORB沒有解決尺度不變性。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("g2.jpg", 1);
Mat image02 = imread("g4.jpg", 1);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
OrbFeatureDetector OrbDetector(1000); // 在這里調(diào)整精度,值越小點越少,越精準
vector<KeyPoint> keyPoint1, keyPoint2;
OrbDetector.detect(image1, keyPoint1);
OrbDetector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
OrbDescriptorExtractor OrbDescriptor;
Mat imageDesc1, imageDesc2;
OrbDescriptor.compute(image1, keyPoint1, imageDesc1);
OrbDescriptor.compute(image2, keyPoint2, imageDesc2);
flann::Index flannIndex(imageDesc1, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);
vector<DMatch> GoodMatchePoints;
Mat macthIndex(imageDesc2.rows, 2, CV_32SC1), matchDistance(imageDesc2.rows, 2, CV_32FC1);
flannIndex.knnSearch(imageDesc2, macthIndex, matchDistance, 2, flann::SearchParams());
// Lowe's algorithm,獲取優(yōu)秀匹配點
for (int i = 0; i < matchDistance.rows; i++)
{
if (matchDistance.at<float>(i,0) < 0.6 * matchDistance.at<float>(i, 1))
{
DMatch dmatches(i, macthIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
GoodMatchePoints.push_back(dmatches);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
imwrite("first_match.jpg", first_match);
waitKey();
return 0;
}


4.FAST
FAST(加速分割測試獲得特征, Features from Accelerated Segment Test) 。 這種算子專門用來快速檢測興趣點, 只需要對比幾個像素,就可以判斷是否為關(guān)鍵點。
跟Harris檢測器的情況一樣, FAST算法源于對構(gòu)成角點的定義。FAST對角點的定義基于候選特征點周圍的圖像強度值。 以某個點為中心作一個圓, 根據(jù)圓上的像素值判斷該點是否為關(guān)鍵點。 如果存在這樣一段圓弧, 它的連續(xù)長度超過周長的3/4, 并且它上面所有像素的強度值都與圓心的強度值明顯不同(全部更黑或更亮) , 那么就認定這是一個關(guān)鍵點。
用這個算法檢測興趣點的速度非??欤?因此十分適合需要優(yōu)先考慮速度的應(yīng)用。 這些應(yīng)用包括實時視覺跟蹤、 目標識別等, 它們需要在實
時視頻流中跟蹤或匹配多個點。
我們使用FastFeatureDetector 進行特征點提取,因為opencv沒有提供fast專用的描述子提取器,所以我們借用SiftDescriptorExtractor 來實現(xiàn)描述子的提取。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("1.jpg", 1);
Mat image02 = imread("2.jpg", 1);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
FastFeatureDetector Detector(50); //閾值
vector<KeyPoint> keyPoint1, keyPoint2;
Detector.detect(image1, keyPoint1);
Detector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SiftDescriptorExtractor Descriptor;
Mat imageDesc1, imageDesc2;
Descriptor.compute(image1, keyPoint1, imageDesc1);
Descriptor.compute(image2, keyPoint2, imageDesc2);
BruteForceMatcher< L2<float> > matcher;
vector<vector<DMatch> > matchePoints;
vector<DMatch> GoodMatchePoints;
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
// Lowe's algorithm,獲取優(yōu)秀匹配點
for (int i = 0; i < matchePoints.size(); i++)
{
if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance)
{
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
imwrite("first_match.jpg", first_match);
waitKey();
return 0;
}


如果我們把描述子換成SurfDescriptorExtractor,即FastFeatureDetector + SurfDescriptorExtractor的組合,看看效果

可以看出,這種組合下的特征點匹配并不精確。
如果我們把描述子換成SurfDescriptorExtractor,即FastFeatureDetector + BriefDescriptorExtractor 的組合,看看效果

速度雖快,但是精度卻差強人意。
看到這里可能很多人會有疑惑:為什么FAST特征點可以用所以我們借用SiftDescriptorExtractor或者其他描述子提取器進行提取?
在這里我說一下自己的理解。要完成特征點的匹配第一個步驟就是找出每幅圖像的特征點,這叫做特征檢測,比如我們使用FastFeatureDetector、SiftFeatureDetector都是特征檢測的模塊。我們得到這些圖像的特征點后,我們就對這些特征點進行進一步的分析,用一些數(shù)學(xué)上的特征對其進行描述,如梯度直方圖,局部隨機二值特征等。所以在這一步我們可以選擇其他描述子提取器對這些點進行特征描述,進而完成特征點的精確匹配。
在opencv中,SURF,ORB,SIFT既包含F(xiàn)eatureDetector,又包含 DescriptorExtractor,所以我們使用上述三種算法做特征匹配時,都用其自帶的方法配套使用。
除此之外,如果我們相用FAST角點檢測并作特征點匹配該怎么辦?此時可以使用上述的FastFeatureDetector + BriefDescriptorExtractor 的方式,這種組合方式其實就是著名的ORB算法。所以特征點檢測和特征點匹配是兩種不同的步驟,我們只需根據(jù)自己項目的需求對這兩個步驟的方法隨意組合就好。
5.Harris角點
在圖像中搜索有價值的特征點時,使用角點是一種不錯的方法。 角點是很容易在圖像中定位的局部特征, 并且大量存在于人造物體中(例如墻壁、 門、 窗戶、 桌子等產(chǎn)生的角點)。 角點的價值在于它是兩條邊緣線的接合點, 是一種二維特征,可以被精確地定位(即使是子像素級精度)。 與此相反的是位于均勻區(qū)域或物體輪廓上的點以及在同一物體的不同圖像上很難重復(fù)精確定位的點。 Harris特征檢測是檢測角點的經(jīng)典方法。
這里僅展示GoodFeaturesToTrackDetector + SiftDescriptorExtractor的組合方式的代碼,其他組合不再演示。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread("1.jpg", 1);
Mat image02 = imread("2.jpg", 1);
imshow("p2", image01);
imshow("p1", image02);
//灰度圖轉(zhuǎn)換
Mat image1, image2;
cvtColor(image01, image1, CV_RGB2GRAY);
cvtColor(image02, image2, CV_RGB2GRAY);
//提取特征點
GoodFeaturesToTrackDetector Detector(500); //最大點數(shù),值越大,點越多
vector<KeyPoint> keyPoint1, keyPoint2;
Detector.detect(image1, keyPoint1);
Detector.detect(image2, keyPoint2);
//特征點描述,為下邊的特征點匹配做準備
SiftDescriptorExtractor Descriptor;
Mat imageDesc1, imageDesc2;
Descriptor.compute(image1, keyPoint1, imageDesc1);
Descriptor.compute(image2, keyPoint2, imageDesc2);
BruteForceMatcher< L2<float> > matcher;
vector<vector<DMatch> > matchePoints;
vector<DMatch> GoodMatchePoints;
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
// Lowe's algorithm,獲取優(yōu)秀匹配點
for (int i = 0; i < matchePoints.size(); i++)
{
if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance)
{
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
imwrite("first_match.jpg", first_match);
waitKey();
return 0;
}
匹配相當精準


計算機視覺領(lǐng)域其實還有了很多特征檢測的方法,比如HOG、Harr、LBP等,這里就不再敘述了,因為方法都是類似,我們根據(jù)自己的需求挑選相應(yīng)的方法就好了。
到此這篇關(guān)于OpenCV實現(xiàn)特征檢測和特征匹配方法匯總的文章就介紹到這了,更多相關(guān)OpenCV 特征檢測和特征匹配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++深入探索類和對象之封裝及class與struct的區(qū)別
C++?類與對象涉及的知識點非常廣泛,所以我準備寫成幾個特定的部分來作為博文分享,這次的blog將詳細講解類的屬性、行為、訪問權(quán)限,class與struct的區(qū)別以及具體案例,希望能夠?qū)δ銈冇袔椭鉀Q入門小白或者對這方面了解不多的朋友們,那么接下來開始今天的內(nèi)容2022-05-05

