圖像檢索----BOW(詞袋)算法

1. BOW算法簡介

Bag-of-Words模型源于文本分類技術(shù)。在信息檢索中捣辆,它假定對于一個(gè)文本蔬螟,忽略其詞序、語法和句法汽畴,將其僅僅看作是一個(gè)詞集合旧巾,或者說是詞的一個(gè)組合。文本中每個(gè)詞的出現(xiàn)都是獨(dú)立的忍些,不依賴于其他詞是否出現(xiàn)鲁猩,或者說這篇文章的作者在任意一個(gè)位置選擇詞匯都不受前面句子的影響而獨(dú)立選擇的。

Bag-of-words在CV中的應(yīng)用首先出現(xiàn)在Andrew Zisserman中為解決對視頻場景的搜索罢坝,其提出了使用Bag-of-words關(guān)鍵點(diǎn)投影的方法來表示圖像信息廓握。后續(xù)更多的研究者歸結(jié)此方法為Bag-of-Features,并用于圖像分類嘁酿、目標(biāo)識別和圖像檢索隙券。Bag-of-Features模型仿照文本檢索領(lǐng)域的Bag-of-Words方法,把每幅圖像描述為一個(gè)局部區(qū)域或關(guān)鍵點(diǎn)(Patches/Key Points)特征的無序集合闹司,這些特征點(diǎn)可以看成一個(gè)詞娱仔。這樣,就能夠把文本檢索及分類的方法用到圖像分類及檢索中去游桩。

使用某種聚類算法(如K-means)將特征進(jìn)行聚類牲迫,每個(gè)聚類中心被看作是詞典中的一個(gè)視覺詞匯(Visual Word),相當(dāng)于文本檢索中的詞众弓,視覺詞匯由聚類中心對應(yīng)特征形成的碼字(code word)來表示(可看當(dāng)為一種特征量化過程)恩溅。所有視覺詞匯形成一個(gè)視覺詞典(Visual Vocabulary),對應(yīng)一個(gè)碼書(code book)谓娃,即碼字的集合脚乡,詞典中所含詞的個(gè)數(shù)反映了詞典的大小。圖像中的每個(gè)特征都將被映射到視覺詞典的某個(gè)詞上滨达,這種映射可以通過計(jì)算特征間的距離去實(shí)現(xiàn)奶稠。然后,統(tǒng)計(jì)每個(gè)視覺詞的出現(xiàn)與否或次數(shù)捡遍,圖像可描述為一個(gè)維數(shù)相同的直方圖向量锌订,即Bag-of-Features。在Bag-of-Features方法的基礎(chǔ)上画株,Andrew Zisserman進(jìn)一步借鑒文本檢索中TF-IDF模型(Term Frequency一Inverse Document Frequency)來計(jì)算Bag-of-Features特征向量辆飘。接下來便可以使用文本搜索引擎中的反向索引技術(shù)對圖像建立索引啦辐,高效的進(jìn)行圖像檢索。

Bag-of-Features更多地是用于圖像分類或?qū)ο笞R別蜈项。在上述思路下對訓(xùn)練集提取Bag-of-Features特征芹关,在某種監(jiān)督學(xué)習(xí)(如:SVM)的策略下,對訓(xùn)練集的Bag-of-Features特征向量進(jìn)行訓(xùn)練紧卒,獲得對象或場景的分類模型侥衬;對于待測圖像,提取局部特征跑芳,計(jì)算局部特征與詞典中每個(gè)碼字的特征距離轴总,選取最近距離的碼字代表該特征,建立一個(gè)統(tǒng)計(jì)直方圖博个,統(tǒng)計(jì)屬于每個(gè)碼字的特征個(gè)數(shù)怀樟,即為待測圖像的Bag-of-Features特征;在分類模型下坡倔,對該特征進(jìn)行預(yù)測漂佩,從而實(shí)現(xiàn)對待測圖像的分類。

2. BOW算法實(shí)現(xiàn)步驟

使用詞袋模型進(jìn)行圖像檢索罪塔。

2.0 圖像預(yù)處理

圖像預(yù)處理包括很多,在此就不介紹了养葵,因?yàn)槲覜]進(jìn)行征堪。
局部特征提取:通過分割关拒、密集或隨機(jī)采集佃蚜、關(guān)鍵點(diǎn)或穩(wěn)定區(qū)域、顯著區(qū)域等方式使圖像形成不同的patches着绊,并獲得各patches處的特征谐算。

2.1 提取圖像特征

鑒于SIFT的優(yōu)異性能,本文提取的是SIFT特征归露。關(guān)于SIFT的具體介紹可以百度查詢相關(guān)資料洲脂,后續(xù)我也會(huì)寫文章具體介紹。在本文中只需要會(huì)調(diào)用相應(yīng)的庫函數(shù)即可剧包,OpenCV庫實(shí)現(xiàn)了SIFT特征的提取恐锦。

2.2 構(gòu)建視覺詞典

將所有圖像的所有SIFT特征點(diǎn)放在一起,進(jìn)行聚類疆液,得出的聚類中心便是視覺詞匯一铅。所有視覺詞匯的集合便是視覺詞典。聚類中心的大小可以設(shè)置堕油,本文采用的是K-Means聚類算法潘飘。

2.3 統(tǒng)計(jì)數(shù)據(jù)肮之,生成碼書

生成碼書,即構(gòu)造Bag-of-Features特征卜录。計(jì)算每幅圖像有哪些視覺詞戈擒,統(tǒng)計(jì)出詞頻矩陣

2.4 引入TF-IDF權(quán)值

計(jì)算TF值和IDF值,進(jìn)而得到TF-IDF矩陣暴凑,并對其進(jìn)行L2歸一化(向量中每個(gè)元素除以向量的L2范數(shù)->x/平方和開根號)峦甩。

2.5 對查詢圖像生成同樣的帶權(quán)BOF特征

重復(fù)上述步驟,對查詢圖像生成同樣的帶權(quán)BOF特征现喳。

2.6 比較帶權(quán)BOF特征

本文使用漢明距離凯傲,比較查詢圖像與數(shù)據(jù)庫里的圖像。

3.實(shí)現(xiàn)代碼

#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("開始計(jì)算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("開始計(jì)算words:\n", words, words.size)
        for w in words:
            # print("開始輸出w:\n", w)
            im_features[i][w] += 1
        '''

        '''
    # Perform Tf-Idf vectorization
    print("-----正在計(jì)算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("開始計(jì)算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   #聚類中心的數(shù)目
    getInverseTable1(trainImagePath,wordsNum)                
    rank = searchImage(searchImagePath ,wordsNum,2)
     

4. 結(jié)果分析

運(yùn)行程序冰单,分析結(jié)果。當(dāng)測試集為60張圖片時(shí)灸促,top5的正確率只有40%左右诫欠。
分析如下:
1、使用k-means聚類浴栽,除了其K和初始聚類中心選擇的問題外荒叼,對于海量數(shù)據(jù),輸入矩陣的巨大將使得內(nèi)存溢出及效率低下典鸡。有方法是在海量圖片中抽取部分訓(xùn)練集分類被廓,使用樸素貝葉斯分類的方法對圖庫中其余圖片進(jìn)行自動(dòng)分類。另外萝玷,由于圖片爬蟲在不斷更新后臺圖像集嫁乘,重新聚類的代價(jià)顯而易見。
2球碉、字典大小的選擇也是問題蜓斧,字典過大,單詞缺乏一般性睁冬,對噪聲敏感挎春,計(jì)算量大,關(guān)鍵是圖象投影后的維數(shù)高痴突;字典太小搂蜓,單詞區(qū)分性能差,對相似的目標(biāo)特征無法表示辽装。
3帮碰、相似性測度函數(shù)用來將圖象特征分類到單詞本的對應(yīng)單詞上,其涉及線型核拾积,塌方距離測度核殉挽,直方圖交叉核等的選擇丰涉。
4、將圖像表示成一個(gè)無序局部特征集的特征包方法斯碌,丟掉了所有的關(guān)于空間特征布局的信息一死,在描述性上具有一定的有限性。為此傻唾, Schmid提出了基于空間金字塔的Bag-of-Features投慈。
5、Jégou提出VLAD(vector of locally aggregated descriptors)冠骄,其方法是如同BOF先建立出含有k個(gè)visual word的codebook伪煤,而不同于BOF將一個(gè)local descriptor用NN分類到最近的visual word中,VLAD所采用的是計(jì)算出local descriptor和每個(gè)visual word在每個(gè)分量上的差距凛辣,將每個(gè)分量的差距形成一個(gè)新的向量來代表圖片抱既。

參考資料

[1] BOW 原理及代碼解析
[2] BoW(詞袋)模型詳細(xì)介紹
[3] BoW算法及DBoW2庫簡介
[4] Bag of Features (BOF)圖像檢索算法
[5] BOW模型理解
[6] 圖像檢索中BOW和LSH的一點(diǎn)理解
[7] 使用詞袋模型對圖像進(jìn)行分類
[8] Bag of Features (BOF)圖像檢索算法
[9] SIFT+BOW 實(shí)現(xiàn)圖像檢索
[10] 基于詞袋模型的圖像檢索與分類系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)
[11] BoW(詞袋模型)+python代碼實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扁誓,隨后出現(xiàn)的幾起案子防泵,更是在濱河造成了極大的恐慌,老刑警劉巖蝗敢,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捷泞,死亡現(xiàn)場離奇詭異,居然都是意外死亡寿谴,警方通過查閱死者的電腦和手機(jī)肚邢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拭卿,“玉大人,你說我怎么就攤上這事贱纠【瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵谆焊,是天一觀的道長惠桃。 經(jīng)常有香客問我,道長辖试,這世上最難降的妖魔是什么辜王? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任绝页,我火速辦了婚禮诅岩,結(jié)果婚禮上灵疮,老公的妹妹穿的比我還像新娘冗荸。我一直安慰自己嘹裂,他們只是感情好冬竟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布贼穆。 她就那樣靜靜地躺著页畦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪收班。 梳的紋絲不亂的頭發(fā)上坟岔,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天,我揣著相機(jī)與錄音摔桦,去河邊找鬼社付。 笑死,一個(gè)胖子當(dāng)著我的面吹牛邻耕,可吹牛的內(nèi)容都是我干的鸥咖。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼赊豌,長吁一口氣:“原來是場噩夢啊……” “哼扛或!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碘饼,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤熙兔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后艾恼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體住涉,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年钠绍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舆声。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,712評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柳爽,死狀恐怖媳握,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磷脯,我是刑警寧澤蛾找,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站赵誓,受9級特大地震影響打毛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俩功,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一幻枉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诡蜓,春花似錦熬甫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洽腺。三九已至,卻和暖如春覆旱,著一層夾襖步出監(jiān)牢的瞬間蘸朋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工扣唱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藕坯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓噪沙,卻偏偏與公主長得像炼彪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子正歼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評論 2 350

推薦閱讀更多精彩內(nèi)容