Python计算机视觉编程学习笔记 七 图像搜索

图像搜索

    • (一)基于内容的图像检索
    • (二)视觉单词
      • 2.1 :创建词汇
      • 2.2 :创建图像索引
      • 2.3 :在数据库中搜索图像
    • (三)使用几何特性对结果排序

(一)基于内容的图像检索

CBIR(Content-Based Image Retrieval,基于内容的图像检索)
CBIR 的实现依赖于两个关键技术的解决:图像特征提取和匹配。

定义 : 即从图像库中查找含有特定目标的图像,也包括从连续的视频图像中检索含有特定目标的视频片段。目前,对于通用的静止图像检索,用于检索的特征主要有颜色(Colour)、纹理(Texture)、草图(Sketch)、形状(Shape)等,其中颜色、形状、纹理等应用尤为普遍。
工作流程:
基于CBIR 技术的图像检索系统,在建立图像数据库时, 系统对输入的图像进行分析并分类统一建模, 然后根据各种图像模型提取图像特征存入特征库, 同时对特征库建立索引以提高查找效率。而用户在通过用户接口设置查询条件时,可以采用一种或几种的特征组合来表示, 然后系统采用相似性匹配算法计算关键图像特征与特征库中图像特征的相似度, 然后按照相似度从大到小的顺序将匹配图像反馈给用户。用户可根据自己的满意程度,选择是否修改查询条件,继续查询,以达到令人满意的查询结果。

矢量空间模型: 矢量空间模型是一个用于表示和搜索文本文档的模型,它可以应用于任何对象类型,包括图像。该名字来源于矢量来表示文本文档,这些矢量是由文本词频直方图构成的。换句话说,矢量包含了每个单词出现的次数,而且在其他别的地方包含很多0元素,由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。

通过单词计数来构建文档直方图向量v,从而建立文档索引。通常,在单词计数时会忽略掉一些常用的词,如“这”、“和”、“是”等,这些常用词称为停用词。由于每篇文档长度不同,故以直方图总和将向量归一化成单位长度。对于直方图向量中的每个元素,一般根据每个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中每一个单词的重要性与他在文档中出现的次数成正比,而它在语料库中出现的次数成反比。

tf-idf权重(term frequency-inverse document frequency,词频-逆向文档频率 ):
TF=(该词在文件中的出现次数)/(整个文档中单词的总数)

单词 w 在文档 d 中的词频是:
t f w , d = n w ∑ j n j tf_{w,d}=\frac{n_{w}}{\sum _{j}n_{j}} tfw,d=jnjnw

IDF=log(语料库D中的文件总数)/(包含单词w 的文档数d)
逆向文档频率为:
i d f w , d = l o g ∣ ( D ) ∣ ∣ d : w ∈ d ∣ idf_{w,d}=log \frac{\left | (D)\right |}{\left | {d:w\in d }\right |} idfw,d=logd:wd(D)

例:
假设现在一篇文件的总词语数是1000个,单词"water"出现了20次,则"water"一词在该文件中的词频就是20/1000=0.02。如果“water"一词在100份文件中出现过,文件总数是10000份,则其逆向文件频率就是log(10000/100)=2,最后tf-idf为0.02*2=0.04。

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))

效果:
Python计算机视觉编程学习笔记 七 图像搜索_第1张图片
分析:
TF-IDF实际上是:TF * IDF。主要思想是:如果某个词或短语在一篇文章中出现的频率高(即TF高),并且在其他文章中很少出现(即IDF高),则认为此词或者短语具有很好的类别区分能力,适合用来分类。

(二)视觉单词

概念:
为了将文本挖掘技术应用到图像中,我们首先需要建立视觉等效单词;这通常可以采用SIFT局部描述子可以做到。它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也被称为视觉码本。 对于给定的问题、图像类型,或在通常情况下仅需呈现视觉内容,剋有创建特定的词汇。

从一个(很大的训练图像)集中提取特征描述子,利用一些聚类算法可以构建出视觉单词。聚类算法中最常用的是K-means。视觉单词是在给定特征描述子空间中的一组向量集,在采用K-means进行聚类时得到的视觉单词是聚类质心。用视觉单词直方图来表示图像,则该模型便称为BOW模型(Bag-of-words)。

方法分类:
1、基于 SIFT 特征方法(本文采用)
2、基于 CNN 的方法

SIFT 局部描述子的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。对于给定的问题、图像类型,或在通常情况下仅需呈现视觉内容,可以创建特定的词汇。

利用一些聚类算法可以构建出视觉单词。在采用 K-means 进行聚类时得到的视觉单词是聚类质心。用视觉单词直方图来表示图像,则该模型便称为 BOW 模型。

基本步骤:
1.建立图像数据库
2.对输入的图像进行分析并分类统一建模
3.根据各种图像模型提取图像特征存入特征库, 同时对特征库建立索引
4.根据条件查询测试图片

先搜集一些比较合适的图像集。

2.1 :创建词汇

为创建视觉单词词汇,首先需要提取特征描述子。这里,使用SIFT特征描述子。运行下面代码,可以得到每幅图像提取的描述子,并将每幅图像的描述子保存在一个文件中:
创建名为 vocabulary.py 的文件,将下面代码添加进去。该代码创建了一个词汇类,以及在训练图像数据集上训练出一个词汇的方法:

import pickle
import sift
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift

#获取图像列表
imlist = get_imlist('D:\\Python Computer Vision\\P\\')
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('D:\\Python Computer Vision\\Vocabulary.pkl', 'wb') as f:
    pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)

Python计算机视觉编程学习笔记 七 图像搜索_第2张图片

说明: Vocabulary是调用PCV下 imagesearch 中的文件 vocabulary.py ,该文件代码的作用是创建一个词汇类,以及在训练图像数据集上训练出一个词汇。其中包含了一个有单词聚类中心与每个单词对应的逆向文档频率构成的向量。该词汇类中还包含了train()函数。train()方法获取包含有.sift描后缀的述子文件列表和词汇单词数k(聚类中心数)。最后用pickle模块保存了整个词汇对象以便后面使用,pickle.dump(voc, f)是将词汇写入到.pkl文件中。

2.2 :创建图像索引

建立数据库 + 添加图像

建立一个数据库对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保持视觉单词及对应图像的单词直方图。首先需要安装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()

添加图像:

import pickle
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift

#获取图像列表
imlist = get_imlist('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)
#保存词汇
with open('first1000/vocabulary.pkl', 'wb') as f:
    pickle.dump(voc, f)
print ('vocabulary is:', voc.name, voc.nbr_words)


说明:
Vocabulary 类包含了一个由单词聚类中心 VOC 与每个单词对应的逆向文档频率构成的向量,为了在某些图像集上训练词汇,train() 方法获取包含有 .sift 描后缀的述子文件列表和词汇单词数k。在 K-means 聚类阶段可以对训练数据下采样,因为如果使用过多特征,会耗费很长时间。可以看到在计算机文件夹中,保存了图像及提取出来的 sift 特征文件。代码最后部分用 pickle 模块保存了整个词汇对象以便后面使用。

创建图像索引的实验代码:

# -*- 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('D:\\Python Computer Vision\\P\\')  # 存放数据集的路径
nbr_images = len(imlist)
# nbr_images = 300
# 获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
 
# 载入词汇
with open('D:\\Python Computer Vision\\Vocabulary.pk', '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())

控制台打印结果如下:

 (‘D:\Python Computer Vision\P\picture\1.jpg’,)

2.3 :在数据库中搜索图像

建立好图像的索引,就可以在数据库中搜索相似的图像了。
使用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()

说明:

  1. Searcher 类中 init() 方法用于初始化数据库的名称。
  2. __ del__() 方法可以确保关闭数据库连接。
  3. candidates_from_word() 用于获得包含特定单词的所有图像id号。
  4. candidates_from_histogram()方法从图像单词直方图的非零项创建单词id列表,检索每个单词获得候选集并将其合并到candidates列表中,然后创建一个元组列表每个元组由单词id和次数count构成,其中次数count是候选列表中每个单词出现的次数。
  5. 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('D:\\Python Computer Vision\\P\\')
nbr_images = len(imlist)
#获取特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
 
#载入词汇
f = open('D:\\Python Computer Vision\\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)

说明:
该 query() 方法获取图像的文件名,检索其单词直方图及候选图像列表(如果你的数据集很大,候选集的大小应该限制在某个最大值)。对于每个候选图像,用标准的欧式距离比较它和查询图像间的直方图,并返回一个经排序的包含距离及图像 id的元组列表。

尝试查询:

src = imagesearch.Searcher('testImaAdd.db', voc)
print 'try a query...'
print src.query(imlist[0])[:10]

Python计算机视觉编程学习笔记 七 图像搜索_第3张图片

(三)使用几何特性对结果排序

这是一种是用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('D:\\Python Computer Vision\\P\\')  # 存放数据集的路径
nbr_images = len(imlist)
# nbr_images = 300
# 载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
# 载入词汇
with open('D:\\Python Computer Vision\\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])  # 重排后的结果

python3的sqlite3包在运行python2基础的代码时,其中pickle包中序列化在python2和python3中不同,产生栈下溢的错误和无法更新库的问题,目前在尝试使用国内镜像解决还未成功,此处先放师姐的运行结果:

控制台输出结果:

top matches (regular): [37, 11, 19, 15, 17, 29, 12, 34, 16, 14]
top matches (homography): [37, 14]

Python计算机视觉编程学习笔记 七 图像搜索_第4张图片
Python计算机视觉编程学习笔记 七 图像搜索_第5张图片
分析:
混入了不同类别的图像后检测结果比较单一,同时不能检测出所有同类别的图像,这个算法在检测程度上的效果还存在一些缺陷,可能在创建词汇、创建索引、提取特征描述子这些环节还不够完善。

你可能感兴趣的:(python,数字图像处理,计算机视觉)