OpenCV特征點檢測算法對比


識別算法概述:

SIFT/SURF基於灰度圖,

一、首先建立圖像金字塔,形成三維的圖像空間,通過Hessian矩陣獲取每一層的局部極大值,然后進行在極值點周圍26個點進行NMS,從而得到粗略的特征點,再使用二次插值法得到精確特征點所在的層(尺度),即完成了尺度不變。

 

二、在特征點選取一個與尺度相應的鄰域,求出主方向,其中SIFT采用在一個正方形鄰域內統計所有點的梯度方向,找到占80%以上的方向作為主方向;而SURF則選擇圓形鄰域,並且使用活動扇形的方法求出特征點主方向,以主方向對齊即完成旋轉不變。

 

三、以主方向為軸可以在每個特征點建立坐標,SIFT在特征點選擇一塊大小與尺度相應的方形區域,分成16塊,統計每一塊沿着八個方向占的比例,於是特征點形成了128維特征向量,對圖像進行歸一化則完成強度不變;而SURF分成64塊,統計每一塊的dx,dy,|dx|,|dy|的累積和,同樣形成128維向量,再進行歸一化則完成了對比度不變與強度不變。

 

haar特征也是基於灰度圖,

首先通過大量的具有比較明顯的haar特征(矩形)的物體圖像用模式識別的方法訓練出分類器,分類器是個級聯的,每級都以大概相同的識別率保留進入下一級的具有物體特征的候選物體,而每一級的子分類器則由許多haar特征構成(由積分圖像計算得到,並保存下位置),有水平的、豎直的、傾斜的,並且每個特征帶一個閾值和兩個分支值,每級子分類器帶一個總的閾值。識別物體的時候,同樣計算積分圖像為后面計算haar特征做准備,然后采用與訓練的時候有物體的窗口同樣大小的窗口遍歷整幅圖像,以后逐漸放大窗口,同樣做遍歷搜索物體;每當窗口移動到一個位置,即計算該窗口內的haar特征,加權后與分類器中haar特征的閾值比較從而選擇左或者右分支值,累加一個級的分支值與相應級的閾值比較,大於該閾值才可以通過進入下一輪篩選。當通過分類器所以級的時候說明這個物體以大概率被識別。

 

廣義hough變換同樣基於灰度圖,

使用輪廓作為特征,融合了梯度信息,以投票的方式識別物體,在本blog的另一篇文章中有詳細討論,這里不再贅述。

特點異同對比及其適用場合:

 

三種算法都只是基於強度(灰度)信息,都是特征方法,但SIFT/SURF的特征是一種具有強烈方向性及亮度性的特征,這使得它適用於剛性形變,稍有透視形變的場合

haar特征識別方法帶有一點人工智能的意味,對於像人臉這種有明顯的、穩定結構的haar特征的物體最適用,只要結構相對固定即使發生扭曲等非線性形變依然可識別;

廣義hough變換完全是精確的匹配,可得到物體的位置方向等參數信息。前兩種方法基本都是通過先獲取局部特征然后再逐個匹配,只是局部特征的計算方法不同,SIFT/SURF比較復雜也相對穩定,haar方法比較簡單,偏向一種統計的方法形成特征,這也使其具有一定的模糊彈性;

廣義hough變換則是一種全局的特征——輪廓梯度,但也可以看做整個輪廓的每一個點的位置和梯度都是特征,每個點都對識別有貢獻,用直觀的投票,看票數多少去確定是否識別出物體。

 

SIFT/SURF算法的深入剖析——談SIFT的精妙與不足

SURF算法是SIFT算法的加速版,opencv的SURF算法在適中的條件下完成兩幅圖像中物體的匹配基本實現了實時處理,其快速的基礎實際上只有一個——積分圖像haar求導,對於它們其他方面的不同可以參考本blog的另外一篇關於SIFT的文章。

 

    不論科研還是應用上都希望可以和人類的視覺一樣通過程序自動找出兩幅圖像里面相同的景物,並且建立它們之間的對應,前幾年才被提出的SIFT(尺度不變特征)算法提供了一種解決方法,通過這個算法可以使得滿足一定條件下兩幅圖像中相同景物的某些點(后面提到的關鍵點)可以匹配起來,為什么不是每一點都匹配呢?下面的論述將會提到。

 

     SIFT算法實現物體識別主要有三大工序,

1、提取關鍵點;

2、對關鍵點附加詳細的信息(局部特征)也就是所謂的描述器;

3、通過兩方特征點(附帶上特征向量的關鍵點)的兩兩比較找出相互匹配的若干對特征點,也就建立了景物間的對應關系。

 

      日常的應用中,多數情況是給出一幅包含物體的參考圖像,然后在另外一幅同樣含有該物體的圖像中實現它們的匹配。兩幅圖像中的物體一般只是旋轉和縮放的關系,加上圖像的亮度及對比度的不同,這些就是最常見的情形。基於這些條件下要實現物體之間的匹配,SIFT算法的先驅及其發明者想到只要找到多於三對物體間的匹配點就可以通過射影幾何的理論建立它們的一一對應。首先在形狀上物體既有旋轉又有縮小放大的變化,如何找到這樣的對應點呢?於是他們的想法是首先找到圖像中的一些“穩定點”,這些點是一些十分突出的點不會因光照條件的改變而消失,比如角點、邊緣點、暗區域的亮點以及亮區域的暗點,既然兩幅圖像中有相同的景物,那么使用某種方法分別提取各自的穩定點,這些點之間會有相互對應的匹配點,正是基於這樣合理的假設,SIFT算法的基礎是穩定點。

 

SIFT算法找穩定點的方法是找灰度圖的局部最值,由於數字圖像是離散的,想求導和求最值這些操作都是使用濾波器,而濾波器是有尺寸大小的,使用同一尺寸的濾波器對兩幅包含有不同尺寸的同一物體的圖像求局部最值將有可能出現一方求得最值而另一方卻沒有的情況,但是容易知道假如物體的尺寸都一致的話它們的局部最值將會相同。SIFT的精妙之處在於采用圖像金字塔的方法解決這一問題,我們可以把兩幅圖像想象成是連續的,分別以它們作為底面作四棱錐,就像金字塔,那么每一個截面與原圖像相似,那么兩個金字塔中必然會有包含大小一致的物體的無窮個截面,但應用只能是離散的,所以我們只能構造有限層,層數越多當然越好,但處理時間會相應增加,層數太少不行,因為向下采樣的截面中可能找不到尺寸大小一致的兩個物體的圖像。有了圖像金字塔就可以對每一層求出局部最值,但是這樣的穩定點數目將會十分可觀,所以需要使用某種方法抑制去除一部分點,但又使得同一尺度下的穩定點得以保存。有了穩定點之后如何去讓程序明白它們之間是物體的同一位置?研究者想到以該點為中心挖出一小塊區域,然后找出區域內的某些特征,讓這些特征附件在穩定點上,SIFT的又一個精妙之處在於穩定點附加上特征向量之后就像一個根系發達的樹根一樣牢牢的抓住它的“土地”,使之成為更穩固的特征點,但是問題又來了,遇到旋轉的情況怎么辦?發明者的解決方法是找一個“主方向”然后以它看齊,就可以知道兩個物體的旋轉夾角了。下面就討論一下SIFT算法的缺陷。

 

      SIFT/SURT采用henssian矩陣獲取圖像局部最值還是十分穩定的,但是在求主方向階段太過於依賴局部區域像素的梯度方向,有可能使得找到的主方向不准確,后面的特征向量提取以及匹配都嚴重依賴於主方向,即使不大偏差角度也可以造成后面特征匹配的放大誤差,從而匹配不成功;另外圖像金字塔的層取得不足夠緊密也會使得尺度有誤差,后面的特征向量提取同樣依賴相應的尺度,發明者在這個問題上的折中解決方法是取適量的層然后進行插值。SIFT是一種只利用到灰度性質的算法,忽略了色彩信息,后面又出現了幾種據說比SIFT更穩定的描述器其中一些利用到了色彩信息,讓我們拭目以待。

 

      最后要提一下,我們知道同樣的景物在不同的照片中可能出現不同的形狀、大小、角度、亮度,甚至扭曲;計算機視覺的知識表明通過光學鏡頭獲取的圖像,對於平面形狀的兩個物體它們之間可以建立射影對應,對於像人臉這種曲面物體在不同角度距離不同相機參數下獲取的兩幅圖像,它們之間不是一個線性對應關系,就是說我們即使獲得兩張圖像中的臉上若干匹配好的點對,還是無法從中推導出其他點的對應。

 

 

opencv源碼解析之(3):特征點檢查前言1

     因為最近准備看特征點檢查方面的源碼,而其中最著名的算法就是sift和surf。因此這次主要是學會怎樣使用opencv中的sift和surf函數來檢測特征點和描述特征點,以及怎樣使用其算法來進行特征點匹配。慶幸的是,sift算法雖然是專利,但是在opencv的努力下也獲得了作者的允許,將其加入了新版本的opencv中了。

使用環境:opencv2.3.1+vs2010

功能:找出2幅圖中特征點,並將其描述出來,且在2幅中進行匹配。2幅圖內容相同,但是經過了曝光,旋轉,縮放處理過。

首先來看sift算法函數的使用。

工程代碼:

copycode

// sift_test.cpp : 定義控制台應用程序的入口點。

#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"//因為在屬性中已經配置了opencv等目錄,所以把其當成了本地目錄一樣
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"

usingnamespace cv;
usingnamespace std;

void readme();

int main(int argc,char* argv[])
{
    Mat img_1=imread("./image/query.png",CV_LOAD_IMAGE_GRAYSCALE);//宏定義時CV_LOAD_IMAGE_GRAYSCALE=0,也就是讀取灰度圖像
    Mat img_2=imread("./image/rs_query.png",CV_LOAD_IMAGE_GRAYSCALE);//一定要記得這里路徑的斜線方向,這與Matlab里面是相反的

if(!img_1.data || !img_2.data)//如果數據為空
    {
        cout<<"opencv error"<<endl;
return -1;
    }
    cout<<"open right"<<endl;

//第一步,用SIFT算子檢測關鍵點

    SiftFeatureDetector detector;//構造函數采用內部默認的
    std::vector<KeyPoint> keypoints_1,keypoints_2;//構造2個專門由點組成的點向量用來存儲特征點

    detector.detect(img_1,keypoints_1);//將img_1圖像中檢測到的特征點存儲起來放在keypoints_1中
    detector.detect(img_2,keypoints_2);//同理

//在圖像中畫出特征點
    Mat img_keypoints_1,img_keypoints_2;

    drawKeypoints(img_1,keypoints_1,img_keypoints_1,Scalar::all(-1),DrawMatchesFlags::DEFAULT);//在內存中畫出特征點
    drawKeypoints(img_2,keypoints_2,img_keypoints_2,Scalar::all(-1),DrawMatchesFlags::DEFAULT);

    imshow("sift_keypoints_1",img_keypoints_1);//顯示特征點
    imshow("sift_keypoints_2",img_keypoints_2);

//計算特征向量
    SiftDescriptorExtractor extractor;//定義描述子對象

    Mat descriptors_1,descriptors_2;//存放特征向量的矩陣

    extractor.compute(img_1,keypoints_1,descriptors_1);//計算特征向量
    extractor.compute(img_2,keypoints_2,descriptors_2);

//用burte force進行匹配特征向量
    BruteForceMatcher<L2<float>>matcher;//定義一個burte force matcher對象
    vector<DMatch>matches;
    matcher.match(descriptors_1,descriptors_2,matches);

//繪制匹配線段
    Mat img_matches;
    drawMatches(img_1,keypoints_1,img_2,keypoints_2,matches,img_matches);//將匹配出來的結果放入內存img_matches中

//顯示匹配線段
    imshow("sift_Matches",img_matches);//顯示的標題為Matches
    waitKey(0);
return0;
}

copycode[1]

運行結果如下:

030810402230

030810404164

030810413932

下面看surf算法函數的使用(和sift基本一樣,就是函數名改了下而已):

工程代碼:

copycode[2]

// surf_test.cpp : 定義控制台應用程序的入口點。
//

#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"//因為在屬性中已經配置了opencv等目錄,所以把其當成了本地目錄一樣
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"

usingnamespace cv;
usingnamespace std;

void readme();

int main(int argc,char* argv[])
{
    Mat img_1=imread("./image/query.png",CV_LOAD_IMAGE_GRAYSCALE);//宏定義時CV_LOAD_IMAGE_GRAYSCALE=0,也就是讀取灰度圖像
    Mat img_2=imread("./image/rs_query.png",CV_LOAD_IMAGE_GRAYSCALE);//一定要記得這里路徑的斜線方向,這與Matlab里面是相反的

if(!img_1.data || !img_2.data)//如果數據為空
    {
        cout<<"opencv error"<<endl;
return -1;
    }
    cout<<"open right"<<endl;

//第一步,用SURF算子檢測關鍵點
int minHessian=400;

    SurfFeatureDetector detector(minHessian);
    std::vector<KeyPoint> keypoints_1,keypoints_2;//構造2個專門由點組成的點向量用來存儲特征點

    detector.detect(img_1,keypoints_1);//將img_1圖像中檢測到的特征點存儲起來放在keypoints_1中
    detector.detect(img_2,keypoints_2);//同理

//在圖像中畫出特征點
    Mat img_keypoints_1,img_keypoints_2;

    drawKeypoints(img_1,keypoints_1,img_keypoints_1,Scalar::all(-1),DrawMatchesFlags::DEFAULT);
    drawKeypoints(img_2,keypoints_2,img_keypoints_2,Scalar::all(-1),DrawMatchesFlags::DEFAULT);

    imshow("surf_keypoints_1",img_keypoints_1);
    imshow("surf_keypoints_2",img_keypoints_2);

//計算特征向量
    SurfDescriptorExtractor extractor;//定義描述子對象

    Mat descriptors_1,descriptors_2;//存放特征向量的矩陣

    extractor.compute(img_1,keypoints_1,descriptors_1);
    extractor.compute(img_2,keypoints_2,descriptors_2);

//用burte force進行匹配特征向量
    BruteForceMatcher<L2<float>>matcher;//定義一個burte force matcher對象
    vector<DMatch>matches;
    matcher.match(descriptors_1,descriptors_2,matches);

//繪制匹配線段
    Mat img_matches;
    drawMatches(img_1,keypoints_1,img_2,keypoints_2,matches,img_matches);//將匹配出來的結果放入內存img_matches中

//顯示匹配線段
    imshow("surf_Matches",img_matches);//顯示的標題為Matches
    waitKey(0);
return0;
}

copycode[3]

其運行結果如下:

030810435391

030810441379

030810451287

      從這個實驗可以知道,在opencv中使用這2個算法是多么的簡單!只需要簡單的幾個參數就可以達到很好的效果。但這只是opencv的低級應用,我們應該在最好能用opencv一些內部函數來幫助實現自己的算法和想法。這就是分析opencv源碼的主要目的。

 

 

 

     另外,從實驗的過程中可以感覺出來surf算法的運行時間比sift快很多,且特征點的數目檢測得比較多,其它的暫時還沒區別出來。歡迎交流,謝謝!

作者:tornadomeet 出處:http://www.cnblogs.com/tornadomeet 歡迎轉載或分享,但請務必聲明文章出處。 (新浪微博:tornadomeet,歡迎交流!)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM