轉載請註明出處:http://blog.csdn.NET/luoshixian099/article/details/47377611
相關: KD樹+BBF算法解析
SURF原理與源碼解析
SIFT的原理已經有很多大牛的博客上做了解析,本文重點將以Rob Hess等人用C實現的代碼做解析,結合代碼SIFT原理會更容易理解。一些難理解點的用了☆標註。
歡迎大家批評指正!
SIFT(Scale-invariant feature transform)即尺度不變特徵轉換,提取的局部特徵點具有尺度不變性,且對於旋轉,亮度,噪聲等有很高的穩定性。
下圖中,涉及到圖像的旋轉,仿射,光照等變化,SIFT算法依然有很好的匹配效果。
SIFT特徵點提取
本文將以下函數爲參照順序介紹SIFT特徵點提取與描述方法。
1.圖像預處理
2.構建高斯金字塔(不同尺度下的圖像)
3.生成DOG尺度空間
4.關鍵點搜索與定位
5.計算特徵點所在的尺度
6.爲特徵點分配方向角
7.構建特徵描述子
-
-
-
-
-
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;
-
CvMemStorage* storage;
-
CvSeq* features;
-
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__ );
-
-
-
init_img = create_init_img( img, img_dbl, sigma );
-
octvs = log( MIN( init_img->width, init_img->height ) ) / log(2) - 2;
-
gauss_pyr = build_gauss_pyr( init_img, octvs, intvls, sigma );
-
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 );
-
calc_feature_scales( features, sigma, intvls );
-
if( img_dbl )
-
adjust_for_img_dbl( features );
-
calc_feature_oris( features, gauss_pyr );
-
compute_descriptors( features, gauss_pyr, descr_width, descr_hist_bins );
-
-
-
cvSeqSort( features, (CvCmpFunc)feature_cmp, NULL );
-
n = features->total;
-
*feat = calloc( n, sizeof(struct feature) );
-
*feat = cvCvtSeqToArray( features, *feat, CV_WHOLE_SEQ );
-
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;
-
}
—————————————————————————————————————————————————————
1.圖像預處理
-
-
-
-
-
-
-
-
-
-
-
static IplImage* create_init_img( IplImage* img, int img_dbl, double sigma )
-
{
-
IplImage* gray, * dbl;
-
double sig_diff;
-
-
gray = convert_to_gray32( img );
-
if( img_dbl )
-
{
-
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 );
-
cvSmooth( dbl, dbl, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );
-
cvReleaseImage( &gray );
-
return dbl;
-
}
-
else
-
{
-
sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA );
-
cvSmooth( gray, gray, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );
-
return gray;
-
}
-
}
lowe建議把初始圖像放大二倍,可以得到更多的特徵點,提取到更多細節,並且認爲圖像在尺度σ = 0.5時圖像最清晰,初始高斯尺度爲σ = 1.6。
☆第19行因爲圖像被放大二倍,此時σ = 1.0 。因爲對二倍化後的圖像平滑是在σ = 0.5 上疊加的高斯模糊,
所以有模糊係數有sig_diff = sqrt (sigma *sigma - 0.5*0.5*4)=sqrt(1.6*1.6 -1) ;
2.構建高斯金字塔
構建高斯金字塔過程即構建出圖像在不同尺度上圖像,提取到的特徵點可有具有尺度不變性。
圖像的尺度空間L(x,y,σ)可以用一個高斯函數G(x,y,σ)與圖像I(x,y)卷積產生,即L(x,y,σ) = G(x,y,σ) * I(x,y)
其中二維高斯核的計算爲 。
☆不同的尺度空間即用不同的高斯核函數平滑圖像, 平滑係數越大,圖像越模糊。即模擬出動物的視覺效果,因爲事先不知道物體的大小,在不同的尺度下,圖像的細節會表現的不同。當尺度由小變大的過程中,是一個細節逐步簡化的過程,圖像中特徵不夠明顯的物體,就模糊的多了,而有些物體還可以看得到大致的輪廓。所以要在不同尺度下,觀察物體的尺度響應,提取到的特徵才能具有尺度不變性。
SIFT算法採用高斯金字塔實現連續的尺度空間的圖像。金字塔共分爲O(octave)組,每組有S(intervals)層 ,下一組是由上一組隔點採樣得到(即降2倍分辨率),這是爲了減輕卷積運算的工作量。
構建高斯金字塔(octave = 5, intervals +3=6):
全部空間尺度爲:
☆1.這個尺度因子都是在原圖上進行的,而在算法實現過程中,採用高斯平滑是在上一層圖像上再疊加高斯平滑,即我們在程序中看到的平滑因子爲
Eg. 在第一層上爲了得到kσ的高斯模糊圖像,可以在原圖上直接採用kσ平滑,也可以在上一層圖像上(已被σ平滑)的圖像上採用平滑因子爲平滑圖像,效果是一樣的。
☆2.我們在源碼上同時也沒有看到組間的2倍的關係,實際在對每組的平滑因子都是一樣的,2倍的關係是由於在降採樣的過程中產生的,第二層的第一張圖是由第一層的平滑因子爲2σ的圖像上(即倒數第三張)降採樣得到,此時圖像平滑因子爲σ,所以繼續採用以上的平滑因子。但在原圖上看,形成了全部的空間尺度。
☆3.每組(octave)有S+3層圖像,是由於在DOG尺度空間上尋找極值點的方法是在一個立方體內進行,即上下層比較,所以不在DOG空間的第一層與最後一層尋找,即DOG需要S+2層圖像,由於DOG尺度空間是由高斯金字塔相鄰圖像相減得到,即每組需要S+3層圖像。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
static IplImage*** build_gauss_pyr( IplImage* base, int octvs,
-
int intvls, double sigma )
-
{
-
IplImage*** gauss_pyr;
-
const int _intvls = intvls;
-
-
double k;
-
int i, o;
-
double *sig = (double *)malloc(sizeof(int)*(_intvls+3));
-
-
gauss_pyr = calloc( octvs, sizeof( IplImage** ) );
-
for( i = 0; i < octvs; i++ )
-
gauss_pyr[i] = calloc( intvls + 3, sizeof( IplImage *) );
-
-
-
-
-
-
-
-
-
-
-
k = pow( 2.0, 1.0 / intvls );
-
sig[0] = sigma;
-
sig[1] = sigma * sqrt( k*k- 1 );
-
for (i = 2; i < intvls + 3; i++)
-
sig[i] = sig[i-1] * k;
-
-
for( o = 0; o < octvs; o++ )
-
for( i = 0; i < intvls + 3; i++ )
-
{
-
if( o == 0 && i == 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 );
-
cvSmooth( gauss_pyr[o][i-1], gauss_pyr[o][i],
-
CV_GAUSSIAN, 0, 0, sig[i], sig[i] );
-
}
-
}
-
-
-
return gauss_pyr;
-
}
3.生成DOG尺度空間
Lindeberg發現高斯差分函數(Difference of Gaussian ,簡稱DOG算子)與尺度歸一化的高斯拉普拉斯函數非常近似,且
差分近似:
lowe建議採用相鄰尺度的圖像相減來獲得高斯差分圖像D(x,y,σ)來近似LOG來進行極值檢測。
D(x,y,σ) = G(x,y,kσ)*I(x,y)-G(x,y,σ)*I(x,y)
=L(x,y,kσ) - L(x,y,σ)
對高斯金字塔的每組內相鄰圖像相減,形成DOG尺度空間,這時DOG中每組有S+2層圖像
-
static IplImage*** build_dog_pyr( IplImage*** gauss_pyr, int octvs, int intvls )
-
{
-
IplImage*** dog_pyr;
-
int i, o;
-
-
dog_pyr = calloc( octvs, sizeof( IplImage** ) );
-
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_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i]),
-
IPL_DEPTH_32F, 1 );
-
cvSub( gauss_pyr[o][i+1], gauss_pyr[o][i], dog_pyr[o][i], NULL );
-
}
-
-
return dog_pyr;
-
}
4.關鍵點搜索與定位
在DOG尺度空間上,首先尋找極值點,插值處理,找到準確的極值點座標,再排除不穩定的特徵點(邊界點)
-
-
-
-
-
-
-
-
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;
-
-
features = cvCreateSeq( 0, sizeof(CvSeq), sizeof(struct feature), storage );
-
for( o = 0; o < octvs; o++ )
-
for( i = 1; i <= intvls; i++ )
-
for(r = SIFT_IMG_BORDER; r < dog_pyr[o][0]->height-SIFT_IMG_BORDER; r++)
-
for(c = SIFT_IMG_BORDER; c < dog_pyr[o][0]->width-SIFT_IMG_BORDER; c++)
-
-
if( ABS( pixval32f( dog_pyr[o][i], r, c ) ) > prelim_contr_thr )
-
if( is_extremum( dog_pyr, o, i, r, c ) )
-
{
-
feat = interp_extremum(dog_pyr, o, i, r, c, intvls, contr_thr);
-
if( feat )
-
{
-
ddata = feat_detection_data( feat );
-
if( ! is_too_edge_like( dog_pyr[ddata->octv][ddata->intvl],
-
ddata->r, ddata->c, curv_thr ) )
-
{
-
cvSeqPush( features, feat );
-
}
-
else
-
free( ddata );
-
free( feat );
-
}
-
}
-
-
return features;
-
}
4.1
尋找極值點
在DOG尺度空間上,每組有S+2層圖像,每一組都從第二層開始每一個像素點都要與它相鄰的像素點比較,看是否比它在圖像域或尺度域的所有點的值大或者小。與它同尺度的相鄰像素點有8個,上下相鄰尺度的點共有2×9=18,共有26個像素點。也就在一個3×3的立方體內進行。搜索的過程是第二層開始到倒數第二層結束,共檢測了octave組,每組S層。
-
-
-
-
-
static int is_extremum( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
-
{
-
double 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;
-
}
4.2
準確定位特徵點
以上的極值點搜索是在離散空間進行的,極值點不真正意義上的極值點。通過對空間尺度函數擬合,可以得到亞像素級像素點座標。
尺度空間的Taylor展開式:
,其中
求導並令其爲0,得到亞像素級:
對應的函數值爲:
是一個三維矢量,矢量在任何一個方向上的偏移量大於0.5時,意味着已經偏離了原像素點,這樣的特徵座標位置需要更新或者繼續插值計算。算法實現過程中,爲了保證插值能夠收斂,設置了最大插值次數(lowe 設置了5次)。同時當
時(本文閾值採用了0.04/S)
,特徵點才被保留,因爲響應值過小的點,容易受噪聲的干擾而不穩定。
對離散空間進行函數擬合(插值):
-
-
-
-
-
-
-
-
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 };
-
-
dD = deriv_3D( dog_pyr, octv, intvl, r, c );
-
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 );
-
cvGEMM( H_inv, dD, -1, NULL, 0, &X, 0 );
-
-
cvReleaseMat( &dD );
-
cvReleaseMat( &H );
-
cvReleaseMat( &H_inv );
-
-
*xi = x[2];
-
*xr = x[1];
-
*xc = x[0];
-
}
-
-
-
-
-
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;
-
double xi, xr, xc, contr;
-
int i = 0;
-
-
while( i < SIFT_MAX_INTERP_STEPS )
-
{
-
interp_step( dog_pyr, octv, intvl, r, c, &xi, &xr, &xc );
-
if( ABS( xi ) < 0.5 && ABS( xr ) < 0.5 && ABS( xc ) < 0.5 )
-
break;
-
-
c += cvRound( xc );
-
r += cvRound( xr );
-
intvl += cvRound( xi );
-
-
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++;
-
}
-
-
-
if( i >= SIFT_MAX_INTERP_STEPS )
-
return NULL;
-
-
contr = interp_contr( dog_pyr, octv, intvl, r, c, xi, xr, xc );
-
if( ABS( contr ) < contr_thr / intvls )
-
return NULL;
-
-
feat = new_feature();
-
ddata = feat_detection_data( feat );
-
feat->img_pt.x = feat->x = ( c + xc ) * pow( 2.0, 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;
-
-
return feat;
-
}
4.3
刪除邊緣效應
爲了得到穩定的特徵點,要刪除掉落在圖像邊緣上的點。一個落在邊緣上的點,可以根據主曲率計算判斷。主曲率可以通過2維的 Hessian矩陣求出;
在邊緣上的點,必定使得Hessian矩陣的兩個特徵值相差比較大,而特徵值與矩陣元素有以下關係;
令α=rβ ,所以有:
我們可以判斷上述公式的比值大小,大於閾值(lowe採用 r =10)的點排除。
-
static int is_too_edge_like( IplImage* dog_img, int r, int c, int curv_thr )
-
{
-
double d, dxx, dyy, dxy, tr, det;
-
-
-
d = pixval32f(dog_img, r, c);
-
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;
-
-
-
if( det <= 0 )
-
return 1;
-
-
if( tr * tr / det < ( curv_thr + 1.0 )*( curv_thr + 1.0 ) / curv_thr )
-
return 0;
-
return 1;
-
}
5.計算特徵點對應的尺度
-
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++ )
-
{
-
feat = CV_GET_SEQ_ELEM( struct feature, features, i );
-
ddata = feat_detection_data( feat );
-
intvl = ddata->intvl + ddata->subintvl;
-
feat->scl = sigma * pow( 2.0, ddata->octv + intvl / intvls );
-
ddata->scl_octv = sigma * pow( 2.0, intvl / intvls );
-
}
-
}
6.爲特徵點分配方向角
這部分包括:計算鄰域內梯度直方圖,平滑直方圖,複製特徵點(有輔方向的特徵點)
-
static void calc_feature_oris( CvSeq* features, IplImage*** gauss_pyr )
-
{
-
struct feature* feat;
-
struct detection_data* ddata;
-
double* hist;
-
double omax;
-
int i, j, n = features->total;
-
-
for( i = 0; i < n; i++ )
-
{
-
feat = malloc( sizeof( struct feature ) );
-
cvSeqPopFront( features, feat );
-
ddata = feat_detection_data( feat );
-
hist = ori_hist( gauss_pyr[ddata->octv][ddata->intvl],
-
ddata->r, ddata->c, SIFT_ORI_HIST_BINS,
-
cvRound( SIFT_ORI_RADIUS * ddata->scl_octv ),
-
SIFT_ORI_SIG_FCTR * ddata->scl_octv );
-
for( j = 0; j < SIFT_ORI_SMOOTH_PASSES; j++ )
-
smooth_ori_hist( hist, SIFT_ORI_HIST_BINS );
-
omax = dominant_ori( hist, SIFT_ORI_HIST_BINS );
-
add_good_ori_features( features, hist, SIFT_ORI_HIST_BINS,
-
omax * SIFT_ORI_PEAK_RATIO, feat );
-
free( ddata );
-
free( feat );
-
free( hist );
-
}
-
}
6.1
建立特徵點鄰域內的直方圖
上一步scl_octv保存了每個特徵點所在的組內尺度,下面計算特徵點所在尺度內的高斯圖像,以3×1.5×scl_octv爲半徑的區域內的所有像素點的梯度幅值與幅角;
計算公式:
在計算完所有特徵點的幅值與幅角後,使用直方圖統計。直方圖橫軸爲梯度方向角,縱軸爲對應幅值的累加值(與權重),梯度方向範圍爲0~360度,劃分爲36個bin,每個bin的寬度爲10。
下圖描述的劃分爲8個bin,每個bin的寬度爲45的效果圖:
其次,每個被加入直方圖的幅值,要進行權重處理,權重也是採用高斯加權函數,其中高斯係數爲1.5×scl_octv。通過高斯加權使特徵點附近的點有較大的權重,可以彌補部分因沒有仿射不變性而產生的不穩定問題;
即每個bin值按下面的公式累加,mag是幅值,後面爲權重;i,j,爲偏離特徵點距離:
☆程序上可以幫助你理解上面的概念:
-
static double* ori_hist( IplImage* img, int r, int c, int n, int rad,
-
double sigma )
-
{
-
double* hist;
-
double mag, ori, w, exp_denom, PI2 = CV_PI * 2.0;
-
int bin, i, j;
-
-
hist = calloc( n, sizeof( double ) );
-
exp_denom = 2.0 * sigma * sigma;
-
for( i = -rad; i <= rad; i++ )
-
for( j = -rad; j <= rad; j++ )
-
if( calc_grad_mag_ori( img, r + i, c + j, &mag, &ori ) )
-
{
-
w = exp( -( i*i + j*j ) / exp_denom );
-
bin = cvRound( n * ( ori + CV_PI ) / PI2 );
-
bin = ( bin < n )? bin : 0;
-
hist[bin] += w * mag;
-
}
-
-
return hist;
-
}
6.2
平滑直方圖
lowe建議對直方圖進行平滑,減少突變的影響。
-
static void smooth_ori_hist( double* hist, int n )
-
{
-
double prev, tmp, h0 = hist[0];
-
int i;
-
-
prev = hist[n-1];
-
for( i = 0; i < n; i++ )
-
{
-
tmp = hist[i];
-
hist[i] = 0.25 * prev + 0.5 * hist[i] +
-
0.25 * ( ( i+1 == n )? h0 : hist[i+1] );
-
prev = tmp;
-
}
-
}
6.3
在上面的直方圖上,我們已經找到了特徵點主方向的峯值omax,當存在另一個大於主峯值80%的峯值時,將這個方向作爲特徵點的輔方向,即一個特徵點有多個方向,這可以增強匹配的魯棒性。在算法上,即把該特徵點複製多份作爲新的特徵點,新特徵點的方向爲這些輔方向,其他屬性保持一致。
-
static void add_good_ori_features( CvSeq* features, double* hist, int n,
-
double mag_thr, struct feature* feat )
-
{
-
struct feature* new_feat;
-
double bin, PI2 = CV_PI * 2.0;
-
int l, r, i;
-
-
for( i = 0; i < n; i++ )
-
{
-
l = ( i == 0 )? n - 1 : i-1;
-
r = ( i + 1 ) % n;
-
-
if( hist[i] > hist[l] && hist[i] > hist[r] && hist[i] >= mag_thr )
-
{
-
bin = i + interp_hist_peak( hist[l], hist[i], hist[r] );
-
bin = ( bin < 0 )? n + bin : ( bin >= n )? bin - n : bin;
-
new_feat = clone_feature( feat );
-
new_feat->ori = ( ( PI2 * bin ) / n ) - CV_PI;
-
cvSeqPush( features, new_feat );
-
free( new_feat );
-
}
-
}
-
}
7.構建特徵描述子
目前每個特徵點具有屬性有位置、方向、尺度三個信息,現在要用一個向量去描述這個特徵點,使其具有高度的唯一特徵性。
1.lowe採用了把特徵點鄰域劃分成 d×d (lowe建議d=4) 個子區域,然後再統計每個子區域的方向直方圖(8個方向),直方圖橫軸有8個bin,縱軸爲梯度幅值(×權重)的累加。這樣描述這個特徵點的向量爲4×4×8=128維。每個子區域的寬度建議爲3×octv,octv爲組內的尺度。考慮到插值問題,特徵點的鄰域範圍邊長爲3×octv×(d+1),考慮到旋轉問題,鄰域的範圍邊長爲3×octv×(d+1)×sqrt(2),最後半徑爲:
2.把座標系旋轉到主方向位置,再次統計鄰域內所有像素點的梯度幅值與方向,計算所在子區域,並把幅值×權重累加到這個子區域的直方圖上。
算法上即統計每個鄰域的方向直方圖時,全部是相對於這個特徵點的主方向的方向。如果主方向爲30度,某個像素點的梯度方向爲50度,這時統計到該子區域直方圖上就成了20度。同時由於旋轉,這時權重也必須是按旋轉後的座標。
計算所在的子區域的位置:
權重按高斯加權函數,係數爲描述子寬度的一半,即0.5d:
-
static double*** descr_hist( IplImage* img, int r, int c, double ori,
-
double scl, int d, int n )
-
{
-
double*** hist;
-
double cos_t, sin_t, hist_width, exp_denom, r_rot, c_rot, grad_mag,
-
grad_ori, w, rbin, cbin, obin, bins_per_rad, PI2 = 2.0 * CV_PI;
-
int radius, i, j;
-
-
hist = calloc( d, sizeof( double** ) );
-
for( i = 0; i < d; i++ )
-
{
-
hist[i] = calloc( d, sizeof( double* ) );
-
for( j = 0; j < d; j++ )
-
hist[i][j] = calloc( n, sizeof( double ) );
-
}
-
-
cos_t = cos( ori );
-
sin_t = sin( ori );
-
bins_per_rad = n / PI2;
-
exp_denom = d * d * 0.5;
-
hist_width = SIFT_DESCR_SCL_FCTR * scl;
-
radius = hist_width * sqrt(2) * ( d + 1.0 ) * 0.5 + 0.5;
-
for( i = -radius; i <= radius; i++ )
-
for( j = -radius; j <= radius; j++ )
-
{
-
-
-
-
-
-
c_rot = ( j * cos_t - i * sin_t ) / hist_width;
-
r_rot = ( j * sin_t + i * cos_t ) / hist_width;
-
rbin = r_rot + d / 2 - 0.5;
-
cbin = c_rot + d / 2 - 0.5;
-
-
if( rbin > -1.0 && rbin < d && cbin > -1.0 && cbin < d )
-
if( calc_grad_mag_ori( img, r + i, c + j, &grad_mag, &grad_ori ))
-
{
-
grad_ori -= ori;
-
while( grad_ori < 0.0 )
-
grad_ori += PI2;
-
while( grad_ori >= PI2 )
-
grad_ori -= PI2;
-
-
obin = grad_ori * bins_per_rad;
-
w = exp( -(c_rot * c_rot + r_rot * r_rot) / exp_denom );
-
interp_hist_entry( hist, rbin, cbin, obin, grad_mag * w, d, n );
-
}
-
}
-
-
return hist;
-
}
每個維度上bin值累加方法,即計算一個像素的幅值對於相鄰的方向,以及位置的貢獻,dr,dc爲相鄰位置,do爲相鄰方向
,
這就是128維向量的數據,計算方法
-
static void interp_hist_entry( double*** hist, double rbin, double cbin,
-
double obin, double mag, int d, int n )
-
{
-
double d_r, d_c, d_o, v_r, v_c, v_o;
-
double** row, * h;
-
int r0, c0, o0, rb, cb, ob, r, c, o;
-
-
r0 = cvFloor( rbin );
-
c0 = cvFloor( cbin );
-
o0 = cvFloor( obin );
-
d_r = rbin - r0;
-
d_c = cbin - c0;
-
d_o = obin - o0;
-
-
-
-
-
-
-
for( r = 0; r <= 1; r++ )
-
{
-
rb = r0 + r;
-
if( rb >= 0 && rb < d )
-
{
-
v_r = mag * ( ( r == 0 )? 1.0 - d_r : d_r );
-
row = hist[rb];
-
for( c = 0; c <= 1; c++ )
-
{
-
cb = c0 + c;
-
if( cb >= 0 && cb < d )
-
{
-
v_c = v_r * ( ( c == 0 )? 1.0 - d_c : d_c );
-
h = row[cb];
-
for( o = 0; o <= 1; o++ )
-
{
-
ob = ( o0 + o ) % n;
-
v_o = v_c * ( ( o == 0 )? 1.0 - d_o : d_o );
-
h[ob] += v_o;
-
}
-
}
-
}
-
}
-
}
-
}
最後爲了去除光照的影響,對128維向量進行歸一化處理,同時設置門限,大於0.2的梯度幅值截斷
-
static void hist_to_descr( double*** hist, int d, int n, struct feature* feat )
-
{
-
int int_val, i, r, c, o, k = 0;
-
-
for( r = 0; r < d; r++ )
-
for( c = 0; c < d; c++ )
-
for( o = 0; o < n; o++ )
-
feat->descr[k++] = hist[r][c][o];
-
-
feat->d = k;
-
normalize_descr( feat );
-
for( i = 0; i < k; i++ )
-
if( feat->descr[i] > SIFT_DESCR_MAG_THR )
-
feat->descr[i] = SIFT_DESCR_MAG_THR;
-
normalize_descr( feat );
-
-
-
for( i = 0; i < k; i++ )
-
{
-
int_val = SIFT_INT_DESCR_FCTR * feat->descr[i];
-
feat->descr[i] = MIN( 255, int_val );
-
}
-
}
最後對特徵點按尺度大小進行排序,強特徵點放在前面;
這樣每個特徵點就對應一個128維的向量,接下來可以用可以用向量做以後的匹配工作了。
特徵點匹配原理後序文章會更新~
------------------------------------------------------------------------------------
在此非常感謝CSDN上幾位圖像上的大牛,我也是通過他們的文章去學習研究的,本文也是參考了他們的文章才寫成!
推薦看大牛們的文章,原理寫的很好!
http://blog.csdn.net/abcjennifer/article/details/7639681
http://blog.csdn.Net/zddblog/article/details/7521424
http://blog.csdn.net/chen825919148/article/details/7685952
http://blog.csdn.net/xiaowei_cqu/article/details/8069548