进行图像检索的时候,我们可以用穷举的方法,但是一一匹配效率太过低,工作量过大,于是参考文本信息检索的模型,提取特征点进行降维处理。在文本信息检索中,BOW模型假定对于一个文档,忽略它的单词顺序和语法、句法等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的,不依赖于其它单词是否出现。也就是说,文档中任意一个位置出现的任何单词,都不受该文档语意影响而独立选择的。
将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。对图像库里的图像进行SITF特征提取,SIFT原理和方法这里不再介绍。这些典型实例可以通 过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。对于给定的问题、图像类型,或在通常情况下仅需 呈现视觉内容,可以创建特定的词汇。
第一步是提取所有图像的特征,称之为“视觉词汇”,将他们聚集在一起,此时他们还是未分类的。
我们用SIFT算法从图像中提取不变特征点,作为视觉词汇,并构造单词表,用单词表中的单词表示一幅图像。相当于给每一张图像打上一个标记(一张身份证)
“视觉词典(visual vocabulary)”的构造的过程就是一个图像分类的过程。这里我们用的图像分类方法是K-means聚类方法,当然不局限于这一种分类方法,比如对于文本分类而言,支持向量机(SVM)是一个可靠的选择。。
K-means算法流程:
可以看到再用K-means算法的时候,你需要人为设置分类数目,体现在下面这段代码(cocabulary.py)的train()函数,(其中“1000”是我的码本数目,“10”表示降采样,我每个10个特征点取一次),这里需要思考一个问题:如何选择码本的大小?
太少:视觉单词无法覆盖所有可能出现的情况
太多: 计算量大,容易过拟合
常用参数设置:视觉单词数量(K-means算法获取的聚类中心)一般为 K=3000-100000,即图像整体描述的直方图维度为 3000~10000。我们要不断尝试,找到相对来说最适合的分类数目,最合适的意思是,你的分类比较正确。
假设Dictionary词典的Size为3000,即有3000个词。那么咱们可以用K-means算法对所有的patch进行聚类,k=3000,我们知道,等k-means收敛时,我们也得到了每一个cluster最后的质心,那么这3000个质心(维数128)就是词典里的3000个词了,词典构建完毕。
`
def train(self,featurefiles,k=100,subsampling=10):
“”" Train a vocabulary from features in files listed
in featurefiles using k-means with k number of words.
Subsampling of training data can be used for speedup. “”"
nbr_images = len(featurefiles)
# read the features from file
descr = []
descr.append(sift.read_features_from_file(featurefiles[0])[1])
descriptors = descr[0] #stack all features for k-means
for i in arange(1,nbr_images):
descr.append(sift.read_features_from_file(featurefiles[i])[1])
descriptors = vstack((descriptors,descr[i]))
# k-means: last number determines number of runs
self.voc,distortion = kmeans(descriptors[::subsampling,:],k,1)
self.nbr_words = self.voc.shape[0]
# go through all training images and project on vocabulary
imwords = zeros((nbr_images,self.nbr_words))
for i in range( nbr_images ):
imwords[i] = self.project(descr[i])
nbr_occurences = sum( (imwords > 0)*1 ,axis=0)
self.idf = log( (1.0*nbr_images) / (1.0*nbr_occurences+1) )
self.trainingdata = featurefiles
`
利用单词表的中词汇表示图像。利用SIFT算法,可以从每幅图像中提取很多个特征点,这些特征点都可以用单词表中的单词近似代替,通过统计单词表中每个单词在图像中出现的次数,可以将图像表示成为一个n维数值向量。但是考虑到很大概率实际会发生这种情况:
通常,在每篇文本中,如“这”“和”“是”这类词出现的概率比较大,但是他们对于区别不同的文本并没有多大的作用,所以在单词计数时会忽略掉这些常用词,这些常用词称为停用词。于是我们为了提高匹配的精度,给每个特征点都赋上权重。
通过单词计数来构建文档直方图向量v,从而建立文档索引。通常,在单词计数时会忽略掉一些常用词,如“这”“和”“是”等,这些常用词称为停用词。由于每篇文档长度不同,故除以直方图总和将向量归一化成单位长度。对于直方图向量中的 每个元素,一般根据每个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中一个单词的重要性与它在文档中出现的次数成正比,而与它在语料库中出现的次数成反比。
最常用的权重是 tf-idf(term frequency-inverse document frequency,词频- 逆向文档频率 ),单词 w 在文档 d 中的词频是:
nw 是单词 w 在文档 d 中出现的次数。为了归一化,将 nw 除以整个文档中单词的总数。
逆向文档频率为:
|D| 是在语料库D 中文档的数目,分母是语料库中包含单词w 的文档数d。将两者 相乘可以得到矢量 v 中对应元素的 tf-idf 权重。所以再下一步把输入图像转化成视觉单词(visual words) 的频率直方图时,我们不能简单地把特征点作为唯一标准,要降低那些比重较大的、影响实验结果的的特征。不过我后来再其他地方发现了这个,原来有可以直接调用的 tf-idf
可以使用scikit-learn中的TfidfVectorizer来完成这项任务,它将文本转换为术语频率乘以逆文档频率(tf-idf)值的矩阵,此方法适用于机器学习。另外,在这个过程中可以删除停用词(一些常用词例如:the,is等)。
先初始化一个3000个bin的初始值为0的直方图h。每一幅图像不是有很多“视觉词汇”么?我们就再次计算这些“视觉词汇”和每一个质心的距离,看看每一个特征离哪一个质心最近,那么直方图h中相对应的bin就加1,然后计算完这幅图像所有的“视觉词汇”之后,就得到了一个bin=100的直方图,然后进行归一化,用这个3000维的向量来表示这幅图像。
这里有几处说明一下:
(1)log(IDF):
当IDF越接近与1,说明出现的比例高,所以log(IDF)越接近0应该降低权重。
(2)每个bin上的值=投票值*IDF权重,并非只是数量
(3)得到的bin最后还有归一化处理,用3000维向量来表示这个图像
Vocabulary.py文件里面的project()函数就是将图像转化为视觉单词的频率直方图,得到回访图的方法上面有介绍
def project(self,descriptors):
""" Project descriptors on the vocabulary
to create a histogram of words. """
# histogram of image words
imhist = zeros((self.nbr_words))
words,distance = vq(descriptors,self.voc)
for w in words:
imhist[w] += 1
return imhist
这是我的数据集,图片都是集美大学的校园景色,分类难度较大,因为大部分图片天空或者草地、水面的比重比较大(比较难匹配特征点),做过处理以后同一个建筑物的照片颜色也差距较大的(颜色是匹配特征点重要因素)
# -*- coding: utf-8 -*-
import pickle
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift
#获取图像列表
imlist = get_imlist('first1000/')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#提取文件夹下图像的sift特征
for i in range(nbr_images):
sift.process_image(imlist[i], featlist[i])
#生成词汇
voc = vocabulary.Vocabulary('ukbenchtest')
voc.train(featlist, 1000, 10)
#保存词汇
# saving vocabulary
with open('first1000/vocabulary.pkl', 'wb') as f:
pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)
这时候我们会在存图像的文件夹里得到每一张图像对应的sift特征。
from numpy import *
import pickle
import sqlite3
from functools import cmp_to_key
import operator
class Indexer(object):
def __init__(self,db,voc):
""" Initialize with the name of the database
and a vocabulary object. """
self.con = sqlite3.connect(db)
self.voc = voc
def __del__(self):
self.con.close()
def db_commit(self):
self.con.commit()
def get_id(self,imname):
""" Get an entry id and add if not present. """
cur = self.con.execute(
"select rowid from imlist where filename='%s'" % imname)
res=cur.fetchone()
if res==None:
cur = self.con.execute(
"insert into imlist(filename) values ('%s')" % imname)
return cur.lastrowid
else:
return res[0]
def is_indexed(self,imname):
""" Returns True if imname has been indexed. """
im = self.con.execute("select rowid from imlist where filename='%s'" % imname).fetchone()
return im != None
def add_to_index(self,imname,descr):
""" Take an image with feature descriptors,
project on vocabulary and add to database. """
if self.is_indexed(imname): return
print ('indexing', imname)
# get the imid
imid = self.get_id(imname)
# get the words
imwords = self.voc.project(descr)
nbr_words = imwords.shape[0]
# link each word to image
for i in range(nbr_words):
word = imwords[i]
# wordid is the word number itself
self.con.execute("insert into imwords(imid,wordid,vocname) values (?,?,?)", (imid,word,self.voc.name))
# store word histogram for image
# use pickle to encode NumPy arrays as strings
self.con.execute("insert into imhistograms(imid,histogram,vocname) values (?,?,?)", (imid,pickle.dumps(imwords),self.voc.name))
def create_tables(self):
""" Create the database tables. """
self.con.execute('create table imlist(filename)')
self.con.execute('create table imwords(imid,wordid,vocname)')
self.con.execute('create table imhistograms(imid,histogram,vocname)')
self.con.execute('create index im_idx on imlist(filename)')
self.con.execute('create index wordid_idx on imwords(wordid)')
self.con.execute('create index imid_idx on imwords(imid)')
self.con.execute('create index imidhist_idx on imhistograms(imid)')
self.db_commit()
class Searcher(object):
def __init__(self,db,voc):
""" Initialize with the name of the database. """
self.con = sqlite3.connect(db)
self.voc = voc
def __del__(self):
self.con.close()
def get_imhistogram(self,imname):
""" Return the word histogram for an image. """
im_id = self.con.execute(
"select rowid from imlist where filename='%s'" % imname).fetchone()
s = self.con.execute(
"select histogram from imhistograms where rowid='%d'" % im_id).fetchone()
# use pickle to decode NumPy arrays from string
return pickle.loads(s[0])
def candidates_from_word(self,imword):
""" Get list of images containing imword. """
im_ids = self.con.execute(
"select distinct imid from imwords where wordid=%d" % imword).fetchall()
return [i[0] for i in im_ids]
def candidates_from_histogram(self,imwords):
""" Get list of images with similar words. """
# get the word ids
words = imwords.nonzero()[0]
# find candidates
candidates = []
for word in words:
c = self.candidates_from_word(word)
candidates+=c
# take all unique words and reverse sort on occurrence
tmp = [(w,candidates.count(w)) for w in set(candidates)]
tmp.sort(key=cmp_to_key(lambda x,y:operator.gt(x[1],y[1])))
tmp.reverse()
# return sorted list, best matches first
return [w[0] for w in tmp]
def query(self,imname):
""" Find a list of matching images for imname. """
h = self.get_imhistogram(imname)
candidates = self.candidates_from_histogram(h)
matchscores = []
for imid in candidates:
# get the name
cand_name = self.con.execute(
"select filename from imlist where rowid=%d" % imid).fetchone()
cand_h = self.get_imhistogram(cand_name)
cand_dist = sqrt( sum( self.voc.idf*(h-cand_h)**2 ) )
matchscores.append( (cand_dist,imid) )
# return a sorted list of distances and database ids
matchscores.sort()
return matchscores
def get_filename(self,imid):
""" Return the filename for an image id. """
s = self.con.execute(
"select filename from imlist where rowid='%d'" % imid).fetchone()
return s[0]
def tf_idf_dist(voc,v1,v2):
v1 /= sum(v1)
v2 /= sum(v2)
return sqrt( sum( voc.idf*(v1-v2)**2 ) )
def compute_ukbench_score(src,imlist):
""" Returns the average number of correct
images on the top four results of queries. """
nbr_images = len(imlist)
pos = zeros((nbr_images,4))
# get first four results for each image
for i in range(nbr_images):
pos[i] = [w[1]-1 for w in src.query(imlist[i])[:4]]
# compute score and return average
score = array([ (pos[i]//4)==(i//4) for i in range(nbr_images)])*1.0
return sum(score) / (nbr_images)
# import PIL and pylab for plotting
from PIL import Image
from pylab import *
def plot_results(src,res):
""" Show images in result list 'res'. """
figure()
nbr_results = len(res)
for i in range(nbr_results):
imname = src.get_filename(res[i])
subplot(1,nbr_results,i+1)
imshow(array(Image.open(imname)))
axis('off')
show()
SQLite 可以从 pysqlite2 模块中导入。 Indexer 类连接数据库,并且一旦创建(调用 init() 方法)后就可以保存词汇对象。del() 方法 可以确保关闭数据库连接,db_commit() 可以将更改写入数据库文件。
我们仅需一个包含三个表单的简单数据库模式。表单 imlist 包含所有要索引的图像
文件名;imwords 包含了一个那些单词的单词索引、用到了哪个词汇、以及单词出现 在哪些图像中;最后,imhistograms 包含了全部每幅图像的单词直方图。根据矢量空间模型,我们需要这些以便进行图像比较。
# -*- coding: utf-8 -*-
import pickle
from PCV.imagesearch import imagesearch
from PCV.localdescriptors import sift
from sqlite3 import dbapi2 as sqlite
from PCV.tools.imtools import get_imlist
#获取图像列表
imlist = get_imlist('first1000/')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
# load vocabulary
#载入词汇
with open('first1000/vocabulary.pkl', 'rb') as f:
voc = pickle.load(f)
#创建索引
indx = imagesearch.Indexer('testImaAdd.db',voc)
indx.create_tables()
# go through all images, project features on vocabulary and insert
#遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:1000]:
locs,descr = sift.read_features_from_file(featlist[i])
indx.add_to_index(imlist[i],descr)
# commit to database
#提交到数据库
indx.db_commit()
con = sqlite.connect('testImaAdd.db')
print (con.execute('select count (filename) from imlist').fetchone())
print (con.execute('select * from imlist').fetchone())
此时当前目录下生成一个testImaAdd.db的文件,里面是图像的检索信息:
添加图像时用到的add_to_index()方法都在imagesearch.py文件里。生成的vocabulary.pkl包含已经训练好的词汇,我们就可以检查数据库中的内容了:
from pysqlite2 import dbapi2 as sqlite
con = sqlite.connect('test.db')
print con.execute('select count (filename) from imlist').fetchone()
print con.execute('select * from imlist').fetchone()
imagesearch.py文件里的Sercher类用于实现搜索,一个新的 Searcher 对象连接到数据库,一旦删除便关闭连接。如果图像数据库很大,逐一比较整个数据库中的所有直方图往往是不可行的。我们 需要找到一个大小合理的候选集(这里的“合理”是通过搜索响应时间、所需内存 等确定的),单词索引的作用便在于此:我们可以利用单词索引获得候选集,然后只需在候选集上进行逐一比较。
import pickle
from PCV.localdescriptors import sift
from PCV.imagesearch import imagesearch
from PCV.geometry import homography
from PCV.tools.imtools import get_imlist
# 载入图像列表和词汇
with open('ukbench_imlist.pkl','rb') as f:
imlist = pickle.load(f) featlist = pickle.load(f)
nbr_images = len(imlist)
with open('vocabulary.pkl', 'rb') as f: voc = pickle.load(f)
src = imagesearch.Searcher('test.db',voc)
# 查询图像的索引号和返回的搜索结果数目
q_ind = 50
nbr_results = 20
# 常规查询
res_reg = [w[1] for w in src.query(imlist[q_ind])[:nbr_results]]
print 'top matches (regular):', res_reg
# 载入查询图像特征
q_locs,q_descr = sift.read_features_from_file(featlist[q_ind])
fp = homography.make_homog(q_locs[:,:2].T)
# 用 RANSAC 模型拟合单应性
model = homography.RansacModel()
rank = {}
# 载入搜索结果的图像特征
for ndx in res_reg[1:]:
locs,descr = sift.read_features_from_file(featlist[ndx])
# 获取匹配数
matches = sift.match(q_descr,descr)
ind = matches.nonzero()[0]
ind2 = matches[ind]
tp = homography.make_homog(locs[:,:2].T)
# 计算单应性,对内点计数。如果没有足够的匹配数则返回空列表
try:
H,inliers = homography.H_from_ransac(fp[:,ind],tp[:,ind2],model,match_theshold=4)
except
: inliers = []
# 存储内点数
rank[ndx] = len(inliers)
# 将字典排序,以首先获取最内层的内点数
sorted_rank = sorted(rank.items(), key=lambda t: t[1], reverse=True)
res_geom = [res_reg[0]]+[s[0] for s in sorted_rank]
print 'top matches (homography):', res_geom
# 显示靠前的搜索结果
imagesearch.plot_results(src,res_reg[:8])
imagesearch.plot_results(src,res_geom[:8])
这里有一个重排序的过程,先将检索出来的挑选出来,再把每一张图像进行一一比对,按照相似程度得到一个排列顺序,具体:
首先,载入图像列表、特征列表(分别包含图像文件名和 SIFT 特征文件)及词汇。 然后,创建一个 Searcher 对象,执行定期查询,并将结果保存在 res_reg 列表中。然 后载入 res_reg 列表中每一幅图像的特征,并和查询图像进行匹配。单应性通过计算 匹配数和计数内点数得到。最终,我们可以通过减少内点的数目对包含图像索引和内点数的字典进行排序。
为了建立这些演示程序,我们将采用CherryPy 包,参见 http://www.cherrypy.org。 CherryPy 是一个纯Python 轻量级Web 服务器,使用面向对象模型
# -*- coding: utf-8 -*-
import cherrypy
import pickle
import urllib
import os
from numpy import *
#from PCV.tools.imtools import get_imlist
from PCV.imagesearch import imagesearch
import random
"""
This is the image search demo in Section 7.6.
"""
class SearchDemo:
def __init__(self):
# 载入图像列表
self.path = 'first1000/'
#self.path = 'D:/python_web/isoutu/first500/'
self.imlist = [os.path.join(self.path,f) for f in os.listdir(self.path) if f.endswith('.jpg')]
#self.imlist = get_imlist('./first500/')
#self.imlist = get_imlist('E:/python/isoutu/first500/')
self.nbr_images = len(self.imlist)
print (self.imlist)
print (self.nbr_images)
self.ndx = list(range(self.nbr_images))
print (self.ndx)
# 载入词汇
# f = open('first1000/vocabulary.pkl', 'rb')
with open('first1000/vocabulary.pkl','rb') as f:
self.voc = pickle.load(f)
#f.close()
# 显示搜索返回的图像数
self.maxres = 10
# header and footer html
self.header = """
Image search
"""
self.footer = """