我们知道,百度在图像方面有一个强大功能——百度识图,其基本操作就是你输入一张图像,百度会帮你检索到与它相似的图像。百度识图其实是相同物体图像检索,即从图像库中找出包含该物体的图像。用户感兴趣的是图像中包含特定物体或目标,并检索到包含该物体的那些图像。相同物体检索不论是在研究还是在商业图像搜索产业中都具有重大的价值,比如做为还是学生的我,看见专卖店好看的衣服都会先拍拍照,然后取淘宝找同款,也是非常机智啦~所以,这个技术对于我们现在生活还是非常有帮助的。今天就来给大家讲讲基于BOW的图像检索技术吧,大家一起来体会一下其中的奥秘!!!
一、BOW模型
二、基于BOW的图像检索
1.特征提取(SIFT算法)
2.学习“视觉词典”(K-means算法)
3.针对输入的特征集,根据视觉词典进行量化
4.把输入图像转化成视觉单词(visual words)的频率直方图
5.构造特征到图像的倒排表,通过倒排表快速索引相关图像
6.根据索引结果进行直方图匹配
三、实现
1.特征提取和学习“视觉词典”
2.创建索引,建立数据库
3.查询图像
4.web应用
四、结果分析
首先,我们就先了解一下什么是BOW模型吧!
BOW其实是Bag of words的缩写,也叫做词袋。BOW模型最早出现在自然语言处理和文本检索领域。该模型忽略掉文本的语法、语序等要素,吧文档看做若干词汇的集合,文档中的单词是独立出现的,使用一组无序的单词(words)表达一个文档,根据文档中单词的统计信息完成对文本的分类。
比如,有如下两个文档:
1:Bob likes to play basketball, Jim likes too.
2:Bob also likes to play football games.
根据这两个文档,我们可以构造一个词典:
Dictionary = {1:”Bob”, 2. “like”, 3. “to”, 4. “play”, 5. “basketball”, 6. “also”, 7. “football”, 8. “games”, 9. “Jim”, 10. “too”}。
这个词典中包含10个不同的单词,利用单词的索引号,我们可以将上面的每个文档都用一个10维向量表示(用0~n表示某个单词在文档中出现的次数,其中n为正整数):
1:[1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
2:[1, 1, 1, 1 ,0, 1, 1, 1, 0, 0]
该向量的维度是单词的个数,值是每个单词在文本中出现的频率。以上的向量也可以用单词的直方图表示,词表相当于直方图的基,要表述的是文档向这个基上映射。
并非所有的单词都用来构建词表,相似的单词用一个单词表示。例如“walk, walking,walks” 都用 “walk” 表示。一个单词如果在所有文档中出现,比如is,a,an等词,那么个单词其实对区分文档就没有那么重要了。反之,如果一个单词只在一个文档值出现,那么只要输入的文档有这个单词,那么一定是属于这个单词所在的文档,就是唯一的,这对于区分文档非常重要。单词对区分文档的重要性通过计算单词TF-IDF(term frequency–inverse document frequency, 词频-逆向文档频率)实现。
单词在文档中出现的词频是:
是单词在文档中出现的次数。为了归一化,将除以整个文档中单词的总数。
逆向文档频率为:
是在语料库中的文档数目,分母是语料库中包含单词在文档数
就是单词的TF-IDF权重了,用这个来表示单词对区分文档的重要性。
BOW模型用于文本分类包括词表的建立、样本训练、新来样本识别三个步骤。
对于两张图片,我们可以利用sift算法来进行特征匹配。但是面对大规模图像特征匹配,我们不可能一个个特征匹配,因为这样计算量是在过于庞大。比如,25000张图像约有310亿个图相对,即使每个图匹配只需要两秒,也需要500台并行计算机工作一年才可以完成,所以我们不能使用这种一个个特征匹配的暴力匹配法,需要寻找其他更快速有效的方法。
我们发现,面对大场景数据集,其实只有少于0.1%的图像具有匹配关系,所以我们可用图像整体特征实现匹配/检索,而非局部特征点。所以,我们找到那个快速有效的方法——BOW模型。将BOW模型应用于图像领域,即把图像视为与位置无关的局部特征集合,局部特征就相当于文本中的单词,称为“视觉单词”,视觉单词的集合称为“视觉词典”(也叫码本)。
图像检索的基本流程是:
①特征提取(SIFT算法)
②学习“视觉词典(visual vocabulary)”(k-means算法)
③针对输入的特征集,根据视觉词典进行量化
④把输入图像转化成视觉单词(visual words)的频率直方图
⑤构造特征到图像的倒排表,通过倒排表快速索引相关图像
⑥根据索引结果进行直方图匹配
接下来,我们来具体了解一下每一步做什么吧!!!
特征提取就是从图像中提取出关键点(或特征点、角点)等,我们在这里采用的SIFT算法。SIFT算法用来侦测与描述影像中的局部特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量。此算法由David Lowe 在1999 年所发表,2004 年完善总结。SIFT 算法的实质是在不同的尺度空间上查找关键点( 特征点),并计算出关键点的方向。SIFT 所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。SIFT 算法在构建好的尺度空间的基础上搜索尺度空间中的极值点( 特征点),然后确定极值点的尺度信息以及位置,再确定极值点的方向( 其邻域梯度的主方向),最终可以得到具有鲁棒性的128 维(4*4*8) 的特征向量。SIFT具体原理和实现参见我另一篇博客,这里我就不再重复赘述啦。传送门https://blog.csdn.net/qq_40369926/article/details/88597406
利用SIFT算法提取出训练图片的示意图如下:
举个例子,假如现在有3 张训练图片:人脸、自行车、小提琴,如下图所示:
对每一张训练图片都提取SIFT 的128 维特征,那么最终可以得到 M = N1+N2+N3 个128 维的特征,Ni 代表第i 张图特征点的个数,如下图所示:
前面已经说过了,面对大场景数据集,只凭特征匹配,由于计算时间过长,是不可能实现图像检索的。所以我们想到了将相似的特征向量聚到一起,用一个视觉单词来表示这些特征,就像文本中用“walk”来表示“walk, walking,walks” 一样。我们采用的算法是K-means算法。
k-means算法是一种基于样本间相似性度量的间接聚类方法,属于非监督学习方法。
输入:聚类个数k,图像的特征集合。
输出:满足方差最小标准的k个聚类。
最小化每个特征 与其相对应的聚类中心 之间的欧式距离。
算法流程是:
① 随机初始化 K 个聚类中心
②重复下述步骤直至算法收敛
对应每个特征,根据距离关系赋值给某个中心/类别(计算每个特征点到聚类中心的距离,将特征点分给离其最近的聚类中心,视为属于类,这里采用的距离是欧式距离。)
对每个类别,根据其对应的特征集重新计算聚类中心
k-means算法的流程示意图如下:
k-means算法是实现视觉词典(码本)的关键,我们将K-means 算法获取的聚类中心作为视觉单词(码本向量)。当训练集准备足够充分是,训练出的码本将具有普适性。
需要注意的是如何选择视觉词典/码本的规模,太少会出现视觉单词无法覆盖所有可能出现的情况;太多又会计算量大,容易过拟合。
就之前的例子而言就是将下述这特征向量进行聚类,将之前的特征向量转化为4个视觉单词,以此来作为视觉词典。
对于文本而言,当一个单词在所有文本都出现那么这个单词就不能区分文本。同理,如果一个视觉单词在每个图像中都出现,那么这个视觉单词就不能区分图像了。类比文本,我们这里也采用TF-IDF权重来表示视觉单词对区分图像的重要程度。
统计词表中每个单词在图像中出现的次数,将图像表示为K 维数值向量。上例中,可以将图像表示成为一个K=4 维数值向量:
人脸:[3,30,3,20]
自行车:[20,3,3,2]
小提琴:[8,12,32,7]
每幅图的视觉单词与词频直方图如图下图所示:
前几步是通过图像来提取视觉单词,用视觉单词的词频直方图来表示图像,是通过图像映射视觉单词。而倒排表是通过视觉单词映射图像。
比如,对于下述两个文本而言:
1:Bob likes to play basketball, Jim likes too.
2:Bob also likes to play football games.
之前是:
1:[ “Jim”, “too”]
2:[“also”, “football”, “games”,]
经过倒排后是:
“Jim”:[1]
“too”:[1]
“also”:[2]
“football”:[2]
“games”:[2]
对于图像来说,经过倒排后,就是视觉单词:[视觉单词所出现的图像集合]。倒排表可以快速使用反转文件来计算新图像与数据库中所有图像之间的相似性,仅考虑其分档与查询图像重叠的数据库图像,大大减少了匹配次数,优化了算法。
常规查询是利用欧氏距离对结果排序,但是实际上这样容易出现偶然现象,所以我们用RANSAC模型进行优化,该模型的具体介绍可以参考我另一篇博客,传说门:https://blog.csdn.net/qq_40369926/article/details/88918489
根据索引结果建立直方图,就可以得出最后的匹配结果了。
# -*- 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('D:/test/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)#单词数为1000,进行k-means训练
#保存词汇
with open('D:/test/first1000/vocabulary.pkl', 'wb') as f:
pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)
上述代码是先对图像集进行SIFT特征提取,然后再用K_means算法生成视觉单词,这里设置的K=1000,也就是说会生成1000个视觉单词。
# -*- 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('D:/test/first1000/')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#载入词汇
with open('D:/test/first1000/vocabulary.pkl', 'rb') as f:
voc = pickle.load(f)
#创建索引
indx = imagesearch.Indexer('testImaAdd.db',voc)#创建索引器Indexer类
indx.create_tables()
#遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:1000]:
locs,descr = sift.read_features_from_file(featlist[i])
indx.add_to_index(imlist[i],descr)
#提交到数据库
indx.db_commit()
con = sqlite.connect('testImaAdd.db')
print (con.execute('select count (filename) from imlist').fetchone())
print (con.execute('select * from imlist').fetchone())
# -*- 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
#载入图像列表
imlist = get_imlist('D:/test/first1000/')
nbr_images = len(imlist)
#载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
#载入词汇
with open('D:/test/first1000/vocabulary.pkl', 'rb') as f:
voc = pickle.load(f)
src = imagesearch.Searcher('testImaAdd.db',voc)
#查询图像索引和查询返回的图像数
q_ind = 0
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]) # because 'ndx' is a rowid of the DB that starts at 1
#获取匹配数
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]) #重排后的结果
这里我们用常规查询和优化查询进行对比。
除了在python里显示查询结果,我们还可以利用CherryPy将图像传递到URL中,在网页中查询图像并显示结果。
# -*- 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 = 'D:/test/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('D:/test/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 = """