Python计算机视觉编程
局部图像描述子
(一)Harris 角点检测器
(二)SIFT(尺度不变特征变换)
2.1 兴趣点
2.2 描述子
2.3 检测兴趣点
2.4 匹配描述子
(三)匹配地理标记图像
3.1 从 Panoramio 下载地理标记图像
3.2 使用局部描述子匹配
3.3 可视化连接的图像
局部图像描述子
本节旨在寻找图像间的对应点和对应区域。介绍用于图像匹配的两种局部描述子算法。图像的局部特征是许多计算机视觉算法的基础,使用特征点来代表图像的内容包括运动目标跟踪,物体识别,图像配准,全景图像拼接,三维重建等。
(一)Harris 角点检测器
角点特征:
轮廓之间的交点。
对于同一场景,即使视角发生变化,通常具备稳定性质的特征。
该点附近区域的像素点无论在梯度方向上还是其梯度幅值上有着较大变化。
角点处的一阶导数最大,二阶导数为0。
角点指示了物体边缘变化不连续的方向。
Harris 角点检测算子:
Harris 算子是一种简单的点特征提取算子,这种算子受信号处理中自相关函数的启发,给出与自相关函数相联系的矩阵M。M阵的特征值是自相关函数的一阶曲率,如果两个曲率值都高,那么就认为该点是特征点。为了消除噪声对于角点检测的影响,可以使用一个高斯滤波器来平滑图像。
Harris 角点检测算法原理:
利用矩形窗在图像上移动,若窗内包含有角点,则窗口向各个方向移动时,窗内的灰度值都会发生变化。从而达到检测图像角点的目的。如果像素周围显示存在多于一个方向的边,我们认为该点为兴趣点。该点就称为角点。
数学公式:
把图像域中点 x 上的对称半正定矩阵 M I M_IMI=M I M_IMI(x)定义为:
其中 ∆I为包含导数I x I_xIx 和 I y I_yIy 的图像梯度。由于该定义,M I M_IMI 的秩为 1,特征值为 λ1=| ∆I |2 和 λ2=0。现在对于图像的每一个 像素,我们可以计算出该矩阵。
选择权重矩阵 W(通常为高斯滤波器 Gσ),我们可以得到卷积:
该卷积的目的是得到 M I M_IMI 在周围像素上的局部平均。计算出的矩阵 M I M_IMI有称为 Harris矩阵。
方法:
计算图像I(x,y)在X和Y两个方向的梯度I x I_xIx 和 I y I_yIy 。
计算图像两个方向梯度的乘积。
使用高斯函数对上述结果进行高斯加权(取σ=1),生成矩阵M的元素A、B和C。
计算每个像素的Harris响应值R,并对小于某一阈值t的R值为0.
在3x3或5x5的领域内进行非最大值抑制,局部最大值点即为图像中的角点。
注意:
增大α的值,将减小角点响应值R,降低角点检测的灵性,减少被检测角点的数量;减小α值,将增大角点响应值R,增加角点检测的灵敏性,增加被检测角点的数量。
Harris角点检测算法优点:
旋转不变性,椭圆转过一定角度但是其形状保持不变(特征值保持不变)。
对于图像灰度的仿射变化具有部分的不变性,由于仅仅使用了图像的一介导数,对于图像灰度平移变化不变;对于图像灰度尺度变化不变。
Harris角点检测算法缺点:
它对尺度很敏感,不具备几何尺度不变性。
提取的角点是像素级的。
编码:
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import harris
# 读入图像
im = array(Image.open('D:\\Python\\chapter2\\jimei2.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')
# 原书采用的PCV中PCV harris模块
# harris.plot_harris_points(im, filtered_coords)
# plot only 200 strongest
# harris.plot_harris_points(im, filtered_coords[:200])
show()
代码运行效果如下:
分析:
增大α的值,将减小角点响应值R,降低角点检测的灵性,减少被检测角点的数量;减小α值,将增大角点响应值R,增加角点检测的灵敏性,增加被检测角点的数量。使用阈值 0.01、0.05 和 0.1 检测出的角点依次减少。
改进:
函数cornerHarris()识别角点:
cv2.cornerHarris(src, blockSize, ksize, k[, dst[,borderType]]) -> dst
参数如下:
1. src - 数据类型为 float32 的输入图像。
2. blockSize - 角点检测中要考虑的领域大小。
3. ksize - Sobel 求导中使用的窗口大小
4. k - Harris 角点检测方程中的自由参数,取值参数为 [0,04,0.06].
cornerHarris函数中最重要的参数是第三个,该参数限定了sobel算子的中孔,sobel算子通过对图像行列的变化来检测边缘,sobel算子会通过核kernel来完成检测。该参数定义了角点检测的敏感度,其取值必须介于3和31之间的奇数。
当ksize参数设为3时:
编写代码:
-*- coding: utf-8 -*-
import cv2
import numpy as np
filename = 'D:\\Python\\chapter2\\jimei2.png'
img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,6,3,0.04)
img[dst>0.01*dst.max()]=[0,0,255]
cv2.imshow('dst',img)
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()
代码运行效果如下:
当ksize参数设为23时:
代码运行效果如下:
可以看到,如果将参数设置为3,当检测到大楼方块的边界时,大楼中方块的所有对角线都会被认为是角点。如果参数设置为23,只有大楼方块的角点才能被检测为角点。
调整cornerHarris的第二个参数可以改变标记角点的记号大小。
当 blockSize参数为6时:
编写代码:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
filename = 'D:\\Python\\chapter2\\jimei2.png'
img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,6,3,0.04)
# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]
cv2.imshow('dst',img)
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()
代码运行效果如下:
当 blockSize参数为2时:
代码运行效果如下:
可以看到 blockSize参数值越小,标记角点的记号越小。
图像中寻找对应点:
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("D:\\Python\\chapter2\\jimei2.jpg").convert("L"))
im2 = array(Image.open("D:\\Python\\chapter2\\jimei2.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()
分析:
该算法的结果存在一些不正确匹配。这是因为,与现代的一些方法(下面将会提到)相比,图像像素块的互相关矩阵具有较弱的描述性。实际运用中,我们通常使用更稳健的方法来处理这些对应匹配。这些描述符还有一个问题,它们不具有尺度不变性和旋转不变性,而算法中像素块的大小也会影响对应匹配的结果。
(二)SIFT(尺度不变特征变换)
前面介绍的cornerHarris函数可以很好的检测角点,这与角点本身的特性有关,这些角点在图像旋转的情况下也能被检测到。然而,如果减小(或增加)图像的大小,可能会丢失图像的某些部分,或者有可能增加角点的质量。
这种特征损失现象需要用一种与图像比例无关的角点检测方法来解决——尺度不变特征变换(Scale-Invariant Feature Transform , SIFT),该函数会对不同的图像尺度(尺度不变特征变换)输出相同的结果。
注意:上述概念中只是进行 “ 特征变换 ”,此意味 SIFT 会通过一个特征向量来描述关键点周围区域情况,而不检测关键点(关键点可由 Difference of Gaussians DoG检测)。
匹配的核心问题是将同一目标在不同时间、不同分辨率、不同光照、不同方向的情况下所成的像对应起来。
传统的匹配算法往往是直接提取角点或边缘,对环境的适应能力较差,需要一种鲁棒性强,能够适应不同情况的有效的目标识别的方法。
SIFT 特征包括兴趣点检测器和描述子。SIFT 描述子具有非常强的稳健性,这在很大程度上也是 SIFT 特征能够成功和 流行的主要原因。自从 SIFT 特征的出现,许多其他本质上使用相同描述子的方法 也相继出现。现在,SIFT 描述符经常和许多不同的兴趣点检测器相结合使用(有些情况下是区域检测器),有时甚至在整幅图像上密集地使用。SIFT 特征对于尺度、 旋转和亮度都具有不变性,因此,它可以用于三维视角和噪声的可靠匹配。
SIFT特征检测的步骤:
尺度空间的极值检测:搜索所有尺度空间上的图像,通过高斯微分函数来识别潜在的对尺度和旋转不变的兴趣点。
特征点定位:在每个候选的位置上,通过一个拟合精细模型来确定位置尺度,关键点的选取依据他们的稳定程度。
特征方向赋值: 基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向,后续的所有操作都是对于关键点的方向、尺度和位置进行变换,从而提供这些特征的不变性。
特征点描述: 在每个特征点周围的邻域内,在选定的尺度上测量图像的局部梯度,这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变换。
SIFT算法的特点
图像的局部特征,对旋转、尺度缩放、亮度变化保持不变,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
独特性好,信息量丰富,适用于海量特征库进行快速、准确的匹配。
多量性,即使是很少几个物体也可以产生大量的SIFT特征
高速性,经优化的SIFT匹配算法甚至可以达到实时性
扩招性,可以很方便的与其他的特征向量进行联合。
可以解决的问题:
目标的旋转、缩放、平移
图像的仿射,投影变换
光照影响
目标遮挡
杂物场景
噪声
2.1 兴趣点
SIFT 特征使用高斯差分函数来定位兴趣点:
其中,Gσ 是上一章中介绍的二维高斯核,Iσ 是使用Gσ 模糊的灰度图像,κ 是决定相差尺度的常数。兴趣点是在图像位置和尺度变化下 D(x,σ) 的最大值和最小值点。这些候选位置点通过滤波去除不稳定点。基于一些准则,比如认为低对比度和位于边上的点不是兴趣点,我们可以去除一些候选兴趣点。
2.2 描述子
上面讨论的兴趣点(关键点)位置描述子给出了兴趣点的位置和尺度信息。为了实现旋转不变性,基于每个点周围图像梯度的方向和大小,SIFT 描述子又引入了参考方向。SIFT 描述子使用主方向描述参考方向。主方向使用方向直方图(以大小为权重)来度量。
2.3 检测兴趣点
使用开源工具包 VLFeat 提供的二进制文件来计算图像的 SIFT 特征 。VLFeat 工具包可以从 http://www.vlfeat.org/ 下载,二进制文件可以在所有主要的平台上运行。
编写代码:
# -*- 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 = 'D:\\Python\\chapter2\\jimei2.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'(a)SIFT特征', fontproperties=font)
subplot(132)
sift.plot_features(im, l1, circle=True)
title(u'(b)用圆圈表示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'(c)Harris角点', fontproperties=font)
show()
分析:
为了比较 Harris 角点和 SIFT 特征的不同,右图(图c)显示的是同一幅图像的 Harris 角点。你可以看到,两个算法所选择特征点的位置不同。
换个方法:
编写代码:
# -*- coding: utf-8 -*-
import cv2
import sys
import numpy as np
imgpath = 'D:\\Python\\chapter2\\lovely.jpg'
img = cv2.imread('D:\\Python\\chapter2\\lovely.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建一个sift对象
sift = cv2.xfeatures2d.SIFT_create()
keypoints, descriptor = sift.detectAndCompute(gray, None)
img = cv2.drawKeypoints(image=img, outImage=img, keypoints=keypoints, flags=4, color=(51, 163, 236))
cv2.imshow('sift_keypoints', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码运行效果如下:
分析:
完成常规方法加载要处理的图像后,为了使脚本具有通用性,使用python的sys模块将图像路径通过命令行参数传递给脚本。
SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量,由方法名称我们就可以知道,它主要包括两个操作:检测和计算,操作的返回值是关键点信息和描述符,最后在图像上绘制关键点,并用imshow函数显示这幅图像。
注意,这里将标志值4传给draeKeypoints函数,标志值4其实是下面这个cv2模块的属性值:CV2.DRAM_MATCHES_FLAGS_DRAW_RICH_KEYPOINT
该代码对图像的每个关键点都绘制了圆圈和方向。
pt: 图像中关键点的x,y坐标点
size: 特征的直径
angle: 特征的方向
response: 关键点的强度。某些特征会通过SIFT来分类,因为它得到的特征比其他特征更好,通过查看response属性可以评估特征强度。
octave: 特征所在金字塔的层级,算法在每次迭代时候,作为参数的图像尺寸和相邻像素都会发生变化
kp.class_id: 对象关键点ID
2.4 匹配描述子
对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则(同样是由 Lowe 提出的)是使用这两个特征距离和两个最匹配特征距离的比率。相比于图像中的其他特征,该准则保证能够找到足够相似的唯一特征。使用该方法可以使错误的匹配数降低。
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 = 'D:\\Python\\chapter2\\jimei2.jpg'
im2f = 'D:\\Python\\chapter2\\jimei2.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()
分析:
SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。
下面扩展一种新算法:
基于ORB的特征检测与特征匹配
ORB 将基于 FAST 关键点检测的技术和基于 BRIEF 描述符的技术相结合,因此首先介绍 FAST 和 BRIEF。
FAST:
FAST算法会在像素周围绘制一个圆,该圆包括16个像素,然后,FAST会将每个像素与加上一个阈值的圆心像素值进行比较,如有连续,比加上一个阈值的圆心的像素值还亮或暗的像素,则可认为圆心是角点。
BRIEF:
BRIEF(Binary Robust Independent Elementary Features ) 是一个描述符,而不是一种算法。检测结果是一组关键点,计算结果是描述符。 关键点描述符是图像的一种表示,因此可比较两个图像的关键点描述符;并找到它们的共同之处,所以描述符可作为特征匹配的一种方法。
在ORB的论文中,作者得到了如下结果:
向 FAST 增加一个快速、准确的方向分量(component)。
能高效计算带方向的 BRIEF 特征。
基于带方向的 BRIEF 特征的方差分析和相关性分析。
在旋转不变性条件下学习一种不相关的 BRIEF 特征,这会在最邻近的应用中得到较好的性能。
编写代码:
# -*- coding: utf-8 -*-
import cv2
from matplotlib import pyplot as plt
# 首先加载两幅图(查询图像和训练图像)
img1 = cv2.imread('D:\\Python\\chapter2\\dragonboat.jpg')
img2 = cv2.imread('D:\\Python\\chapter2\\dragonboat2.jpg')
# 创建ORB特征检测器和描述符
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 对查询图像和训练图像都要检测,然后计算关键点和描述符
# BFMatcher 创建匹配器对象,
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# match 实现暴力匹配
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 现已经获取所有需要的信息,将其图标来绘制这些匹配
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:40], img2, flags=2)
plt.imshow(img3)
plt.show()
分析:
匹配非常简单,遍历描述符,确定描述符是否已经匹配,然后计算匹配质量(距离)并排序,这样就可以在一定置信度下显示前 n 个匹配,以此得到那两幅图是匹配的。
ORB 旨在优化和加快操作速度,同时包括非常重要的一步:以旋转感知(rotation-aware)的方式使用 BRIEF ,这样即使在训练图像和查询图像之间旋转差别很大的情况下也能够提高匹配效果。可以看到龙舟图匹配SIFT特征时匹配结果为0和1,匹配情况并不理想,而使用ORB 算法,出现了一些正确的匹配。
(三)匹配地理标记图像
3.1 从 Panoramio 下载地理标记图像
根据课本要求编写的代码,发现谷歌的Panoramio已经关闭,暂时找不到类似的网站,只能先这样搁置了。
# -*- coding: utf-8 -*-
import json
import os
import urllib
import urlparse
from PCV.tools.imtools import get_imlist
from pylab import *
from PIL import Image
# change the longitude and latitude here
# here is the longitude and latitude for Oriental Pearl
minx = '-77.037564'
maxx = '-77.035564'
miny = '38.896662'
maxy = '38.898662'
# number of photos
numfrom = '0'
numto = '20'
url = 'http://www.panoramio.com/map/get_panoramas.php?order=popularity&set=public&from=' + numfrom + '&to=' + numto + '&minx=' + minx + '&miny=' + miny + '&maxx=' + maxx + '&maxy=' + maxy + '&size=medium'
c = urllib.urlopen(url)
j = json.loads(c.read())
imurls = []
for im in j['photos']:
imurls.append(im['photo_file_url'])
for url in imurls:
image = urllib.URLopener()
image.retrieve(url, os.path.basename(urlparse.urlparse(url).path))
print 'downloading:', url
# 显示下载到的20幅图像
figure()
gray()
filelist = get_imlist('./')
for i, imlist in enumerate(filelist):
im = Image.open(imlist)
subplot(4, 5, i + 1)
imshow(im)
axis('off')
show()
3.2 使用局部描述子匹配
下面将已经下载在本地的图像,提取局部描述子。
编写代码:
# -*- coding: utf-8 -*-
import json
import os
import urllib
import urlparse
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
download_path = "D:\\Python\\chapter2\\jimei"
path = "D:\\Python\\chapter2\\jimei\\"
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 = 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]
代码运行效果如下:
将每对图像间的匹配特征数保存在 matchscores 数组中。因为该“距离度量”是 对称的,所以可以不在代码的最后部分复制数值,来将 matchscores 矩阵填充完整;填充完整后的 matchscores 矩阵只是看起来更好。这些特定图像的 matchscores 矩阵里的数值如下:
129 0 0 2 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 8
0 83 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 2
0 0 41 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
2 1 0 51 0 0 2 2 0 0 0 2 2 0 0 0 2 3 2 0
0 0 0 0 316 0 0 1 0 0 0 0 0 2 0 0 0 0 0 1
0 0 0 0 0 245 0 0 1 0 0 0 0 0 0 0 0 1 1 0
0 0 0 2 0 0 82 0 0 0 1 4 4 0 2 0 0 5 1 0
0 1 0 2 1 0 0 249 0 0 0 1 0 0 1 0 2 0 1 1
1 1 0 0 0 1 0 0 260 0 0 0 0 0 0 0 1 0 0 20
0 0 0 0 0 0 0 0 0 348 0 0 1 0 0 0 0 0 0 2
0 0 0 0 0 0 1 0 0 0 258 0 0 0 0 0 1 1 1 0
1 1 0 2 0 0 4 1 0 0 0 332 5 2 15 0 3 6 0 0
2 0 0 2 0 0 4 0 0 1 0 5 268 1 4 0 3 37 1 0
0 0 1 0 2 0 0 0 0 0 0 2 1 103 1 0 0 1 0 0
3 0 0 0 0 0 2 1 0 0 0 15 4 1 368 0 6 9 1 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2273 0 1 0 0
19 0 0 2 0 0 0 2 1 0 1 3 3 0 6 0 542 0 0 0
1 0 0 3 0 1 5 0 0 0 1 6 37 1 9 1 0 527 3 0
0 1 0 2 0 1 1 1 0 0 1 0 1 0 1 0 0 3 1139 0
2 2 0 0 1 0 0 1 20 2 0 0 0 0 0 0 0 0 0 499
使用该matchscores 矩阵作为图像间简单的距离度量方式(具有相似内容的图像间拥有更多的匹配特征数)
3.3 可视化连接的图像
首先通过图像间是否具有匹配的局部描述子来定义图像间的连接,然后可视化这些连接情况。为了完成可视化,我们可以在图中显示这些图像,图的边代表连接。 可以使用pydot 工具包(http://code.google.com/p/pydot/),该工具包是功能强大的GraphViz 图形库的Python 接口。
为了创建显示可能图像组的图,如果匹配的数目高于一个阈值,我们使用边来连接相应的图像节点。为了得到图中的 图像,需要使用图像的全路径(在下面例子中,使用 path 变量表示)。为了使图像看起来漂亮,我们需要将每幅图像尺度化为缩略图形式,缩略图的最大边为 100 像素。
编写代码:
# -*- coding: utf-8 -*-
import json
import os
import urllib
import urlparse
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
download_path = "D:\\Python\\chapter2\\lovely"
path = "D:\\Python\\chapter2\\lovely\\"
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 = 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]
# 可视化
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('D:\\Python\\chapter2\\lovely\\lovely.jpg')
代码运行效果如下: