图像检索----BOW(词袋)算法

1. BOW算法简介

Bag-of-Words模型源于文本分类技术。在信息检索中,它假定对于一个文本,忽略其词序、语法和句法,将其仅仅看作是一个词集合,或者说是词的一个组合。文本中每个词的出现都是独立的,不依赖于其他词是否出现,或者说这篇文章的作者在任意一个位置选择词汇都不受前面句子的影响而独立选择的。

Bag-of-words在CV中的应用首先出现在Andrew Zisserman中为解决对视频场景的搜索,其提出了使用Bag-of-words关键点投影的方法来表示图像信息。后续更多的研究者归结此方法为Bag-of-Features,并用于图像分类、目标识别和图像检索。Bag-of-Features模型仿照文本检索领域的Bag-of-Words方法,把每幅图像描述为一个局部区域或关键点(Patches/Key Points)特征的无序集合,这些特征点可以看成一个词。这样,就能够把文本检索及分类的方法用到图像分类及检索中去。

使用某种聚类算法(如K-means)将特征进行聚类,每个聚类中心被看作是词典中的一个视觉词汇(Visual Word),相当于文本检索中的词,视觉词汇由聚类中心对应特征形成的码字(code word)来表示(可看当为一种特征量化过程)。所有视觉词汇形成一个视觉词典(Visual Vocabulary),对应一个码书(code book),即码字的集合,词典中所含词的个数反映了词典的大小。图像中的每个特征都将被映射到视觉词典的某个词上,这种映射可以通过计算特征间的距离去实现。然后,统计每个视觉词的出现与否或次数,图像可描述为一个维数相同的直方图向量,即Bag-of-Features。在Bag-of-Features方法的基础上,Andrew Zisserman进一步借鉴文本检索中TF-IDF模型(Term Frequency一Inverse Document Frequency)来计算Bag-of-Features特征向量。接下来便可以使用文本搜索引擎中的反向索引技术对图像建立索引,高效的进行图像检索。

Bag-of-Features更多地是用于图像分类或对象识别。在上述思路下对训练集提取Bag-of-Features特征,在某种监督学习(如:SVM)的策略下,对训练集的Bag-of-Features特征向量进行训练,获得对象或场景的分类模型;对于待测图像,提取局部特征,计算局部特征与词典中每个码字的特征距离,选取最近距离的码字代表该特征,建立一个统计直方图,统计属于每个码字的特征个数,即为待测图像的Bag-of-Features特征;在分类模型下,对该特征进行预测,从而实现对待测图像的分类。

2. BOW算法实现步骤

使用词袋模型进行图像检索。

2.0 图像预处理

图像预处理包括很多,在此就不介绍了,因为我没进行。
局部特征提取:通过分割、密集或随机采集、关键点或稳定区域、显著区域等方式使图像形成不同的patches,并获得各patches处的特征。

2.1 提取图像特征

鉴于SIFT的优异性能,本文提取的是SIFT特征。关于SIFT的具体介绍可以百度查询相关资料,后续我也会写文章具体介绍。在本文中只需要会调用相应的库函数即可,OpenCV库实现了SIFT特征的提取。

2.2 构建视觉词典

将所有图像的所有SIFT特征点放在一起,进行聚类,得出的聚类中心便是视觉词汇。所有视觉词汇的集合便是视觉词典。聚类中心的大小可以设置,本文采用的是K-Means聚类算法。

2.3 统计数据,生成码书

生成码书,即构造Bag-of-Features特征。计算每幅图像有哪些视觉词,统计出词频矩阵

2.4 引入TF-IDF权值

计算TF值和IDF值,进而得到TF-IDF矩阵,并对其进行L2归一化(向量中每个元素除以向量的L2范数->x/平方和开根号)。

2.5 对查询图像生成同样的带权BOF特征

重复上述步骤,对查询图像生成同样的带权BOF特征。

2.6 比较带权BOF特征

本文使用汉明距离,比较查询图像与数据库里的图像。

3.实现代码

#python findFeatures.py -t dataset/train/

import argparse as ap
import cv2
import numpy as np
import os
from sklearn.externals import joblib
from scipy.cluster.vq import *
import scipy
import scipy.cluster.hierarchy as sch
from scipy.cluster.vq import vq,kmeans,whiten
from sklearn import preprocessing
#from rootsift import RootSIFT
import math

def getInverseTable1(train_path, wordsNum):
    path = "E:\\"
    txtPath = "E:\\"
    count = 0
    f = open(txtPath, "w")
    for file in os.listdir(path):
        count = count + 1
        # ff='/'+filename+"/"+file+" "+filename+"\n"
        ff = file + " " + "\n"
        f.write(ff)
        print('{} class: {}'.format(file, count))
    f.close()

    # Get the training classes names and store them in a list
    # train_path = args["trainingSet"]
    # train_path = "dataset/train/"
    train_path = "E:\\"
    training_names = os.listdir(train_path)

    numWords = wordsNum
    # Get all the path to the images and save them in a list
    # image_paths and the corresponding label in image_paths
    image_paths = []
    for training_name in training_names:
        image_path = os.path.join(train_path, training_name)
        image_paths += [image_path]

    # Create feature extraction and keypoint detector objects
    # fea_det = cv2.FeatureDetector_create("SIFT")
    # des_ext = cv2.DescriptorExtractor_create("SIFT")
    surf = cv2.xfeatures2d.SURF_create()
    sift = cv2.xfeatures2d.SIFT_create()
    # sift = cv2.SIFT()
    # List where all the descriptors are stored
    des_list = []
    count = 0
    for i, image_path in enumerate(image_paths):
        # if count<3:
        count = count + 1
        img = cv2.imread(image_path)
        print("Extract SIFT of %s image, %d of %d images" % (training_names[i], i, len(image_paths)))
        # kpts = fea_det.detect(img)
        # kpts, des = des_ext.compute(img, kpts)
        kpts, des = sift.detectAndCompute(img, None)
        # rootsift
        # rs = RootSIFT()
        # des = rs.compute(kpts, des)
        des_list.append((image_path, des))

    # Stack all the descriptors vertically in a numpy array
    # downsampling = 1
    # descriptors = des_list[0][1][::downsampling,:]
    # for image_path, descriptor in des_list[1:]:
    #    descriptors = np.vstack((descriptors, descriptor[::downsampling,:]))

    # Stack all the descriptors vertically in a numpy array
    descriptors = des_list[0][1]
    for image_path, descriptor in des_list[1:]:
        descriptors = np.vstack((descriptors, descriptor))

    # Perform k-means clustering
    print("Start k-means: %d words, %d key points,%d points dimension" % (
    numWords, descriptors.shape[0], descriptors.shape[1]))
    data = whiten(descriptors)
    # print ("data  by whiten:\n",data)
    # centroid=kmeans(data,numWords)[0]
    # print ("centroid by k-means:\n",centroid)
    voc, variance = kmeans(descriptors, numWords, 1)
    print("损失大小:",variance)
    print("start k-means:\n")


    # Calculate the histogram of features
    print("开始计算TF-IDF:\n")
    im_features = np.zeros((len(image_paths), numWords), "float32")

    for i in range(len(image_paths)):
        words, distance = vq(des_list[i][1], voc)
        print("开始计算words:\n", words, words.size)
        for w in words:
            # print("开始输出w:\n", w)
            im_features[i][w] += 1
        '''

        '''
    # Perform Tf-Idf vectorization
    print("-----正在计算TF-IDF:\n")
    np.savetxt("im_features0.txt", im_features)
    tf = np.zeros((len(image_paths), numWords), "float32")

    nbr_occurences = np.sum((im_features > 0) * 1, axis=0)
    idf = np.array(np.log((1.0 * len(image_paths) + 1) / (1.0 * nbr_occurences + 1)), 'float32')
    # print ("-----TF-IDF:\n",nbr_occurences,idf)
    # Perform L2 normalization
    # im_features = im_features*idf*tf
    im_features = im_features * idf
    for i in range(len(image_paths)):
        tf[i] = im_features[i] / (np.sum(im_features, axis=1).reshape(60, 1)[i])
        #print("tf[i]:",tf[i])
        im_features[i] = im_features [i]* tf[i]
    inverse_table = im_features.T
    np.savetxt("inverse_table.txt", inverse_table)
    np.savetxt("im_features.txt", im_features)
    print("-----输出inverse_table.txt:\n")
    im_features = preprocessing.normalize(im_features, norm='l2')

    joblib.dump((im_features, image_paths, idf, numWords, voc), "bof.pkl", compress=3)
def searchImage(searchImagePath,wordsNum,flag):
    if flag==1:
        im_features, image_paths, idf, numWords, centers = joblib.load("bof.pkl")
    if flag==2:
        im_features, image_paths, idf, numWords, voc=joblib.load("bof.pkl")
        centers = voc
    img = cv2.imread(searchImagePath)
    #print("Extract SIFT of %s image" % (searchImagePath))
    surf =  cv2.xfeatures2d.SURF_create()
    sift = cv2.xfeatures2d.SIFT_create()
    # kpts = fea_det.detect(img)
    # kpts, des = des_ext.compute(img, kpts)
    kpts, des = sift.detectAndCompute(img, None)
    words, distance = vq(des, centers)
    #print("开始计算words:\n", words, words.size)
    im_features_search = np.zeros((wordsNum), "float32")
    for w in words:
        # print("开始输出w:\n", w)
        im_features_search[w] += 1
    inverseTable = np.loadtxt("inverse_table.txt")

    tf = im_features_search / (np.sum(im_features_search))
    #print("tf:", tf)
    im_features_search = im_features_search*tf
    im_features_search = im_features_search.reshape(1, wordsNum) * idf
    im_features_search = preprocessing.normalize(im_features_search, norm='l2')
    temp = np.dot(im_features_search,inverseTable)
    #print("开始求和:\n",temp)
    rank = np.argsort(-temp)
    #print("开始输出rank:\n", rank)
    return rank

if __name__ == '__main__':
    print("-----开始匹配:\n")
    trainImagePath ="E:\\"
    searchImagePath="E:\\"
    wordsNum = 1000   #聚类中心的数目
    getInverseTable1(trainImagePath,wordsNum)                
    rank = searchImage(searchImagePath ,wordsNum,2)
     

4. 结果分析

运行程序,分析结果。当测试集为60张图片时,top5的正确率只有40%左右。
分析如下:
1、使用k-means聚类,除了其K和初始聚类中心选择的问题外,对于海量数据,输入矩阵的巨大将使得内存溢出及效率低下。有方法是在海量图片中抽取部分训练集分类,使用朴素贝叶斯分类的方法对图库中其余图片进行自动分类。另外,由于图片爬虫在不断更新后台图像集,重新聚类的代价显而易见。
2、字典大小的选择也是问题,字典过大,单词缺乏一般性,对噪声敏感,计算量大,关键是图象投影后的维数高;字典太小,单词区分性能差,对相似的目标特征无法表示。
3、相似性测度函数用来将图象特征分类到单词本的对应单词上,其涉及线型核,塌方距离测度核,直方图交叉核等的选择。
4、将图像表示成一个无序局部特征集的特征包方法,丢掉了所有的关于空间特征布局的信息,在描述性上具有一定的有限性。为此, Schmid提出了基于空间金字塔的Bag-of-Features。
5、Jégou提出VLAD(vector of locally aggregated descriptors),其方法是如同BOF先建立出含有k个visual word的codebook,而不同于BOF将一个local descriptor用NN分类到最近的visual word中,VLAD所采用的是计算出local descriptor和每个visual word在每个分量上的差距,将每个分量的差距形成一个新的向量来代表图片。

参考资料

[1] BOW 原理及代码解析
[2] BoW(词袋)模型详细介绍
[3] BoW算法及DBoW2库简介
[4] Bag of Features (BOF)图像检索算法
[5] BOW模型理解
[6] 图像检索中BOW和LSH的一点理解
[7] 使用词袋模型对图像进行分类
[8] Bag of Features (BOF)图像检索算法
[9] SIFT+BOW 实现图像检索
[10] 基于词袋模型的图像检索与分类系统的设计与实现
[11] BoW(词袋模型)+python代码实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容