目录
- 基于内容的图像检索 (CBIR)
- 视觉单词
- 图像索引
- 建立数据库
- 添加图像
- 在数据库中搜索图像
- 利用索引获取候选图像
- 确定对比基准并绘制结果
- 使用几何特性对结果排序
- 建立演示程序及Web应用
- 图像搜索演示程序
基于内容的图像检索 (CBIR)
定义: CBIR是一种技术,通过直接分析图像的内容来检索图像,而不是依赖于元数据或关键词。
流程:
1.特征提取: 从图像中提取有意义的特征,如颜色直方图、纹理特征或形状轮廓。
2.索引建立: 将提取的特征存储到索引结构中,便于快速检索。
3.相似度度量: 使用某种距离或相似性度量方法来比较查询图像与索引中的特征。
4.结果排序: 根据相似度得分对检索结果进行排序。
从文本挖掘中获取灵感——矢量空间模型
矢量空间模型是一个用于表示和搜索文本文档的模型。它基本上可以应用于任何对象类型,包括图像。该名字来源于用矢量来表示文本文档,这些矢量是由文本词频直方图构成的。换句话说,矢量包含了每个单词出现的次数,而且在其他别的地方包含很多0元素。由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。
视觉单词
为了将文本挖掘技术应用到图像中,我们首先需要建立视觉等效单词.它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。
算法步骤:
1.特征点检测: 使用SIFT、SURF等算法找到图像的关键点。
2.特征描述: 提取关键点的描述子。
3.聚类: 对所有训练图像的描述子进行K-means聚类,得到视觉词汇表。
4.图像表示: 每幅图像用一个视觉词汇的直方图表示。
创建词汇
为创建视觉单词词汇,首先需要提取特征描述子。这里,我们使用SIFT特征描述子。运行下面的代码,可以得到每幅图像提取出的描述子,并将每幅图像的描述子保存在一个文件中:
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])
添加下面代码,该代码创建一个词汇类,以及在训练图像数据集上训练出一个词汇的方法:
from scipy.cluster.vq import *
import vlfeat as 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])
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
下面的代码会创建一个长为k≈1000的词汇表。
import pickle
import vocabulary
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
代码最后部分用pickle模块保存了整个词汇对象以便后面使用。
图像索引
建立数据库
使用关系型数据库或NoSQL数据库存储图像的特征向量。
可以使用如SQLite, MySQL, 或MongoDB等工具。
添加图像
添加新图像到图像检索系统的步骤涉及特征提取、表示以及最终将这些信息存入数据库。
特征提取
首先,需要从图像中提取有用的特征。这些特征可以包括颜色直方图、纹理特征、形状轮廓或者更复杂的特征,如SIFT(尺度不变特征变换)、SURF(加速鲁棒特征)等。
代码:
import cv2
def extract_sift_features(image_path):
# 加载图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 初始化SIFT特征检测器
sift = cv2.SIFT_create()
# 计算关键点和描述符
keypoints, descriptors = sift.detectAndCompute(img, None)
return keypoints, descriptors
特征表示
特征表示指的是将提取到的特征转化为可以在数据库中存储的形式。常见的做法是使用“词袋”模型,即将图像表示为一组视觉词汇的直方图。
代码:构建视觉词汇并表示图像
from sklearn.cluster import MiniBatchKMeans
def build_visual_vocabulary(descriptors_list, n_clusters=100):
# 合并所有图像的描述符
all_descriptors = np.vstack(descriptors_list)
# 使用MiniBatchKMeans进行聚类
kmeans = MiniBatchKMeans(n_clusters=n_clusters, random_state=0).fit(all_descriptors)
return kmeans
def represent_image_with_vocabulary(keypoints, descriptors, vocabulary):
# 分配每个描述符到最近的簇中心
visual_word_hist = np.zeros(len(vocabulary.cluster_centers_))
labels = vocabulary.predict(descriptors)
# 构建直方图
np.add.at(visual_word_hist, labels, 1)
return visual_word_hist
存入数据库
一旦有了图像的特征表示,接下来就需要将这些信息存入数据库。可以选择使用关系型数据库如MySQL,也可以选择使用NoSQL数据库如MongoDB。下面是一个使用SQLite的简单例子。
import sqlite3
import io
import base64
def store_image_features(image_path, visual_word_hist):
# 创建或连接到SQLite数据库
conn = sqlite3.connect('image_features.db')
cursor = conn.cursor()
# 创建表
cursor.execute('''CREATE TABLE IF NOT EXISTS image_features
(id INTEGER PRIMARY KEY AUTOINCREMENT,
image_name TEXT,
features BLOB)''')
# 将特征向量序列化为base64编码
features_base64 = base64.b64encode(visual_word_hist.tobytes()).decode()
# 插入数据
cursor.execute("INSERT INTO image_features (image_name, features) VALUES (?, ?)",
(image_path, features_base64))
# 提交事务
conn.commit()
# 关闭连接
conn.close()
在数据库中搜索图像
利用索引获取候选图像
步骤:
对查询图像执行相同的特征提取过程。
使用余弦相似度、欧氏距离等度量方法计算查询图像特征与数据库中图像特征之间的相似度。
我们可以利用建立起来的索引找到包含特定单词的所有图像,这不过是对数据库做一次简单的查询。
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]
确定对比基准并绘制结果
为了评价搜索结果的好坏,我们可以计算前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幅图像为一组时相似图像文件名是连续的这一事实,我们用整数相除计算得到最终的分数。
下面代码用于显示实际搜索结果
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()
使用几何特性对结果排序
BoW模型的一个主要缺点是在用视觉单词表示图像时不包含图像特征的位置信息,这是为获取速度和可
伸缩性而付出的代价。
上图是在ukbench数据集上用一些查询图像进行搜索给出的一些结果。查询图像在最左边,后面是检索到的前5幅图像。
下面是一个载入所有模型文件并用单应性对靠前的图像进行重排的完整例子:
import pickle
import sift
import imagesearch
import homography
#载入图像列表和词汇
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])
结果如下:
建立演示程序及Web应用
图像搜索演示程序
首先,我们需要用一些HTML标签进行初始化,并用Pickle载入数据。另外,还需要有与数据库进行交互的Searcher对象词汇。创建一个名为searchdemo.py的文件,并添加下面具有两个方法的Search Demo类:
import cherrypy, os, urllib, pickle
import imagesearch
class SearchDemo(object):
def __init__(self):
# 载入图像列表
with open('webimlist.txt') as f:
self.imlist = f.readlines()
self.nbr_images = len(self.imlist)
self.ndx = range(self.nbr_images)
# 载入词汇
with open('vocabulary.pkl', 'rb') as f:
self.voc = pickle.load(f)
# 设置可以显示多少幅图像
self.maxres = 15
# html 的头部和尾部
self.header = """
<!doctype html>
<head>
<title>Image search example</title>
</head>
<body>
"""
self.footer = """
</body>
</html>
if query:
# 查询数据库并获取靠前的图像
res = self.src.query(query)[:self.maxres]
for dist,ndx in res:
imname = self.src.get_filename(ndx)
html += "<a href='?query="+imname+"'>"
html += "<img src='"+imname+"' width='100' />"
html += "</a>"
else:
# 如果没有查询图像,则显示随机选择的图像
random.shuffle(self.ndx)
for i in self.ndx[:self.maxres]:
imname = self.imlist[i]
html += "<a href='?query="+imname+"'>"
html += "<img src='"+imname+"' width='100' />"
html += "</a>"
html += self.footer
return html
index.exposed = True
cherrypy.quickstart(SearchDemo(), '/',
config=os.path.join(os.path.dirname(__file__), 'service.conf'))
上图是在ukbench数据集上进行搜索的示例。上方是开始页面,显示了一些随机选择的图 像;下方是一些查询示例。左上角是查询图像,之后的是搜索到的一些结果靠前的图像。