计算机视觉-SIFT特征提取及匹配
文章目录
1、SIFT描述子介绍
1.1 SIFT描述子定义
SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,由加拿大教授David G.Lowe提出的。SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。
1.2 SIFT特征
1、图像的局部特征,对旋转、尺度缩放、亮度变化保持不变,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
2、独特性好,信息量丰富,适用于海量特征库进行快速、准确的匹配。
3、 多量性,即使是很少几个物体也可以产生大量的SIFT特征
4、高速性,经优化的SIFT匹配算法甚至可以达到实时性
5、 扩招性,可以很方便的与其他的特征向量进行联合。
1.3 SIFT检测过程
1.构造高斯差分空间图像。
2.寻找极大极小值点
3.精确定位极值点
4.选取特征点主方向
5. 构造特征点描述算子。
1.4算法介绍
1.尺度空间极值检测——即找特征点
对于二维图像I(x,y),建立图像的DOG (difference of guassians,即高斯差分)金字塔,DOG尺度空间含义为:可以用一个尺度空间的高斯函数和图像的卷积来表示。
G为尺度可变的高斯函数,***I***为空间坐标,其中西伽马为尺度。
为确定特征点所在的位置,首先需要建立一个高斯金字塔。
得到高斯金字塔后,再通过两个相邻的高斯尺度空间做差,得到高斯差分DOC金字塔,整个过程的公式如下:
高斯差分金字塔建立后,特征点就是DOG尺度空间众多极值点,(个人认为对其进行求偏导数即可得到特征点的位置坐标),查找该极值点需要把每个点与邻域内的周围的26个点进行比较,这些点包括,在同一尺度上相邻的8个点,以及相邻尺度上相邻的18个点。
2.特征点的尺度方向的确定
得到了特征点的坐标是完全不够的,必须要增加方向尺度信息。
· 采用有限差分的方法,求出在以特征点为圆心,以3倍西伽马为半径的范围内的图像梯度的幅值和相位。
· 利用直方图统计方法,求助邻域内所有像素点的梯度方向以及幅值。特征点的主方向就是直方图的峰值所代表的方向,确定了主方向就可以使SIFT算法具备旋转不变性。
3.特征向量的生成
特征向量最终是通过求得的特征点的邻域梯度信息来计算的。
· 先把坐标轴位置旋转到特征点所在的主方向上
· 接着以特征点为圆心,选择特征点附近的16个点作为种子点,分别求出8个方向上的梯度大小
· 最后得到的128维向量即为所求的特征向量
4.特征点的匹配
通常采用最邻近的方法,即查找每一个特征点在另外一副图像中的最邻近,即最短的欧式距离。
欧式距离的含义就是:两点连线的距离长度
具体如下:
对目标图像A中的某个特征点,求出该点与图像B中的所有特征点的特征向量的欧式距离;
· 将所求得的欧式距离值的大小进行排序;
· 找出最小的和次小(倒数第二小的)欧式距离值对于的目标图B中的特征点,并对这两个距离的比值进行计算;
· 假设该比值小于某个阈值,则这两个点为匹配点,否则不匹配。
(注:该阈值是一个经验阈值:是影响误匹配的主要原因,实验中一般选取的阈值大小是0.6左右)
2、数据集准备
由于疫情原因,拍摄了家里及周围的场景共15张作为本次实验的数据集
3、图片的SIFT特征提取并展示特征点
3.1 代码实现
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from PCV.localdescriptors import sift
from PCV.localdescriptors import harris
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
imname = r'C:\Users\13799\PycharmProjects\untitled1\pic\4.jpg'
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'empire.sift')
l1, d1 = sift.read_features_from_file('empire.sift')
figure()
gray()
subplot(131)
sift.plot_features(im, l1, circle=False)
title(u'SIFT特征',fontproperties=font)
subplot(132)
sift.plot_features(im, l1, circle=True)
title(u'用圆圈表示SIFT特征尺度',fontproperties=font)
# 检测harris角点
harrisim = harris.compute_harris_response(im)
subplot(133)
filtered_coords = harris.get_harris_points(harrisim, 6, 0.1)
imshow(im)
plot([p[1] for p in filtered_coords], [p[0] for p in filtered_coords], '*')
axis('off')
title(u'Harris角点',fontproperties=font)
show()
3.2 结果展示
3.3 实验分析
通过多个场景的图片对比可以发现SIFT算法的特征点比Harris角点的特征点提取的更多,并且更加精准;它需要建立高斯图像金字塔和高斯差分金字塔之后再检测极值,而Harris角点只是对原图进行角点检测和变化,Harris特征点检测存在角点信息丢失与偏移的现象。在运行程序时,与Harris的效率相比,SIFT的效率有大的提升,程序的运行很快。
4、特征匹配
4.1代码实现
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 = r'C:\Users\13799\PycharmProjects\untitled1\pic\1.jpg'
im2f = r'C:\Users\13799\PycharmProjects\untitled1\pic\2.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()
4.2结果展示
匹配成功时:
SIFT检测两张图片兴趣点的结果图
兴趣点匹配结果
当选取两张毫无关联的图片时:
两张图片的兴趣点提取
匹配结果:
4.3实验结果
从匹配的结果看:SIFT算法匹配出的特征点很多很齐全,这是因为SIFT算法具有尺度和旋转不变性,即使两张图大小不一样、角度不一致也不会影响匹配结果。在进行匹配时,用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图像1中的某个关键点,并找出其与图像2中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离少于某个比例阈值,则接受这一对匹配点。两个不同场景则无法检测到相似点,说明了SIFT的准确性很高。
5、图像匹配
实验要求:
输入:
一张新图像
输出:
图像集中与其最匹配的三张图
5.1代码实现
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
""" This is the example graph illustration of matching images from Figure 2-10.
To download the images, see ch2_download_panoramio.py."""
download_path ="C:/Users/13799/PycharmProjects/untitled1/pic/" # set this to the path where you downloaded the panoramio images
path = "C:/Users/13799/PycharmProjects/untitled1/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])
print featlist
imname = 'C:/Users/13799/PycharmProjects/untitled1/pic/16.jpg'
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'empire.sift')
l1, d1 = sift.read_features_from_file('empire.sift')
matchscores = zeros((nbr_images, nbr_images))
arr=[]
for i in range(nbr_images):
print 'comparing 12', imlist[i]
# l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[i])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
arr.append(nbr_matches)
# arr2[i]=dict{nbr_matches:imlist[i]}
print 'number of matches = ', nbr_matches
# matchscores[i, j] = nbr_matches
arr2=arr[:] #切片浅拷贝 arr2=arr深拷贝
arr2.sort(reverse = True)
print arr
print arr2
i=1
while (i<4):
for j in range(nbr_images):
if(arr[j]==arr2[i]):
print imlist[j]
figure()
axis('off')
img=array(Image.open(imlist[j]))
imshow(img)
break
i=i+1
show()
5.2结果展示
输入图片:
输出:
5.3 实验结果
本题输入一张图像,然后输出三张匹配度最高的图片,需要将数据集中的全部图片与新图像都进行匹配,算出特征匹配值,并对求出的特征匹配值进行排序,选出值最高的三幅图像作为输出。实验发现sift特征匹配较为精准,输出的图像基本正确。SIFT特征的信息量大,适合在海量数据库中快速准确匹配,准确度也相当高。
6、数据集匹配
6.1代码实现
# -*- coding: utf-8 -*-
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 = "panoimages" # set this to the path where you downloaded the panoramio images
#path = "/FULLPATH/panoimages/" # path to save thumbnails (pydot needs the full system path)
download_path = "C:\Users\13799\PycharmProjects\untitled1\pic" # set this to the path where you downloaded the panoramio images
path = "C:\Users\13799\PycharmProjects\untitled1\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('11.jpg')
6.2结果展示
6.3实验结果
该图展示的是整个数据集的图片的关联,SIFT根据局部描述子找出了数据集中和它匹配的几张图,当匹配的特征点数量小于等于2时就不显示在该匹配图中。图片分别在不同场景和地点拍摄的,每栋建筑风格相似,整体环境相同,但是SIFT算法还是很精确地匹配出了结果。说明使用 SIFT特征描述对于部分物体遮蔽的侦测率相当高,能快速准确地计算出位置与方位。且因为SIFT所查找到的关键点不会因光照,仿射变换和噪音等因素而变化,如角点、边缘点、暗区的亮点及亮区的暗点等,很好地体现了它的独特性和多量性。
7、RANSAC算法去除误匹配
7.1RANSAC算法原理
RANSAC为Random Sample Consensus的缩写,它是根据一组包含异常数据的样本数据集,计算出数据的数学模型参数,得到有效样本数据的算法。它于1981年由Fischler和Bolles最先提出 。该算法在计算机视觉中广泛应用。
RANSAC算法的基本假设是样本中包含正确数据(inliers,可以被模型描述的数据),也包含异常数据(outliers,偏离正常范围很远、无法适应数学模型的数据),即数据集中含有噪声。这些异常数据可能是由于错误的测量、错误的假设、错误的计算等产生的。同时RANSAC也假设,给定一组正确的数据,存在可以计算出符合这些数据的模型参数的方法。 因此,RANSAC通过反复选择数据中的一组随机子集来达成目标。
7.2RANSAC算法步骤
- 随机选择四对匹配特征
- 根据DLT计算单应矩阵 H (唯一解)
- 对所有匹配点,计算映射误差ε= ||pi’, H pi||
- 根据误差阈值,确定inliers(例如3-5像素)
- 针对最大inliers集合,重新计算单应矩阵 H
7.3RANSAC实现
7.3.1景深丰富
原图如下:
sift特征匹配结果:
RANSAC去除误匹配:
7.3.2景深单一
原图如下:
sift特征匹配结果:
RANSAC算法去除误匹配:
7.4 实验结果分析
对于景深丰富的图来说:在整个建筑群中,特征点匹配数量多,中间部分的匹配点十分密集,很难看出区别。这边主要选择了右上部分和左下部分进行区分(如下图所示),明显可以得到右上和左下画圈部分不是匹配点,而经过RANSAC算法剔除后,可以删除这些误配。
对于景深单一的图来说:如下图所示,景深单一的图的匹配点比较少,可以比较明显的看出对比。在下图中,中间部分的粉色线是一组误配点,在经过RANSAC后删除了该条匹配点,但是同时可以发现,上方的绿色线所指的是一条正确的匹配点,但是经过算法后他删除了该组匹配点。由此可以知道RANSAC在删除误配的同时也可能将正确的点也一起删除掉,这种误差不可避免,但总体来说,已经达到了非常理想的效果。
8、实验问题及总结
1、本次实验用到的sift库在安装过程中需要注意:在修改Anaconda中PCV下sift的路径时要注意是要添加你所创建的项目里面的sift.exe,并且sift.exe在添加的时候要注意他后面有空格,不然会报错。
2、在进行图像特征匹配的时候要注意图片大小要一致,建议照片尽量调小一点不然匹配过程中可能会出错。
3、总的来说,SIFT特征具有尺度不变性,即使改变旋转角度,图像亮度或拍摄视角,仍然能够得到好的检测效果,检测出来的特征点更多更精准一点,这是相比Harris算法更优的地方。
4、安装graphviz
在安装graphviz的时候使用pip进行安装出错了,这边采用了navigator的下载源进行下载,步骤如下:
4.1.在navigator中下载graphviz(这边需要注意的是原本anacoda是国外的下载源,速度可能会比较慢,因此我把它替换成了国内的镜像网站https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/)
4.2.配置graphviz的环境变量
4.3.pip install pydot
5、本次代码里面使用了opencv,原本使用anaconda直接pip
但是后来查看了anconda的列表后发现没有这个包
之后用了pip install opencv-contrib-python==3.4.0.12 -i https://pypi.tuna.tsinghua.edu.cn/simple some-package语句,成功下载了下来。