python—SIFT、Harris的简单实现与地理标记图像匹配

一、SIFT算法原理

这里关于SIFT算法的描述是我参考网上的资料提了一些加上自己的理解来作为大概的描述,若是想更具体地了解SIFT的相关原理可以参考以下网址:https://www.cnblogs.com/wangguchangqing/p/4853263.html

1.概述

SIFT算法的主要思想是在尺度空间寻找极值点,然后对极值点进行过滤,找出稳定的特征点,最后在每个稳定的特征点周围提取图像的局部特性,形成局部描述子并将其用在以后的匹配中。SIFT 算法有以下几个主要步骤:
(1)尺度空间的极值检测。
(2)特征点定位。
(3)特征方向赋值。
(4)特征点描述。

2.构建尺度空间

尺度空间的主要思想是对原始图像进行尺度变换,并满足特征点的尺度不变性。
搜索所有尺度空间上的图像,通过高斯微分函数来识别潜在的对尺度和选择不变的兴趣点。学过数字图像处理应该知道,通过将图像与高斯函数做卷积运算能够对图像做相应程度的模糊,高斯卷积核是实现尺度变换的唯一线性核,所以一幅二维图像的尺度空间可定义为函数L(x,y,σ),其中G(x,y,σ)为尺度可变高斯函数:
在这里插入图片描述
其中(x,y)为图像点的像素坐标,I(x,y)为该点数据。σ是高斯正态分布的方差,称为尺度空间因子,其反映了图像被平滑的程度,其值越小表征图像被平滑程度越小,相应尺度越小,通俗点说,方差越小,图像越清晰,细节部分便比较清楚,方差越大,则图像越模糊,体现的图像轮廓比较清楚。
为了有效地检测图像中一定尺度空间的稳定关键点,提出了利用尺度空间中DoG(Difference-of-Gaussian)极值作为判断依据,表达式为D(x,y,σ),DoG算子定义为两个不同尺度的高斯核的差分,简单点理解就是将尺度空间看为一层一层的平面,相邻两层之间有一定的距离,这些距离便是差分,根据下面的构造图会更好地理解。设 k 为两相邻尺度空间的比例因子,则DoG算子定义如下:
在这里插入图片描述
D(x,y,σ)构造方式如下图所示:
python—SIFT、Harris的简单实现与地理标记图像匹配_第1张图片
3.检测DoG尺度空间极值点

极值点,顾名思义就是在一定的范围内最大或者最小的点,为了寻找尺度空间的极值点,每个像素点要和其同一尺度空间和相邻的尺度空间的所有相邻点进行比较,即和当前所在的那一层和相邻的两层上面的像素点做比较,当某一点大于或者小于所有相邻点时,该点就是极值点。
如下图所示,中间的检测点要和其所在图像的3×3邻域8个像素点,以及其相邻的上下两层的3×3领域18个像素点,共26个像素点进行比较,以确保在尺度空间和二维图像空间都能检测到局部极值。图中用红色圈出标记为叉号的像素若比相邻26个像素的DoG值都大或都小,则该点将作为一个局部极值点,记下它的位置和对应尺度。
python—SIFT、Harris的简单实现与地理标记图像匹配_第2张图片
4.删除不好的极值点

在上一步中算法搜索出了尺度空间的极值点,但并不是所有的极值点都可以作为合适的关键点,因为通过比较检测得到的DoG的局部极值点是在离散的空间搜索得到的,由于离散空间是对连续空间采样得到的结果,因此在离散空间找到的极值点不一定是真正意义上的极值点,因此要设法将不满足条件的点剔除掉。那么以下两种方法便是针对局部、尺度和主曲率比率与邻近像素进行详细比对,去除低对比度(敏感噪声)的关键点或不稳定的局部边缘响应点。

(1)剔除低对比度的特征点

设候选特征点为 x,其偏移量定义为 Δx,其对比度为D(x)的绝对值∣D(x)∣,对D(x)应用泰勒展开式:
在这里插入图片描述
由于x是D(x)的极值点,所以对上式求导并令其为0,得到:
在这里插入图片描述
然后再把求得的Δx代入到D(x)的泰勒展开式中
在这里插入图片描述
设对比度的阈值为 T,若∣D(x^)∣≥T,则该特征点保留,否则剔除掉。

(2)剔除不稳定的边缘响应点

在边缘梯度的方向上主曲率值比较大,而沿着边缘方向则主曲率值较小。候选特征点的DoG函数D(x)的主曲率与2×2Hessian矩阵H的特征值成正比
在这里插入图片描述
其中,Dxx,Dxy,Dyy是候选点邻域对应位置的差分求得的。
为了避免求具体的值,可以使用H特征值得比例。设α=λmax为H的最大特征值,β=λmin为H的最小特征值,则
在这里插入图片描述
其中,Tr(H)为矩阵H的迹,Det(H)为矩阵H的行列式。
设γ=αβ表示最大特征值和最小特征值的比值,则
在这里插入图片描述
上式的结果与两个特征值的比例有关,和具体的大小无关,当两个特征值想等时其值最小,并且随着γ的增大而增大。因此为了检测主曲率是否在某个阈值Tγ下,只需检测
在这里插入图片描述
如果上式成立,则剔除该特征点,否则保留。

5.确定特征点的主方向

特征点方向的计算方法为:统计关键点领域内的像素点方向,取大多数点的方向作为主方向。关键点领域内像素的梯模值与主方向的计算公式如下:
在这里插入图片描述在这里插入图片描述
m(x,y)为每个点L(x,y)的梯度的模,θ(x,y)为方向。其实也可以通过直方图来筛选,直方图最高则代表概率最高,也就是被指向最多的一个方向。

5.生成特征描述

一个特征点描述符是在每个图像采样点周围区域中位置计算梯度大小和方向产生的。首先,在特征点周围位置对图像梯度大小进行采样,利用特征点的尺度选择图像高斯模糊水平。将坐标轴旋转为特征点的方向,以确保旋转不变性。对每个关键点使用4×4共16个种子点来描述,这样一个关键点就可以产生128维的SIFT特征向量。
python—SIFT、Harris的简单实现与地理标记图像匹配_第3张图片

二、SIFT特征匹配

1.SIFT特征提取

在《python计算机视觉编程》这本书中,作者采用了开源工具包VLfeat,因为用Python重新实现SIFT特征提取的全过程不会很高效,而且也超出了这本书的范围,虽然VLfeat这个库是用C语言写的,不过我们可以利用它的命令行接口来使用。VLfeat的安装在上一篇博客中已经介绍过,这里给出VLfeat官网地址:http://www.vlfeat.org/download.html

(1)在这里,我使用了集美大学的部分场景图片来进行基于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 = '../data/sf_view1.jpg'
#  im2f = '../data/sf_view2.jpg'
  im1f = 'image/24.jpg'
  im2f = 'image/25.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)
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()

(2)运行结果
这里采用集美大学的部分场景图作为测试,原图如下:
python—SIFT、Harris的简单实现与地理标记图像匹配_第4张图片
python—SIFT、Harris的简单实现与地理标记图像匹配_第5张图片
运行前有一点需要大家注意,我们所匹配的两张图片它们的格式大小是经过软件修改成为一样大的,如果两张图片尺寸不一那么就会出现报错,若是图片太大,那么运行就会很慢。

运行过程:

①在代码运行时出现了这么一个错误:No such file or directory: 'empire.sift',报错提示问题出现在sift.py这个文件中,我便仔细看了一下并重新核对了安装过程,发现是在修改sift.py文件时路径后少了一个空格,补上就可以解决,若是读者也遇到类似的问题,可以去检查检查自己的代码是否有遗漏。

②解决了上面这个问题后,代码出现了新的报错,如下图:
在这里插入图片描述
数组越界的意思,我在网上查阅了资料,发现大多数都是说数据集和矩阵的问题,但是并没有找到合适的修改代码的方法,而后发现可能是因为VLfeat版本的问题,我的系统中使用的是VLfeat 0.9.21的版本,官网现在的版本便是这个,但是我们所使用的代码是从2014年出版的《python计算机视觉编程》这本书中得到的,译者当时使用的是VLfeat 0.9.17版本,所以使用现在的版本可能会有一些差异,所以我便在重新选择VLfeat 0.9.20版本进行安装,这里给出下载地址:https://github.com/houhongyi/vlfeat-0.9.20 ,大家也可以去官网上下载。

根据上一篇博客的介绍,安装完成后,代码便可以成功运行了,运行结果如下:
python—SIFT、Harris的简单实现与地理标记图像匹配_第6张图片
python—SIFT、Harris的简单实现与地理标记图像匹配_第7张图片
2.与Harris角点匹配作对比

(1)Harris角点检测代码

# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image

from PCV.localdescriptors import harris
from PCV.tools.imtools import imresize

im1 = array(Image.open("image/1.jpg").convert("L"))
im2 = array(Image.open("image/7.jpg").convert("L"))

# resize加快匹配速度
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()
gray() 
harris.plot_matches(im1, im2, filtered_coords1, filtered_coords2, matches)
show()

(2)运行结果
python—SIFT、Harris的简单实现与地理标记图像匹配_第8张图片
可以看到,Harris角点检测匹配的精确度比SIFT特征匹配的精确度低了许多,在运行速度上也较慢。同时,我还选取了对称性的建筑图像进行了测试,发现不管是使用SIFT还是Harris,对于这类相似性大的图像,特征点提取并不能很好地识别,所以在匹配时出现了如下左跟右相匹配的情况。
python—SIFT、Harris的简单实现与地理标记图像匹配_第9张图片

三、地理位置标记

在此之前,我们需要安装pydot工具包,这里给出简单的安装教程:

1、命令行输入pip install graphviz

2、到网址 http://www.graphviz.org/download/ 下载graphviz-2.38.msi文件,进行安装,安装完成后将其bin目录添加到系统的环境变量Path中去。

3.命令行输入pip install pydot,一开始会出现报错,窗口会提示你应该先更新你的pip,尝试输入python -m pip install --upgrade pip。在这里我安装的是新版本,也有人安装pydot 1.1.0版本,可以参考 https://blog.csdn.net/lyy14011305/article/details/57422512?utm_source=blogxgwz8。

1.在这里先给出代码:

# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from numpy import *
import os
import pydot
from PCV.localdescriptors import sift

def get_imlist(path):
    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]


# pydot需要绝对路径,路径分隔符为/而非\
download_path = "D:/pythonCode/image"
path = "D:/pythonCode/image/"

# list of downloaded filenames
imlist = 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 craete 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:
            #图像对中的第一幅图像
            im = Image.open(imlist[i])
            im.thumbnail((100,100))
            filename = path + str(i) + '.png'
            im.save(filename) #需要一定大小的临时文件
            g.add_node(pydot.Node(str(i), fontcolor='transparent',
                       shape='rectangle', image=filename))

            #图像对中的第二幅图像
            im = Image.open(imlist[j])
            im.thumbnail((100,100))
            filename = path + str(j) + '.png'
            im.save(filename) #需要一定大小的临时文件
            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('jmu.png')

这段代码先将文件夹中的图片根据sift描述子算法生成.sift文件,这里有一点要注意,给图片命名的时候千万不要加空格!否则会无法生成.sift文件而报错,往往因为忽视一个小细节会耗费大部分时间去查错。在可视化连接部分,根据所给链接的步骤进行安装,注意顺序以及版本问题,在安装pydot时,我使用pip install pydot命令,安装最新的版本,若是安装pydot1.1.0版本可能会有不适用的问题,还需要再调整,若是能安装成功可单独测试可视化连接部分,一般没什么大问题。

2.运行结果

同样的,我选用了集美大学的场景作为测试图,以下为15张的运行结果,因为计算量大所以运行速度偏慢,若是有读者试运行则需要耐心等待。
python—SIFT、Harris的简单实现与地理标记图像匹配_第10张图片
以上便是根据拍摄的图像进行分类后的结果,可以看到从上面五张图依次是中山纪念馆,嘉庚图书馆前,尚大楼远景,万人食堂,月明楼,这五个场景的分类匹配率为100%。

你可能感兴趣的:(计算机视觉)