本文主要讲解SIFT特征点的提取及匹配的原理,并与Harris角点检测器进行对比,比较算法优良性。本文还有一个用局部描述子对地理标记图像进行匹配的例子,并对匹配后的图像进行连接可视化(运用了pydot的工具包)。
David Lowe提出的SIFT(Scale-Invariant Feature Transform)是最成功的图像局部描述子之一。SIFT特征包括兴趣点检测器和描述子,其中SIFT描述子具有非常强的稳健性,这在很大程度上也是SIFT特征能够成功和流行的主要原因。SIFT特征对于尺度、旋转、亮度都具有不变性,下面会详细介绍其原理。
图像的尺度空间是这幅图像在不同解析度下的表示。一幅图像可以产生几组(octave)图像,一组图像包括几层图像。构造尺度空间传统的方法即构造一个高斯金字塔,原始图像作为最底层,然后对图像进行高斯模糊再降采样(2倍)作为下一层图像(即尺度越大,图像越模糊),循环迭代下去。
对图像进行尺度变换,以满足特征点的尺度不变性,保留图像轮廓和细节。
DoG(Difference of Gaussian)函数:
该函数在计算上只需相邻高斯平滑后图像相减,因此简化了计算。
对应DOG算子,需构建DOG金字塔。
可以通过高斯差分图像看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。)DOG图像描绘的是目标的轮廓。
从图像中可看出越位于金字塔上层(差分越大)图像越模糊,但能得到图像的轮廓;反之,越处于金字塔底端(差分越小)图像的细节就越清晰。即保留了图像的轮廓和细节。
特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点,每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。如下图中检测特征点与自身尺度层中其余8个点和在其之上及之下的两个尺度层9个点进行比较,共26个点,图中标记‘x’的像素点的特征值若大于周围像素则可确定该点为该区域的特征点。
由于DoG函数在图像边缘有较强的边缘响应,因此需要排除边缘响应。DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率可以通过计算在该点位置尺度的2×2的Hessian矩阵得到,导数由采样点相邻差来估计:
Dxx 表示DOG金字塔中某一尺度的图像x方向求导两次。
D的主曲率和H的特征值成正比。令 α ,β为特征,则
该值在两特征值相等时达最小。Lowe论文中建议阈值T为1.2,即
时保留关键点,反之剔除。
通过尺度不变性求极值点,可以使其具有缩放不变的性质。而利用关键点邻域像素的梯度方向分布特性,可以为每个关键点指定方向参数方向,从而使描述子对图像旋转具有不变性通过求每个极值点的梯度来为极值点赋予方向。
像素点的梯度表示:
确定关键点的方向采用梯度直方图统计法,统计以关键点为原点,一定区域内的图像像素点对关键点方向生成所作的贡献。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向
关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值80%能量的峰值时,则将这个方向认为是该关键点的辅方向。
这可以增强匹配的鲁棒性,Lowe的论文指出大概有15%关键点具有多方向,但这些点对匹配的稳定性至为关键。
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特征点由4个种子点的信息所组成。
Lowe实验结果表明:描述子采用4×4×8=128维向量表征,综合效果最优(不变性与独特性)。
分别对模板图(参考图,reference image)和实时图(观测图,observation image)建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似性度量采用欧式距离。欧氏距离越短,代表两个特征点的匹配度越好。
关键点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一般都采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的关键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻近的原图像特征点。
下面给出算法的主要代码,及与Harris角点检测算法的比对结果。
为了计算图像的SIFT特征,我们需要用到开源工具包VLFeat。VLFeat的下载地址:www.vlfeat.org(运用该工具包的过程中可能会出现一些问题,详细的解决方法下次再另说。)
#SIFT算法
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift
im1f = 'C:/Users/ace/Pictures/ims/17.jpg'
im2f = 'C:/Users/ace/Pictures/ims/18.jpg'
im1 = array(Image.open(im1f).convert('L'))
im2 = array(Image.open(im2f).convert('L'))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
sift.plot_features(im1, l1, circle=False)
sift.process_image(im2f, 'out_sift_2.txt')
l2, d2 = sift.read_features_from_file('out_sift_2.txt')
subplot(122)
sift.plot_features(im2, l2, circle=False)
#matches = sift.match(d1, d2)
matches = sift.match_twosided(d1, d2)
print ('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
sift.plot_matches(im1, im2, l1, l2, matches, show_below=True)
show()
#Harris算法
from pylab import *
from PIL import Image
from PCV.localdescriptors import harris
from PCV.tools.imtools import imresize
im1 = array(Image.open("C:/Users/ace/Pictures/ims/17.jpg").convert("L"))
im2 = array(Image.open("C:/Users/ace/Pictures/ims/18.jpg").convert("L"))
wid = 5
harrisim = harris.compute_harris_response(im1, 5)
filtered_coords1 = harris.get_harris_points(harrisim, wid+1)
d1 = harris.get_descriptors(im1, filtered_coords1, wid)
harrisim = harris.compute_harris_response(im2, 5)
filtered_coords2 = harris.get_harris_points(harrisim, wid+1)
d2 = harris.get_descriptors(im2, filtered_coords2, wid)
print ('starting matching')
matches = harris.match_twosided(d1, d2)
figure()
gray()
harris.plot_matches(im1, im2, filtered_coords1, filtered_coords2, matches)
show()
比对分析:
从匹配的准确率看来,harris算法的结果存在一些不正确的匹配,而sift算法几乎没有匹配错误。显而易见地,sift算法具有较高的准确度及稳健性(对图片亮度不敏感);
从时间上来说,sift算法的效率远高与harris算法,所以sift算法具有更高的计算效率;
总的来说,两个算法由于选择特征点的方法不同,而显示出了差异。sift算法综合了许多前人已经整理出的算法的优点,最后才得到了现在的算法(更优)。
图像拍摄于集美大学,其标志性建筑——尚大楼,拍摄了多张位于不同角度及不同光线下的图像,还有另外几张其他校内建筑的图像。共取了19张图像进行匹配地理标记。
由于这个例子会对匹配后的图像进行连接可视化,所以需要运用pydot工具包来实现在一个图中用边线表示它们之间是相连的。(因为pydot工具包需要跟其他工具包一起安装才能用,所以这里直接放上别人写的安装链接:https://blog.csdn.net/wuchangi/article/details/79589542)
下面是实现代码:
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
images_path = "C:/Users/ace/Pictures/ims/"
path = "C:/Users/ace/Pictures/ims/"
# list of images-filenames
imlist = imtools.get_imlist(images_path)
nbr_images = len(imlist)
# extract features
featlist = [imname[:-3] + 'sift' for imname in imlist]
for i, imname in enumerate(imlist):
sift.process_image(imname, featlist[i])
matchscores = zeros((nbr_images, nbr_images))
for i in range(nbr_images):
for j in range(i, nbr_images): # only compute upper triangle
print ('comparing ', imlist[i], imlist[j])
l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[j])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
print ('number of matches = ', nbr_matches)
matchscores[i, j] = nbr_matches
print ("The match scores is: \n", matchscores)
# copy values
for i in range(nbr_images):
for j in range(i + 1, nbr_images): # no need to copy diagonal
matchscores[j, i] = matchscores[i, j]
#可视化
threshold = 2 # min number of matches needed to create link
g = pydot.Dot(graph_type='graph') # don't want the default directed graph
for i in range(nbr_images):
for j in range(i + 1, nbr_images):
if matchscores[i, j] > threshold:
# first image in pair
im = Image.open(imlist[i])
im.thumbnail((100, 100))
filename = path + str(i) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(i), fontcolor='transparent', shape='rectangle', image=filename))
# second image in pair
im = Image.open(imlist[j])
im.thumbnail((100, 100))
filename = path + str(j) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(j), fontcolor='transparent', shape='rectangle', image=filename))
g.add_edge(pydot.Edge(str(i), str(j)))
g.write_png('connect-images.png')
运行结果:
可以从结果看出,匹配的最终结果并不很好,一张图出现了重复匹配的现象。可能是图像压缩得太小了,失去了图像原本的特征,导致匹配结果出错。