1. 程式人生 > >opencv之SURF演算法原理及關鍵點檢測

opencv之SURF演算法原理及關鍵點檢測

1.概述

在基礎篇裡面講模板匹配的時候已經介紹過,影象匹配主要有基於灰度和基於特徵兩種方法。基於特徵匹配的方法有很多種如:FAST、HARRIS、SIFT、SURF、SUSAN等。其中SIFT演算法由D.G.Lowe於1999年提出,2004年完善總結。SIFT是一種魯棒性好的尺度不變特徵描述方法,但SIFT演算法計算資料量大、時間複雜度高、演算法耗時長。針對上述缺點許多研究者對SIFT演算法做了不同的改進,Yanke等人提出用PCA-SIFT方法對特徵描述進行資料降維,但在沒有任何先驗知識的情況下反而增加了計算量;Delpont等人提出用SVD方法進行特徵匹配,但匹配過程計算複雜,且不能用於寬基線匹配;Grabner等人用積分影象雖提高了SIFT的計算速度,但是降低了SIFT方法的優越性。
Herbert Bay等人於2006年提出了SIFT演算法的改進演算法SURF演算法,其效能超過了SIFT演算法且能夠獲得更快的速度。SURF在光照變化和視角變化不變性方面的效能接近SIFT演算法,尤其對影象嚴重模糊和旋轉處理得非常好。且標準的SURF運算元比SIFT運算元快好幾倍,SURF演算法最大的特徵在於採用了harr特徵以及積分影象的概念,這大大加快了程式的執行速度。
SURF演算法和SIFT演算法在opencv中是一種很高階的演算法,opencv提供了SURF演算法的API介面。需要說明的是SURF和SIFT演算法在OpenCV提供的nonfree中,而且在OpenCV3.x後不再整合到OpenCV發行版中,同意存放到opencv_contrib中,需要去opencv github頁面手動下載編譯。

點選跳轉,編譯方法網上有很多教程。

2.Surf原理

特徵點的提取基於尺度空間理論,我們通過檢測影象區域性極值點來鎖定特徵點座標,即區域性的最亮點或最暗點。

2.1構建Hessian矩陣

SURF演算法檢測特徵點是基於Hessian矩陣實現的,Hessian矩陣是SURF演算法的核心。設f(x,y)為二階可微函式,Hessian矩陣H是函式、偏導陣列成如下:
這裡寫圖片描述
Hessian矩陣判別式為:
這裡寫圖片描述
判別式的值是H矩陣的特徵值,可以利用判別式的符號確定是否是極值,若det⁡(H)<0則可判斷(x, y)不是區域性極值點,若det⁡(H)>0則可判斷點(x, y)為區域性極值點。
將上述方法應用到影象中,給出影象中的一個點,其畫素可表示為I(x, y),在尺度為σ其Hessian矩陣定義如下:
這裡寫圖片描述


式中:L_xx是高斯濾波二階導這裡寫圖片描述同I=(x,y)卷積的結果,其中這裡寫圖片描述L_xy、L_yy的含義類似。
空間尺度理論中高斯是最優化的分析方法。Bay等人指出高斯分析需要對影象進行離散化和裁剪,即使使用高斯濾波對影象進行取樣也會出現走樣的情況。所以可以使用方框濾波來代替高斯濾波,使用積分影象來加快卷積以提高計算速度。
在原始影象上,使用方框濾波器的效果反映在掩膜版尺寸上。如圖所示為9×9方框濾波掩膜版,其中灰色部分掩膜版值為0.對應二階高斯濾波係數σ=1.2,方框濾波模板同影象卷積運算後的值記分別記為D_xx,D_yy,D_xy。
這裡寫圖片描述
為平衡準確值與近似值間的誤差引入權值,權值隨尺度變化,H矩陣判別式可以表示為:
這裡寫圖片描述

由於高斯濾波與近似高斯濾波的差異性,我們用根據公式這裡寫圖片描述計算濾波響應的相對權重係數進一步平衡Hessian矩陣行列式的相對權重,其中|��|��是Frobenius範數,這樣就保證了Frobenius範數能夠適用於任何尺寸的濾波器模板。在實際應用中,用常量0.9表示其相對權重係數不會對結果產生較大的影響。

2.2尺度不變性

為了使影象具有尺度不變性以適應不同的影象中目標尺度的變化,我們需要構建尺度空間進行SURF特徵點的提取。影象金字塔是影象多尺度表達的一種方式,為了獲取影象在不同尺度下通過Hessian矩陣判別式得到極值點,用類似SIFT的方法構建尺度影象金字塔,將尺度空間分為若干階(octave),每一階儲存了不同尺寸的方框濾波對輸入影象進行濾波後得到的模糊程度不同的圖片。但SURF演算法中圖片大小是一直不變的,只是不同階中方框濾波模板大小不相同。在每一階中選擇4層的尺度影象,構建引數如圖所示:
這裡寫圖片描述
灰色底的數字表示方框濾波模板的大小。如果影象尺寸遠大於模板大小,還可以繼續增加階數。若模板尺寸為N×N,則該模板對應的尺度為σ=1.2×9/N。通過Hessian矩陣求出個尺度極值後,在3×3×3的立體鄰域內進行非極大值比較,若該極值點仍為最大值或最小值,則該極值點為候選特徵點,然後在尺度空間和影象空間中進行插值運算,得到穩定的特徵點位置及所在的尺度值。

2.3特徵點主方向選取

SIFT演算法選取特徵點主方向是採用在特徵點領域內統計其梯度直方圖,取直方圖bin值最大的以及超過最大bin值80%的那些方向作為特徵點的主方向。而SURF演算法是通過統計特徵點領域內的Haar小波特徵確定其主方向。
這裡寫圖片描述
為保證旋轉不變性,以特徵點為中心,計算特徵點鄰域(如半徑為6s的圓,s為該點所在尺度)內的點在x,y方向的Haar小波響應,Haar小波邊長取4s,這樣一個扇形得到了一個值。然後60°扇形以一定間隔進行旋轉,將60°範圍內的響應相加以形成新的向量,遍歷整個圓形區域,選擇最長向量的方向為該特徵點的主方向。

2.4特徵點描述運算元

SURF演算法中,以特徵點為中心,將座標軸旋轉到主方向以確保旋轉不變性。按照主方向選取邊長為20s的正方形區域,然後將該區域劃分為4×4的子區域,每個子區域計算5s×5s範圍內的小波響應。
這裡寫圖片描述
相對於主方向的水平、垂直方向的Haar小波響應分別記做d_x 、d_y,同樣賦予響應值以權值係數,以增加對集合變換的魯棒性;之後將每個子區域的響應及其絕對值相加形成這裡寫圖片描述,這樣在每個子區域形成四維分量的向量這裡寫圖片描述,因此對每一個特徵點,則形成64維的描述向量,再進行向量的歸一化,從而對光照具有一定的魯棒性。

3.OpenCV API

在opencv中可以看到如下定義

typedef SURF    cv::xfeatures2d::SurfDescriptorExtractor

typedef SURF    cv::xfeatures2d::SurfFeatureDetector

也就是說在我們實際使用中是根據不同的功能分別呼叫SurfDescriptorExtractor和SurfFeatureDetector兩個函式的,同樣SIFT演算法也有類似定義如下:

typedef SIFT    cv::xfeatures2d::SiftDescriptorExtractor

typedef SIFT    cv::xfeatures2d::SiftFeatureDetector

有需要使用SIFT演算法的同學可以參考下。重點說一下SURF演算法的兩個函式。
SURF演算法作為一個大類,其繼承關係可參照下圖:
這裡寫圖片描述
其成員函式有很多,如下:

virtual bool    getExtended () const =0

virtual double  getHessianThreshold () const =0

virtual int     getNOctaveLayers () const =0

virtual int     getNOctaves () const =0

virtual bool    getUpright () const =0

virtual void    setExtended (bool extended)=0

virtual void    setHessianThreshold (double hessianThreshold)=0

virtual void    setNOctaveLayers (int nOctaveLayers)=0

virtual void    setNOctaves (int nOctaves)=0

virtual void    setUpright (bool upright)=0

函式詳細含義可以查詢OpenCV文件得知。這裡介紹一下特徵點的檢測。
利用SURF演算法進行特徵點檢測可以使用SurfFeatureDetector及它的子函式detect(位於其父類Feature2D中)來實現檢測過程,使用drawKeypoints函式繪製檢測到的關鍵點。
drawKeypoints

void cv::drawKeypoints  (   InputArray  image,
                            const std::vector< KeyPoint > &     keypoints,
                            InputOutputArray    outImage,
                            const Scalar &  color = Scalar::all(-1),
                            int     flags = DrawMatchesFlags::DEFAULT 
                        )

image:輸入影象
**keyPoint:**SURF演算法檢測到的特徵點
outImage:輸出影象,其內容取決於第五個引數識別符號
color:繪製特徵點顏色,有預設值Scalar::all(-1)
flags:繪製特徵點的特徵識別符號,有預設值,有如下方式:

enum    { 
            DEFAULT = 0, //建立輸出影象矩陣,使用現存的輸出影象繪製匹配對和特徵點,對每一個關鍵點只繪製中間點
            DRAW_OVER_OUTIMG = 1,//不建立輸出影象矩陣,而是在輸出影象上繪製匹配對 
            NOT_DRAW_SINGLE_POINTS = 2, //單點特徵點不被繪製 
            DRAW_RICH_KEYPOINTS = 4 //對每一個特徵點繪製帶大小和方向的關鍵點圖形。
        }

這裡有必要說一下KeyPoint()類,是一個為特徵點檢測而形成的資料結構,用於表示特徵點,其類結構如下:

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

_pt:特徵點座標
_size特徵點直徑
_angle:特徵點方向,範圍為[0,360),負值表示不使用
_response:關鍵點檢測器對於關鍵點的響應程度,也就是關鍵點強度
_octave:特徵點所在金字塔的層
_class_id:用於聚類的id
函式還有另外一種定義形式如下:

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

4.示例程式碼

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
    Mat srcImage = imread("Surf_test_image.jpg");

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

    namedWindow("原影象",WINDOW_AUTOSIZE);
    imshow("原影象",srcImage);

    Mat imageMid;   //定義濾波後圖像
    //GaussianBlur(srcImage,imageMid,Size(9, 9),0,0);       //kernel尺寸為3x3的高斯濾波
    //namedWindow("高斯濾波後圖像",WINDOW_AUTOSIZE);
    //imshow("高斯濾波後圖像",imageMid);

    int minHessian = 700;                           //定義Hessian矩陣閾值特徵點檢測運算元
    SurfFeatureDetector detector(minHessian);       //定義SURF檢測器

    vector<KeyPoint> keypoints;                     //定義KeyPoint型別的向量容器vector儲存檢測到的特徵點
    detector.detect(srcImage,keypoints);            //呼叫detect檢測特徵點

    //繪製檢測到的特徵點
    Mat dstImage;
    //drawKeypoints(imageMid,keypoints,dstImage,Scalar::all(-1),DrawMatchesFlags::DEFAULT); //高斯濾波後關鍵點檢測
    drawKeypoints(srcImage, keypoints, dstImage, Scalar::all(-1), DrawMatchesFlags::DEFAULT);

    namedWindow("特徵點檢測",WINDOW_AUTOSIZE);
    imshow("特徵點檢測",dstImage);


    waitKey(0);

    return 0;
}

執行結果這裡寫圖片描述