定义:
像素周围显示存在多于一个方向的边,认为该点为兴趣点,也就是角点
检测角点:
以一个点为中心点,在该点周围设置一个窗口,局部窗口沿各方向移动。移动的情况可分为三种:
1.各方向均无灰度变化:可能位于平滑部位
2.左右方向有明显变化,上下无明显变化(或上下有明显变化,左右无明显变化):可能位于边缘位置
3.各个方向均产生明显灰度变化:可能位于角点位置
1999年David G.Lowe教授总结了基于特征不变技术的检测方法,在图像尺度空间基础上,提出了对图像缩放、旋转保持不变性的图像局部特征描述算子-SIFT(尺度不变特征变换),该算法在2004年被加以完善。
Sitf算法在物体上的一些局部外观的兴趣点与图像本身的大小和旋转无关,这是相比于Harris算法的一个很大的改进。Harris算法只能保证旋转不变形,但对于缩放后的图像,再使用Harris算法就检测不出来了,这是因为Harris角点检测算法是基于窗口中像素分布和变化的原理,在图像放大且窗口大小不发生变化的时,窗口中的像素信息则会有很大的不同,所以无法检测。
1.图像的局部特征,对旋转、尺度缩放、亮度变化保持不变,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
2.独特性好,信息量丰富,适用于海量特征库进行快速、准确的匹配。
3.多量性,即使是很少几个物体也可以产生大量的SIFT特征
4.高速性,经优化的SIFT匹配算法甚至可以达到实时性
5.扩招性,可以很方便的与其他的特征向量进行联合。
目标的旋转、缩放、平移
图像仿射/投影变换
弱光照影响
部分目标遮挡
杂物场景
噪声
算法实质:在不同尺度空间上寻找特征点
主要流程:
1.提取关键点
2.对关键点附加详细信息(局部特征),即描述符
3.通过特征点(附带上特征向量的关键点)的两两比较找出相互匹配的若干对特征点,建立景物间的对应关系。
所要寻找的关键点就是一些十分突出的点,也就是在视觉领域中一些有别于其周围的地方,但这些点不会因光照、尺度、旋转等因素的改变而消失,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。
既然两幅图像中有相同的景物,那么使用某种方法分别提取各自的稳定点,这些点之间会有相互对应的匹配点。
尺度空间理论最早于1962年提出,其主要思想是通过对原始图像进行尺度变换,获得图像多尺度下的空间表示。从而实现边缘、角点检测和不同分辨率上的特征提取,以
满足特征点的尺度不变性。在一定范围内,人眼可以分辨出物体的大小,但计算机并未具有相同的能力,因此在未知场景中,计算机视觉不能够提供物体的尺度大小,所以采用的方法是:将物体不同尺度下的图像都提供给计算机这样计算机就能够对一个物体的不同尺度有一个统一认识,也就是说,要考虑图像在不同尺度下都存在的特点。
尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标
在视网膜上的形成过程。
高斯核是唯一可以产生多尺度空间的核,尺度空间的获取需要使用高斯模糊来实现
其中(x,y)是空间坐标,L(x, y, σ) 为原始图像, I(x, y)与一个可变尺度的2维高斯函数G(x, y, σ) 卷积运算。
σ为正态分布的方差,方差越大,滤波后的图像较模糊,边缘越清晰。
图4.1.1 二维高斯曲面
在这里的“尺度”就是指,一张照片与二维高斯函数卷积后得到很多张不同σ值的高斯图像。所有不同尺度下的图像,构成单个原始图像的尺度空间。
在二维空间中,这个公式生成的曲面的等高线是从中心开始呈正态分布的同心圆,如图4.1.1所示。分布不为零的像素组成的卷积矩阵与原始图像做变换。每个像素的值都是周围相邻像素值的加权平均。相邻像素随着距离原始像素越来越远,其权重也越来越小。这样进行模糊处理比其它的均衡模糊滤波器更高地保留了边缘效果。
理论上来讲,图像中每点的分布都不为零,也就是说每个像素的计算都需要包含整幅图像。在实际应用中,在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。通常,图像处理程序只需要计算(6σ+1)(6σ-1)的矩阵就可以保证相关像素影响。
高斯模糊是在Adobe Photoshop等图像处理软件中广泛使用的处理效果,通常用它来减小图像噪声以及降低细节层次。这种模糊技术生成的图像的视觉效果是好像经过一个半透明的屏幕观察图像。
根据σ的值,计算出高斯模板矩阵的大小(6σ+1)(6σ-1),使用公式计算高斯模板矩阵的值,与原图像做卷积,即可获得原图像的平滑(高斯模糊)图像。为了确保模板矩阵中的元素在[0,1]之间,需将模板矩阵归一化。5*5的高斯模板如表4.2所示。
表4.2 σ=0.6的高斯模板
下图是5*5的高斯模板卷积计算示意图。高斯模板是中心对称的。
图4.2 二维高斯卷积
为什么要进行高斯模糊处理呢,这是因为计算机只能分辨出变化率最快的点。彩色图是三通道的,不好检测出灰度值明显变化的点,因此要把图像先转化为灰度图像,此时图片为单通道,灰度值范围在0~255。在做完高斯卷积操作,图像变模糊,但由于整体的轮廓没变,因此可以比较容易找到灰度值突变的点。这些点,就可以作为候选特征点。
图像的金字塔模型是指,将原始图像不断降阶采样,得到一系列大小不一的图像,由大到小,从下到上构成的塔状模型。原图像为金子塔的第一层,每次降采样所得到的新图像为金字塔的一层(每层一张图像),金字塔共s层,越往上,方差越大。
尺度空间在实现时使用高斯金字塔表示,高斯金字塔构建分为两部分:
1.对图像做高斯平滑;
2.对图像做降采样
图5.1
其中为尺度空间坐标,s为sub-level层坐标,0为初始尺度,S为每组层数(一般为3~5)
最后可将组内和组间尺度归为
i为金字塔组数,n为每一组的层数。
为了让尺度体现其连续性,高斯金字塔在简单降采样的基础上加上了高斯滤波。如图5.1所示,将图像金字塔每层的一张图像使用不同参数做高斯模糊,使得金字塔的每层含有多张高斯模糊图像,将金字塔每层多张图像合称为一组(Octave),金字塔每层只有一组图像,组数和金字塔层数相等,每组含有多张(也叫层Interval)图像。
在计算时,使用高斯金字塔每组中相邻上下两层图像相减,得到高斯差分图像
图6.1 高斯差分图
可以通过图6.1看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。)DOG图像描绘的是目标的轮廓。
(1)将原始图像做高斯卷积
(2)将处理后的图像做高斯差分,此时可输出金字塔类型图
(3)找候选极值点
(4)通过构造Hessian矩阵去除边缘响应
特征点是由DOG空间的局部极值点组成的。为了寻找尺度空间的极值点,每个像素点要和该点的周围所有相邻点进行比较,当其与相邻点有明显差别的灰度值时,该点就是极值点。
此时找到的极值点标为候选特征点,因为存在噪声的干扰。如图所示,中间的检测点要和其所在图像的3×3邻域8个像素点,以及其相邻的上下两层的3×3领域18个像素点,共26个像素点进行比较,确保在尺度空间和二维图像空间都检测到极值点。
从上面的描述中可以知道,每组图像的第一层和最后一层是无法进行比较取得极值的。为了满足尺度变换的连续性,在每一组图像的顶层继续使用高斯模糊生成3幅图像,高斯金字塔每组有S+3层图像,DoG金字塔的每组有S+2组图像。这样产生的极值点并不全都是稳定的特征点,因为某些极值点响应较弱,而且DOG算子会产生较强的边缘响应。
通过比较检测得到的DoG的局部极值点是在离散的空间搜索得到的,由于离散空间是对连续空间采样得到的结果,物体的边缘轮廓在灰度图中,存在着灰度值的突变,这样的点在计算中就被“误以为”是特征值。因此在离散空间找到的极值点不一定是真正意义上的极值点,因此要将不满足条件的点剔除掉。
DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率可以通过计算在该点位置尺度的2×2的Hessian矩阵得到,导数由采样点相邻差来估计
Dxx 表示DOG金字塔中某一尺度的图像x方向求导两次
D的主曲率和H的特征值成正比。令 α ,β为特征值,则
该值在两特征值相等时达最小。Lowe论文中建议阈值T为1.2,即
时保留关键点,反之剔除。
主要删除两类:低对比度的极值点以及不稳定的边缘响应点。
为了实现图像旋转不变性,需要给特征点的方向进行赋值。利用特征点邻域像素的梯度分布特性来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。
像素点的梯度表示:
梯度幅值:
梯度方向:
利用以特征点为中心划出周边的16 x 16图像块,计算每个点的幅值和梯度方向,再离散为8(根据划分的角度设定)个bin方向,产生离散直方图
直方图:计算得到梯度方向后,使用直方图统计特征点邻域内像素对应的梯度方向和幅值。梯度方向的直方图的横轴是梯度方向的角度(这里以8个为例),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向
关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值80%能量的峰值时,则将这个方向认为是该关键点的辅方向
因此,对于同一梯度值的多个峰值的关键点位置,在相同位置和尺度将会有多个关键点被创建但方向不同。仅有15%的关键点被赋予多个方向,但可以明显的提高关键点匹配的稳定性。实际编程实现中,就是把该关键点复制成多份关键点,并将方向值分别赋给这些复制后的关键点,并且,离散的梯度方向直方图要进行插值拟合处理,来求得更精确的方向角度值。
以旋转之后的主方向为中心取8x8的窗口,左图中点为当前关键点的位置,每一个小格都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方向,箭头长度代表该像素的幅值。然后在每个4x4的小块上绘制8个方向的梯度直方图,计算每个梯度方向的累加值,即可形成一个种子点,如右图所示,每个特征的由4个种子点组成,每个种子点有8个方向的向量信息,这种邻域方向性信息联合增强了算法的抗噪能力,同时对于含有定位误差的特征匹配也提供了比较理性的容错性。
旋转后的新坐标:
Lowe实验结果表明:描述子采用4×4×8=128维向量表征综合效果最优(不变性与独特性
分别对模板图和实时图建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似性度量采用欧式距离
穷举匹配:
对比次数过多,算法复杂度大,因此不采用。一般都采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的关键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻近的原图像特征点。
(1)实时性低
(2) 对边缘光滑的目标无法准确提取特征点
为了描述图像的SIFT特征,使用到工具包VLfeat。VLfeat可以在www.vlfeat.org上下载。
1.打开网址,下载9.20版本
(下载9.21版本运行程序找不到响应的sift文件)
2.下载完成解压后,保存至目录,这里我新建了一个文件夹vlfeat用来保存
3.根据自己电脑是32位还是64位选择
4.打开bin文件夹中的sift.exe文件,在项目目录下新建一个sift.py文件,将目录改成sift.exe的绝对路径
注意:
在输入路径时,最好不要有中文字符,路径的符号用“/”或者“\\”,因为“\”为转义字符,系统会找不到这个路径
#python
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PIL import Image
from pylab import *
from PCV.localdescriptors import sift
from PCV.localdescriptors import harris
import os
root=os.getcwd()+"\\"
imname = ('empire.jpg')
siftname = ('empire.sift')
im = array(Image.open(root+imname).convert('L'))
sift.process_image(root+imname,root+siftname)
l1,d1= sift.read_features_from_file(root+siftname)
figure()
gray()
subplot(131)
"""
图1 :SIFT特征
"""
sift.plot_features(im,l1,circle = False)
title('sift-features')
subplot(132)
"""
图2 :使用圆圈表示特征尺度的SIFT特征
"""
sift.plot_features(im,l1,circle = True)
title('sift_features_det')
harrisim = harris.compute_harris_response(im)
filtered_coords = harris.get_harris_points(harrisim,6,0.1)
subplot(133)
"""
图3 :harris角点检测的结果
"""
imshow(im)
plot([p[1]for p in filtered_coords],[p[0] for p in filtered_coords])
axis('off')
title('harris')
show()
运行结果:
实验结果可以很好的展示出使用sift算法和Harris算法所找到的特征点的不同,Harris角点检测的显示在了图像的最后侧。
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift
import os
root=os.getcwd()+"\\"
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 = '../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(root+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(root+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()
这个图像是白宫的图像,由于已经有了,就不需要下载了
在运行这个代码之前,要先使用软件,然后在命令行输入语句
conda install pydot-ng
conda install graphviz
等待安装后,就可以运行了
在这个程序中,使用到pydot工具包,这个安装较为简单,打开命令行输入pip install pydot即可。
import pydot
g = pydot.Dot(graph_type='graph')
g.add_node(pydot.Node(str(0), fontcolor='transparent'))
for i in range(5):
g.add_node(pydot.Node(str(i + 1)))
g.add_edge(pydot.Edge(str(0), str(i + 1)))
for j in range(5):
g.add_node(pydot.Node(str(j + 1) + '0' + str(i + 1)))
g.add_edge(pydot.Edge(str(j + 1) + '0' + str(i + 1), str(j + 1)))
g.write_png('ch02_fig2-9_graph.png', prog='neato')
为了是得到的可视化结果比较好看,我们对每幅图像用100*100的缩略图缩放它们。
#-*- 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 = "../data/JMU" # set this to the path where you downloaded the panoramio images
path = "../data/JMU" # 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')
root="C:\\Users\\WeiLinLin\\PycharmProjects\\untitled\\实验二\\data\\JMU\\"
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 = root + str(i) + '.jpg'
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 = root + str(j) + '.jpg'
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('jmu.png')
在第一次运行后,发现运行结果是这样的
这是我第一次使用的路径
后来查找错误后发现,这个root图片的路径要使用绝对路径
修改后的实验结果