詳解詞袋模型

 個人博客:http://www.chenjianqu.com/

原文鏈接:http://www.chenjianqu.com/show-101.html

本文是<視覺SLAM14講>的學習筆記,今天學習到詞袋模型,可以用來計算圖像間的相似度。

基本概念

    詞袋(Bag-of-Words,BoW),是用“圖像上有哪幾種特徵”來描述一個圖像的方法。圖像的詞袋模型可以度量兩個圖像的相似性:首先需要確定BoW中的“單詞”,許多單詞放在一起,組成“字典”。然後確定一張圖像中出現了哪些單詞(這裏的單詞對應的是特徵),把圖像轉換成了一個向量。最後根據向量,設計一定的計算方式,就能確定圖像間的相似性了。

    通過字典和單詞,只需一個向量就可以描述整張圖像了。該向量描述的是“圖像是否含有某類特徵”的信息,比單純的灰度值更加穩定。因爲描述向量說的是“是否出現”,而不管它們“在哪兒出現”,所以與物體的空間位置和排列順序無關,因此稱它爲Bag-of-Words。在相機發生少量運動時,只要物體仍在視野中出現,就仍然保證描述向量不發生變化。

 

單詞的權重

    一張圖像可以提取出N個特徵,這N個特徵可以對應到字典中的N各單詞,即得到了該圖像的單詞序列。考慮到不同的單詞(特徵)在區分性上的重要性並不相同,我們希望對單詞的區分性或重要性加以評估,給它們不同的權值以起到更好的效果,常用的一種做法稱爲 TF-IDF(Term Frequency–Inverse Document Frequency,頻率-逆文檔頻率)。TF 部分的思想是,某單詞在一個圖像中經常出現,它的區分度就高。IDF 的思想是,某單詞在字典中出現的頻率越低,則分類圖像時區分度越高。

    在詞袋模型中,在建立字典時計算 IDF 部分。統計某個單詞 wi 中的特徵數量相對於所有特徵數量的比例,作爲 IDF 部分。假設所有特徵數量爲 n,wi 數量爲 ni,那麼:IDFi=log(n/ni) 

    TF 部分則是指某個特徵在單個圖像中出現的頻率。假設圖像 A 中,單詞wi 出現了 ni 次,而一共出現的單詞次數爲 n,那麼:TFi = ni/n

    單詞 wi 的TF-IDF權重:ni TFi * IDFi

    對於某個圖像 A,它的特徵點可對應到許多個單詞,組成它的 Bag-of Words:A = {(w1,η1),(w2,η2), . . . ,(wn,ηn)} = vA 。通過詞袋,我們用單個向量 vA 描述了圖像 A。向量 vA 是一個稀疏的向量,它的非零部分指示出圖像 A 中含有哪些單詞,而這些部分的值爲 TF-IDF 的值。

 

字典的構建

    字典由很多單詞組成,而每一個單詞代表了一類特徵。一個單詞與一個單獨的特徵點不同,它不是從單個圖像上提取出來的,而是某一類特徵的組合。所以,字典生成問題類似於一個聚類(Clustering)問題。假設對大量的圖像提取了N 個特徵點,我們想找一個有 k 個單詞的字典,每個單詞可以看作局部相鄰特徵點的集合,這可以用經典的 K-means(K 均值)算法解決。K-means算法流程:

1. 隨機選取 k 箇中心點:c1, . . . , ck;

2. 對每一個樣本,計算與每個中心點之間的距離,取最小的作爲它的歸類;

3. 重新計算每個類的中心點。

4. 如果每個中心點都變化很小,則算法收斂,退出;否則返回 1。

    考慮到字典的通用性,通常會使用一個較大規模的字典,以保證當前使用環境中的圖像特徵都曾在字典裏出現過。爲了加快字典的查找效率,常用K叉樹表達字典。假定有 N 個特徵點,希望構建一個深度爲 d,每次分叉爲 k 的樹,那麼做法如下:

1. 在根節點,用 k-means 把所有樣本聚成 k 類(實際中爲保證聚類均勻性會使用k-means++)。這樣得到了第一層。

2. 對第一層的每個節點,把屬於該節點的樣本再聚成 k 類,得到下一層。

3. 依此類推,最後得到葉子層。葉子層即爲所謂的 Words。

如下圖:

A.jpg

    最終在葉子層構建了單詞,樹結構中的中間節點僅供快速查找時使用。這樣一個 k 分支,深度爲 d 的樹,可以容納 kd 個單詞。在查找某個給定特徵對應的單詞時,只需將它與每個中間結點的聚類中心比較(一共 d 次),即可找到最後的單詞,保證了對數級別的查找效率。

 

相似度計算

    給定兩張圖像的單詞向量vA ,vB,可以通過多種方式計算它們的差異。比如這裏使用L1範數形式:

B.jpg

代碼實現

    這裏使用ORB特徵的描述子作爲BoW的特徵,使用DBoW3庫實現詞袋模型。字典往往是從更大的數據集中生成的,而且最好是來自目標應該環境類似的地方。我們通常使用較大規模的字典——越大代表字典單詞量越豐富,容易找到與當前圖像對應的單詞。下面是代碼實現:

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(dbow3_test)
set( CMAKE_CXX_FLAGS "-std=c++11" )
find_package( OpenCV 3 REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
set( DBoW3_INCLUDE_DIRS "/usr/local/include" )
set( DBoW3_LIBS "/usr/local/lib/libDBoW3.a" )
add_executable(dbow3_test main.cpp)
target_link_libraries(dbow3_test ${OpenCV_LIBS} ${DBoW3_LIBS})
install(TARGETS dbow3_test RUNTIME DESTINATION bin)

main.cpp

#include <iostream>
#include <string>
#include <vector>
#include <string.h>
#include <dirent.h>
#include <DBoW3/DBoW3.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>

using namespace std;
using namespace cv;

//讀取某路徑下的所有文件名
int getFiles(const string path, vector<string>& files)
{
    int iFileCnt = 0;
    DIR *dirptr = NULL;
    struct dirent *dirp;
    if((dirptr = opendir(path.c_str())) == NULL)//打開一個目錄
        return 0;
    while ((dirp = readdir(dirptr)) != NULL){
        if ((dirp->d_type == DT_REG) && 0 ==(strcmp(strchr(dirp->d_name, '.'), ".png")))//判斷是否爲文件以及文件後綴名
        files.push_back(dirp->d_name);
        iFileCnt++;
    }
    closedir(dirptr);
    return iFileCnt;
}

//構建字典
void generateDict()
{
    string dataPath = "/media/chen/chen/SLAM/projects_test/DBow3_test/data";
    vector<string> files;
    getFiles(dataPath,files);//獲取圖片名
    //讀取圖片並提取ORB描述子
    vector<Mat> descriptors;
    Ptr< Feature2D > detector = ORB::create();
    for(const auto &x: files)
    {
        string picName=dataPath+"/"+x;
        cout << picName << endl;
        vector<KeyPoint> keypoints; 
        Mat descriptor;
        detector->detectAndCompute( imread(picName), Mat(), keypoints, descriptor );
        descriptors.push_back( descriptor );
    }
    DBoW3::Vocabulary vocab;
    vocab.create( descriptors );
    cout<<"vocabulary info: "<<vocab<<endl;
    vocab.save( "../vocabulary.yml.gz" );
    cout<<"done"<<endl;  
}

int main(int argc, char** argv)
{
  //生成字典
  //generateDict();
  
    //用字典判斷圖像相似度
    //DBoW3::Vocabulary vocab("../vocab_larger.yml.gz");//10張圖像得到的字典
    DBoW3::Vocabulary vocab("../vocab_larger.yml.gz");//2900張圖像得到的字典
    
    //提取ORB的描述子
    Mat despA,despB,despC;
    Ptr< Feature2D > detector = ORB::create();
    vector<KeyPoint> keypoints; 
    detector->detectAndCompute( imread("../test/A.png"), Mat(), keypoints, despA );
    detector->detectAndCompute( imread("../test/B.png"), Mat(), keypoints, despB );
    detector->detectAndCompute( imread("../test/C.png"), Mat(), keypoints, despC );
    
    //構建圖像的單詞向量
    DBoW3::BowVector vA,vB,vC;
    vocab.transform(despA,vA );
    vocab.transform(despB,vB );
    vocab.transform(despC,vC );
    
    //比較各圖片單詞向量的相似度
    double scoreAB=vocab.score(vA, vB);
    double scoreAC=vocab.score(vA, vC);
    double scoreBC=vocab.score(vB, vC);
    double scoreAA=vocab.score(vA, vA);
    
    cout<<"scoreAB:"<<scoreAB<<endl;
    cout<<"scoreAC:"<<scoreAC<<endl;
    cout<<"scoreBC:"<<scoreBC<<endl;
    cout<<"scoreAA:"<<scoreAA<<endl;
    return 0;
}

    代碼中的A.png、B.png、C.png分別如下:

C.jpg

    代碼輸出如下:

Starting: /media/chen/chen/SLAM/projects_test/DBow3_test/build/dbow3_test
scoreAB:0.214842
scoreAC:0.0299757
scoreBC:0.0276582
scoreAA:1
*** Exited normally ***

    可以看到A-B相似,因此得分較高,而A-C、B-C不相似,因此得分較低。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章