python计算机视觉编程第七章——图像搜索

一、基于内容是图像检索

在大型图像数据库上,CBlR (Content-Based Image Retrieval,基于内容的图像检索)技术用于检索在视觉上具相似性的图像。这样返回的图像可以是颜色相似、纹理相似、图像中的物体或场景相似;总之,基本上可以是这些图像自身共有的任何信息。

从文本挖掘中获取灵感——矢量空间模西

矢量空间模型是一个用于表示和搜索文本文档的模型。它基本上可以应用于任何对象类型,包括图像。该名字来源于用矢量来表示文本文档,这些矢量是由文本词频直方图构成的。由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。

通过单词计数来构建文档直方图向量v,从而建立文档索引。对于直方图向量中的每个元素,一般根据每个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中一个单词的重要性与它在文档中出现的次数成正比,而与它在语料库中出现的次数成反比。

最常用的权重是tf-idf (term frequency-inverse document frequency,词频-逆向文档频率),单词w在文档d中的词频是:

tf_{w,d}=\frac{n_{w}}{\sum _{j}n_{j}}

逆向文档频率为:

二、视觉单词

为了将文本挖掘技术应用到图像中,我们首先需要建立视觉等效单词。它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。

这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。对于给定的问题、图像类型,或在通常情况下仅需呈现视觉内容,可以创建特定的词汇。

创建词汇

创建一个词汇类,以及在训练图像数据集上训练出一个词汇的方法:
from scipy.cluster.vq import *
from pylab import *
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])
        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 的词汇表。再次假设 imlist 是一个包含了图像文件名的列表
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

三、图像索引

1 建立数据库

在索引图像前,我们需要建立一个数据库。这里,对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保存视觉单词及对应图像的单词直方图。从而可以利用图像对数据库进行查询,并返回相似的图像作为搜索结果。这里,我们使用sQLite作为数据库。

在开始之前,我们需要创建表、索引和索引器Indexer类,以便将图像数据写入数据库。
 

import pickle
from pysqlite2 import dbapi2 as sqlite


class Indexer(object):
    def __init__(self, db, voc):
        """ 初始化数据库的名称及词汇对象 """
        self.con = sqlite.connect(db)
        self.voc = voc

    def __del__(self):
        self.con.close()

    def db_commit(self):
        self.con.commit()

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

该方法获取图像文件名与Numpy数组,该数组包含的是在图像找到的描述子。这些描述子投影到词汇上,并插入到imwords(逐字)和 imhistograms表单中。我们使用两个辅助函数: is_indxed()用来检查图像是否已经被索引,get_id()则对一幅图像文件名给定id号。
 

def is_indexed(self, imname):
    """ 如果图像名字(imname)被索引到,就返回 True"""
    im = self.con.execute("select rowid from imlist wherefilename='%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]

四、 在数据库中搜索图像

建立好图像的索引,我们就可以在数据库中搜索相似的图像了。这里,我们用BoW(Bag-of-Word,词袋模型)来表示整个图像,不过这里介绍的过程是通用的,可以应用于寻找相似的物体、相似的脸、相似的颜色等,它完全取决于图像及所用的描述子。为实现搜索,我们在 imagesearch.py 中添加Searcher类:

class Searcher(object):
 def __init__(self,db,voc):
    """ 初始化数据库的名称 """
    self.con = sqlite.connect(db)
    self.voc = voc
 def __del__(self):
    self.con.close()

1 利用索引获取候选图像

我们可以利用建立起来的索引找到包含特定单词的所有图像,这不过是对数据库做一次简单的查询。在Searcher类中加入 candidates_from_word()方法:

    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]

2 用一幅图像进行查询

利用一幅图像进行查询时,没有必要进行完全的搜索。为了比较单词直方图,Searcher类需要从数据库读入图像的单词直方图。将下面的方法添加到 Searcher 类中:

def get_imhistogram(self, imname):
    """返回一幅图像的单词直方图"""
    im_id = self.con.execute(
        "select rowid from imlist where filename='%s'" % imname
    ).fetchone()
    
    if im_id is None:
        return None

    s = self.con.execute(
        "select histogram from imhistograms where rowid='%d'" % im_id[0]
    ).fetchone()
    
    # 用 pickle 模块从字符串解码 Numpy 数组
    return pickle.loads(s[0])

这里,为了在字符串和 NumPy 数组间进行转换,我们再次用到了 pickle 模块,这次使用的是 loads()。

现在,我们可以全部合并到查询方法中

def query(self, imname):
    """查找所有与 imname 匹配的图像列表"""
    h = self.get_imhistogram(imname)
    if h is None:
        return []

    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()[0]

        cand_h = self.get_imhistogram(cand_name)
        if cand_h is None:
            continue

        # 用 L2 距离度量相似性
        cand_dist = sqrt(sum(self.voc.idf * (h - cand_h) ** 2))
        matchscores.append((cand_dist, imid))

    # 返回排序后的距离及对应数据库 ids 列表
    matchscores.sort()
    return matchscores

该 query() 方法获取图像的文件名,检索其单词直方图及候选图像列表

尝试对图像进行查询

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

为了评价搜索结果的好坏,我们可以计算前 4 个位置中搜索到相似图像数。计算分数的函数,将它添加到 imagesearch.py 中:

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 幅图像为一组时相似图像文件名是连续的这一事实,我们用整数相除计算得到最终的分数。分数为 4 时结果最理想;没有一个是准确的,分数为 0;仅检索到相同图像时,分数为 1;找到相同的图像并且其他三个中的两个相同时,分数为 3。

显示实际搜索结果的函数如下:
 

def plot_results(src, res):
    """显示在列表 res 中的图像"""
    plt.figure()
    nbr_results = len(res)
    
    for i in range(nbr_results):
        imname = src.get_filename(res[i])
        plt.subplot(1, nbr_results, i + 1)
        plt.imshow(array(Image.open(imname)))
        plt.axis('off')
    
    plt.show()

五、使用几何特性对结果排序

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 列表中每一幅图像的特征,并和查询图像进行匹配。单应性通过计算 匹配数和计数内点数得到。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值