(1)同名点
同一目标点在不同像片上的构像点。
(2)同名点匹配
是图像拼接、三维重建、相机标定等应用的关键步骤。
(3)特征匹配的基本流程:
①找点(找到左右边图像中比较显著的关键点)
②为每个关键特征点打上一个标签即一个身份,用向量表示,向量称为特征描述符
③有向量后,我们可以去右边图像中找到特征点看哪里与左边图像接近(找到匹配关系)
角点:角点可以是两条线的交叉处,也可以是位于相邻的两个主要方向不同的事物上的点,即某方面特别突出的点。角点在保留图像图形重要特征的同时,可以有效地减少信息的数据量,使其信息的含量很高,有效地提高了计算的速度,有利于图像的可靠匹配。
如图不同类型的角点:
角点特征
①局部窗口沿各方面移动,均产生明显变化的点
②图像局部曲率突变的点
角点检测算法如今可以归纳为3类:基于灰度图像的角点检测、基于二值图像的角点检测、基于轮廓曲线的角点检测。
什么是好的角点检测算法?
①检测出图像中“真实的”角点
②准确的定位性能
③很高的稳定性
④具有对噪声的鲁棒性
⑤具有较高的计算效率
Harris角点检测:是特征点检测的基础,提出了应用邻近像素点灰度差值概念,从而进行判断是否为角点、边缘、平滑区域。
Harris角点检测原理:是利用移动的窗口在图像中计算灰度变化值,其中关键流程包括转化为灰度图像、计算差分图像、高斯平滑、计算局部极值、确认角点。
Harris角点检测基本思想:
在窗口任意方向移动角点的图像灰度都会发生明显变化;
边缘区域只有在左右移动时变化明显,上下移动无变化;
平坦区域任意方向移动都无变化。如下图分别为平坦区域,边缘区域和角点时的变化情况。
Harris角点数学表达:
将图像窗口平移[u,v]产生灰度变化E(u,v)
通过一元和二元泰勒产开后:
对于局部微小的移动量[u,v],可以近似得到下面的表达:
其中M是2*2的矩阵,可由图像的导数求得:
①矩阵M,将其对角化之后,特征值λ1, λ2 分别代表了X 和Y 方向的灰度变化率:
②通过M的两个特征值λ1, λ2的大小对图像点进行分类:
如图所示:
如果λ1, λ2都很小, 图像窗口在所有方向上移动都无明显灰度变化。
如果λ1, λ2都很大, 图像窗口在所有方向上移动都产生明显灰度变化。
③定义:角点响应函数R:
R只与M的特征值有关:
在角点丰富的图中:R值为正值,因为角点丰富的图入1入2值都较大即detM值大,则R值大。
在平坦的图中:R值为小数值,且平坦的图中含有边缘,因此R值也有负数值;因为在平坦的图中,入1入2值都很小,所以R值小。
在边缘丰富的图中:入1和入2的值一方大一方小,所以R值为大值负数。
④角点响应函数R对于旋转具有不变性
对于图像的灰度变化具有部分不变性
对于图像尺度变化不具有不变性
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import harris
"""
Example of detecting Harris corner points (Figure 2-1 in the book).
"""
# 读入图像
im = array(Image.open(r'C:\Users\ltt\Documents\Subjects\大三下\计算机视觉(蔡国榕)\第二章\Cap2\1\gq.jpg').convert('L'))
# 检测harris角点
harrisim = harris.compute_harris_response(im)
# Harris响应函数
harrisim1 = 255 - harrisim
figure()
gray()
#画出Harris响应图
subplot(141)
imshow(harrisim1)
print (harrisim1.shape)
axis('off')
axis('equal')
threshold = [0.01, 0.05, 0.1]
for i, thres in enumerate(threshold):
filtered_coords = harris.get_harris_points(harrisim, 6, thres)
subplot(1, 4, i+2)
imshow(im)
print(im.shape)
plot([p[1] for p in filtered_coords], [p[0] for p in filtered_coords], '*')
axis('off')
show()
原图:
实验结果:
结论:
①该代码先代入一幅图像,将其转换成灰度图像,然后计算相响应函数,通过响应值选择角点。最后,将这些检测的角点在原图上显示出来。
②本次实验中,角点较多的区域是目标的轮廓和色彩比较深的区域。
增大threshold的值,将减小角点响应值R RR,降低角点检测的灵性,减少被检测角点的数量;减小threshold值,将增大角点响应值R RR,增加角点检测的灵敏性,增加被检测角点的数量。
Harris算法的主要特点是阈值决定检测点数量;
角点检测算子对亮度和对比度的变化不敏感;
具有旋转不变性,不具有尺度不变性;
harris算法中的阈值依赖于实际图像的属性,不具有直观的物理意义,具体值难确定。
在之前的学习中,我们学习了角点的基本知识,并对Harris角点检测算法应用到了图像的角点检测。
当我们在观察图像中的物体时,物体所在的图像不论背景或者角度如何变换,我们依然能很快的识别出图像中的目标物体。对机器来说,则需要定义图像上的关键特征并且能够根据这些特征搜索到一张新的图片。
SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT)。是在计算机视觉任务中特征提取算法。
SIFT算法的实质可以归为在不同尺度空间上查找特征点(关键点)的问题。SIFT算法实现特征匹配主要有三个流程:
a.提取关键点;
b.对关键点附加详细的信息(局部特征),即描述符;
c.通过特征点(附带上特征向量的关键点)的两两比较找出相互匹配的若干对特征点,建立景物间的对应关系。
①构建尺度空间,检测极值点,获得尺度不变性
构建尺度空间——特征点的性质之一就是对尺度的变化保持不变性。因此寻找的特征点要在不同尺度下都能被检测出来.根据文献可知,变换到尺度空间唯一的核函数是高斯函数。因此一个图像的尺度空间定义为:L( x,y,σ)是由可变尺度的高斯函数G(x,y,σ) 与输入图像I(x,y卷积得到
即: L(x,y,σ)=G(x,y,σ)∗I(x,y)
( x , y )是尺度坐标,σ大小决定图像的平滑程度,大尺度对应图像的概括特征,小尺度对应图像的细节特征,大的σ对应粗糙尺度(低分辨率),小σ对应高分辨率。为使计算相对高效,真正使用的是差分高斯尺度空间
D(x,y,σ):
高斯模糊——降低图像中的噪点,强调了图像的重要特征。进行高斯模糊之和,纹理和次要细节将从图像中删除,并且保留形状和边缘之类的相关信息。
实现DOG——初始图像与不同σ值的高斯函数卷积,得到一垛模糊后图像,然后将这一垛图像临近两两相减得到对应的DOG。一副图像可以产生几组图像,一组图像包括几层图像。s为每组层数一般为3~5层,最后可以将s和k的关系确立为k=2^(1/s)
尺度空间检测极值点——DOG上某个像素要和本尺度的8个像素以及上下相邻尺度各9个相邻像素共26个像素值进行比较,以确定是否为局部最大或最小值。如果它是相邻像素中最高或者最低的像素,则确定该像素点是尺度空间的极值点之一。
②筛选关键点,剔除不稳定的极值点
去掉对比度低的以及处于不理想边缘处的关键点,得到符合要求的特征关键点。
③在特征点处提取特征描述符,为特征点分配方向值
确定关键点的方向采用梯度直方图统计法,统计以关键点为原 点,一定区域内的图像像素点对关键点方向生成所作的贡献。
④生成特征描述符,利用特征描述符寻找匹配特征点
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是
2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格
都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方
向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方
向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特
征点由4个种子点的信息所组成。
⑤关键点匹配
为了对这些特征点进行匹配,需要对这些特征点运用适当的比较方法来找到相应关系。
特征点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一 般都采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的特征点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻 近的原图像特征点。
为了计算图像的SIFT特征,我们用开源工具包VLFeat。用Python重新实现SIFT特征提取的全过程不会很高效。VLFeat可以在www.vlfeat.org上下载,它的二进制文件可以用于一些主要的平台。这个库是用C写的,不过我们可以利用它的命令行接口。此外,它还有Matlab接口。
from pylab import *
from PIL import Image
from numpy import *
import os
def process_image(imagename, resultname, params="--edge-thresh 10 --peak-thresh 5"):
"""处理一幅图像,然后将结果保存在文件中"""
if imagename[-3:] != 'pgm':
# 创建一个pgm文件
im = Image.open(imagename).convert('L')
im.save('tmp.pgm')
imagename = 'tmp.pgm'
cmmd = str(r"C:\Users\ltt\Documents\Subjects\大三下\计算机视觉(蔡国榕)\win64vlfeat\sift.exe " + imagename + " --output=" + resultname + " " + params)
os.system(cmmd)
print('processed', imagename, 'to', resultname)
def read_features_from_file(filename):
"""读取特征值属性值,然后将其以矩阵形式返回"""
f = loadtxt(filename)
return f[:, :4], f[:, 4:] # 特征位置,描述子
def plot_features(im, locs, circle=False):
"""显示带有特征的图像
输入:im(数组图像),locs(每个特征的行、列、尺度和方向角度)"""
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')
return
def match(desc1, desc2):
"""对于第一幅图像的每个描述子,选取其在第二幅图像中的匹配
输入: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
# 反余弦和反排序,返回第二幅图像中特征的索引
index = argsort(arccos(dotprods))
# 检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度
if arccos(dotprods)[index[0]] < dist_ratio * arccos(dotprods)[index[1]]:
matchscores[i] = int(index[0])
return matchscores
def match_twosided(desc1,decs2):
"""双向对称版本的match"""
matches_12 = match(desc1, decs2)
matches_21 = match(decs2, decs2)
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
def appendimages(im1, im2):
"""返回将两幅图像并排拼接成的一幅新图像"""
# 选取具有最少行数的图像,然后填充足够的空行
row1 = im1.shape[0]
row2 = im2.shape[0]
if row1 < row2:
im1 = concatenate((im1,zeros((row2-row1,im1.shape[1]))), axis=0)
elif row1 > row2:
im2 = concatenate((im2,zeros((row1-row2,im2.shape[1]))), axis=0)
# 如果这些情况都没有,那么他们的行数相同,不需要进行填充
return concatenate((im1,im2), axis=1)
def plot_matches(im1, im2, locs1, locs2, matchscores, show_below=True):
"""显示一幅带有连接匹配之间连线的图片
输入:im1,im2(数组图像),locs1,locs2(特征位置),matchscores(match的输出),
show_below(如果图像应该显示再匹配下方)"""
im3 = appendimages(im1,im2)
if show_below:
im3 = vstack((im3,im3))
imshow(im3)
cols1 = im1.shape[1]
for i in range(len(matchscores)):
if matchscores[i] > 0:
plot([locs1[i, 0], locs2[matchscores[i, 0], 0] + cols1], [locs1[i, 1], locs2[matchscores[i, 0], 1]], 'c')
axis('off')
if __name__ == '__main__':
# imname = 'raccoon.jpg'
# im1 = array(Image.open(imname).convert('L'))
# process_image(imname, 'raccoon.sift')
# l1, d1 = read_features_from_file('raccoon.sift')
#
# figure()
# gray()
# plot_features(im1, l1, circle=True)
# show()
im1f = r'C:/Users/ltt/Documents/Subjects/大三下/计算机视觉(蔡国榕)/第二章/Cap2/1/gq.jpg'
im2f = r'C:/Users/ltt/Documents/Subjects/大三下/计算机视觉(蔡国榕)/第二章/Cap2/1/gq.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
process_image(im1f, 'out_sift_1.txt')
l1, d1 = read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
plot_features(im1, l1, circle=False)
process_image(im2f, 'out_sift_2.txt')
l2, d2 = read_features_from_file('out_sift_2.txt')
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_below=True)
show()
原图:
library1:
library2:
实验结果:
结论:
先对图像使用SIFT特征提取代码进行处理,并且将特征保存在和图像同名(但文件名是.sift,而不是.jpg)的文件路径下。
download_path = r"C:\Users\ltt\Documents\Subjects\大三下\计算机视觉(蔡国榕)\第二章\data"
path = r"C:\Users\ltt\Documents\Subjects\大三下\计算机视觉(蔡国榕)\第二章\data"
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
featlist = [imname[:-3] + 'sift' for imname in imlist]
for i, imname in enumerate(imlist):
sift.process_image(imname, featlist[i])
我们可以对所有的组合图像进行逐个匹配,我们将每对图像间的匹配特征数保存在matchscores数组中。
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
# 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]
我们会用到pydot工具包,该工具包要需要用到GraphViz,先去官网下载安装即可graphviz。注意记得安装路径,下载后需要添加环境变量。配置后在命令行输入dot -version,查看graphviz是否安装成功。
可视化连接图像需要构建线条对图片进行相连操作,而pydot工具包提供了GraphViz graphing库的Python接口,可以实现连接可视化。
在Windows系统上安装配置Graphviz:
首先下载安装包graphviz-2.38.msi,下载地址为https://graphviz.gitlab.io/_pages/Download/Download_windows.html
双击msi文件,然后一直选择next(默认安装路径为C:\Program Files(x86)\Graphviz2.38\),安装完成之后,会在windows开始菜单创建快捷信息。
配置环境变量:计算机→属性→高级系统设置→高级→环境变量→系统变量→path,在path中加入路径:C:\Program Files(x86)\Graphviz2.38\bin
验证:在windows命令行界面,输入dot
-version,然后按回车,如果显示如下图所示的graphviz相关版本信息,则安装配置成功。
pip install graphviz
pip install pydot
# 可视化
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) + '.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 = path + 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_jpg('tt.jpg')
运行结果:
(图片过大容易发生过多索引的错误,并且过大也会运行很久,建议图像都压缩一下)
1.我们需要先下载安装graphviz
2.配置环境变量
3.在cmd后输入pip install pydot进行pydot的安装下载。
1.OSError: XXXX.sift not found
此时需要检查系统中各个包是否导入,路径是否正确。
2.too many indices for array