SIFT即尺度不变特征变换,是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。
SIFT算法的特点有:
SIFT算法可以解决的问题:
目标的自身状态、场景所处的环境和成像器材的成像特性等因素影响图像配准/目标识别跟踪的性能。而SIFT算法在一定程度上可解决:
SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。
SIFT特征使用高斯差分函数来定位兴趣点:
其中,Iσ是使用Gσ模糊的灰度图像,κ是决定相差尺度的常数。兴趣点是在图像位置和尺度变化下D(x, σ)的最大值和最小值点。这些候选位置点通过滤波去除不稳定点。基于一些准则,比如认为低对比度和位于边上的点不是兴趣点,可以去除一些候选兴趣点。
SIFT描述子使用主方向描述参考方向。主方向使用方向直方图来度量。
为了对图像亮度具有稳定性,SIFT描述子使用图像梯度。SIFT描述子在每个像素点附近选取子区域网格,在每个子区域内计算图像梯度方向直方图。每个子区域的直方图拼接起来自称描述子向量。SIFT描述子的标准设置使用4*4的子区域,每个子区域使用8个小区间的方向直方图,会产生共128个小区间的直方图。
创建sift.py文件,将下面调用可执行文件的函数添加到该文件中:
def process_image(imagename, resultname, params = "--edge-thresh 10 --peak-thresh 5"):
if imagename[-3:] != 'pgm':
im = Image.open(imagename).convert('L')
im.save('tmp.pgm')
imagename = 'tmp.pgm'
cmmd = str("sift "+imagename+" --output="+resultname+" "+params)
os.system(cmmd)
print('processed', imagename, 'to', resultname)
由于该二进制文件需要的图像格式为灰度.pgm,所以如果图像为其他格式,就需要将其转换为.pgm文件。
为了从输出文件中将特征读取到NumPy数组,使用如下函数:
def read_features_from_file(filename):
f = loadtxt(filename)
return f[:, :4], f[:, 4:]
如果在Python会话中修改描述子,则需要将输出的结果保存在特征文件中,下边的函数使用NumPy中的savetxt()函数实现这一功能:
def write_features_to_file(filename, locs, desc):
savetxt(filename, hstack((locs, desc)))
上边用到的hstack()函数通过拼接不同的行向量来实现水平堆叠两个向量的功能。
读取特征后,通过在图像上绘制出他们的位置,将其可视化。
def plot_features(im, locs, circle = False):
def draw_circle(c, r):
t = arange(0, 1.01, .01) * 2 * pi
x = r * cos(t) + c[0]
y = r * sin(t) + c[1]
plot = (x, y, 'b', linewidth = 2)
imshow(im)
if circle:
for p in locs:
draw_circle(p[:2], p[2])
else:
plot(locs[:, 0], locs[:, 1], 'ob')
axis('off')
该函数在原始图像上使用蓝色的点绘制出SIFT特征点的位置。将参数circle的选项设置成True,该函数将使用draw_circle()函数绘制出圆圈,圆圈的半径为特征的尺度。
最后,通过下边的命令绘制出SIFT特征位置的图像:
import sift
imname = 'jimei.jpg'
im1 = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'jimei.sift')
l1, d1 = sift.read_features_from_file('jimei.sift')
figure()
gray()
sift.plot_features(im, l1, circle=True)
show()
通过对比可以看出,两种方法提取到的兴趣点的位置有所不同。
对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则是使用这两个特征距离和两个最匹配特征距离的比率。相比于图像中的其他特征,该准则保证能够找到足够相似的唯一特征,并使错误的匹配数降低。
def match(desc1, desc2):
desc1 = array([d / linalg.norm(d) for d in desc1])
desc2 = array([d / linalg.norm(d) for d in desc2])
dist_ratio = 0.6
desc1_size = desc1.shape
matchscores = zeros((desc1_size[0], 1), 'int')
desc2t = desc2.T
for i in range(desc1_size[0]):
dotprods = dot(desc1[i, :], desc2t)
dotprods = 0.9999 * dotprods
indx = argsort(arccos(dotprods))
if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
matchscores[i] = int(indx[0])
return matchscores
该函数使用描述子向量间的夹角作为距离度量。在此之前,我们需要将描述子向量归一化到单位长度。因为这种匹配是单向的,即我们将每个特征向另一幅图像中的所有特征进行匹配,所以可以先计算第二幅图像兴趣点描述子向量的转置矩阵。
为了进一步增加匹配的稳健性,可以再反过来执行一次该步骤,用另外的方法匹配(从第二幅图像的特征向第一幅图像中的特征匹配。)最后仅保留同时满足这两种匹配准则的对应。
def match_twosided(desc1, desc2):
matches_12 = match(desc1, desc2)
matches_21 = match(desc2, desc1)
ndx_12 = matches_12.nonzero()[0]
for n in ndx_12:
if matches_21[int(matches_12[n])] != n:
matches_12[n] = 0
return matches_12
im1f = 'jimei_grey.jpg'
im2f = 'jimei2.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f).convert('L'))
process_image(im1f,'jimei.sift')
l1, d1 = read_features_from_file('jimei.sift')
figure()
gray()
subplot(121)
plot_features(im1, l1, circle=False)
process_image(im2f, 'jimei2.sift')
l2, d2 = read_features_from_file('jimei2.sift')
subplot(122)
plot_features(im2, l2, circle=False)
matches = match_twosided(d1, d2)
print ('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
plot_matches(im1, im2, l1, l2, matches)
show()
SIFT特征对于尺度、旋转和亮度都具有不变性,因此可以用于三维视角和噪声的可靠匹配。
SIFT特征检测基本步骤
(1)尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点;
(2)关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度;
(3)方向确定,基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性;
(4)关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。