- 共同點:
SIFT/SURF爲了實現不同圖像中相同場景的匹配,主要包括三個步驟:
1、尺度空間的建立;
2、特徵點的提取;
OpenCV中:FeatureDetector,具體實現類有
class FastFeatureDetector (角點檢測)
class GoodFeaturesToTrackDetector
class MserFeatureDetector(最大穩定區域極值提取)
class StarFeatureDetector
class SiftFeatureDetector
class SurfFeatureDetector
3、利用特徵點周圍鄰域的信息生成特徵描述子;DescriptorExtractor,具體實現類
class SiftDescriptorExtractor
class SurfDescriptorExtractor
class CalonderDescriptorExtractor
class BriefDescriptorExtractor
class OpponentColorDescriptorExtractor
4、特徵點匹配。DescriptorMatcher, DMatch ,具體實現類
class BruteForceMatcher(暴力匹配方法)
class FlannBasedMatcher
可參照:http://blog.csdn.net/wanglang3081/article/details/17791121http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/features2d/doc/features2d.html
如果兩幅圖像中的物體一般只是旋轉和縮放的關係,加上圖像的亮度及對比度的不同,要在這些條件下要實現物體之間的匹配,SIFT算法的先驅及其發明者想到只要找到多於三對物體間的匹配點就可以通過射影幾何的理論建立它們的一一對應。
如何找到這樣的匹配點呢?SIFT/SURF作者的想法是首先找到圖像中的一些“穩定點”,這些點是一些特殊的點,不會因爲視角的改變、光照的變化、噪音的干擾而消失,比如角點、邊緣點、暗區域的亮點以及亮區域的暗點。這樣如果兩幅圖像中有相同的景物,那麼這些穩定點就會在兩幅圖像的相同景物上同時出現,這樣就能實現匹配。因此,SIFT/SURF算法的基礎是穩定點。
SIFT/SURF提取的穩定點,首先都要求是局部極值。但是,當兩個物體的大小比例不一樣時,大圖像的局部極值點在小圖像的對應位置上有可能不是極值點。於是SIFT/SURF都採用圖像金字塔的方法,每一個截面與原圖像相似,這樣兩個金字塔中就有可能包含大小最近似的兩個截面了。
這樣找到的特徵點會比較多,經過一些處理後濾掉一些相對不穩定的點。
接下來如何去匹配相同物體上對應的點呢?SIFT/SURF的作者都想到以特徵點爲中心,在周圍鄰域內統計特徵,將特徵附加到穩定點上,生成特徵描述子。在遇到旋轉的情況下,作者們都決定找出一個主方向,然後以這個方向爲參考座標進行後面的特徵統計,就解決了旋轉的問題。
- 共同的大問題有以下幾個:
1、爲什麼選用高斯金字塔來作特徵提取?
爲什麼是DOG的金字塔?因爲它接近LOG,而LOG的極值點提供了最穩定的特徵,而且DOG方便計算(只要做減法。)
爲什麼LOG的極值點提供的特徵最穩定。
直觀理解:特徵明顯的點經過不同尺度的高斯濾波器進行濾波後,差別較大,所以用到的是DOG。
2、Hessian矩陣爲什麼能用來篩選極值點?
SIFT先利用非極大抑制,再用到Hessian矩陣進行濾除。SURF先用Hessian矩陣,再進行非極大抑制。SURF的順序可以加快篩選速度麼?(Hessian矩陣濾除的點更多?)
至於SURF先用Hessian矩陣,再進行非極大抑制的原因,是不管先極大值抑制還是判斷Hessian矩陣的行列式,金字塔上的點的行列式都是要計算出來的。先判斷是否大於0只要進行1次判斷,而判斷是否是極大值點或者極小值點要與周圍26個點比較,只比較1次肯定快。
而在SIFT中,構建的高斯金字塔只有一座(不想SURF是有3座),要進行非極大抑制可以直接用金字塔的結果進行比較。而如果計算Hessian矩陣的行列式,還要再計算Dxx、Dxy、Dyy。因此先進行非極大抑制。這兩個步驟的先後與SIFT/SURF的實際計算情況有關的,都是當前算法下的最佳順序,而不是說哪種先計算一定更好。3、爲什麼採用梯度特徵作爲局部不變特徵?
這與人的視覺神經相關。採用梯度作爲描述子的原因是,人的視覺皮層上的神經元對特定方向和空間頻率的梯度相應很敏感,經過SIFT作者的一些實驗驗證,用梯度的方法進行匹配效果很好。
4、爲什麼可以採用某些特徵點的局部不變特徵進行整幅圖像的匹配?
參考: http://apps.hi.baidu.com/share/detail/32318290
從直觀的人類視覺印象來看,人類視覺對物體的描述也是局部化的,基於局部不變特徵的圖像識別方法十分接近於人類視覺機理,通過局部化的特徵組合,形成對目標物體的整體印象,這就爲局部不變特徵提取方法提供了生物學上的解釋,因此局部不變特徵也得到了廣泛應用。
還有:
圖像中的每個局部區域的重要性和影響範圍並非同等重要,即特徵不是同等顯著的,其主要理論來源是Marr的計算機視覺理論和Treisman的特徵整合理論,一般也稱爲“原子論”。該理論認爲視覺的過程開始於對物體的特徵性質和簡單組成部分的分析,是從局部性質到大範圍性質。
SIFT/SURF都是對特徵點的局部區域的描述,這些特徵點應該是影響重要的點,對這些點的分析更加重要。所以在局部不變特徵的提取和描述時也遵循與人眼視覺注意選擇原理相類似的機制,所以SIFT/SURF用於匹配有效果。
- 不同點的比較:
從博客上看到一個總結,我修改了一些內容。大家可以參看以下鏈接:
http://blog.csdn.net/ijuliet/archive/2009/10/07/4640624.aspx
|
SIFT |
SURF |
尺度空間 |
DOG與不同尺度的圖片卷積 |
不同尺度的box filters與原圖片卷積 |
特徵點檢測 |
先進行非極大抑制,再去除低對比度的點。再通過Hessian矩陣去除邊緣的點 |
先利用Hessian矩陣確定候選點,然後進行非極大抑制 |
方向 |
在正方形區域內統計梯度的幅值的直方圖,找max對應的方向。可以有多個方向。 |
在圓形區域內,計算各個扇形範圍內x、y方向的haar小波響應,找模最大的扇形方向 |
特徵描述子 |
16*16的採樣點劃分爲4*4的區域,計算每個區域的採樣點的梯度方向和幅值,統計成8bin直方圖,一共4*4*8=128維 (2013.5.9 note: 不一定要是16 × 16,區域也可以不用是 4 × 4) |
20*20s的區域劃分爲4*4的子區域,每個子區域找5*5個採樣點,計算採樣點的haar小波響應,記錄∑dx,∑dy,∑|dx|,∑|dy|,一共4*4*4=64維 |
SURF—金字塔僅僅是用來做特徵點的檢測。在計算描述子的時候,haar小波響應是計算在原圖像(利用積分圖)。而SIFT是計算在高斯金字塔上(注意不是高斯差分金字塔。)
- 性能的比較:
論文:A comparison of SIFT, PCA-SIFT and SURF 對三種方法給出了性能上的比較,源圖片來源於Graffiti dataset,對原圖像進行尺度、旋轉、模糊、亮度變化、仿射變換等變化後,再與原圖像進行匹配,統計匹配的效果。效果以可重複出現性爲評價指標。
比較的結果如下:
method |
Time |
Scale |
Rotation |
Blur |
Illumination |
Affine |
Sift |
common |
best |
best |
common |
common |
good |
Pca-sift |
good |
good |
good |
best |
good |
best |
Surf |
best |
common |
common |
good |
best |
good |
由此可見,SIFT在尺度和旋轉變換的情況下效果最好,SURF在亮度變化下匹配效果最好,在模糊方面優於SIFT,而尺度和旋轉的變化不及SIFT,旋轉不變上比SIFT差很多。速度上看,SURF是SIFT速度的3倍。
OpenCV算法實現
<pre name="code" class="cpp">/**
* @file SURF_descriptor
* @brief SURF detector + descritpor + BruteForce Matcher + drawing matches with OpenCV functions
* @author A. Huaman
*/
#include <stdio.h>
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
void readme();
/**
* @function main
* @brief Main function
*/
int main( int argc, char** argv )
{
if( argc != 3 )
{ return -1; }
Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );
if( !img_1.data || !img_2.data )
{ return -1; }
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector<KeyPoint> keypoints_1, keypoints_2;
detector.detect( img_1, keypoints_1 );
detector.detect( img_2, keypoints_2 );
//-- Step 2: Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_1, descriptors_2;
extractor.compute( img_1, keypoints_1, descriptors_1 );
extractor.compute( img_2, keypoints_2, descriptors_2 );
//-- Step 3: Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
double max_dist = 0; double min_dist = 100;
//-- Quick calculation of max and min distances between keypoints
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;
}
printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );
//-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist )
//-- PS.- radiusMatch can also be used here.
std::vector< DMatch > good_matches;
for( int i = 0; i < descriptors_1.rows; i++ )
{ if( matches[i].distance < 2*min_dist )
{ good_matches.push_back( matches[i]); }
}
<pre name="code" class="cpp"> //-- Draw only "good" matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//-- Show detected matches
imshow( "Good Matches", img_matches );
//-- Localize the object from img_1 in img_2 std::vector<Point2f> obj;
std::vector<Point2f> scene;
for( int i = 0; i < good_matches.size(); i++ )
{
//-- Get the keypoints from the good matches
obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
}
Mat H = findHomography( obj, scene, CV_RANSAC );
//-- Get the corners from the image_1 ( the object to be "detected" )
Point2f obj_corners[4] = { cvPoint(0,0), cvPoint( img_1.cols, 0 ), cvPoint( img_1.cols, img_1.rows ), cvPoint( 0, img_1.rows ) };
Point scene_corners[4];
//-- Map these corners in the scene ( image_2)
for( int i = 0; i < 4; i++ )
{
double x = obj_corners[i].x;
double y = obj_corners[i].y;
double Z = 1./( H.at<double>(2,0)*x + H.at<double>(2,1)*y + H.at<double>(2,2) );
double X = ( H.at<double>(0,0)*x + H.at<double>(0,1)*y + H.at<double>(0,2) )*Z;
double Y = ( H.at<double>(1,0)*x + H.at<double>(1,1)*y + H.at<double>(1,2) )*Z;
scene_corners[i] = cvPoint( cvRound(X) + img_1.cols, cvRound(Y) );
}
//-- Draw lines between the corners (the mapped object in the scene - image_2 )
line( img_matches, scene_corners[0], scene_corners[1], Scalar(0, 255, 0), 2 );
line( img_matches, scene_corners[1], scene_corners[2], Scalar( 0, 255, 0), 2 );
line( img_matches, scene_corners[2], scene_corners[3], Scalar( 0, 255, 0), 2 );
line( img_matches, scene_corners[3], scene_corners[0], Scalar( 0, 255, 0), 2 );
//-- Show detected matches
imshow( "Good Matches & Object detection", img_matches );
waitKey(0);
return(0);
}
/**
* @function readme
*/
void readme()
{ std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }
參考:http://blog.csdn.net/yang_xian521/article/details/6901762