ORB、SIFT、SURF特徵提取與匹配

  • 1、ORB特徵提取與匹配

ORB算法分爲兩部分,分別是特徵點提取和特徵點描述。特徵提取是由FAST(Features from  Accelerated Segment Test)算法發展來的,特徵點描述是根據BRIEF(Binary Robust IndependentElementary Features)特徵描述算法改進的。ORB特徵是將FAST特徵點的檢測方法與BRIEF特徵描述子結合起來,並在它們原來的基礎上做了改進與優化。

對於FAST角點特徵的提取主要分爲:粗提取,機器學習的方法篩選最優特徵點,非極大值抑制去除局部較密集特徵點,特徵點的尺度不變形,特徵點的旋轉不變性5個步驟。

BRIEF 是 Binary Robust Independent Elementary Features 的簡稱,它的作用是根據一組關鍵點創建二元特徵向量。它是在一個特徵點的鄰域內,選擇n對像素點pi、qi(i=1,2,…,n)。然後比較每個點對的灰度值的大小。如果I(pi)> I(qi),則生成二進制串中的1,否則爲0。所有的點對都進行比較,則生成長度爲n的二進制串。一般n取128、256或512,opencv默認爲256。

具體的詳細細節參見:

https://blog.csdn.net/qq_20791919/article/details/80176643

https://www.cnblogs.com/alexme/p/11345701.html

在ORB-SLAM算法中對ORB特徵提取與匹配進行了改進,使得圖像中提取的特徵點分佈更加均勻。主要思想是:設定提取的ORB特徵點數量爲1000個。將圖像進行網格劃分,設置FAST角點的最大閾值爲12,最小爲5。然後使用最大閾值對每一個網格圖像都進行FAST特徵點提取,如果沒有提取到特徵點,則減小閾值再次提取,如若到了最小閾值還沒有提取到特徵點,那麼跳過該網格。在匹配過程中,與傳統的暴力匹配不同,搜索對應網格區域內的ORB特徵點作爲匹配點,提高匹配的正確性。

在ubuntu系統上使用OpenCV3.3.1,對OpenCV中的ORB特徵提取與匹配算法進行測試,,測試代碼(SLAM14講代碼略做修改)爲:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

int main ( int argc, char** argv )
{
    if ( argc != 3 )
    {
        cout<<"usage: feature_extraction img1 img2"<<endl;
        return 1;
    }
    //-- 讀取圖像
    Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );
    Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );

    //-- 初始化,提取1000個ORB特徵點
    std::vector<KeyPoint> keypoints_1, keypoints_2;
    Mat descriptors_1, descriptors_2;
    Ptr<FeatureDetector> detector = ORB::create(1000);
    Ptr<DescriptorExtractor> descriptor = ORB::create(1000);
    // Ptr<FeatureDetector> detector = FeatureDetector::create(detector_name);
    // Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create(descriptor_name);
    Ptr<DescriptorMatcher> matcher  = DescriptorMatcher::create ( "BruteForce-Hamming" );

    //-- 第一步:檢測 Oriented FAST 角點位置
    detector->detect ( img_1,keypoints_1 );
    detector->detect ( img_2,keypoints_2 );

    //-- 第二步:根據角點位置計算 BRIEF 描述子
    descriptor->compute ( img_1, keypoints_1, descriptors_1 );
    descriptor->compute ( img_2, keypoints_2, descriptors_2 );

    Mat outimg1, outimg2;
    drawKeypoints( img_1, keypoints_1, outimg1, Scalar(0, 255, 0), DrawMatchesFlags::DEFAULT );
    drawKeypoints( img_2, keypoints_2, outimg2, Scalar(0, 255, 0), DrawMatchesFlags::DEFAULT );

    imwrite("./result/orb_feature.png", outimg1);

    //-- 第三步:對兩幅圖像中的BRIEF描述子進行匹配,使用 Hamming 距離
    vector<DMatch> matches;
    //BFMatcher matcher ( NORM_HAMMING );
    matcher->match ( descriptors_1, descriptors_2, matches );

    //-- 第四步:匹配點對篩選
    double min_dist=10000, max_dist=0;

    //找出所有匹配之間的最小距離和最大距離, 即是最相似的和最不相似的兩組點之間的距離
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        double dist = matches[i].distance;
        if ( dist < min_dist ) min_dist = dist;
        if ( dist > max_dist ) max_dist = dist;
    }
    
    // 僅供娛樂的寫法
    min_dist = min_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;
    max_dist = max_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;

    printf ( "-- Max dist : %f \n", max_dist );
    printf ( "-- Min dist : %f \n", min_dist );

    //當描述子之間的距離大於兩倍的最小距離時,即認爲匹配有誤.但有時候最小距離會非常小,設置一個經驗值30作爲下限.
    std::vector< DMatch > good_matches;
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        if ( matches[i].distance <= max ( 2*min_dist, 30.0 ) )
        {
            good_matches.push_back ( matches[i] );
        }
    }

    //-- 第五步:繪製匹配結果
    Mat img_match;
    Mat img_goodmatch;
    drawMatches ( outimg1, keypoints_1, outimg2, keypoints_2, matches, img_match, Scalar(0, 255, 255), Scalar(0, 255, 0));
    drawMatches ( outimg1, keypoints_1, outimg2, keypoints_2, good_matches, img_goodmatch, Scalar(0, 255, 255), Scalar(0, 255, 0));

    imwrite("./result/orb_match1.png", img_match);
    imwrite("./result/orb_match2.png", img_goodmatch);

    return 0;
}

CMakeLists可以簡單的寫爲:

cmake_minimum_required( VERSION 2.8 )
project( orb )

set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )

find_package( OpenCV REQUIRED )

include_directories( ${OpenCV_INCLUDE_DIRS} )

add_executable( feature_extraction feature_extraction.cpp  )
target_link_libraries( feature_extraction ${OpenCV_LIBS} )

使用時需要讀入圖片,並對保存結果的路徑進行修改。若在Windows上使用,需要對輸入的參數進行修改。以上代碼的測試結果爲:             

   

可以看出:特徵點的分佈較爲集中,特和那個匹配篩選前誤匹配較多,篩選後結果相對較好。對於均勻分佈ORB特徵提取與匹配的測試代碼爲:https://github.com/zwl2017/ORB_Feature

測試結果爲:

    

2、SIFT和SURF特徵提取與匹配

尺度不變特徵轉換(Scale-invariant feature transform或SIFT)是一種電腦視覺的算法用來偵測與描述影像中的局部性特徵,它在空間尺度中尋找極值點,並提取出其位置、尺度、旋轉不變量,此算法由 David Lowe在1999年所發表,2004年完善總結。Lowe將SIFT算法分解爲如下四步:

尺度空間極值檢測:搜索所有尺度上的圖像位置。通過高斯微分函數來識別潛在的對於尺度和旋轉不變的興趣點。

關鍵點定位:在每個候選的位置上,通過一個擬合精細的模型來確定位置和尺度。關鍵點的選擇依據於它們的穩定程度。

方向確定:基於圖像局部的梯度方向,分配給每個關鍵點位置一個或多個方向。所有後面的對圖像數據的操作都相對於關鍵點的方向、尺度和位置進行變換,從而提供對於這些變換的不變性。

關鍵點描述:在每個關鍵點周圍的鄰域內,在選定的尺度上測量圖像局部的梯度。這些梯度被變換成一種表示,這種表示允許比較大的局部形狀的變形和光照變化

其具體的詳細細節可以參考:https://blog.csdn.net/zddblog/article/details/7521424

可以通過C++代碼自己實現SIFT算法:https://blog.csdn.net/maweifei/article/details/58227605

由於OpenCV3中的SIFT和SURF算法被移植到contrib模塊中,因此爲了簡單起見,使用OpenCV2進行實驗的測試,SIFT代碼如下:

///SIFT特徵點匹配  
#include "opencv2/opencv.hpp"  
#include "opencv2/nonfree/nonfree.hpp"//SIFT相關  
#include "opencv2/legacy/legacy.hpp"//匹配器相關  
#include <iostream>  

using namespace cv;
using namespace std;

int main()
{
	//1.SURF特徵點提取——detect()方法    
	Mat srcImg1 = imread("nn_left.jpg", CV_LOAD_IMAGE_COLOR);
	Mat srcImg2 = imread("nn_right.jpg", CV_LOAD_IMAGE_COLOR);

	double t = getTickCount();//當前滴答數
	Mat dstImg1, dstImg2;
	//定義SIFT特徵檢測類對象    
	SiftFeatureDetector siftDetector;//SiftFeatureDetector是SIFT類的別名    
	//定義KeyPoint變量    
	vector<KeyPoint> keyPoints1;
	vector<KeyPoint> keyPoints2;
	//特徵點檢測    
	siftDetector.detect(srcImg1, keyPoints1);
	siftDetector.detect(srcImg2, keyPoints2);
	//繪製特徵點(關鍵點)    
	drawKeypoints(srcImg1, keyPoints1, dstImg1);
	drawKeypoints(srcImg2, keyPoints2, dstImg2);
	//顯示結果
	resize(dstImg1, dstImg1, Size(dstImg1.cols*0.5, dstImg1.rows*0.5));
	imshow("dstImg1", dstImg1);
	imshow("dstImg2", dstImg2);
	//2.特徵點描述符(特徵向量)提取——compute()方法    
	SiftFeatureDetector descriptor;//siftDescriptorExtractor是SIFT類的別名     
	Mat description1;
	Mat description2;
	descriptor.compute(srcImg1, keyPoints1, description1);
	descriptor.compute(srcImg2, keyPoints2, description2);
	//3.使用Flann匹配器進行匹配——FlannBasedMatcher類的match()方法    
	FlannBasedMatcher matcher;//實例化Flann匹配器  
	vector<DMatch> matches;
	matcher.match(description1, description2, matches);
	//4.對匹配結果進行篩選(依據DMatch結構體中的float類型變量distance進行篩選)    
	float minDistance = 100;
	float maxDistance = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < minDistance)
			minDistance = matches[i].distance;
		if (matches[i].distance > maxDistance)
			maxDistance = matches[i].distance;
	}
	cout << "minDistance: " << minDistance << endl;
	cout << "maxDistance: " << maxDistance << endl;
	vector<DMatch> goodMatches;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < 2 * minDistance)
		{
			goodMatches.push_back(matches[i]);
		}
	}
	//5.繪製匹配結果——drawMatches()    
	Mat dstImg3;
	Mat dstImg4;
	drawMatches(srcImg1, keyPoints1, srcImg2, keyPoints2, goodMatches, dstImg3);
	resize(dstImg3, dstImg4, Size(dstImg3.cols*0.5, dstImg3.rows*0.5));

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "算法用時:" << t << "秒" << endl;
	imshow("dstImg4", dstImg4);
	waitKey(0);
	return 0;
}

SURF的代碼與SIFT類似,僅僅是實例化的類不一樣,代碼如下:

///SURF特徵點匹配  
#include "opencv2/opencv.hpp"  
#include "opencv2/nonfree/nonfree.hpp"//SURF相關  
#include "opencv2/legacy/legacy.hpp"//匹配器相關  
#include <iostream>  

using namespace cv;
using namespace std;

int main()
{
	//1.SURF特徵點提取——detect()方法    
	Mat srcImg1 = imread("nn_left.jpg", CV_LOAD_IMAGE_COLOR);
	Mat srcImg2 = imread("nn_right.jpg", CV_LOAD_IMAGE_COLOR);

	double t = getTickCount();//當前滴答數 
	Mat dstImg1, dstImg2;
	//定義SURF特徵檢測類對象    
	SurfFeatureDetector surfDetector;//SurfFeatureDetector是SURF類的別名    
	//定義KeyPoint變量    
	vector<KeyPoint> keyPoints1;
	vector<KeyPoint> keyPoints2;
	//特徵點檢測    
	surfDetector.detect(srcImg1, keyPoints1);
	surfDetector.detect(srcImg2, keyPoints2);
	//繪製特徵點(關鍵點)    
	drawKeypoints(srcImg1, keyPoints1, dstImg1);
	drawKeypoints(srcImg2, keyPoints2, dstImg2);
	//顯示結果    
	resize(dstImg1, dstImg1, Size(dstImg1.cols*0.5, dstImg1.rows*0.5));
	imshow("dstImg1", dstImg1);
	imshow("dstImg2", dstImg2);
	//2.特徵點描述符(特徵向量)提取——compute()方法    
	SurfDescriptorExtractor descriptor;//SurfDescriptorExtractor是SURF類的別名     
	Mat description1;
	Mat description2;
	descriptor.compute(srcImg1, keyPoints1, description1);
	descriptor.compute(srcImg2, keyPoints2, description2);
	//3.使用Flann匹配器進行匹配——FlannBasedMatcher類的match()方法    
	FlannBasedMatcher matcher;//實例化Flann匹配器  
	vector<DMatch> matches;
	matcher.match(description1, description2, matches);
	//4.對匹配結果進行篩選(依據DMatch結構體中的float類型變量distance進行篩選)    
	float minDistance = 100;
	float maxDistance = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < minDistance)
			minDistance = matches[i].distance;
		if (matches[i].distance > maxDistance)
			maxDistance = matches[i].distance;
	}
	cout << "minDistance: " << minDistance << endl;
	cout << "maxDistance: " << maxDistance << endl;
	vector<DMatch> goodMatches;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < 2 * minDistance)
		{
			goodMatches.push_back(matches[i]);
		}
	}
	//5.繪製匹配結果——drawMatches()    
	Mat dstImg3;
	drawMatches(srcImg1, keyPoints1, srcImg2, keyPoints2, goodMatches, dstImg3);
	resize(dstImg3, dstImg3, Size(dstImg3.cols*0.5, dstImg3.rows*0.5));

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "算法用時:" << t << "秒" << endl;

	imshow("dstImg3", dstImg3);
	waitKey(0);
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章