Rob Hess的SIFT算法实现理解及注释
SIFT算法不用我多解释了,这是一个很强大的算法,主要用于图像配准和物体识别等领域,但是其计算量相比也比较大,性价比比较高的算法包括PCA-SIFT和SURF其中OpenCV提供了SURF算法,但是为了方便理解。这里给出了Rob Hess所实现的SIFT算法的实现以及注释,结合我自己的理解,如果,您有关于SIFT算法不理解的地方咱们可以一起交流一下。或者您认为不详细的地方提出来。
SIFT算法的主要实现在sift.c这个文件,其主要流程为:
(1)首先创建初始图像,即通过将图像转换为32位的灰度图,然后将图像使用三次插值来方大,之后通过高斯模糊处理
(2)在此基础上进行高斯金字塔的构建以及高斯差分金字塔的构建
(3)对图像进行极值点检测
(4)计算特征向量的尺度
(5)调整图像大小
(6)计算特征的方向
(7)计算描述子,其中包括计算二维方向直方图并转换直方图为特征描述子
首先给出sift算法的整体框架代码:
输入参数:
img为输入图像;
feat为所要提取的特征指针;
intvl指的是高斯金字塔和差分金字塔的层数;
sigma指的是图像初始化过程中高斯模糊所使用的参数;
contr_thr是归一化之后的去除不稳定特征的阈值;
curv_thr指的是去除边缘的特征的主曲率阈值;
img_dbl是是否将图像放大为之前的两倍;
descr_with用来计算特征描述子的方向直方图的宽度;
descr_hist_bins是直方图中的条数
- 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( double MIN( init_img->width, init_img->height ) ) / log(2.0) - 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 = static_cast<feature *>( calloc( n, sizeof(struct feature) ) );
- *feat = static_cast<feature *>( 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)初始化图像
输入参数:
这里不需要解释了
该函数主要用来初始化图像,转换图像为32位灰度图以及进行高斯模糊。
- static IplImage* create_init_img( IplImage* img, int img_dbl, double sigma )
- {
- IplImage* gray, * dbl;
- float 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;
- }
- }
(2)构建高斯金字塔
输入参数:
octvs是高斯金字塔的组
invls是高斯金字塔的层数
sigma是初始的高斯模糊参数,后续也通过它计算每一层所使用的sigma
- <span style="font-size:13px;">static IplImage*** build_gauss_pyr( IplImage* base, int octvs,int intvls, double sigma )
- {
- IplImage*** gauss_pyr;
- double* sig = static_cast<double *>( calloc( intvls + 3, sizeof(double)) );
- double sig_total, sig_prev, k;
- int i, o;
-
- gauss_pyr = static_cast<IplImage ***>( calloc( octvs, sizeof( IplImage** ) ) );
- for( i = 0; i < octvs; i++ )
- gauss_pyr[i] = static_cast<IplImage **>( calloc( intvls + 3, sizeof( IplImage* ) ) );
-
-
-
-
-
-
-
- sig[0] = sigma;
- k = pow( 2.0, 1.0 / intvls );
- for( i = 1; i < intvls + 3; i++ )
- {
- sig_prev = pow( k, i - 1 ) * sigma;
- sig_total = sig_prev * k;
- 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 )
- 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] );
- }
- }
-
- free( sig );
- return gauss_pyr;
- }</span>
降采样处理
输入参数:
不解释
这就是降采样,其实就是将图像通过最近邻算法缩小为原来的一半
- 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;
- }
(3)构建高斯差分金字塔
输入参数:
不解释了参见上面的说明即可
实际上差分金字塔的构成是通过对相邻层的图像进行相减获得的
- <span style="font-size:16px;">static IplImage*** build_dog_pyr( IplImage*** gauss_pyr, int octvs, int intvls )
- {
- IplImage*** dog_pyr;
- int i, o;
-
- dog_pyr = static_cast<IplImage ***>( calloc( octvs, sizeof( IplImage** ) ) );
- for( i = 0; i < octvs; i++ )
- dog_pyr[i] = static_cast<IplImage **>( 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;
- }</span>
(4)极值点检测
输入参数:
contr_thr是去除对比度低的点所采用的阈值
curv_thr是去除边缘特征的阈值
- 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;
- }
SIFT_IMG_BORDER是预定义的图像边缘;
通过和对比度阈值比较去掉低对比度的点;
而通过is_extremum来判断是否为极值点,如果是则通过极值点插值的方式获取亚像素的极值点的位置。
然后通过is_too_eage_like和所给的主曲率阈值判断是否为边缘点
*判断是否为极值点
其原理为:通过和高斯金字塔的上一层的9个像素+本层的除了本像素自己的其他的8个像素和下一层的9个像素进行比较看是否为这26个像素中最小的一个或者是否为最大的一个,如果是则为极值点。
- static int is_extremum( IplImage*** dog_pyr, int octv, int intvl, int r, int 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;
- }
*获取亚像素的极值点的位置
- 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;
- }
*获取亚像素位置中所用到的函数
- 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];
- }
*计算三维偏导数
计算在x和y方向上的偏导数,高斯差分尺度空间金字塔中像素的尺度
实际上在离散数据中计算偏导数是通过相邻像素的相减来计算的
比如说计算x方向的偏导数dx,则通过该向所的x方向的后一个减去前一个然后除以2即可求的dx
- static CvMat* deriv_3D( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
- {
- CvMat* dI;
- double dx, dy, ds;
-
- dx = ( pixval32f( dog_pyr[octv][intvl], r, c+1 ) -
- pixval32f( dog_pyr[octv][intvl], r, c-1 ) ) / 2.0;
- 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;
- }
*计算三维海森矩阵
不需要讲什么,其实就是计算二次导数,计算方法也和一次导数的计算如出一辙。
然后将结果放入到一个矩阵中去。
- 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 = ( 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;
- }
*计算插入像素的对比度
- 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 };
-
- cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );
- cvInitMatHeader( &T, 1, 1, CV_64FC1, t, CV_AUTOSTEP );
- dD = deriv_3D( dog_pyr, octv, intvl, r, c );
- cvGEMM( dD, &X, 1, NULL, 0, &T, CV_GEMM_A_T );
- cvReleaseMat( &dD );
-
- return pixval32f( dog_pyr[octv][intvl], r, c ) + t[0] * 0.5;
- }
其中cvGEMM是矩阵的通用计算函数,至于CV_GEMM_A_T是计算dD的转置矩阵放入T中
*去除边缘相应
通过计算所在特征向量的主曲率半径来判断特征是边缘的从而导致不稳定
即去除边缘响应
- 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;
- }
(4)计算特征向量的尺度
实际上是通过最初的sigma来获得每一层每一组的尺度
- 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 );
- }
- }
(5)调整图像特征坐标、尺度、点的坐标大小为原来的一半
- static void adjust_for_img_dbl( CvSeq* features )
- {
- struct feature* feat;
- int i, n;
-
- n = features->total;
- for( i = 0; i < n; i++ )
- {
- feat = CV_GET_SEQ_ELEM( struct feature, features, i );
- feat->x /= 2.0;
- feat->y /= 2.0;
- feat->scl /= 2.0;
- feat->img_pt.x /= 2.0;
- feat->img_pt.y /= 2.0;
- }
- }
(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 = static_cast<feature *>( 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 );
- }
- }
*对所给像素计算灰度方向直方图
以关键点为中心的邻域窗口内采样,并用直方图统计邻域像素的梯度
方向。梯度直方图的范围是0~360度,其中每10度一个柱,总共36个柱
- 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 = static_cast<double *>( 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;
- }
*计算所给像素的梯度大小和方向
每一个小格都代表了特征点邻域所在的尺度空间的一个像素 ,箭头方向代表了像素梯
度方向,箭头长度代表该像素的幅值也就是梯度的值
- static int calc_grad_mag_ori( IplImage* img, int r, int c, double* mag, double* ori )
- {
- double dx, dy;
-
- if( r > 0 && r < img->height - 1 && c > 0 && c < img->width - 1 )
- {
- dx = pixval32f( img, r, c+1 ) - pixval32f( img, r, c-1 );
- dy = pixval32f( img, r-1, c ) - pixval32f( img, r+1, c );
- *mag = sqrt( dx*dx + dy*dy );
- *ori = atan2( dy, dx );
- return 1;
- }
-
- else
- return 0;
- }
*对方向直方图进行高斯模糊
使用高斯函数对直方图进行平滑,减少突变的影响。
- 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;
- }
- }
*在直方图中找到主方向的梯度
利用关键点邻域像素的梯度方向分布特性为每个关键点指定方向参数,使算子具备
旋转不变性。
- static double dominant_ori( double* hist, int n )
- {
- double omax;
- int maxbin, i;
-
- omax = hist[0];
- maxbin = 0;
- for( i = 1; i < n; i++ )
- if( hist[i] > omax )
- {
- omax = hist[i];
- maxbin = i;
- }
- return omax;
- }
*将大于某一个梯度大小阈值的特征向量加入到直方图中去
n为方向的个数
- <span style="font-size:18px;">mag_thr描述子向量门限一般取0.2</span>
- 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)计算特征描述子
- static void compute_descriptors( CvSeq* features, IplImage*** gauss_pyr, int d, int n)
- {
- struct feature* feat;
- struct detection_data* ddata;
- double*** hist;
- int i, k = features->total;
-
- for( i = 0; i < k; i++ )
- {
- feat = CV_GET_SEQ_ELEM( struct feature, features, i );
- ddata = feat_detection_data( feat );
-
- hist = descr_hist( gauss_pyr[ddata->octv][ddata->intvl], ddata->r,
- ddata->c, feat->ori, ddata->scl_octv, d, n );
-
- hist_to_descr( hist, d, n, feat );
- release_descr_hist( &hist, d );
- }
- }
*计算二维方向直方图
- 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 = static_cast<double ***>( calloc( d, sizeof( double** ) ) );
- for( i = 0; i < d; i++ )
- {
- hist[i] =static_cast<double **>( calloc( d, sizeof( double* ) ) );
- for( j = 0; j < d; j++ )
- hist[i][j] = static_cast<double *>( 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.0) * ( 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;
- }
*插入一个entry进入到方向直方图中从而形成特征描述子
这个,我也不怎么明白。。。
- 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;
- }
- }
- }
- }
- }
- }
*将二维直方图转换为特征描述子
实际上是归一化描述子和转换为整数
- 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 );
- }
- }
*归一化描述子
- static void normalize_descr( struct feature* feat )
- {
- double cur, len_inv, len_sq = 0.0;
- int i, d = feat->d;