本文只记录sift特征提取过程和sift的扩展应用,并分析了opensift的代码。如果想详细理解sift的理论知识请参见Rachel-Zhang的文章。这里没分析OpenCV的代码,是因为相比之下opensift代码结构更加清楚,可读性更好。
一、SIFT提取过程
- 对图像宽高放大1倍,并假定图像已被0.5高斯滤波过,为了达到初始为1.6高斯的效果,再用 1.62−0.52−−−−−−−−−√ 高斯滤波一次,公式参考wiki-高斯模糊;
- 创建高斯金字塔,塔数 o=log2min(w,h) ,每塔含6层,高斯方差为 σ,kσ,k2σ,k3σ,k4σ,k5σ ,下一个塔时将图像宽高缩小1倍再高斯滤过;
- 高斯差分,对每个塔内各层做差,形成DoG图像;
- 对DoG图像求局部极值点并亚像素化,并用
X^=−∂2D−1∂X2∂D∂X
去迭代
X
,这里的理论本质是利用牛顿迭代法来最优化
D ,可参见李航的《统计学习方法》讲牛顿迭代法那部分; - 过滤过 D 值太小的点;
- 去除边缘响应的点,利用Hessian矩阵模和迹的关系设计滤波器,
H=[DxxDxyDxyDyy] ,这里区分一下harris角点中的自相关矩阵。图像二阶导数为 Dxx=Dx+1+Dx−1−2Dx , Dxy=(Dx+1,y+1−Dx+1,y−1−Dx−1,y+1+Dx−1,y−1)/4 ; - 计算关键点窗口下的梯度直方图,窗口大小为 3×1.5σoct ,高斯加权中方差为 1.5σoct ,其中 oct 为关键点所在塔内的尺度,注意这里的尺度已亚像素化了,并且累加梯度强度时未采用线性插值法,计算比较粗略,但在后面计算描述子时就非常精细了;
- 计算梯度直方图的峰值,取峰值的80%作为阈值,直方图中大于阈值的极值点会成为新关键点,并附上该方向加后到特征点列表中,所以一个关键点可以会多次加到关键点列表中,但关键点的附属方向不一样;
- 计算关键点旋转窗口下的梯度直方图,窗口大小为
3σoct×2√×(d+1)+12
,其中
d
表示描述子的宽度为4。计算时以关键点为中点,从
(−r,−r) 、 (−r+1,−r) 、…、 (r,r) 坐标进行旋转 [cosθsinθ−sinθcosθ]×[xy] ,然后 [x′y′] 映射到 4×4 的网格内,角度值为 tan−1dydx− 主方向角,累加梯度强度时采用了线性插值法,最后形成 4×4×8=128 维向量; - 对特征向量归一化,可以用 L2−Hys 归一化。此时所有关键点的描述子计算完成;
- 对所有关键点按照所在尺度( σ×2oct+kS ,其中 k 已被亚像素化过)进行降序排序,这是为了方便以后关键点匹配。
二、扩展知识
- SIFT算法的扩展
- PCA-SIFT,其它步骤都一样,只在特征描述阶段有所区别,具体为:对每个特征点周围选择一个大小为41*41像素的像块(主方向旋转后的),计算垂直和水平的梯度,形成39*39*2=3042的向量,对所有的特征点做成一个矩阵k*3042做PCA,选取n个特征向量做成投影矩阵。一般为36维,匹配速度更快,但区分度下降,并且延长了特征的计算时间。
- GLOH(Gradient Location-Orientation Histogram),也只是在特征描述阶段有所区别,具体为:把原来SIFT中4*4棋盘格的子块改成放射状的3个同心圆,对大的两个同心圆等分8份,共形成8+8+1=17的区域,直方图16维,再做PCA。区分度更高但是数据压缩花销时间太长。
- SIFT的一些应用
- sparse sift,做特征比对,有图像拼接的应用,图像拷贝检测;
- dense sift,用BoW模型,做图像分类,目标识别(链接以后加上);
三、代码分析
SIFT特征检测的数据结构
struct detection_data
{
int r; // 所在图像中的行号
int c; // 所在图像中的列号
int octv; // 所在尺度空间的金字塔数,octv越大图像越小
int intvl; // 所在金字塔中的层数
double subintvl; // 亚像素化时金字塔中的层数的小数位
double scl_octv; // sigma * pow(2, (intvl + subintval) / intvls)
};
SIFT关键点检测与描述的代码结构
int _sift_features(...)
{
init_img = create_init_img(...); // 图像宽高放大1倍,假定图像初始化尺度为1.6
gauss_pyr = build_gauss_pyr(...); // 建立高斯金字塔图像,存在gauss_pyr[octvs][intval+3]中,函数中为了减少使用pow(),做了优化,见补充1
dog_pyr = build_dog_pyr(...); // 建立高斯差分金字塔
features = scale_space_extrema(...) // 寻找高斯差分空间极大值点,完成了亚像素化和边缘响应
calc_feature_scales(...); // 计算关键点的各尺度,即关键点的尺度值=sigma*pow(2, octv + (intvl + subintvl) / intvls)
if(img_dbl) // 如果初始化时图像double过,则需要把关键点的坐标缩小
adjust_for_img_dbl( features );
calc_feature_oris(...); // 计算关键点主方向,此时一个关键点有多个主方向,则会复制产生新的关键点,这里计算的直方图未插值,但最后对直方图做了平滑
compute_descriptors(...); // 对关键点进行描述
cvSeqSort(...); // 对关键点按尺度降序排序
}
补充
1、计算高斯金字塔时的trick,普通情况下是利用