简介
SIFT特征描述子是David G. Lowe 在2004年的ijcv会议上发表的论文中提出来的,论文名为<<Distinctive Image Featuresfrom Scale-Invariant Keypoints>>。这是一个很强大的算法,主要用于图像配准和物体识别等领域,但是其计算量相比也比较大,性价比比较高的算法包括PCA-SIFT和SURF,其中OpenCV提供了SURF算法,OpenCV2.3版本后的SIFT算法是Rob Hess的源码,github的项目地址是http://blogs.oregonstate.edu/hess/code/sift/
SIFT特征描述子的优点:
- 提取出来的特征对图像大小的变化和旋转具有不变形。
- 提取出来的特征对光照和3D相机拍摄视角(差不多就是仿射变换)具有部分不变性。
- 在空间域和频率域的局部特性非常好,降低了光照,噪声及一些其他杂乱干扰的影响。
- 从一幅图像中可以比较快速提取出大量的特征,并且特征是高度唯一性的,可以在很多特征多唯一识别,因此在图像拼接,物体识别中很有用。
- 尺度空间极值检测:建立高斯差分金字塔,搜索所有尺度上的极值点。
- 关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
- 方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
- 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。
算法主要流程
- 首先创建初始图像,即通过将图像转换为32位的灰度图,然后将图像使用三次插值来方大,之后通过高斯模糊处理
- 在此基础上进行高斯金字塔的构建以及高斯差分金字塔的构建
- 对图像进行极值点检测
- 计算特征向量的尺度
- 调整图像大小
- 计算特征的方向
- 计算描述子,其中包括计算二维方向直方图并转换直方图为特征描述子
- 对描述子进行排序
算法框架
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 );
}
struct feature
{
double x; /**< x coord */
double y; /**< y coord */
double a; /**< Oxford-type affine region parameter */
double b; /**< Oxford-type affine region parameter */
double c; /**< Oxford-type affine region parameter */
double scl; /**< scale of a Lowe-style feature */
double ori; /**< orientation of a Lowe-style feature */
int d; /**< descriptor length */
double descr[FEATURE_MAX_D]; /**< descriptor */
int type; /**< feature type, OXFD or LOWE */
int category; /**< all-purpose feature category */
struct feature* fwd_match; /**< matching feature from forward image */
struct feature* bck_match; /**< matching feature from backmward image */
struct feature* mdl_match; /**< matching feature from model */
CvPoint2D64f img_pt; /**< location in image */
CvPoint2D64f mdl_pt; /**< location in model */
void* feature_data; /**< user-definable data */
};
/** holds feature data relevant to detection */
struct detection_data
{
int r;
int c;
int octv;
int intvl;
double subintvl;
double scl_octv;
};
以及一些常量和宏定义
/** default number of sampled intervals per octave */
#define SIFT_INTVLS 3
/** default sigma for initial gaussian smoothing */
#define SIFT_SIGMA 1.6
/** default threshold on keypoint contrast |D(x)| */
#define SIFT_CONTR_THR 0.04
/** default threshold on keypoint ratio of principle curvatures */
#define SIFT_CURV_THR 10
/** double image size before pyramid construction? */
#define SIFT_IMG_DBL 1
/** default width of descriptor histogram array */
#define SIFT_DESCR_WIDTH 4
/** default number of bins per histogram in descriptor array */
#define SIFT_DESCR_HIST_BINS 8
/* assumed gaussian blur for input image */
#define SIFT_INIT_SIGMA 0.5
/* width of border in which to ignore keypoints */
#define SIFT_IMG_BORDER 5
/* maximum steps of keypoint interpolation before failure */
#define SIFT_MAX_INTERP_STEPS 5
/* default number of bins in histogram for orientation assignment */
#define SIFT_ORI_HIST_BINS 36
/* determines gaussian sigma for orientation assignment */
#define SIFT_ORI_SIG_FCTR 1.5
/* determines the radius of the region used in orientation assignment */
#define SIFT_ORI_RADIUS 3.0 * SIFT_ORI_SIG_FCTR
/* number of passes of orientation histogram smoothing */
#define SIFT_ORI_SMOOTH_PASSES 2
/* orientation magnitude relative to max that results in new feature */
#define SIFT_ORI_PEAK_RATIO 0.8
/* determines the size of a single descriptor orientation histogram */
#define SIFT_DESCR_SCL_FCTR 3.0
/* threshold on magnitude of elements of descriptor vector */
#define SIFT_DESCR_MAG_THR 0.2
/* factor used to convert floating-point descriptor to unsigned char */
#define SIFT_INT_DESCR_FCTR 512.0
/* returns a feature's detection data */
#define feat_detection_data(f) ( (struct detection_data*)(f->feature_data) )
让我们看一看真正的 _sift_features 函数
输入参数:
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;
/* check arguments */
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 );
/* 算法第二步,建立高斯差分金字塔(也就是所谓的尺度空间)最顶层4pixels */
octvs = log( MIN( init_img->width, init_img->height ) ) / log(2) -2; //octvs是整个金字塔层数
gauss_pyr = build_gauss_pyr( init_img, octvs, intvls, sigma ); //建立高斯金字塔
dog_pyr = build_dog_pyr( gauss_pyr, octvs, intvls ); //建立高斯差分金字塔,octvs是金字塔层数,intvls是层数(每层金字塔有几张图片)
storage = cvCreateMemStorage( 0 );
/* 算法第三步,寻找尺度空间极值,contr_thr是去除对比度低的点所采用的阀值,curv_thr是去除边缘特征所采取的阀值 */
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;
}
}
算法第2步:建立尺度空间(高斯差分金字塔)
关于图像金字塔和尺度空间的讨论,我在下面两篇文章中有涉及
这里,我再好好整理一下。
也就是说:对一张图像用不同的sigma进行高斯模糊,再相减就得到了高斯差分.
因此,建立尺度空间的简述过程如下:先建立高斯金字塔,过程如下:首先建立第1层,对原图分别用不同的sigma进行高斯模糊。其中sigma按如下规则获取:
k = pow( 2.0, 1.0 / intvls );
sig[0] = sigma;
sig[1] = sigma * sqrt( k*k- 1 );
for (i = 2; i < intvls +3; i++) // intvls是每层的照片数! 为什么+3等下介绍
sig[i] = sig[i-1] * k;

为了在每一组中检测S个尺度的极值点,则DOG金字塔每组需S+2层图像,这是因为一个点不仅要跟周围8个点比较,同时也要跟前一张后一张的各9各点比较。而DOG金字塔由高斯金字塔相邻两层相减得到,则高斯金字塔每组需S+3层图像。如下:
下面用组来表示高斯金字塔层数,用层数来表示每一组的图片个数,也就是s
按照刚才的描述,我们首先建立高斯金字塔
输入参数:
octvs是高斯金字塔的组
invls是高斯金字塔的层数
sigma是初始的高斯模糊参数,后续也通过它计算每一层所使用的sigma
static IplImage*** build_gauss_pyr( IplImage* base,int octvs,
int intvls, double sigma )
{
IplImage*** gauss_pyr;
const int _intvls = intvls;
double sig[_intvls+3], sig_total, k; //+3的原因在之前已经讨论过
int i, o;