赞
踩
目录
CBIR技术用于检索在视觉上具有相似性的图像,返回的图像可以是颜色相似、纹理相似、图像中的物体或场景相似。对于高层查询,比如寻找相似的物体,将查询图像与数据库中所有的图像进行完全比较往往是不可行的,因为当数据库很大时,这种查询方式会耗费很长的时间。不过,研究者目前已经成功地引入文本挖掘技术到CBIR中处理问题,因此在数百万图像中搜索具有相似内容的图像成为了可能。
矢量空间模型
矢量空间模型是一个用于表示和搜索文本文档的模型。它基本上可以应用于任何对象类型,包括图像。矢量空间模型的名字来源于用矢量表示文本文档,这些矢量是由文本词频直方图构成,即矢量包含了每个单词出现的次数,且在其他别的地方包含了很多0元素。由于其忽略了单词出现的顺序及位置故也称为BOW表示模型。
通过单词计数来构建文档直方图向量v,从而建立文档索引。由于每篇文档的长度不同,因此除以直方图总和将向量归一化成单位长度。对于直方图向量中的每个元素,一般根据每个单词的重要性来赋予相应的权重。
常用的权重是tf-idf,单词w在文档d中的词频是:
是单词w在文档d中出现的次数。为了归一化,将除以整个文档中单词的总数。逆向文档频率就为:
是在语料库D中文档的数目,分母是语料库中包含单词w的文档数d。将两者相乘就可以得到矢量v中对应元素的tf-idf权重。
可以利用SIFT局部描述子建立视觉等效单词,从而将文本挖掘技术应用到图像中。它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中,这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。视觉单词组成的集合称为视觉词汇,亦或者视觉码本。
从一个很大的训练图像集提取特征描述子,利用一些聚类算法就可以构建出视觉单词,聚类算法中最常用的是K-means,这时得到的视觉单词是聚类质心。这里使用的数据集是书中给出的肯塔基大学物体识别数据集。可以在上一章给出的链接里下载得到。
创建词汇
创建视觉单词词汇,首先要提取特征描述子,这里使用的是SIFT特征描述子。由于提供的图片太多这里就选取了其中的109幅图像。
- from PCV.tools import imtools
- from PCV.localdescriptors import sift
-
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
- for i in range(nbr_images):
- sift.process_image(imlist[i], featlist[i])
之后创建名为vocabulary,py文件,创建一个词汇类,以及在训练图像数据集上训练出一个词汇的方法:
- class Vocabulary(object):
-
- def __init__(self,name):
- self.name = name
- self.voc = []
- self.idf = []
- self.trainingdata = []
- self.nbr_words = 0
-
- def train(self,featurefiles,k=100,subsampling=10):
- """ 用含有k个单词的K-means列出在featurefiles中的特征文件训练出一个词汇,对训练数据下采样可以加快训练速度 """
-
- nbr_images = len(featurefiles)
- # 从文件中读取特征
- descr = []
- descr.append(sift.read_features_from_file(featurefiles[0])[1])
- descriptors = descr[0] # 将所有的特征并在一,以便后面进行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: 最后一个参数决定运行次数
- self.voc,distortion = kmeans(descriptors[::subsampling,:],k,1)
- self.nbr_words = self.voc.shape[0]
-
- # 遍历所有的训练图像,并投影到词汇上
- 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
-
- def project(self,descriptors):
- """ 将描述子投影到词汇上,以创建单词直方图 """
-
- # 图像单词直方图
- imhist = zeros((self.nbr_words))
- words,distance = vq(descriptors,self.voc)
- for w in words:
- imhist[w] += 1
-
- return imhist
这个类中包含了一个由单词聚类中心VOC与每个单词对应的逆向文档频率构成的向量,为了在某些图像集上训练词汇,train()方法获取包含由.sift描后缀的述子文件列表和词汇单词数k。我们可以在K-means聚类阶段对训练数据下采样,避免消耗过多时间。
接着保存视觉词汇:
- import pickle
- from PCV.imagesearch import vocabulary
- from PCV.tools import imtools
-
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
-
- voc = vocabulary.Vocabulary('ukbenchtest')
- voc.train(featlist, 1000, 10)
- # 保存词汇
- with open('vocabulary.pkl', 'wb') as f:
- pickle.dump(voc, f)
- print('vocabulary is:', voc.name, voc.nbr_words)
在索引前,需要先建立一个数据库,这里对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保存视觉单词及对应图像的单词直方图。从而利用图像对数据库进行查询,并返回相似的图像作为结果。
使用SQLite作为数据库,SQLite将所有信息都保存到一个文件,在pycharm中可以直接通过settings里的python interpreter 进行安装。
创建一个imagesearch.py的文件:
- def __init__(self,db,voc):
- """ 初始化数据库的名称及词汇对象 """
-
- self.con = sqlite.connect(db)
- self.voc = voc
-
- def __del__(self):
- self.con.close()
-
- def db_commit(self):
- self.con.commit()
使用pickle模块将数组编码成字符串以及将字符串进行解码,SQLite可以从sqlite3模块中导入,上述方法存在indexer类中,创建之后就可以保存词汇对象。__del__()方法确保关闭数据库连接,db_commit()可以将更改写入数据库文件。
下面为存储图像及视觉单词的简单数据库模式:
lmlist包含所有要索引的图像文件名,imwords包含了单词的单词索引、用到了哪个词汇、以及单词出现在哪些图像中,imhistograms包含了全部每幅图像的单词直方图。
在indexer类中添加下面的代码可以加快搜索速度:
- def create_tables(self):
- """ 创建数据库表单 """
-
- 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()
在创建完数据库表单后,就可以在索引中添加图像,这里要在indexer类中添加add_to_index()方法,
- def add_to_index(self,imname,descr):
- """ 获取一幅带有特征描述子 的图像,投影到词汇上并添加到数据库 """
-
- if self.is_indexed(imname): return
- print ('indexing', imname)
-
- # 获得图像id
- imid = self.get_id(imname)
-
- # 获取单词
- imwords = self.voc.project(descr)
- nbr_words = imwords.shape[0]
-
- # 将每个单词与图像链接起来
- for i in range(nbr_words):
- word = imwords[i]
- # wordid就是单词本身的数字
- self.con.execute("insert into imwords(imid,wordid,vocname) values (?,?,?)", (imid,word,self.voc.name))
-
- # 存储图像的单词直方图
- # 用pickle模块将NumPy数组编码成字符串
- self.con.execute("insert into imhistograms(imid,histogram,vocname) values (?,?,?)", (imid,pickle.dumps(imwords),self.voc.name))
这个方法获取图像文件名与Numpy数组,该数组包含的是在图像找到的描述子。这些描述子投影到词汇上,并插入到imwords和imhistograms表单中。通过使用辅助函数is_indexed()检查图像是否以及被索引以及get_id()对一幅图像文件名给定id好。
- def get_id(self,imname):
- """ 获取图像id,不存在就进行添加 """
-
- 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):
- """ 当图像名字被索引返回True """
-
- im = self.con.execute("select rowid from imlist where filename='%s'" % imname).fetchone()
- return im != None
由于SQLite的数据库在存储对象或数组时并没有一个标准类型,因此使用pickle的dumps()函数创建一个字符串表示并将其写入数据库。
实验:
遍历整个文件数据库中的样本图像,并为其添加索引:
- import pickle
- from PCV.tools import imtools
- from PCV.imagesearch import imagesearch
- from PCV.localdescriptors import sift
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
- # 载入词汇
- with open('vocabulary.pkl', 'rb') as f:
- voc = pickle.load(f)
-
- # 创建索引器
- indx = imagesearch.Indexer('test.db',voc)
- indx.create_tables()
-
- # 遍历这个图像库,将特征投影到词汇上并添加到索引中
- for i in range(nbr_images)[:109]:
- locs,descr = sift.read_features_from_file(featlist[i])
- indx.add_to_index(imlist[i],descr)
- # 提交到数据库
- indx.db_commit()
接着数据库的内容进行检查:
- from sqlite3 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())
就可以得到下面的结果,这里得到图像数为109幅,以及其保存的位置。
建立好图像的索引后,就可以在数据库中搜索相似的图像了,书中使用的是BoW来表示整个图像,为了实现搜索,在imagesearch.py中添加Scanner类。
- class Searcher(object):
-
- def __init__(self,db,voc):
- """ 初始化数据库的名称 """
- self.con = sqlite.connect(db)
- self.voc = voc
-
- def __del__(self):
- self.con.close()
当一个新的searcher对象连接到数据库时,一旦删除就关闭连接。
可以利用建立起来的索引找到包含特定单词的所有图像,这是对数据库的一次简单的查询,在Searcher类中加入candidates_from_word()方法:
- def candidates_from_word(self,imword):
- """ G获取包含imword的图像列表 """
-
- im_ids = self.con.execute(
- "select distinct imid from imwords where wordid=%d" % imword).fetchall()
- return [i[0] for i in im_ids]
上述函数会给出包含特定单词的所有图像id号。为了对合并了的列表中的每一幅图像id出现的次数进行跟踪,这样做就可以显示有多少单词与单词匹配直方图中的单词匹配,这个过程通过candidates_from_histogram方法完成:
- def candidates_from_histogram(self,imwords):
- """ 获取具有相似单词的图像列表 """
-
- # 获取单词id
- words = imwords.nonzero()[0]
-
- # 寻找候选图像
- candidates = []
- for word in words:
- c = self.candidates_from_word(word)
- candidates+=c
-
- # 获取所有唯一的单词,并按出现次数反向排序
- tmp = [(w,candidates.count(w)) for w in set(candidates)]
- tmp.sort(cmp=lambda x,y:cmp(x[1],y[1]))
- tmp.reverse()
-
- # 返回排序后的列表,最匹配的排在最前面
- return [w[0] for w in tmp]
上述方法从图像单词直方图的非零项创建单词id列表,检索每个单词获得候选集并将其合并到candidates列表中,然后创建一个元组列表每个元组由单词id和次数count构成,count是候选列表中每个单词出现的次数。同时,还以元组中的第二个元组为准,用sort()方法和一个自定义的比较函数对列表进行排序。
在利用一幅图像进行查询时,就没有必要进行完全的搜索,为了对单词直方图进行比较Searcher类就需要从数据库中读入图像的单词直方图。
- def candidates_from_histogram(self,imwords):
- """ 获取具有相似单词的图像列表 """
-
- # 获取单词id
- words = imwords.nonzero()[0]
-
- # 寻找候选图像
- candidates = []
- for word in words:
- c = self.candidates_from_word(word)
- candidates+=c
-
- # 获取所有唯一的单词,并按出现次数反向排序
- tmp = [(w,candidates.count(w)) for w in set(candidates)]
- tmp.sort(cmp=lambda x,y:cmp(x[1],y[1]))
- tmp.reverse()
-
- # 返回排序后的列表,最匹配的排在最前面
- return [w[0] for w in tmp]
为了在字符串与Numpy数组间进行转换,再次使用到了pickle模块,这里用到的是loads()方法。
- def get_imhistogram(self,imname):
- """ 返回一幅图像的单词直方图 """
-
- 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()
-
- # 用pickle模块从字符串解码Numpy
- return pickle.loads(str(s[0]))
合并到查询方法中:
- def query(self,imname):
- """ 查找所有与imname匹配的图像列表 """
-
- h = self.get_imhistogram(imname)
- candidates = self.candidates_from_histogram(h)
-
- matchscores = []
- for imid in candidates:
- # 获取名字
- cand_name = self.con.execute(
- "select filename from imlist where rowid=%d" % imid).fetchone()
- cand_h = self.get_imhistogram(cand_name)
- # 用L2距离度量相似性
- cand_dist = sqrt( sum( self.voc.idf*(h-cand_h)**2 ) )
- matchscores.append( (cand_dist,imid) )
-
- # 返回排序后的距离及对应数据库ids列表
- matchscores.sort()
- return matchscores
query()方法获取图像的文件名,检索其单词直方图及候选图像列表。对于每一个候选图像,我们用标准的欧式距离比较他和查询图像间的直方图,并放回一个经排序的包含距离及图像的id的元组列表。
尝试查询:
- import pickle
- from PCV.tools import imtools
- from PCV.imagesearch import imagesearch
- from PCV.localdescriptors import sift
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
- # 载入词汇
- with open('vocabulary.pkl', 'rb') as f:
- voc = pickle.load(f)
-
- src = imagesearch.Searcher('test.db', voc)
- print('try a query....')
- print(src.query(imlist[0])[:10])
这里需要注意一下,在上面candidates_from_histogram()的代码中,书中使用的是python3.6版本以前的方法,给出的排序方法是
tmp.sort(cmp=lambda x,y:cmp(x[1],y[1]))
但cmp方法在python3.6版本之后就弃用了,此时使用时会出现报错的现象,因此需要对其进行更改,查阅资料后发现3.6版本后可以通过使用import functools中的cmp_to_key来实现。将代码改成:
- import functools
- tmp.sort(key=functools.cmp_to_key(lambda x,y:(x[1] - y[1])))
此时代码运行后的结果为:
这里给出了10个结果,距离为0的图像对于查询图像本身;三幅与查询图像具有相同场景的图像有两幅在除查询图像本身外的前两个位置,第三幅则出现在第五个位置。
为评价搜索结果的好坏,可以计算前4个位置中搜索到相似图像数。计算分数的函数为:
- def compute_ukbench_score(src,imlist):
- """ 对查询返回的前4个结果计算平均相似图像数,并返回结果 """
-
- nbr_images = len(imlist)
- pos = zeros((nbr_images,4))
- # 获取每幅查询图像的前4个结果
- for i in range(nbr_images):
- pos[i] = [w[1]-1 for w in src.query(imlist[i])[:4]]
-
- # 计算分数,并返回平均分数
- score = array([ (pos[i]//4)==(i//4) for i in range(nbr_images)])*1.0
- return sum(score) / (nbr_images)
获取搜素的前4个结果,将query()返回的索引减去1,因为数据库索引是从1开始的,而图像列表的索引是从0开始的。利用每4幅图像为一组时相似图像文件名是连续的这一事实,用整数相除计算得到最终的分数。分数为4时结果最为理想;没有一个是准确的时,分数为0;仅检索到相同图像时,分数为1,当找到相同的图像并且其他三个中的两个相同时,分数为3。
显示实际搜素结果:
- def plot_results(src,res):
- """ 显示在列表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()
实验:
- import pickle
- from PCV.tools import imtools
- from PCV.imagesearch import imagesearch
- from PCV.localdescriptors import sift
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
- # 载入词汇
- with open('vocabulary.pkl', 'rb') as f:
- voc = pickle.load(f)
-
- src = imagesearch.Searcher('test.db', voc)
- nbr_results = 6
- res = [w[1] for w in src.query(imlist[0])[:nbr_results]]
- imagesearch.plot_results(src,res)
结果如下图所示:
上图中第一幅图是拿出来查询的图,后面的都是相似的图,前三幅最为相似。
BoW模型的一个主要缺点是在用视觉单词表示图像时不包含图像特征的位置信息,是为获取速度和可伸缩性而付出的代价。
这里实验一下载入所有模型文件并用单应性对靠前的图像进行重排的完整例子:
- import pickle
- from PCV.imagesearch import imagesearch
- from PCV.localdescriptors import sift
- from PCV.geometry import homography
- from PCV.tools import imtools
- imlist = imtools.get_imlist('D:\\picture\\first1000\\')
- nbr_images = len(imlist)
- featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
- # 载入词汇
- 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]) # 重排后的结果
这里就可以很号的看出第二次的查询结果更加准确。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。