sift演算法c語言實現
阿新 • • 發佈:2019-02-17
前段時間在做三維測量方面的研究,需要得到物體表面三維資料,sift演算法是立體匹配中的經典演算法,下面是對RobHess的SIFT原始碼的註釋。部分內容參考網上,在這裡向各位大神表示感謝!
http://blog.csdn.net/lsh_2013/article/details/46826141
標頭檔案及函式宣告
#include "sift.h"
#include "imgfeatures.h"
#include "utils.h"
#include <cxcore.h>
#include <cv.h>
//將原圖轉換為32位灰度圖並歸一化,然後進行一次高斯平滑,並根據引數img_dbl決定
//是否將影象尺寸放大為原圖的2倍
static IplImage* create_init_img( IplImage*, int, double );
//將輸入影象轉換為32位灰度圖,並進行歸一化
static IplImage* convert_to_gray32( IplImage* );
//根據輸入引數建立高斯金字塔
static IplImage*** build_gauss_pyr( IplImage*, int, int, double );
//對輸入影象做下采樣生成其四分之一大小的影象(每個維度上減半),使用最近鄰差值方法
static IplImage* downsample( IplImage* );
//通過對高斯金字塔中每相鄰兩層影象相減來建立高斯差分金字塔
static IplImage*** build_dog_pyr( IplImage***, int, int );
//在尺度空間中檢測極值點,通過插值精確定位,去除低對比度的點,去除邊緣點,返回檢測
//到的特徵點序列
static CvSeq* scale_space_extrema( IplImage***, int, int, double, int, CvMemStorage*);
//通過在尺度空間中將一個畫素點的值與其周圍3*3*3鄰域內的點比較來決定此點是否極值
//點(極大值或極小都行)
static int is_extremum( IplImage***, int, int, int , int );
//通過亞畫素級插值進行極值點精確定位(修正極值點座標),並去除低對比度的極值點,
//將修正後的特徵點組成feature結構返回
static struct feature* interp_extremum( IplImage***, int, int, int, int, int, double);
//進行一次極值點差值,計算x,y,σ方向(層方向)上的子畫素偏移量(增量)
static void interp_step( IplImage***, int, int, int, int, double*, double*, double* );
//在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數
static CvMat* deriv_3D( IplImage***, int, int, int, int );
//在DoG金字塔中計算某點的3*3海森矩陣
static CvMat* hessian_3D( IplImage***, int, int, int, int );
//計算被插值點的對比度:D + 0.5 * dD^T * X
static double interp_contr( IplImage***, int, int, int, int, double, double, double );
//為一個feature結構分配空間並初始化
static struct feature* new_feature( void );
//去除邊緣響應,即通過計算主曲率比值判斷某點是否邊緣點
static int is_too_edge_like( IplImage*, int, int, int );
//計算特徵點序列中每個特徵點的尺度
static void calc_feature_scales( CvSeq*, double, int );
//將特徵點序列中每個特徵點的座標減半(當設定了將影象放大為原圖的2倍時,特徵點檢測完之後
//呼叫)
static void adjust_for_img_dbl( CvSeq* );
//計算每個特徵點的梯度直方圖,找出其主方向,若一個特徵點有不止一個主方向,將其分為
//兩個特徵點
static void calc_feature_oris( CvSeq*, IplImage*** );
//計算指定畫素點的梯度方向直方圖,返回存放直方圖的陣列
static double* ori_hist( IplImage*, int, int, int, int, double );
//計算指定點的梯度的幅值magnitude和方向orientation
static int calc_grad_mag_ori( IplImage*, int, int, double*, double* );
//對梯度方向直方圖進行高斯平滑,彌補因沒有仿射不變性而產生的特徵點不穩定的問題
static void smooth_ori_hist( double*, int );
//查詢梯度直方圖中主方向的梯度幅值,即查詢直方圖中最大bin的值
static double dominant_ori( double*, int );
//若當前特徵點的直方圖中某個bin的值大於給定的閾值,則新生成一個特徵點並新增到特徵點
//序列末尾
static void add_good_ori_features( CvSeq*, double*, int, double, struct feature* );
//對輸入的feature結構特徵點做深拷貝,返回克隆生成的特徵點的指標
static struct feature* clone_feature( struct feature* );
//計算特徵點序列中每個特徵點的特徵描述子向量
static void compute_descriptors( CvSeq*, IplImage***, int, int );
//計算特徵點附近區域的方向直方圖,此直方圖在計算特徵描述子中要用到,返回值是一個
//d*d*n的三維陣列
static double*** descr_hist( IplImage*, int, int, double, double, int, int );
static void interp_hist_entry( double***, double, double, double, double, int, int);
//將某特徵點的方向直方圖轉換為特徵描述子向量,對特徵描述子歸一化並將所有元素轉化為整型,
//存入指定特徵點中
static void hist_to_descr( double***, int, int, struct feature* );
//歸一化特徵點的特徵描述子,即將特徵描述子陣列中每個元素除以特徵描述子的模
static void normalize_descr( struct feature* );
//比較函式,將特徵點按尺度的降序排列,用在序列排序函式CvSeqSort中
static int feature_cmp( void*, void*, void* );
//釋放計算特徵描述子過程中用到的方向直方圖的記憶體空間
static void release_descr_hist( double****, int );
//釋放金字塔影象組的儲存空間
static void release_pyr( IplImage****, int, int );
int sift_features( IplImage* img, struct feature** feat )
{
return _sift_features( img, feat, SIFT_INTVLS, SIFT_SIGMA, SIFT_CONTR_THR,
SIFT_CURV_THR, SIFT_IMG_DBL, SIFT_DESCR_WIDTH,
SIFT_DESCR_HIST_BINS );
}
函式實現
主函式
/*使用使用者指定的引數在影象中提取SIFT特徵點
引數:
img:輸入影象
feat:儲存特徵點的陣列的指標,此陣列的記憶體將在本函式中被分配,使用完後必須在調用出釋放:free(*feat)
intvls:每組的層數
sigma:初始高斯平滑引數σ
contr_thr:對比度閾值,針對歸一化後的影象,用來去除不穩定特徵
curv_thr:去除邊緣的特徵的主曲率閾值
img_dbl:是否將影象放大為之前的兩倍
descr_width:特徵描述過程中,計算方向直方圖時,將特徵點附近劃分為descr_width*descr_width個區域,每個區域生成一個直方圖
descr_hist_bins:特徵描述過程中,每個直方圖中bin的個數
返回值:提取的特徵點個數,若返回-1表明提取失敗*/
int _sift_features( IplImage* img, struct feature** feat, int intvls,
double sigma, double contr_thr, int curv_thr,
int img_dbl, int descr_width, int descr_hist_bins )
{
IplImage* init_img;//原圖經初始化後的影象
IplImage*** gauss_pyr, *** dog_pyr;//三級指標,高斯金字塔影象組,DoG金字塔影象組
CvMemStorage* storage;//儲存器
CvSeq* features;//儲存特徵點的序列,序列中存放的是struct feature型別的指標
int octvs, i, n = 0;
//輸入引數檢查
if( ! img )
fatal_error( "NULL pointer error, %s, line %d", __FILE__, __LINE__ );
if( ! feat )
fatal_error( "NULL pointer error, %s, line %d", __FILE__, __LINE__ );
/*步驟一:建立尺度空間,即建立高斯差分(DoG)金字塔dog_pyr
將原圖轉換為32位灰度圖並歸一化,然後進行一次高斯平滑,
並根據引數img_dbl決定是否將影象尺寸放大為原圖的2倍*/
init_img = create_init_img( img, img_dbl, sigma );
//計算高斯金字塔的組數octvs
octvs = log( MIN( init_img->width, init_img->height ) ) / log(2) - 2;
//為了保證連續性,在每一層的頂層繼續用高斯模糊生成3幅影象,所以高斯金字塔每組
//有intvls+3層,DOG金字塔每組有intvls+2層
//建立高斯金字塔gauss_pyr,是一個octvs*(intvls+3)的影象陣列
gauss_pyr = build_gauss_pyr( init_img, octvs, intvls, sigma );
//建立高斯差分(DoG)金字塔dog_pyr,是一個octvs*(intvls+2)的影象陣列
dog_pyr = build_dog_pyr( gauss_pyr, octvs, intvls );
/*步驟二:在尺度空間中檢測極值點,並進行精確定位和篩選建立預設大小的記憶體儲存器*/
storage = cvCreateMemStorage( 0 );
//在尺度空間中檢測極值點,通過插值精確定位,去除低對比度的點,去除邊緣點,
//返回檢測到的特徵點序列
features = scale_space_extrema( dog_pyr, octvs, intvls, contr_thr,
curv_thr, storage );
//計算特徵點序列features中每個特徵點的尺度
calc_feature_scales( features, sigma, intvls );
if( img_dbl ) //若設定了將影象放大為原圖的2倍
adjust_for_img_dbl( features );//將特徵點序列中每個特徵點的座標減半
//(當設定了將影象放大為原圖的2倍時,特徵點檢測完之後呼叫)
/*步驟三:特徵點方向賦值,完成此步驟後,每個特徵點有三個資訊:位置、尺度、方向*/
//計算每個特徵點的梯度直方圖,找出其主方向,若一個特徵點有不止一個主方向,將其分為
//兩個特徵點
calc_feature_oris( features, gauss_pyr );
/*步驟四:計算特徵描述子*/
//計算特徵點序列中每個特徵點的特徵描述子向量
compute_descriptors( features, gauss_pyr, descr_width, descr_hist_bins );
//按特徵點尺度的降序排列序列中的元素的順序,feature_cmp是自定義的比較函式
cvSeqSort( features, (CvCmpFunc)feature_cmp, NULL );
//將CvSeq型別的特徵點序列features轉換為通用的struct feature型別的陣列feat
n = features->total;//特徵點個數
*feat = calloc( n, sizeof(struct feature) );//分配控制元件
//將序列features中的元素拷貝到陣列feat中,返回陣列指標給feat
*feat = cvCvtSeqToArray( features, *feat, CV_WHOLE_SEQ );
//釋放特徵點陣列feat中所有特徵點的feature_data成員,因為此成員中的資料在檢測完特徵
//點後就沒用了
for( i = 0; i < n; i++ )
{
free( (*feat)[i].feature_data );
(*feat)[i].feature_data = NULL;
}
//釋放各種臨時資料的儲存空間
cvReleaseMemStorage( &storage );
cvReleaseImage( &init_img );
release_pyr( &gauss_pyr, octvs, intvls + 3 );
release_pyr( &dog_pyr, octvs, intvls + 2 );
//返回檢測到的特徵點的個數
return n;
}
尺度空間構造
/*將原圖轉換為32位灰度圖並歸一化,然後進行一次高斯平滑,並根據引數img_dbl決定是否將
影象尺寸放大為原圖的2倍
引數:
img:輸入的原影象
img_dbl:是否將影象放大為之前的兩倍
sigma:初始高斯平滑引數σ
返回值:初始化完成的影象*/
static IplImage* create_init_img( IplImage* img, int img_dbl, double sigma )
{
IplImage* gray, * dbl;
float sig_diff;
//呼叫函式,將輸入影象轉換為32位灰度圖,並歸一化
gray = convert_to_gray32( img );
if( img_dbl ) //若設定了將影象放大為原圖的2倍
{
//將影象長寬擴充套件一倍時,便有了底-1層,該層尺度為:
sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA * 4 );
//建立放大影象
dbl = cvCreateImage( cvSize( img->width*2, img->height*2 ),
IPL_DEPTH_32F, 1 );
//放大原圖的尺寸
cvResize( gray, dbl, CV_INTER_CUBIC );
//高斯平滑,高斯核在x,y方向上的標準差都是sig_diff
cvSmooth( dbl, dbl, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );
cvReleaseImage( &gray );
return dbl;
}
else//不用放大為原圖的2倍
{
//計算第0層的尺度
sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA );
//高斯平滑,高斯核在x,y方向上的標準差都是sig_diff
cvSmooth( gray, gray, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );
return gray;
}
}
/*將輸入影象轉換為32位灰度圖,並進行歸一化
引數:
img:輸入影象,3通道8位彩色圖(BGR)或8位灰度圖
返回值:32位灰度圖*/
static IplImage* convert_to_gray32( IplImage* img )
{
IplImage* gray8, * gray32;
gray32 = cvCreateImage( cvGetSize(img), IPL_DEPTH_32F, 1 );
//首先將原圖轉換為8位單通道影象
if( img->nChannels == 1 )//若原圖本身就是單通道,直接克隆原圖
gray8 = cvClone( img );
else//若原圖是3通道影象
{
gray8 = cvCreateImage( cvGetSize(img), IPL_DEPTH_8U, 1 );//建立8位單通道影象
cvCvtColor( img, gray8, CV_BGR2GRAY );//將原圖轉換為8為單通道影象
}
//然後將8為單通道影象gray8轉換為32位單通道影象,並進行歸一化處理(除以255)
cvConvertScale( gray8, gray32, 1.0 / 255.0, 0 );
//釋放臨時影象
cvReleaseImage( &gray8 );
//返回32位單通道影象
return gray32;
}
/*根據輸入引數建立高斯金字塔
引數:
base:輸入影象,作為高斯金字塔的基影象
octvs:高斯金字塔的組數
intvls:每組的層數
sigma:初始尺度
返回值:高斯金字塔,是一個octvs*(intvls+3)的影象陣列*/
static IplImage*** build_gauss_pyr( IplImage* base, int octvs,
int intvls, double sigma )
{
IplImage*** gauss_pyr;
//為了保證連續性,在每一層的頂層繼續用高斯模糊生成3幅影象,所以高斯金字塔每組有
//intvls+3層,DOG金字塔每組有intvls+2層
double* sig = calloc( intvls + 3, sizeof(double));//每層的sigma引數陣列
double sig_total, sig_prev, k;
int i, o;
//為高斯金字塔gauss_pyr分配空間,共octvs個元素,每個元素是一組影象的首指標
gauss_pyr = calloc( octvs, sizeof( IplImage** ) );
//為第i組影象gauss_pyr[i]分配空間,共intvls+3個元素,每個元素是一個影象指標
for( i = 0; i < octvs; i++ )
gauss_pyr[i] = calloc( intvls + 3, sizeof( IplImage* ) );
//計算每次高斯模糊的sigma引數
sig[0] = sigma;//初始尺度
k = pow( 2.0, 1.0 / intvls );
for( i = 1; i < intvls + 3; i++ )
{
sig_prev = pow( k, i - 1 ) * sigma;//i-1層的尺度
sig_total = sig_prev * k;//i層的尺度
sig[i] = sqrt( sig_total * sig_total - sig_prev * sig_prev );
}
//逐組逐層生成高斯金字塔
for( o = 0; o < octvs; o++ )//遍歷組
for( i = 0; i < intvls + 3; i++ )//遍歷層
{
if( o == 0 && i == 0 )//第0組,第0層,就是原影象
gauss_pyr[o][i] = cvCloneImage(base);
else if( i == 0 )//新的一組的首層影象是由上一組最後一層影象下采樣得到
gauss_pyr[o][i] = downsample( gauss_pyr[o-1][intvls] );
else//對上一層影象進行高斯平滑得到當前層影象
{ //建立影象
gauss_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i-1]),IPL_DEPTH_32F, 1 );
//對上一層影象gauss_pyr[o][i-1]進行引數為sig[i]的高斯平滑,得到當前層影象
//gauss_pyr[o][i]
cvSmooth( gauss_pyr[o][i-1], gauss_pyr[o][i], CV_GAUSSIAN, 0, 0, sig[i], sig[i] );
}
}
free( sig );//釋放sigma引數陣列
return gauss_pyr;//返回高斯金字塔
}
/*對輸入影象做下采樣生成其四分之一大小的影象(每個維度上減半),使用最近鄰差值方法
引數:
img:輸入影象
返回值:下采樣後的影象*/
static IplImage* downsample( IplImage* img )
{
//下采樣影象
IplImage* smaller = cvCreateImage( cvSize(img->width / 2, img->height / 2), img->depth, img->nChannels );
cvResize( img, smaller, CV_INTER_NN );//尺寸變換
return smaller;
}
/*通過對高斯金字塔中每相鄰兩層影象相減來建立高斯差分金字塔
引數:
gauss_pyr:高斯金字塔
octvs:組數
intvls:每組的層數
返回值:高斯差分金字塔,是一個octvs*(intvls+2)的影象陣列*/
static IplImage*** build_dog_pyr( IplImage*** gauss_pyr, int octvs, int intvls )
{
IplImage*** dog_pyr;
int i, o;
//為高斯差分金字塔分配空間,共octvs個元素,每個元素是一組影象的首指標
dog_pyr = calloc( octvs, sizeof( IplImage** ) );
//為第i組影象dog_pyr[i]分配空間,共(intvls+2)個元素,每個元素是一個影象指標
for( i = 0; i < octvs; i++ )
dog_pyr[i] = calloc( intvls + 2, sizeof(IplImage*) );
//逐組逐層計算差分影象
for( o = 0; o < octvs; o++ )//遍歷組
for( i = 0; i < intvls + 2; i++ )//遍歷層
{ //建立DoG金字塔的第o組第i層的差分影象
dog_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i]), IPL_DEPTH_32F, 1 );
//將高斯金字塔的第o組第i+1層影象和第i層影象相減來得到DoG金字塔的第o組第i層
//影象
cvSub( gauss_pyr[o][i+1], gauss_pyr[o][i], dog_pyr[o][i], NULL );
}
return dog_pyr;//返回高斯差分金字塔
}
區域性空間極值點檢測
/*在尺度空間中檢測極值點,通過插值精確定位,去除低對比度的點,去除邊緣點,返回檢測到
的特徵點序列
引數:
dog_pyr:高斯差分金字塔
octvs:高斯差分金字塔的組數
intvls:每組的層數
contr_thr:對比度閾值,針對歸一化後的影象,用來去除不穩定特徵
cur_thr:主曲率比值的閾值,用來去除邊緣特徵
storage:儲存器
返回值:返回檢測到的特徵點的序列*/
static CvSeq* scale_space_extrema( IplImage*** dog_pyr, int octvs, int intvls,
double contr_thr, int curv_thr, CvMemStorage* storage )
{
CvSeq* features;//特徵點序列
double prelim_contr_thr = 0.5 * contr_thr / intvls;//畫素的對比度閾值
struct feature* feat;
struct detection_data* ddata;
int o, i, r, c;
//在儲存器storage上建立儲存極值點的序列,其中儲存feature結構型別的資料
features = cvCreateSeq( 0, sizeof(CvSeq), sizeof(struct feature), storage );
/*遍歷高斯差分金字塔,檢測極值點*/
//SIFT_IMG_BORDER指明邊界寬度,只檢測邊界線以內的極值點
for( o = 0; o < octvs; o++ )//第o組
for( i = 1; i <= intvls; i++ )//遍i層
for(r = SIFT_IMG_BORDER; r < dog_pyr[o][0]->height-SIFT_IMG_BORDER; r++)//第r行
for(c = SIFT_IMG_BORDER; c < dog_pyr[o][0]->width-SIFT_IMG_BORDER; c++)//第c列
//進行初步的對比度檢查,只有當歸一化後的畫素值大於對比度
//閾值prelim_contr_thr時才繼續檢測此畫素點是否可能是極值
//呼叫函式pixval32f獲取影象dog_pyr[o][i]的第r行第c列的點的座標值,
//然後呼叫ABS巨集求其絕對值
if( ABS( pixval32f( dog_pyr[o][i], r, c ) ) > prelim_contr_thr )
//通過在尺度空間中將一個畫素點的值與其周圍3*3*3鄰域內的點比較來
//決定此點是否極值點(極大值或極小都行)
if( is_extremum( dog_pyr, o, i, r, c ) )//若是極值點
{
//由於極值點的檢測是在離散空間中進行的,所以檢測到的極值點並
//不一定是真正意義上的極值點
//因為真正的極值點可能位於兩個畫素之間,而在離散空間中只能精
//確到座標點精度上
//通過亞畫素級插值進行極值點精確定位(修正極值點座標),並去除
//低對比度的極值點,將修正後的特徵點組成feature結構返回
feat = interp_extremum(dog_pyr, o, i, r, c, intvls, contr_thr);
//返回值非空,表明此點已被成功修正
if( feat )
{
//呼叫巨集feat_detection_data來提取引數feat中的feature_data成員
//並轉換為detection_data型別的指標
ddata = feat_detection_data( feat );
//去除邊緣響應,即通過計算主曲率比值判斷某點是否邊緣點,
//返回值為0表示不是邊緣點,可做特徵點
if( ! is_too_edge_like( dog_pyr[ddata->octv][ddata->intvl],
ddata->r, ddata->c, curv_thr ) )
{
cvSeqPush( features, feat );//向特徵點序列features末尾插
//入新檢測到的特徵點feat
}
else
free( ddata );
free( feat );
}
}
return features;//返回特徵點序列
}
/*通過在尺度空間中將一個畫素點的值與其周圍3*3*3鄰域內的點比較來決定此點是否極值點
(極大值或極小都行)
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
返回值:若指定的畫素點是極值點(極大值或極小值),返回1;否則返回0*/
static int is_extremum( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
{
//呼叫函式pixval32f獲取影象dog_pyr[octv][intvl]的第r行第c列的點的座標值
float val = pixval32f( dog_pyr[octv][intvl], r, c );
int i, j, k;
//檢查是否最大值
if( val > 0 )
{
for( i = -1; i <= 1; i++ )//層
for( j = -1; j <= 1; j++ )//行
for( k = -1; k <= 1; k++ )//列
if( val < pixval32f( dog_pyr[octv][intvl+i], r + j, c + k ) )
return 0;
}
//檢查是否最小值
else
{
for( i = -1; i <= 1; i++ )//層
for( j = -1; j <= 1; j++ )//行
for( k = -1; k <= 1; k++ )//列
if( val > pixval32f( dog_pyr[octv][intvl+i], r + j, c + k ) )
return 0;
}
return 1;
}
剔除不穩定點,精確定位關鍵點位置
/*通過亞畫素級插值進行極值點精確定位(修正極值點座標),並去除低對比度的極值點,
將修正後的特徵點組成feature結構返回
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
intvls:每組的層數
contr_thr:對比度閾值,針對歸一化後的影象,用來去除不穩定特徵
返回值:返回經插值修正後的特徵點(feature型別);若經有限次插值依然無法精確到理想情況
或者該點對比度過低,返回NULL*/
static struct feature* interp_extremum( IplImage*** dog_pyr, int octv, int intvl,
int r, int c, int intvls, double contr_thr )
{
struct feature* feat;//修正後的特徵點
struct detection_data* ddata;//與特徵檢測有關的結構,存在feature結構的feature_data成員中
double xi, xr, xc, contr;//xi,xr,xc分別為亞畫素的intvl(層),row(y),col(x)方向上的
//增量(偏移量)
int i = 0;//插值次數
//SIFT_MAX_INTERP_STEPS指定了關鍵點的最大插值次數,即最多修正多少次,預設是5
while( i < SIFT_MAX_INTERP_STEPS )
{
//進行一次極值點差值,計算σ(層方向,intvl方向),y,x方向上的子畫素偏移量(增量)
interp_step( dog_pyr, octv, intvl, r, c, &xi, &xr, &xc );
//若在任意方向上的偏移量大於0.5時,意味著差值中心已經偏移到它的臨近點上,
//所以必須改變當前關鍵點的位置座標
if( ABS( xi ) < 0.5 && ABS( xr ) < 0.5 && ABS( xc ) < 0.5 )//若三方向上偏移量
//都小於0.5,表示已經夠精確,則不用繼續插值
break;
//修正關鍵點的座標,x,y,σ三方向上的原座標加上偏移量取整(四捨五入)
c += cvRound( xc );//x座標修正
r += cvRound( xr );//y座標修正
intvl += cvRound( xi );//σ方向,即層方向
//若座標修正後超出範圍,則結束插值,返回NULL
if( intvl < 1 || //層座標插之後越界
intvl > intvls ||
c < SIFT_IMG_BORDER || //行列座標插之後到邊界線內
r < SIFT_IMG_BORDER ||
c >= dog_pyr[octv][0]->width - SIFT_IMG_BORDER ||
r >= dog_pyr[octv][0]->height - SIFT_IMG_BORDER )
{
return NULL;
}
i++;
}
//若經過SIFT_MAX_INTERP_STEPS次插值後還沒有修正到理想的精確位置,則返回NULL,
//即捨棄此極值點
if( i >= SIFT_MAX_INTERP_STEPS )
return NULL;
//計算被插值點的對比度:D + 0.5 * dD^T * X
contr = interp_contr( dog_pyr, octv, intvl, r, c, xi, xr, xc );
if( ABS( contr ) < contr_thr / intvls )//若該點對比度過小,捨棄,返回NULL
return NULL;
//為一個特徵點feature結構分配空間並初始化,返回特徵點指標
feat = new_feature();
//呼叫巨集feat_detection_data來提取引數feat中的feature_data成員並轉換為
//detection_data型別的指標
ddata = feat_detection_data( feat );
//將修正後的座標賦值給特徵點feat
//原圖中特徵點的x座標,因為第octv組中的圖的尺寸比原圖小2^octv倍,
//所以座標值要乘以2^octv
feat->img_pt.x = feat->x = ( c + xc ) * pow( 2.0, octv );
//原圖中特徵點的y座標,因為第octv組中的圖的尺寸比原圖小2^octv倍,
//所以座標值要乘以2^octv
feat->img_pt.y = feat->y = ( r + xr ) * pow( 2.0, octv );
ddata->r = r;//特徵點所在的行
ddata->c = c;//特徵點所在的列
ddata->octv = octv;//高斯差分金字塔中,特徵點所在的組
ddata->intvl = intvl;//高斯差分金字塔中,特徵點所在的組中的層
ddata->subintvl = xi;//特徵點在層方向(σ方向,intvl方向)上的亞畫素偏移量
return feat;//返回特徵點指標
}
/*進行一次極值點差值,計算x,y,σ方向(層方向)上的子畫素偏移量(增量)
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
xi:輸出引數,層方向上的子畫素增量(偏移)
xr:輸出引數,y方向上的子畫素增量(偏移)
xc:輸出引數,x方向上的子畫素增量(偏移)*/
static void interp_step( IplImage*** dog_pyr, int octv, int intvl, int r, int c,
double* xi, double* xr, double* xc )
{
CvMat* dD, * H, * H_inv, X;
double x[3] = { 0 };
//在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數,結果存放在列向量dD中
dD = deriv_3D( dog_pyr, octv, intvl, r, c );
//在DoG金字塔中計算某點的3*3海森矩陣
H = hessian_3D( dog_pyr, octv, intvl, r, c );
H_inv = cvCreateMat( 3, 3, CV_64FC1 );//海森矩陣的逆陣
cvInvert( H, H_inv, CV_SVD );
cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );
//X = - H^(-1) * dD,H的三個元素分別是x,y,σ方向上的偏移量(具體見SIFT演算法說明)
cvGEMM( H_inv, dD, -1, NULL, 0, &X, 0 );
cvReleaseMat( &dD );
cvReleaseMat( &H );
cvReleaseMat( &H_inv );
*xi = x[2];//σ方向(層方向)偏移量
*xr = x[1];//y方向上偏移量
*xc = x[0];//x方向上偏移量
}
/*在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
返回值:返回3個偏導陣列成的列向量{ dI/dx, dI/dy, dI/ds }^T*/
static CvMat* deriv_3D( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
{
CvMat* dI;
double dx, dy, ds;
//求差分來代替偏導,這裡是用的隔行求差取中值的梯度計算方法
//求x方向上的差分來近似代替偏導數
dx = ( pixval32f( dog_pyr[octv][intvl], r, c+1 ) -
pixval32f( dog_pyr[octv][intvl], r, c-1 ) ) / 2.0;
//求y方向上的差分來近似代替偏導數
dy = ( pixval32f( dog_pyr[octv][intvl], r+1, c ) -
pixval32f( dog_pyr[octv][intvl], r-1, c ) ) / 2.0;
//求層間的差分來近似代替尺度方向上的偏導數
ds = ( pixval32f( dog_pyr[octv][intvl+1], r, c ) -
pixval32f( dog_pyr[octv][intvl-1], r, c ) ) / 2.0;
//組成列向量
dI = cvCreateMat( 3, 1, CV_64FC1 );
cvmSet( dI, 0, 0, dx );
cvmSet( dI, 1, 0, dy );
cvmSet( dI, 2, 0, ds );
return dI;
}
/*在DoG金字塔中計算某點的3*3海森矩陣
/ Ixx Ixy Ixs \
| Ixy Iyy Iys |
\ Ixs Iys Iss /
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
返回值:返回3*3的海森矩陣
*/
static CvMat* hessian_3D( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
{
CvMat* H;
double v, dxx, dyy, dss, dxy, dxs, dys;
v = pixval32f( dog_pyr[octv][intvl], r, c );//該點的畫素值
//用差分近似代替倒數(具體公式見各種梯度的求法)
//dxx = f(i+1,j) - 2f(i,j) + f(i-1,j)
//dyy = f(i,j+1) - 2f(i,j) + f(i,j-1)
dxx = ( pixval32f( dog_pyr[octv][intvl], r, c+1 ) +
pixval32f( dog_pyr[octv][intvl], r, c-1 ) - 2 * v );
dyy = ( pixval32f( dog_pyr[octv][intvl], r+1, c ) +
pixval32f( dog_pyr[octv][intvl], r-1, c ) - 2 * v );
dss = ( pixval32f( dog_pyr[octv][intvl+1], r, c ) +
pixval32f( dog_pyr[octv][intvl-1], r, c ) - 2 * v );
dxy = ( pixval32f( dog_pyr[octv][intvl], r+1, c+1 ) -
pixval32f( dog_pyr[octv][intvl], r+1, c-1 ) -
pixval32f( dog_pyr[octv][intvl], r-1, c+1 ) +
pixval32f( dog_pyr[octv][intvl], r-1, c-1 ) ) / 4.0;
dxs = ( pixval32f( dog_pyr[octv][intvl+1], r, c+1 ) -
pixval32f( dog_pyr[octv][intvl+1], r, c-1 ) -
pixval32f( dog_pyr[octv][intvl-1], r, c+1 ) +
pixval32f( dog_pyr[octv][intvl-1], r, c-1 ) ) / 4.0;
dys = ( pixval32f( dog_pyr[octv][intvl+1], r+1, c ) -
pixval32f( dog_pyr[octv][intvl+1], r-1, c ) -
pixval32f( dog_pyr[octv][intvl-1], r+1, c ) +
pixval32f( dog_pyr[octv][intvl-1], r-1, c ) ) / 4.0;
//組成海森矩陣
H = cvCreateMat( 3, 3, CV_64FC1 );
cvmSet( H, 0, 0, dxx );
cvmSet( H, 0, 1, dxy );
cvmSet( H, 0, 2, dxs );
cvmSet( H, 1, 0, dxy );
cvmSet( H, 1, 1, dyy );
cvmSet( H, 1, 2, dys );
cvmSet( H, 2, 0, dxs );
cvmSet( H, 2, 1, dys );
cvmSet( H, 2, 2, dss );
return H;
}
/*計算被插值點的對比度:D + 0.5 * dD^T * X
引數:
dog_pyr:高斯差分金字塔
octv:畫素點所在的組
intvl:畫素點所在的層
r:畫素點所在的行
c:畫素點所在的列
xi:層方向上的子畫素增量
xr:y方向上的子畫素增量
xc:x方向上的子畫素增量
返回值:插值點的對比度*/
static double interp_contr( IplImage*** dog_pyr, int octv, int intvl, int r,
int c, double xi, double xr, double xc )
{
CvMat* dD, X, T;
double t[1], x[3] = { xc, xr, xi };
//偏移量組成的列向量X,其中是x,y,σ三方向上的偏移量
cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );
//矩陣乘法的結果T,是一個數值
cvInitMatHeader( &T, 1, 1, CV_64FC1, t, CV_AUTOSTEP );
//在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數,結果存放在列向量dD中
dD = deriv_3D( dog_pyr, octv, intvl, r, c );
//矩陣乘法:T = dD^T * X
cvGEMM( dD, &X, 1, NULL, 0, &T, CV_GEMM_A_T );
cvReleaseMat( &dD );
//返回計算出的對比度值:D + 0.5 * dD^T * X (具體公式推導見SIFT演算法說明)
return pixval32f( dog_pyr[octv][intvl], r, c ) + t[0] * 0.5;
}
/*為一個feature結構分配空間並初始化
返回值:初始化完成的feature結構的指標*/
static struct feature* new_feature( void )
{
struct feature* feat;//特徵點指標
struct detection_data* ddata;//與特徵檢測相關的結構
feat = malloc( sizeof( struct feature ) );//分配空間
memset( feat, 0, sizeof( struct feature ) );//清零
ddata = malloc( sizeof( struct detection_data ) );
memset( ddata, 0, sizeof( struct detection_data ) );
feat->feature_data = ddata;//將特徵檢測相關的結構指標賦值給特徵點的feature_data成員
feat->type = FEATURE_LOWE;//預設是LOWE型別的特徵點
return feat;
}
/*去除邊緣響應,即通過計算主曲率比值判斷某點是否邊緣點
引數:
dog_img:此特徵點所在的DoG影象
r:特徵點所在的行
c:特徵點所在的列
cur_thr:主曲率比值的閾值,用來去除邊緣特徵
返回值:0:此點是非邊緣點;1:此點是邊緣點*/
static int is_too_edge_like( IplImage* dog_img, int r, int c, int curv_thr )
{
double d, dxx, dyy, dxy, tr, det;
/*某點的主曲率與其海森矩陣的特徵值成正比,為了避免直接計算特徵值,這裡只考慮
特徵值的比值可通過計算海森矩陣的跡tr(H)和行列式det(H)來計算特徵值的比值
設a是海森矩陣的較大特徵值,b是較小的特徵值,有a = r*b,r是大小特徵值的比值
tr(H) = a + b; det(H) = a*b;
tr(H)^2 / det(H) = (a+b)^2 / ab = (r+1)^2/r
r越大,越可能是邊緣點;伴隨r的增大,(r+1)^2/r 的值也增大,所以可通過(r+1)^2/r 判斷
主曲率比值是否滿足條件*/
/* principal curvatures are computed using the trace and det of Hessian */
d = pixval32f(dog_img, r, c);//呼叫函式pixval32f獲取影象dog_img的第r行第c列的點的座標值
//用差分近似代替偏導,求出海森矩陣的幾個元素值
/* / dxx dxy \
\ dxy dyy / */
dxx = pixval32f( dog_img, r, c+1 ) + pixval32f( dog_img, r, c-1 ) - 2 * d;
dyy = pixval32f( dog_img, r+1, c ) + pixval32f( dog_img, r-1, c ) - 2 * d;
dxy = ( pixval32f(dog_img, r+1, c+1) - pixval32f(dog_img, r+1, c-1) -
pixval32f(dog_img, r-1, c+1) + pixval32f(dog_img, r-1, c-1) ) / 4.0;
tr = dxx + dyy;//海森矩陣的跡
det = dxx * dyy - dxy * dxy;//海森矩陣的行列式
//若行列式為負,表明曲率有不同的符號,去除此點
/* negative determinant -> curvatures have different signs; reject feature */
if( det <= 0 )
return 1;//返回1表明此點是邊緣點
//通過式子:(r+1)^2/r 判斷主曲率的比值是否滿足條件,若小於閾值,表明不是邊緣點
if( tr * tr / det < ( curv_thr + 1.0 )*( curv_thr + 1.0 ) / curv_thr )
return 0;//不是邊緣點
return 1;//是邊緣點
}
/*計算特徵點序列中每個特徵點的尺度
引數:
features:特徵點序列
sigma:初始高斯平滑引數,即初始尺度
intvls:尺度空間中每組的層數*/
static void calc_feature_scales( CvSeq* features, double sigma, int intvls )
{
struct feature* feat;
struct detection_data* ddata;
double intvl;
int i, n;
n = features->total;//總的特徵點個數
//遍歷特徵點
for( i = 0; i < n; i++ )
{
//呼叫巨集,獲取序列features中的第i個元素,並強制轉換為struct feature型別
feat = CV_GET_SEQ_ELEM( struct feature, features, i );
//呼叫巨集feat_detection_data來提取引數feat中的feature_data成員並轉換為
//detection_data型別的指標
ddata = feat_detection_data( feat );