文章目录
1、基于内容的图像检索
1.1 定义
图像检索:从图像库中查找含有特定目标的图像,也包括从连续的视频图像中检索含有特定目标的视频片段。目前,对于通用的静止图像检索,用于检索的特征主要有颜色、纹理、形状等。
1.2 特点
1)模糊查询,检索结果不唯一 。(基于内容的图像检索与文字检索的一个主要区别是,基于内容的检索都是属于模糊查询,一般不会给出单一的检索结果,输出的是一个结果排序集合,按图像的相似程度,从最像到最不像)
2)结果逐步逼近。(在检索过程中不断修订检索条件,可以逐步达到最终的检索结果,通过调整特征参数组合,可以得到不同的检索结果)
3)计算工作量大。(每次查询都需要根据临时提交的特征标准,对全部特征值进行匹配运算;数据计算时对计算机的要求高,查询时间相对长一些)
1.3 层次划分
基于内容的图像检索分为三个层次:
1、依据提取图像本身的颜色、形状、纹理等低层特征进行检索;
颜色检索:基于颜色特征的图像检索需要解决三个问题:颜色的表示(取决于色彩空间的选择,不同场合采用的方式也是不同,常见的有RGB,HSV模型等)、颜色特征的提取(在基于颜色特征的索引算法中通常用颜色统计直方图、颜色相关图,颜色矩,颜色一致性矢量来表示图像的颜色特征)和基于颜色的相似度量(颜色特征索引的相似度匹配算法根据其颜色索引内容和算法的不同而不同,主要包括直方图相交法,Manhattan距离、绝对距离(L1)、二次距离(L2)、欧几里德距离等。
形状检索:基于形状的检索包括两种:一是基于轮廓线的检索,二是直接针对特定形状的图形进行检索。形状是物体的一个重要特征,但由于物体形状的自动获取很困难,基于形状的检索一般仅限于非常容易识别的物体。形状可以用面积、离心率、圆形度、形状度、曲率、分形维等全局和局部特征来表示。
纹理检索:纹理特征代表了物体的视觉模式,它包含了物体表面的组织结构以及与周围环境之间的关系。图像的纹理特征主要有六个方面:粗糙度、对比度、方向度、线像度、规整度、粗略度,其中最重要的特征主要是纹理的粗糙度、对比度、方向度。
2、基于图像的低层特征,通过识别图像中的对象类别以及对象之间的空间拓扑关系进行检索;
对象检索:基于对象的方法用于检索图像对象或其子对象,是针对局部特征的。
3、基于图像抽象属性(场景语义、行为语义、情感语义等)的推理学习进行检索;
1.4 矢量空间模型
矢量空间模型是一个用于表示和搜索文本文档的模型,文档和查询都用向量表示。矢量包含了每个单词出现的次数,而且在其他别的地方包含很多0元素。由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。
建立BoW模型主要分为如下几个步骤:
-
特征提取
假设有N张图像, 第i张图像图像可由n(i)个image patch组成, 也即可以由n(i)个特征向量表达. 则总共能得到sum(n(i))个特征向量(即单词). -
生成字典/码本(codebook)
对上一步得到的特征向量进行聚类(可以使用K-means等聚类方法), 得到K个聚类中心, 用聚类中心构建码本. -
根据码本生成直方图
对每张图片, 通过最近邻计算该图片的每个 “单词”应该属于codebook中的 “哪一类”单词, 从而得到该图片对应于该码本的BoW表示.
2、视觉单词
为了将文本挖掘技术应用到图像中,我们首先需要采用SIFT 局部描述子建立视觉等效单词,将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。
利用一些聚类算法可以构建出视觉单词,其中,最常用的聚类算法是k-means算法。在采用 K-means 进行聚类时得到的视觉单词是聚类质心。用视觉单词直方图来表示图像,则该模型便称为 BOW 模型。
2.1 创建词汇
from numpy import *
from scipy.cluster.vq import *
from PCV.localdescriptors import sift
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])
# 将所有的特征并在一起,以便后面进行 K-means 聚类
descriptors = descr[0]
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
import pickle
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift
#获取图像列表
imlist = get_imlist('d:/imdata1')
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)
#保存词汇
with open('d:/imdata1/vocabulary.pkl', 'wb') as f:
pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)
运行完成上述代码后,我们就可以创建出视觉词汇并将其保存了。
3、图像索引
在开始搜索之前,需要事先建立图像数据库和图像的视觉单词表示。
3.1 建立数据库
在索引图像前,需要提前建立一个数据库。对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保存视觉单词及对应图像的单词直方图。从而可以利用图像对数据库进行查询,并返回相似的图像作为搜索结果。这里,使用SQLite 作为数据库。SQLite 将所有信息都保存到一个文件,是一个易于安装和使用的数据库。不涉及数据库和服务器的配置,很容易上手。
在开始之前,首先需要创建表、索引和索引器 Indexer 类,以便将图像数据写入数据库,代码如下:
from numpy import *
import pickle
import sqlite3
from functools import cmp_to_key
import operator
class Indexer(object):
def __init__(self,db,voc):
""" 初始化数据库的名称及词汇对象 """
self.con = sqlite3.connect(db)
self.voc = voc
def __del__(self):
self.con.close()
def db_commit(self):
self.con.commit()
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()
3.2 添加图像
有了数据库表单,便可以在索引中添加图像。为了实现该功能,需要在 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))
该方法可以获取图像文件名与在图像找到的描述子。这些描述子投影到词汇上,并插入到imwords(逐字)和 imhistograms 表单中。使用两个辅助函数:is_indxed() 用来检查图像是否已经被索引,get_id() 则对一幅图像文件名给定 id 号:
def is_indexed(self,imname):
""" 如果图像名字(imname)被索引到,就返回 True"""
im = self.con.execute("select rowid from imlist where filename='%s'" % imname).fetchone()
return im != None
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]
下面就可以遍历数据库中的图像并将其加入我们的索引:
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('d:/imdata1')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#载入词汇
with open('d:/imdata1/vocabulary.pkl', 'rb') as f:
voc = pickle.load(f)
#创建索引
indx = imagesearch.Indexer('test2.db',voc)
indx.create_tables()
#遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:500]:
locs,descr = sift.read_features_from_file(featlist[i])
indx.add_to_index(imlist[i],descr)
#提交到数据库
indx.db_commit()
现在我们可以检索数据库中的内容了;
con = sqlite.connect('test2.db')
print con.execute('select count (filename) from imlist').fetchone()
print con.execute('select * from imlist').fetchone()
结果如下:
4、在数据库中搜索图像
建立好图像的索引,就可以在数据库中搜索相似的图像了。为实现搜索的功能,我们添加searcher类:
class Searcher(object):
def __init__(self,db,voc):
""" 初始化数据库的名称. """
self.con = sqlite.connect(db)
self.voc = voc
def __del__(self):
self.con.close()
当图像数据库很大时,我们要通过单词索引找到一个大小合适的候选集,然后就只需在候选集上进行逐一比较。
4.1 利用索引获取候选图像
可以利用建立起来的索引找到包含特定单词的所有图像,代码如下:
def candidates_from_word(self, imword):
""" 获取包含 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 出现的次数进行跟踪,因为这可以显示有多少单词与单词直方图中的单词匹配。代码如下:
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(key=cmp_to_key(lambda x, y: operator.gt(x[1], y[1])))
tmp.reverse()
# 返回排序后的列表,最匹配的排在最前面
return [w[0] for w in tmp]
该方法从图像单词直方图的非零项创建单词 id 列表,检索每个单词获得候选集并将其合并到candidates 列表中,然后创建一个元组列表,每个元组由单词 id 和次数 count 构成,其中次数 count 是候选列表中每个单词出现的次数。同时,以元组中的第二个元素为准,用 sort() 方法和一个自定义的比较函数对列表进行排序(考虑到后面的效率)。
4.2 用一幅图像进行查询
利用一幅图像进行查询时,没有必要进行完全的搜索。为了比较单词直方图,Searcher 类需要从数据库读入图像的单词直方图。将下面的方法添加到 Searcher 类中:
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)
cand_dist = sqrt(sum(self.voc.idf * (h - cand_h) ** 2))
matchscores.append((cand_dist, imid))
# 返回排序后的距离及对应数据库 ids 列表
matchscores.sort()
return matchscores
通过query() 方法可获取图像的文件名,检索其单词直方图及候选图像列表。对于每个候选图像,用标准的欧式距离比较它和查询图像间的直方图,并返回一个经排序的包含距离及图像 id的元组列表。
尝试对前一节的图像进行查询:
src = imagesearch.Searcher('test2.db', voc)
print 'try a query...'
print src.query(imlist[0])[:10]
实验结果如下:
5、使用几何特性对结果排序
BoW模型的一个主要缺点是在用视觉单词表示图像时不包含图像特征的位置信息,这是为获取速度和可伸缩性而付出的代价。利用一些考虑到特征几何关系的准则重排搜索到的靠前结果,可以提高准确率。最常用的方法是在查询图像与靠前图像的特征位置间拟合单应性。为了提高效率,可以将特征位置存储在数据库中,并由特征的单词 id 决定它们之间的关联。
下面是一个载入所有模型文件并用单应性对靠前的图像进行重排的完整例子:
import pickle
from PCV.localdescriptors import sift
from PCV.imagesearch import imagesearch
from PCV.geometry import homography
from PCV.tools.imtools import get_imlist
#载入图像列表
imlist = get_imlist('d:/imdata')
nbr_images = len(imlist)
#载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#载入词汇
with open('d:/imdata', 'rb') as f:
voc = pickle.load(f)
src = imagesearch.Searcher('testImaAdd.db',voc)
#查询图像索引和查询返回的图像数
q_ind = 0
nbr_results = 20
#regular query
#常规查询(按欧式距离对结果排序)
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]) # because 'ndx' is a rowid of the DB that starts at 1
# get matches
matches = sift.match(q_descr,descr)
ind = matches.nonzero()[0]
ind2 = matches[ind]
tp = homography.make_homog(locs[:,:2].T)
# compute homography, count inliers. if not enough matches return empty list
try:
H,inliers = homography.H_from_ransac(fp[:,ind],tp[:,ind2],model,match_theshold=4)
except:
inliers = []
# store inlier count
rank[ndx] = len(inliers)
#sort dictionary to get the most inliers first
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 列表中每一幅图像的特征,并和查询图像进行匹配。单应性通过计算 匹配数和计数内点数得到。最终,我们可以通过减少内点的数目对包含图像索引和内点数的字典进行排序,可以看到排序效果:
可以发现,内容相近或相同的图片被排在了一起。