概念:
基于内容的图像检索,即CBIR(Content-based image retrieval),是计算机视觉领域中关注大规模数字图像内容检索的研究分支。典型的CBIR系统,允许用户输入一张图片,以查找具有相同或相似内容的其他图片。 该技术之用于检索在视觉上相似性的图像,这样返回的图像可以是颜色相似、纹理相似、图像中的物体或场景相似,总之,基本上可以是这些图像自身共有的任何信息。目前现在的各大搜索引擎都支持这样的功能操作。一类是基于文本的图像检索(TBIR, Text Based Image Retrieval),另一类是基于内容的图像检索(CBIR, Content Based Image Retrieval)。
工作流程:
基于CBIR 技术的图像检索系统,在建立图像数据库时, 系统对输入的图像进行分析并分类统一建模, 然后根据各种图像模型提取图像特征存入特征库, 同时对特征库建立索引以提高查找效率。而用户在通过用户接口设置查询条件时,可以采用一种或几种的特征组合来表示, 然后系统采用相似性匹配算法计算关键图像特征与特征库中图像特征的相似度, 然后按照相似度从大到小的顺序将匹配图像反馈给用户。用户可根据自己的满意程度,选择是否修改查询条件,继续查询,以达到令人满意的查询结果。
CBIR 的实现依赖于两个关键技术的解决:图像特征提取和匹配。
来源:矢量空间模型的介绍
这样的功能出现,是从文本挖掘中获得的灵感——矢量空间模型。矢量空间模型是一个用于表示和搜索文本文档的模型,它可以应用于任何对象类型,包括图像。该名字来源于矢量来表示文本文档,这些矢量是由文本词频直方图构成的。换句话说,矢量包含了每个单词出现的次数,而且在其他别的地方包含很多0元素,由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。
通过单词计数来构建文档直方图向量v,从而建立文档索引。通常,在单词计数时会忽略掉一些常用的词,如“这”、“和”、“是”等,这些常用词称为停用词。由于每篇文档长度不同,故以直方图总和将向量归一化成单位长度。对于直方图向量中的每个元素,一般根据每个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中每一个单词的重要性与他在文档中出现的次数成正比,而它在语料库中出现的次数成反比。
常用的权重是tf-idf(term frequency-inverse document frequency,词频-逆向文档频率),单词w在文档d中的词频是: t f w , d = n w ∑ j n j tf_{w,d}=\frac{n_{w}}{\sum_{j}^{ }n_{j}} tfw,d=∑jnjnw nw是单词w在文档d中出现的次数。为了归一化,将nw除以整个文档中单词的总数。
逆向文档频率为: i d f w , d = l o g ∣ ( D ) ∣ ∣ { d : w ∈ d } ∣ idf_{w,d}=log\frac{\left | (D) \right |}{\left | \left \{ d:w\in d \right \} \right |} idfw,d=log∣{d:w∈d}∣∣(D)∣ ∣ D ∣ |D| ∣D∣是语料库 D D D中文档的数目,分母是语料库中包含单词w的文档数d。将两者相乘可以得到矢量v中对应元素的 t f − i d f tf-idf tf−idf权重。
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。就目前来说,如果一个关键词只在很少的网页中出现,我们通过它就容易锁定搜索目标,它的权重也就应该大。反之如果一个词在大量网页中出现,我们看到它仍然不是很清楚要找什么内容,因此它应该小。
TF-IDF应用:
(1)搜索引擎;(2)关键词提取;(3)文本相似性;(4)文本摘要
用python3实现tf-idf算法:
# -*- coding: utf-8 -*-
from collections import defaultdict
import math
import operator
"""
函数说明:创建数据样本
Returns:
dataset - 实验样本切分的词条
classVec - 类别标签向量
"""
def loadDataSet():
dataset = [ ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], # 切分的词条
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid'] ]
classVec = [0, 1, 0, 1, 0, 1] # 类别标签向量,1代表好,0代表不好
return dataset, classVec
"""
函数说明:特征选择TF-IDF算法
Parameters:
list_words:词列表
Returns:
dict_feature_select:特征选择词字典
"""
def feature_select(list_words):
#总词频统计
doc_frequency=defaultdict(int)
for word_list in list_words:
for i in word_list:
doc_frequency[i]+=1
#计算每个词的TF值
word_tf={} #存储没个词的tf值
for i in doc_frequency:
word_tf[i]=doc_frequency[i]/sum(doc_frequency.values())
#计算每个词的IDF值
doc_num=len(list_words)
word_idf={} #存储每个词的idf值
word_doc=defaultdict(int) #存储包含该词的文档数
for i in doc_frequency:
for j in list_words:
if i in j:
word_doc[i]+=1
for i in doc_frequency:
word_idf[i]=math.log(doc_num/(word_doc[i]+1))
#计算每个词的TF*IDF的值
word_tf_idf={}
for i in doc_frequency:
word_tf_idf[i]=word_tf[i]*word_idf[i]
# 对字典按值由大到小排序
dict_feature_select=sorted(word_tf_idf.items(),key=operator.itemgetter(1),reverse=True)
return dict_feature_select
if __name__=='__main__':
data_list,label_list=loadDataSet() #加载数据
features=feature_select(data_list) #所有词的TF-IDF值
print(features)
print(len(features))
博主小白现在学习的只是皮毛,感兴趣的朋友们可以多多搜索这类的博客或者论文进行深入研究!
概念:
为了将文本挖掘技术应用到图像中,我们首先需要建立视觉等效单词;这通常可以采用SIFT局部描述子可以做到。它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也被称为视觉码本。 对于给定的问题、图像类型,或在通常情况下仅需呈现视觉内容,剋有创建特定的词汇。
从一个(很大的训练图像)集中提取特征描述子,利用一些聚类算法可以构建出视觉单词。聚类算法中最常用的是K-means。视觉单词是在给定特征描述子空间中的一组向量集,在采用K-means进行聚类时得到的视觉单词是聚类质心。用视觉单词直方图来表示图像,则该模型便称为BOW模型(Bag-of-words)。
借助一个图来理解这个概念,如下图所示:
视觉词典生成的流程图:
我们需要首先从图像中提取出相互独立的视觉词汇,这通常需要经过三个步骤:
(1)特征检测,(2)特征表示,(3)单词本的生成。
构建BOW词汇步骤:
实现图像的检索的步骤:
下面编写代码来实现图像检索:
创建词汇前,需要搜集一些比较合适的图像集。
为创建视觉单词词汇,首先需要提取特征描述子。这里,使用SIFT特征描述子。运行下面代码,可以得到每幅图像提取的描述子,并将每幅图像的描述子保存在一个文件中:
# -*- 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('F:\\Anaconda\\chapter7\\picture\\')
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('F:\\Anaconda\\chapter7\\picture\\vocabulary.pkl', 'wb') as f:
pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)
说明: Vocabulary是调用PCV下 imagesearch 中的文件 vocabulary.py ,该文件代码的作用是创建一个词汇类,以及在训练图像数据集上训练出一个词汇。其中包含了一个有单词聚类中心与每个单词对应的逆向文档频率构成的向量。该词汇类中还包含了train()函数。train()方法获取包含有.sift描后缀的述子文件列表和词汇单词数k(聚类中心数)。最后用pickle模块保存了整个词汇对象以便后面使用,pickle.dump(voc, f)是将词汇写入到.pkl文件中。
在索引图像前,需要建立一个数据库。对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保持视觉单词及对应图像的单词直方图。首先需要安装pysqlite库,这是需要自己安装的。
添加一个文档,命名为imagesearch.py,代码如下:
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()
说明: Indexer 类中__init__()方法用于初始化;__ del__()方法可以确保关闭数据库连接;db_commit()可以将更改写入数据库文件;create_tables()用于创建表单及一些有用的索引以加快搜索速度;add_to_index()方法用于在索引中添加图像,该方法获取图像文件名与Numpy数组,该数组包含的是在图像找到的描述子;is_indexed()用来检查图像是否已经被索引;get_id()方法对一幅图像文件名给定id号。
编写创建图像索引的实验代码:
# -*- 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
# 要记得将PCV放置在对应的路径下
# 获取图像列表
imlist = get_imlist('F:\\Anaconda\\chapter7\\picture\\') # 存放数据集的路径
nbr_images = len(imlist)
# nbr_images = 300
# 获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
# 载入词汇
with open('F:\\Anaconda\\chapter7\\picture\\vocabulary.pkl', 'rb') as f: # 读取再上一步中保存的.pkl文件
voc = pickle.load(f)
# 创建索引
index = imagesearch.Indexer('test.db', voc) # 创建数据库
index.create_tables() # 创建数据库表单
# 遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:300]:
locs, descr = sift.read_features_from_file(featlist[i])
index.add_to_index(imlist[i], descr)
# 提交到数据库
index.db_commit()
con = sqlite.connect('test.db') # 连接到数据库
print (con.execute('select count (filename) from imlist').fetchone()) # 数据库操作
print (con.execute('select * from imlist').fetchone())
控制台打印结果如下:
(38,)
(‘F:\Anaconda\chapter7\picture\1.jpg’,)
建立好图像的索引,就可以在数据库中搜索相似的图像了。这里,使用BoW(词袋模型)来表示整个图像,这是通用的,可以应用于寻找相似的物体、相似的脸、相似的颜色等,它完全取决于图像及所用的描述子。为了实现搜索,在Imagesearch.py中添加Searcher类。
如果图像数据库很大,逐一比较整个数据库中的所有直方图往往是不可行的。这就需要找到一个大小合理的候选集(这里的“合理”是通过搜索响应时间、所需内存等确定的),单词索引的作用便在于此:可以利用单词索引获得候选集,然后只需在候选集上进行逐一比较。
把下面的代码添加到imagesearch.py中:
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()
说明: Searcher 类中 init() 方法用于初始化数据库的名称。 __ del__() 方法可以确保关闭数据库连接。candidates_from_word() 用于获得包含特定单词的所有图像id号。candidates_from_histogram() 方法从图像单词直方图的非零项创建单词id列表,检索每个单词获得候选集并将其合并到candidates列表中,然后创建一个元组列表每个元组由单词id和次数count构成,其中次数count是候选列表中每个单词出现的次数。同时,还以元组中的第二个元素为准,用sort()方法和一个自定义的比较函数对列表进行排序。该自定义比较函数进行用lambda函数内联声明,对于单行函数声明,使用lambda函数非常方便。最后结果返回一个包含图像id的列表,排在列表最前面的是最好的匹配图像。get_imhistogram() 用于返回一幅图像的单词直方图。query() 方法用于获取图像的文件名,检索其单词直方图及候选图像列表。对于每个候选图像,用标准的欧氏距离比较它和查询图像间的直方图,并返回一个经排序的包含距离及图像id的元组列表。
获取候选图像,编写实验代码:
# -*- 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('F:\\Anaconda\\chapter7\\picture\\')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#载入词汇
f = open('F:\\Anaconda\\chapter7\\picture\\vocabulary.pkl', 'rb')
voc = pickle.load(f)
f.close()
src = imagesearch.Searcher('testImaAdd.db',voc)
locs,descr = sift.read_features_from_file(featlist[0])
iw = voc.project(descr)
print ('ask using a histogram...')
#获取imlist[0]的前十幅候选图像
print (src.candidates_from_histogram(iw)[:10])
src = imagesearch.Searcher('testImaAdd.db',voc)
print('try a query...')
print(src.query(imlist[0])[:10])
#imagesearch.compute_ukbench_score(src,imlist)
nbr_results = 20
res = [w[1] for w in src.query(imlist[0])[:nbr_results]]
imagesearch.plot_results(src,res)
实验结果如下:
分析: 查找出来的前10个候选图像是任意的,可以从中取出该列表中任意数量的元素并比较它们的直方图。对上面的图像进行查询后,再次打印前10个结果,包括候选图像和查询图像间的距离,这一结果比查找出来的结果好一些,距离为0的图像对应查询图像本身。上图还显示了使用plot_results()在实验所使用的数据集上进行查询的结果,经过对比,从数据集查询到的结果是基本满足条件的,搜索出来的图像都具有相似性。
这是一种是用BoW模型改进检索结果的常用方法。BoW模型的一个主要缺点是在用视觉单词表示图像时不包含图像特征的位置信息,这是为了获取速度和可伸缩性而付出的代价。最常用的方法是在查询图像与靠前图像的特征位置间拟合单应性。
总结上面的过程,在数据库中搜索图像,编写实验代码:
# -*- coding: utf-8 -*-
import pickle
from PCV.localdescriptors import sift
from PCV.imagesearch import imagesearch
from PCV.geometry import homography
from PCV.tools.imtools import get_imlist
# 要记得将PCV放置在对应的路径下
# 载入图像列表
imlist = get_imlist('F:\\Anaconda\\chapter7\\picture\\') # 存放数据集的路径
nbr_images = len(imlist)
# nbr_images = 300
# 载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
# 载入词汇
with open('F:\\Anaconda\\chapter7\\picture\\vocabulary.pkl', 'rb') as f: # 存放模型的路径
voc = pickle.load(f)
src = imagesearch.Searcher('testImaAdd.db', voc)
# 查询图像索引和查询返回的图像数
q_ind =46 # 查询图片的索引
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])
# 获取匹配数
# get matches执行完后会出现两张图片
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]) # 重排后的结果
实验结果:
top matches (regular): [31, 35, 32, 36, 13, 10, 34, 33, 37, 1]
top matches (homography): [31, 1]
top matches (regular): [37, 11, 19, 15, 17, 29, 12, 34, 16, 14]
top matches (homography): [37, 14]
top matches (regular): [4, 30, 29, 39, 15, 17, 12, 11, 19, 34, 37, 1, 18, 10, 36, 9]
top matches (homography): [4, 9]
分析: 由上面三组实验结果看出,第一组的检测比较准确,所有检测出的图像都是建筑类。第二组除了一张人物图像之外,基本检索到的图像都是建筑类的。第三组想要检索的是人物类图像,但是发现混入了几张建筑类和风景的图像。这说明了,这个算法在检测程度上的效果还存在一些缺陷,可能在创建词汇、创建索引、提取特征描述子这些环节还不够完善,这就需要继续探究算法的原理来改善这些问题。