SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,它对物体的尺度变化,刚体变换,光照强度和遮挡都具有较好的稳定性,可在图像中检测出关键点,是一种局部特征描述子。SIFT 算法被认为是图像匹配效果好的方法之 一算法实现特征匹配主要有三个流程:
①特征点提取;
②特征点主方向确定;
③特征点描述;
④特征点匹配;
其中特征点提取主要包括生成高斯差分(DifferenceofGaussian,DOG)尺度空间、寻找局部极值点、特征点筛选、确定特征点方向;特征点匹配主要包括根据描述子相似性进行匹配、匹配对比值提纯、RANSAC方法剔除离群匹配对。
两种图像在匹配的时候可能因为拍摄的距离、拍摄的角度问题,会导致在特征点提取的时候差异很大,所以我们希望SIFT的特征点可以具有尺度不变性和方向不变性。
对于我们人类来说,在一定的范围内,无论物体远还是近,我们都可以一眼分辨出来,当一个人从远处走来的时候,我们可以从轮廓就判断出这是一个人,但是还看不清细节,当他走进的时候,我们才会去注意其他的细节特征。而计算机没有主观意识去识别哪里是特征点,它能做的,只是分辨出变化率最快的点。彩色图是三通道的,不好检测突变点。需要将RGB图转换为灰度图,此时灰度图为单通道,灰度值在0~255之间分布。而且当图像放大或者缩小时,它读取的特征点与原先可能差异很大,所以其中一个办法就是把物体的尺度空间图像集合提供给计算机,让它针对考虑不同尺度下都存在的特征点。
尺度空间的基本思想为:高斯核是唯一可以产生多尺度空间的核,在输入的图像模型中,通过高斯模糊函数连续的对尺度进行参数变换,最终得到多尺度空间序列。图像中某一尺度的空间函数 L(x ,y, σ)由可变参数的高斯函数 G(x, y, σ)和原输入图像I(x ,y)卷积得出:
其中,σ 表示为尺度空间因子,σ 越小,反应的局部点越清晰。反之 σ 越大,图像越模糊,越不能反应出图像的细节。
在早期图像的多尺度通常使用图像金字塔表述形式。图像金字塔是同一图象在不同的分辨率下得到的一组结果,其生成过程一半包含两个步骤:
(1)对图像做高斯平滑(高斯模糊)。
(2)对图像做降采样,降维采样后得到一系列尺寸不断缩小的图像。
传统的SIFT算法是通过建立高斯差分函数(DOG) 方法来提取特征点。首先,在不同尺度参数的组数中,高斯差分图像是由某一相同尺度层的相邻图像作差值 得出。然后,将得到的差分图像与原图像 I(x, y)做卷积得到公式(3)的 DOG 函数:
从上式可以知道,将相邻的两个高斯空间的图像相减就得到了DoG的响应图像。为了得到DoG图像,先要构造高斯尺度空间,而高斯的尺度空间可以在图像金字塔降维采样的基础上加上高斯滤波得到,也就是对图像金字塔的每层图像使用不同的尺度参数σ进行高斯模糊,使每层金字塔有多张高斯模糊过的图像,然后我们把得到的同一尺寸大小的图像划分为一组。
特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点, 每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。
中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个 点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
有些极值点的位置是在图像的边缘位置的,因为图像的边缘点很难定位,同时也容易受到噪声的干扰,我们把这些点看做是不稳定的极值点,需要进行去除。边缘梯度的方向上主曲率值比较大,而沿着边缘方向则主曲率值较小。候选特征点的DoG函数D(x)的主曲率与2×2Hessian矩阵H的特征值成正比,Dxx表示DOG金字塔中某一尺度的图像x方向求导两次:在在边缘梯度的方向上主曲率值比较大,而沿着边缘方向则主曲率值较小。设α=λmax=Dxx为H的最大特征值,β=λmin=Dyy为H的最小特征值,则有:
Tr(H) 为矩阵H的迹,Det(H)为矩阵H的行列式.当两个特征值相等时其值最小,因此为了检测主曲率是否在某个阈值Tr下,只需检测该比值与阈值T的大小关系,过滤不稳定的边缘响应点。
所以,特征点提取可以概括为以下几个步骤:
经过上面的步骤已经找到了在不同尺度下都存在的特征点,为了实现图像旋转不变性,需要给特征点的方向进行赋值。利用特征点邻域像素的梯度来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。
对于已经检测到的特征点,我们可以得到该特征点的尺度值σ ,所以确定该参数可以得到该尺度下的高斯图像:
我们通过每个极值点的梯度来为极值点赋予方向,梯度幅值等于上下两点像素值差的平方加上左右两点像素值差的平方,梯度方向则为上下两点像素值差与左右两点像素值差的商。
当然,我们的目标是用特征点邻域像素的梯度来确定其方向参数,确定关键点的方向采用梯度直方图统计法,统计以关键点为原点,一定区域内的图像像素点对关键点方向生成所作的贡献。
通过以上的图可以看出,梯度方向角为横轴刻度,取45度为一个单位,那么横轴就有8个刻度;纵轴是对应梯度的幅值累加值。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向。
关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值 80%能量的峰值时,则将这个方向认为是该关键点的辅方向。
图中所示的是180度方向为主方向,45度方向可以看做是辅方向。仅有15%的关键点被赋予多个方向,但可以明显的提高关键点匹配的稳定性 。
通过以上步骤,对于每一个关键点,拥有三个信息:位置、尺度以及方向。接下来就是为每个关键点建立一个描述符,使其不随各种变化而改变,比如光照变化、视角变化等等。并且描述符应该有较高的独特性,以便于提高特征点正确匹配的概率。
1.3.1 确定描述子计算区域和并校正主方向
在计算描述子之前,我们需要先确定计算区域,Lowe实验结果表明:
描述子采用4×4×8=128维向量表征,综合效果最优 (不变性与独特性)。
为了保证特征矢量具有旋转不变性,需要以特征点为中心,将特征点附近邻域内图像梯度的位置和方向旋转一个方向角θ,即将原图像x轴转到与主方向相同的方向。
1.3.2 生成描述子
下图所示,左图的中央为当前关键点的位置,每个小格代表为关键点邻域所在尺度空间的一个像素,求取每个像素的梯度幅值与梯度方向,箭头方向代表该像素的梯度方向,长度代表梯度幅值,然后利用高斯窗口对其进行加权运算。最后在每个3σ×3σ的小块上(图像中每个正方形的区域的边长为4)绘制8个方向的梯度直方图,计算每个梯度方向的累加值,即可形成一个种子点,如右图所示。每个特征点由4个种子点组成,每个种子点有8个方向的向量信息。这种邻域方向性信息联合增强了算法的抗噪声能力,同时对于含有定位误差的特征匹配也提供了比较理性的容错性。
在实际的计算过程中,为了增强匹配的稳健性,Lowe建议对每个关键点使用4×4共16个种子点来描述,这样一个关键点就可以产生128维的SIFT特征向量。
这个128维的SIFT特征向量就像是我们每个人的身份证一样,拥有绝对标识,几乎不可能重复,在这里可以对128维向量进行归一化处理,可以去除光照变化的影响。我们对模板图和目标图分别建立描述子集合。特征点的匹配是通过两点集合内关键点描述子的比对来完成,描述子的相似度量采用欧氏距离。假设如下:
最终留下来的配对的关键点描述子,需要满足条件:
上一期做了一个Harris的角点检测和特征匹配,这次对于两个算法进行特征匹配的比较,照片拍摄于我的本科大学—集美大学,建筑物为本部校区的延奎图书馆。先看一下图书馆的风景原图,非常漂亮哈哈:
这里我先把Harris特征匹配和SIFT特征的代码分享一下:
Harris特征匹配代码:
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import harris
from PCV.tools.imtools import imresize
"""
This is the Harris point matching example in Figure 2-2.
"""
# Figure 2-2上面的图
#im1 = array(Image.open("02.jpg").convert("L"))
#im2 = array(Image.open("../data/crans_2_small.jpg").convert("L"))
# Figure 2-2下面的图
im1 = array(Image.open("../pic/11.jpg").convert("L"))
im2 = array(Image.open("../pic/12.jpg").convert("L"))
# resize to make matching faster
im1 = imresize(im1, (im1.shape[1]/2, im1.shape[0]/2))
im2 = imresize(im2, (im2.shape[1]/2, im2.shape[0]/2))
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()
suptitle("Harris matching")
gray()
harris.plot_matches(im1, im2, filtered_coords1, filtered_coords2, matches)
show()
SIFT特征匹配代码:
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift
if len(sys.argv) >= 3:
im1f, im2f = sys.argv[1], sys.argv[2]
else:
im1f = '../pic/11.jpg'
im2f = '../pic/12.jpg'
# im1f = '../data/crans_1_small.jpg'
# im2f = '../data/crans_2_small.jpg'
# im1f = '../data/climbing_1_small.jpg'
# im2f = '../data/climbing_2_small.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
suptitle("SIFT feature points")
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)
suptitle("SIFT matching")
show()
下面对几组不同角度拍摄图分别进行Harris和SIFT的特征匹配比对:
①–Harris特征匹配:
②—Harris特征匹配:
③—SIFT特征匹配:
这三组图明显可以看出,Harris的匹配点对数远远多于SIFT匹配点对数,但是精确度却不是很高,出现较多匹配错误的情况,但是可以发现在SIFT的特征匹配中,匹配的关键点很少,但是准确率非常高,关键点少的原因之音可能是最后的阈值设置过高,符合条件的关键点比较少。可以看到,拍的两张图片分别是左右半边,图书馆是对称结构,Harris出现匹配错位的情况非常多,但是在SIFT匹配中几乎没有出现错误的情况。可以看出,在多角点的复杂建筑图像中,SIFT的匹配性能要高于Harris。
最后,我们来使用局部描述子来匹配带有地标的图像。同样在学校里选择了图书馆的正面和侧面两个不同的视点进行拍摄。
延奎图书馆正面:
延奎图书馆侧面:
总共拍摄了15张图照片,将使用局部描述算子对同一地理位置点拍摄的图像进行分类。这里我们需要下载一个Pydot的库,我们严格按照以下步骤来进行安装,否则顺序不对会出错:
1. 安装graphviz-2.28.0.msi
下载地址:http://download.csdn.NET/detail/shouwangzhelv/9492517
配置系统环境变量:C:\Program Files (x86)\Graphviz 2.28\bin添加到path中
2. 安装pyparsing-1.5.7.win32-py2.7.exe
https://pypi.python.org/pypi/pyparsing/1.5.7
3. pydot安装
下载地址:https://pypi.python.org/pypi/pydot2/1.0.33然后安装,
1)解压
2)在当前文件夹内按住shift键,右击选择运行PowerShell窗口,进入该路径。
3) 执行python setup.py install
然后我们开始运行以下代码:
匹配地理标记图像
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
""" This is the example graph illustration of matching images from Figure 2-10.
To download the images, see ch2_download_panoramio.py."""
download_path = r"D:\Python\pcv-book-code-master\pic" # set this to the path where you downloaded the panoramio images
path = r"D:\Python\pcv-book-code-master\pic" # path to save thumbnails (pydot needs the full system path)
# list of downloaded filenames
imlist = imtools.get_imlist(download_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('whitehouse.png')