计算机视觉(四)

词袋模型(BoW)

Posted by Gavin on November 5, 2019

一叶舟轻

双桨鸿惊

概念

前言

计算机视觉作业,利用词袋模型进行图像检索,并计算检索的正确率和召回率,评估效果

BoW(词袋模型)

词袋模型(Bag of Words)最初被用到文本分类中,将文档表示成特征向量。其对于文本来说,忽略词法、句法,将文本仅看作单词的集合。那么每篇文档就都相当于一个袋子,里面装着各个单词。例如:

A: My name is OzyMandias, King of kings!
B: My name is Airkuit, princess of Vampire!

基于这两个文档,可以构造一个词典:

Dictionary = {1:"My", 2:"name", 3:"is", 4:"OzyMandias", 5:"King", 6:"of", 7:"kings", 8:"Airkuit", 9:"princess", 10:"Vampire"} 

那么A、B可以以如下特征向量形式表示:

A: [1,1,1,1,1,1,1,0,0,0]
B: [1,1,1,0,0,1,0,1,1,1]

词典一共10维,特征向量则也是10维,每一维特征表示次特征出现次数,这便对文本完成了降维。
在图像处理中,也可以应用这种方法。文本中,以单词作为特征,图像中则以SIFTSURF等经典算子提取的矢量进行后续处理。但是图像中提取出的矢量数目是非常大的,很难进行处理,因此对其进行聚类,得到指定数目的特征簇,这些特征簇则相当于文本中的单词。那么一幅图像就可以用特征向量来描述了,完成图像降维。

图像检索或分类

以特征向量描述图像之后,就可以进行图像检索或分类了,我们已有一堆已知标签的图像的特征向量,对新进入的图像进行上述操作得到其特征向量之后,可以使用最简单的KNN方法即可检索出与这张图最相似的图,或者预测这张图的类别。当然如果数据量比较大,这里还是会很耗时,也可以采用其他机器学习方法。不过我们这次不是为此而来,我们要进行正统的图像检索。这里就用到了另一个文本处理的方法——倒排索引(Inverted File Index)
如之前所述,A、B目前被表示为10维特征向量,那么我们可以通过某一个特征找到A或B吗?相当于我们使用百度、谷歌等搜索引擎一样,通过关键字,能检索到相应文本。在这里就是将特征作为索引,该特征出现在图像中的次数作为向量值,如下所示

1: [1,1]
2: [1,1]
3: [1,1]
4: [1,0]
5: [1,1]
6: [1,0]
7: [0,1]
8: [0,1]
9: [0,1]
10: [0,1]

接下来,检索一张图片可以使用简单的投票法,将图片表示成特征向量之后,逐一检索其特征,根据倒排索引表统计该图片最相思的那些图片。当然这里是存在问题的。比方说,文本中最常见的单词是诸如”我“、”是“这些无意义词,图像中也一样。所以需要对特征向量加一个权重,降低这些无意义特征权重,增加决定特征权重。这里使用tf-idf权重,tf为词频,idf为逆文档词频,tf-idf值越大,说明这个词对这篇文章越重要

\[tf = 某词文章中出现次数 / 文章总词数\] \[idf = log(文档总数/(包含该词文档数+1))\] \[tf-idf = tf*idf\]

在图像检索中,特征出现次数可以直接作为词频使用,那么再乘一个idf值,得到tf-idf向量,然后\(l_2\)归一化,再与倒排索引矩阵求内积进行相似性度量即可。


实验

数据

vgg的衍射变换数据集,下载地址:http://www.robots.ox.ac.uk/~vgg/data/affine/

步骤

  1. 提取SIFT特征

     def chooseFeatures(imgPath,resPath=None):
         sift = cv2.xfeatures2d.SIFT_create()
         img = cv2.imread(imgPath)
         #gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
         keypoints, descriptors = sift.detectAndCompute(img,None)
         return descriptors
    
  2. 对特征进行K-Means聚类

     def createClusters(descriptors, numClusters):  
         clusters = KMeans(numClusters)
         clusters.fit(np.vstack(descriptors))
         # returns a trained KMeans cluster
         return clusters
    
  3. 将图像用词向量表示

     def createBoW(descriptors, clusters):
         imageBoW = np.zeros(clusters.k)
         for i in range(0, len(descriptors)):
             cluster = clusters.predict(descriptors[i])
             imageBoW[cluster] = imageBoW[cluster] + 1
         # returns the bag of words representation of a single image
         return imageBoW
    
     array([[ 16.,  12.,  15., ...,   9.,  99.,  19.],
        [ 10.,   9.,   7., ...,  11.,  13.,  13.],
        [  7.,   6.,   5., ...,   5.,  10.,   6.],
        ...,
        [ 54.,  52.,  50., ..., 101.,  89.,  42.],
        [ 57.,  58.,  56., ..., 111.,  76.,  52.],
        [ 56.,  61.,  58., ...,  92.,  75.,  49.]])
    
  4. 构建倒排索引表

     def createInvertedTable(imageBows):
         imageBows = imageBows * idf
         imageBows = normalize(imageBows, norm='l2')
         return imageBows.T
    
     array([[0.        , 0.        , 0.        , ..., 0.        , 0.        ,
         0.        ],
        [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
         0.        ],
        [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
         0.        ],
        ...,
        [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
         0.        ],
        [0.28282037, 0.08420714, 0.09850629, ..., 0.07760871, 0.06366686,
         0.06512577],
        [0.05427866, 0.08420714, 0.05910378, ..., 0.03662434, 0.04356154,
         0.04254884]])
    
  5. 选取图片计算准确率和召回率

     def getPrecisionAndRecall(imagePath,imageLabels,imageType):
         descriptors = chooseFeatures(imagePath)
         imageBow = createBoW(descriptors, clusters)
         #imageBow = scaler.transform(imageBow.reshape(1,-1))
         imageBow = imageBow * idf
         imageBow = normalize([imageBow], norm='l2')
         temp = np.dot(imageBow,invertedTable)
         rank = np.argsort(-temp)[0]
         results = imageLabels[rank[0:7]]
         #claculate precision and recall
         tp = np.sum(results == imageType)
         precision = tp / 7
         recall = tp / 7
         return precision, recall
    
     (0.7714285714285714, 0.7714285714285714)
    

总结

在CNN未出现之前,BoW是非常热门的图像检索和分类方法,具有很强的可解释性。