基于内容的图像检索技术是采用某种算法来提取图像中的特征,并将特征存储起来,组成图像特征数据库。当须要检索图像时,采用相同的特征提取技术提取出待检索图像的特征,并根据某种类似性准则计算获得特征数据库中图像与待检索图像的相关度,最后经过由大到小排序,获得与待检索图像最相关的图像,实现图像检索。图像检索的结果优劣取决于图像特征提取的好坏,在面对海量数据检索环境中,咱们还须要考虑到图像比对(图像类似性考量)的过程,采用高效的算法快速找到类似图像也相当重要。html
在构建图像特征库的时候,一般不会使用原始的图像特征,这是因为Raw Feature有不少冗余信息,并且维度太高在构建特征数据库和匹配的时候效率较低。因此,一般要对提取到的原始特征进行从新编码。比较经常使用的三种编码方式:算法
构建图像特征数据库,一般有如下几个步骤:数据库
图像的特征库构建完成后,在检索阶段,主要涉及到特征的类似性度量准则,排序,搜索函数
SIFT特征的讲解已经不少了,以前的博客也有过介绍。本文就借助vlfeat
对SIFT特征的提取过程作一个总结。
一个SIFT特征有两部分组成:关键点(keypoint)和对应特征描述子(Descriptor)。使用SIFT detector
进行SIFT关键点的提取,而后使用SIFT descriptor
计算关键点的描述子。也能够独立的使用SIFT detector
进行SIFT 关键点的提取,或者使用SIFT descriptor
进行别的关键点描述子的计算。编码
一个SIFT keypoint是一块圆形区域而且带有方向,使用4个参数描述该区域的几何结构:spa
一个SIFT关键点由4个参数肯定:
\[ k(x,y,r,\theta) \]
SIFT在多尺度空间检测关键点,肯定关键点的位置和尺度,保证了关键点的尺度不变性;利用关键点邻域像素的梯度分布来肯定关键点的方向,保证关键点的旋转(方向)不变性。code
SIFT在高斯尺度空间进行特征点检测,一个高斯尺度空间有图像和不一样的高斯卷积核卷积获得:
\[ L(x,y,\sigma) = G(x,y,\sigma) * I(x,y) \]
\(L(x,y,\sigma)\)表示图像的高斯尺度空间,\(\sigma\)称为尺度空间因子,它是高斯函数标准差,反映了图像的模糊程度,其值越大图像越模糊,对应的尺度也就越大。htm
而对图像关键点的检测比较好的算子是\(\Delta^2G\),高斯拉普拉斯(LoG)。可是该算子的运算量较大,因此一般使用\(DoG\)(Difference of Gaussian)来近似计算LoG。
\(DoG\)的定义为:
\[ D(x,y,\sigma) = [G(x,y,k\sigma)-G(x,y,\sigma)] * I(x,y) = L(x,y,k\sigma)-L(x,y,\sigma) \]
\(L(x,y,\sigma)\)表示高斯尺度空间,则相邻两个高斯尺度空间相减就获得来的\(DoG\)的响应图像。blog
因此为了的获得\(DoG\)的响应图像,就须要先构建高斯尺度空间。高斯尺度空间是由图像金字塔降采样结合高斯滤波获得的。高斯尺度空间分为多个组\(Octave\)(每组图像的分辨率同样),每组有多层\(Level\)(使用不一样的\(\sigma\)进行高斯模糊获得)。以一个\(512 \times 512\)的图像\(I\),其构建高斯尺度空间的步骤:(倒立的金字塔)排序
在Lowe的算法实现中\(\sigma_0 = 1.6,o_min = -1\)。\(o_min = -1\)表示金字塔的第0组是原图像上采样获得的,宽和高加一倍。
高斯图像金字塔构建完成后,将同一组的相邻两层相减就获得了\(DoG\)金字塔。
每组的层数\(S = 3\),也就是说每组能够获得两层的\(DoG\)图像,以第一组为例:其尺度为\(\sigma,k\sigma\),只有两项是没法求取极值的,须要左右两边都有尺度。因为没法比较取得极值,那么咱们就须要继续对每组的图像进行高斯模糊,使得尺度造成\(\sigma,k\sigma,k^2\sigma,k^3\sigma,k^4\sigma\)这样就能够选择中间的三项\(k\sigma,k^2\sigma,k^3\sigma\)
检测关键点,就是在\(DoG\)的图像空间中寻找极值点,每一个像素点要和其图像域(同一尺度空间)和尺度域(相邻的尺度空间)的全部相邻点进行比较,当其大于(或者小于)全部相邻点时,改点就是极值点。如图所示,中间的检测点要和其所在图像的\(3 \times 3\)邻域8个像素点,以及其相邻的上下两层的\(3\times 3\)领域18个像素点,共26个像素点进行比较。
删除两类极值点
统计关键点邻域像素的梯度方向分布来肯定关键点的方向。具体步骤以下:
计算以特征点为中心,以\(3 \times1.5 \sigma\)为半径的区域图像的幅角和幅值,每一个像点\(L(x,y)\)的梯度的模\(m(x,y)\)以及方向\(\theta(x,y)\)可经过下面公式求得
\[ m(x,y) = \sqrt{[L(x+1,y)-L(x-1,y)]^2 + [L(x,y+1)-L(x,y-1)]^2} \\ \theta(x,y) = artan\frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)-L(x-1,y)} \]
统计像素点的幅角和幅值的直方图,梯度方向的直方图的横轴是梯度方向的角度(梯度方向的范围是0到360度,直方图每36度一个柱共10个柱,或者没45度一个柱共8个柱),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。在梯度直方图中,当存在一个至关于主峰值80%能量的柱值时,则能够将这个方向认为是该特征点辅助方向。因此,一个特征点可能检测到多个方向(也能够理解为,一个特征点可能产生多个坐标、尺度相同,可是方向不一样的特征点)。
获得特征点的主方向后,对于每一个特征点能够获得三个信息\(k(x,y,r,\theta)\),即位置、尺度和方向。由此能够肯定一个SIFT特征区域,一个SIFT特征区域由三个值表示,中心表示特征点位置,半径表示关键点的尺度,箭头表示主方向。
具备多个方向的关键点能够被复制成多份,而后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,可是方向不一样的特征点。
在检测部分已经获得了SIFT关键点的位置,尺度和方向信息,生成关键点的描述子,就是使用一个向量来描述关键点及其邻域像素的信息。
由如下步骤生成描述子:
为了保证旋转不变性,将关键点为中心的邻域像素的坐标轴进行旋转,将\(x\)轴旋转相当键点主方向,以下图:
分块计算邻域内像素的梯度方向直方图,以关键点为中心的\(16\times16\)的区域内,划分\(4\times4\)个块,分别计算每一个块的梯度直方图,以下图:
每一个块的梯度直方方向直方图的计算方式,和求关键点主方向时相似:此时每一个区域的梯度直方图在0-360之间划分为8个方向区间,每一个区间为45度,即每一个种子点有8个方向的梯度强度信息,最后将获得的\(4\times4\times8=128\)维的特征向量。
vlfeat
是一个开源的轻量级的计算机视觉库,主要实现图像局部特征的提取和匹配以及一些经常使用的聚类算法。其对sift特征提取的各个步骤进行了封装,使用的方法以下:
vl_sift_new
初始化VlSiftFilt
,设置sift提取时参数信息,如:图像的大小,Octave的个数,每一个Octave的中的层数,起始的Octave的index. 各个参数的具体含义能够参考上面sift特征提取的方法。vl_sift_set_peak_thresh
设置接受极值点是一个关键点的最小对比度。 该值越小,提取到的关键点就越多。y vl_sift_set_edge_thresh()
设置一个极值点是在边缘上的阈值。 该值越小,提取到的关键点就越多。这两个参数对最终提取到的特征点个数有很大的影响。
vl_sift_process_first_octave()
和vl_sift_process_next_octave()
来计算下一个DoG尺度空间。vl_sift_detect
进行关键点提取vl_sift_calc_keypoint_orientations
计算关键点的方向,可能多于一个l_sift_calc_keypoint_descriptor
计算每一个方向的特征描述子。vl_sift_delete
释放资源。具体代码以下:
// 初始化 const string file = "../0.jpg"; Mat img = imread(file,IMREAD_GRAYSCALE); Mat color_img = imread(file); Mat float_img; img.convertTo(float_img,CV_32F); int rows = img.rows; int cols = img.cols; VlSiftFilt* vl_sift = vl_sift_new(cols,rows,4,3,0); vl_sift_set_peak_thresh(vl_sift,0.04); vl_sift_set_edge_thresh(vl_sift,10); vl_sift_pix *data = (vl_sift_pix*)(float_img.data); vector<VlSiftKeypoint> kpts; vector<float*> descriptors; vl_sift_extract(vl_sift,data,kpts,descriptors); /* Extract sift using vlfeat parameters: vl_sfit, VlSiftFilt* data , image pixel data ,to be convert to float kpts, keypoint list descriptors, descriptor. Need to free the memory after using. */ void vl_sift_extract(VlSiftFilt *vl_sift, vl_sift_pix* data, vector<VlSiftKeypoint> &kpts,vector<float*> &descriptors) { // Detect keypoint and compute descriptor in each octave if(vl_sift_process_first_octave(vl_sift,data) != VL_ERR_EOF){ while(true){ vl_sift_detect(vl_sift); VlSiftKeypoint* pKpts = vl_sift->keys; for(int i = 0; i < vl_sift->nkeys; i ++) { double angles[4]; // 计算特征点的方向,包括主方向和辅方向,最多4个 int angleCount = vl_sift_calc_keypoint_orientations(vl_sift,angles,pKpts); // 对于方向多于一个的特征点,每一个方向分别计算特征描述符 // 而且将特征点复制多个 for(int i = 0 ; i < angleCount; i ++){ float *des = new float[128]; vl_sift_calc_keypoint_descriptor(vl_sift,des,pKpts,angles[0]); descriptors.push_back(des); kpts.push_back(*pKpts); } pKpts ++; } // Process next octave if(vl_sift_process_next_octave(vl_sift) == VL_ERR_EOF) { break ; } } } }
vlfeat中sift提取接受的是float
类型的数据,因此要先将读到的数据图像转换为float
。
和OpenCV中的sift提取的对比结果以下:
几年前写过一篇关于SIFT的文章,SIFT特征详解 当时可能是从理论上。如今在作图像检索的时候,发现仍是有不少东西理解的不是很清晰,好比:关键点的多个方向,不稳定极值点的剔除以及梯度方向直方图计算等等。
正在作一个图像检索的项目,陆续将项目的中学到一些知识总结下来,下一篇是关于均值聚类的,对提取到的图像特征进行聚类生成视觉特征(Visul Feature)表。本系列的代码会陆续更新到Github上,欢迎start/fork 。