圖像檢索(2):均值聚類-構建BoF


在圖像檢索時,通常首先提取圖像的局部特征,這些局部特征通常有很高的維度(例如,sift是128維),有很多的冗余信息,直接利用局部特征進行檢索,效率和准確度上都不是很好。這就需要重新對提取到的局部特征進行編碼,以便於匹配檢索。
常用的局部特征編碼方法有三種:

  • BoF
  • VLAD
  • FV

本文主要介紹基於k-means聚類算法的BoF的實現。

  • BoF的原理
  • k均值聚類概述
  • 使用OpenCV實現的BoF

BoF

該方法源自於文本處理的詞袋模型。Bag-of-words model (BoW model) 最早出現在NLP和IR領域. 該模型忽略掉文本的語法和語序, 用一組無序的單詞(words)來表達一段文字或一個文檔. 近年來, BoW模型被廣泛應用於計算機視覺中. 與應用於文本的BoW類比, 圖像的特征(feature)被當作單詞(Word).

例如下面的句子:
John likes to watch movies. Mary likes too. John also likes to watch football games.
就可以構建一個詞典

{"John": 1, "likes": 2, "to": 3, "watch": 4, "movies": 5, "also": 6, "football": 7, "games": 8, "Mary": 9, "too": 10}

該字典中包含10個單詞, 每個單詞有唯一索引, 注意它們的順序和出現在句子中的順序沒有關聯. 根據這個字典, 我們能將上述兩句話重新表達為下述兩個向量:

[1, 2, 1, 1, 1, 0, 0, 0, 1, 1]  [1, 1, 1, 1, 0, 1, 1, 1, 0, 0]

這兩個向量共包含10個元素, 其中第i個元素表示字典中第i個單詞在句子中出現的次數。
統計詞頻的時候有兩種方法:

  • 詞集模型(set of words model) 將每個詞出現與否作為特征,忽略詞出現的次數,這種模型得到的向量只有0和1兩個值;
  • 詞袋模型(bag of words model)要統計詞出現的次數。

特征詞袋(BoF,Bag Of Feature)借鑒文本處理的詞袋(BoW,Bag Of Bag)算法,將圖像表示成視覺關鍵詞的統計直方圖。就像上面對文本的處理一樣,提取文本中出現單詞組成詞匯表,這里關鍵是得到圖像庫的“詞匯表”。為了得到圖像庫的“詞匯表",通常對提取到的圖像特征進行聚類,得到一定個數的簇。這些聚類得到的簇,就是圖像的”詞匯“,可以稱為視覺詞(Visual Word)。聚類形成的簇,可以使用聚類中心來描述,所以,視覺詞指的是圖像的局部區域特征(如紋理,特征點)經過聚類形成的聚類中心。

有了視覺詞的集合后,就可以將一幅圖像表示為\(K\)維的向量(\(K\)為聚類中心的個數,也就是視覺詞的個數),向量的每個分量表示某個視覺詞在圖像中出現的次數。

構建圖像的BoF的步驟如下:以SIFT特征為例

  1. SIFT特征提取 提取訓練集中所有圖像的SIFT特征,設有\(M\)幅圖像,共得到\(N\)個SIFT特征。
  2. 構建視覺詞匯表 對提取到的\(N\)個SIFT特征進行聚類,得到\(K\)個聚類中心,組成圖像的視覺詞匯表
  3. 圖像的視覺詞向量表示,統計每幅圖像中視覺詞匯的出現的次數,得到圖像的特征向量。在檢索時,該特征向量就代表該幅圖像。統計時,計算圖像中提取到的SIFT特征點到各個視覺詞(聚類中心)的距離,將其歸類到聚類最近的視覺詞中。

所以聚類在構建BoF是很重要的一步,接下來簡單的介紹下聚類的基本知識以及最常用的聚類算法k-means算法。

聚類概述

聚類(Clustering)是一種無監督學習算法,其目的是將數據集中的樣本划分為若干個不相交的子集,每個子集稱為一個簇(Cluster)。聚類的時候並不關心某一類是什么,只根據數據的相似性,將數據划分到不同的組中。每個組內的成員具有相似的性質。

聚類算法說白了就是給你一大堆點的坐標(維度可以是很高),然后通過一個向量的相似性准則(通常是距離,比如歐拉距離),然后把相近的點放在一個集合里面,歸為一類。

更正式的說,假設有樣本集 \(D = \{x_1,x_2,\dots,x_m\}\)\(m\)個無標記的樣本,每個樣本可以使用一個\(n\)維特征向量表示:\(x_i = (x_{i1};x_{i2};\dots;x_{in})\),根據相似的准則,將集合\(D\)划分為\(k\)個不相交的簇\(\{C_l|l = 1,2,\dots,k\}\)。每個簇可以用其聚類中心來描述\(\lambda_l = (x_{l1},x_{l2},\dots,x_{ln}),l = 1,2,\dots,k\).

相似性度量(距離計算)

兩個向量的相似性,通常可以使用距離度量,距離越大,相似性越小;距離越小,相似性越大。給定兩個樣本\(x = (x_1,x_2,\dots,x_n), y = (y_1,y_x,\dots,y_n)\),常用的距離計算有:

  • 歐氏距離 Euclidean distance,這個應該是最有名的了。\(dist = \|x_i-y_i\|_2 \sqrt{\sum_{i=1}^n(x_i - y_i)^2}\),歐氏距離也是一種\(l_2\)范數。
  • 曼哈頓距離 Manhattan distance,也被稱為城市街區距離。\(dist = \|x_i-y_i\|_1= \sum_{i=1}^n\|x_i -y_i|\),曼哈頓距離也是\(l_1\)范數。
  • 切比雪夫距離 Chebyshev distance,\(dist = max(|x_i - y_i|)\)

以上三種距離可以統稱為閔可夫斯基距離 Minkowski distance,\(dist = (\sum_{i=1}^n|x_i-y_i|^p)^{\frac{1}{p}}\)

  • \(p=1\)為曼哈頓距離
  • \(p=2\)為歐氏距離
  • \(p\to\infty\)為切比雪夫距離。

當然,度量兩個向量相似性的方法還有很多種,這里只列舉了最常用的,在均值聚類算法中經常的使用的是歐氏距離和曼哈頓距離。

k-means

聚類算法可以分為三類:

  • 原型聚類,此類算法假設聚類結構能夠通過一組原型描述,這里原型指的是樣本空間中具有代表性的點。
  • 密度距離,該類算法假設聚類結構能夠通過樣本分布的緊密程度來確定。
  • 層次聚類,在不同的層次對數據集進行划分,從而形成樹形的聚結構。

\(k\)均值聚類是原型聚類的一種,它使用簇內的均值向量來描述每個簇,假設給定的樣本集\(D = \{x_1,x_2,\dots,x_m\}\),得到\(k\)個簇,\(C = {C_1,C_2,\dots,C_k}\)\(k\)means算法的目標是使,簇內樣本到簇的質心(簇內的均值向量)距離最小

\[E = \sum_{i=1}^k\sum_{x\in C_i}\|x-u_i\|_2^2,u_i = \frac{1}{|C_i|}\sum x\in C_i \]

\(u_i\)是簇\(C_i\)的均值向量。\(E\)就表示了簇內樣本圍繞着均值向量(簇的中心)的緊密程度,\(E\)越小則簇內樣本相似度越高。
要使得\(E\)的值最小,是一個NP難題,因此均值聚類使用貪心策略,通過迭代的方法來求解最優解。

Lioyd's Algorithm

均值聚類算法多數是基於Lioyd's Algorithm,其流程很簡單。首先,隨機的確定\(k\)個初始點作為各個簇的質心。然后將數據集中每個點分配到與其最近的質心代表的簇中。然后更新各個簇的質心為該簇所有向量的均值。具體表示如下:

創建k個點作為起始質心(通常隨機選擇)
當任意一個點所在的簇發生變化時
    對數據集中的每個數據點
        對每個質心
            計算質心與數據點之間的距離
        將數據點分配到與其最近的簇中
    對每個簇,計算簇中所有點的均值作為新的質心

k-means算法有兩個輸入參數簇的個數\(k\)以及初始的簇的質心

  • 簇的個數\(k\)通常可以使用“肘點法”,通過最小化\(E\)來確定
  • 對於初始的質心的選擇,可以隨機確定或者使用k-means++來確定

vlfeat以及OpenCV實現

vlfeat

vlfeat實現了三種的k-means算法:

  • Lioyd's Algorithm
  • Elkan's Algorithm 使用三角形不等式對Lioyd算法的一種優化,提高了其計算的速度,本質上兩者是一樣的。
  • ANN Algorithm 適用於大規模的數據集(百萬級)簇的個數成百上千

可以使用如下代碼來初始化k-means算法:

VlKMeans * fkmeans = vl_kmeans_new(VL_TYPE_FLOAT, VlDistanceL2);
vl_kmeans_set_algorithm(fkmeans, VlKMeansElkan);
vl_kmeans_init_centers_with_rand_data(fkmeans,data,data_dim,data_num,k);

首先設置聚類時的數據類型為float,相似性度量使用l2距離也就是歐氏距離;接着設置使用的算法為是Elkan,並且使用隨機的方法確定k個簇的中心。
初始化完成后,使用如下代碼進行聚類

vl_kmeans_cluster(fkmeans, data, data_dim, data_num, k);  

需要指定數據,數據的維度,數據的個數以及簇的中心,這里需要注意的是數據的維度。聚類數據的維度指的是,一個數據有幾個分量組成。例如,

  • 一幅灰度圖像,其聚類的對象是像素的像素值。灰度圖,一個像素只有一個分量,則灰度圖聚類數據的維度就是1維。
  • RGB圖像,一個像素有RGB三個分量組成,則其聚類數據的維度就是3維。
  • sift描述子,一個sift描述子是128維的向量,則其聚類數據的維度就是128維。
OpenCV

相較於vlfeat,OpenCV中的kmeans則更易於調用。


double cv::kmeans(InputArray data,
	int 	K,
	InputOutputArray 	bestLabels,
	TermCriteria 	criteria,
	int 	attempts,
	int 	flags,
	OutputArray 	centers = noArray() 
)
  • data 數據集,每一行代表數據集中的一個樣本
  • k 聚類形成簇的個數
  • bestLabels 數據集中每個樣本在簇的index
  • criteria 迭代終止的條件。
  • attempts 算法執行的次數
  • flags 初始質心的指定方法,KMEANS_RANDOM_CENTERS 隨機指定;KMEANS_PP_CENTERSk-means++;KMEANS_USE_INITIAL_LABELS 算法第一次執行時,使用用戶提供的初始質心;第二次及以后的執行使用隨機或者半隨機的方式初始化質心

在OpenCV中TermCriteria表示迭代算法結束的兩種條件:

  • 達到了迭代的次數
  • 迭代產生的結果達到了指定的精度

該類的初始化需要三個參數

  • type 有三種選擇 COUNT, EPS or COUNT + EPS
  • maxCount 最大的迭代次數
  • epsilon 精度

構建BoF

在上一篇文章圖像檢索(1): 再論SIFT-基於vlfeat實現中實現了SIFT特征點的提取,這里再對提取到的特征點進行聚類,構建圖像集的視覺詞匯表。

基於SIFT特征構建BoF的步驟:

  • 提取sift特征點
  • 聚類生成視覺詞匯表 Visual Vocabulary
  • 統計視覺詞在每張圖像中出現的頻率,形成BoF

基於OpenCV的實現如下:

void bof_encode(const string &image_folder,int k,vector<Mat> &bof) {

    vector<string> image_file_list;
    get_file_name_list(image_folder,image_file_list);

    // 提取圖像的sift
    vector<Mat> descriptor_list;
    Ptr<xfeatures2d::SIFT> sift = xfeatures2d::SIFT::create();
    for(const string & file: image_file_list){
        cout << "Extracte sift feature #" << file << endl;
        vector<KeyPoint> kpts;
        Mat des;
        Mat img = imread(file);
        CV_Assert(!img.empty());
        sift->detectAndCompute(img,noArray(),kpts,des);
        descriptor_list.push_back(des);
    }

    // 將各個圖像的sift特征組合到一起
    Mat descriptor_stack;
    vconcat(descriptor_list,descriptor_stack);

    // 聚類
    Mat cluster_centers;
    vector<int> labels;
    kmeans(descriptor_stack,k,labels,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 
        10, 1.0),3, KMEANS_RANDOM_CENTERS,cluster_centers);

    // labels已經得到了每個樣本(特征點)所屬的簇,需要進行統計得到每一張圖像的BoF
    int index = 0;
    for(Mat img : descriptor_list){
        // For all keypoints of each image 
        auto cluster = new int[k];
        for(int i = 0; i < img.rows; i ++){
            cluster[labels[index]] ++;
            index ++;
        }

        Mat mat(1,k,CV_32S);
        auto ptr = mat.ptr<int>(0);
        mempcpy(ptr,cluster,sizeof(int) * k);

        bof.push_back(mat);
        delete cluster;
    }
}

提取特征點后,需要將得到的sift的特征描述子組合到一起,進行聚類,需要用到函數vconcat,該函數在y方向上將Mat組合在一起,需要各個Mat的列是一樣,組合得到的Mat仍然有相同的列;同樣的函數hconcat在水平方向上組合Mat,組合得到的Mat的行保持不變。

在聚類后可以得到所有圖像的各個sift特征所屬的簇,上述代碼的:

    // labels已經得到了每個樣本(特征點)所屬的簇,需要進行統計得到每一張圖像的BoF
    int index = 0;
    for(Mat img : descriptor_list){
        // For all keypoints of each image 
        auto cluster = new int[k];
        for(int i = 0; i < img.rows; i ++){
            cluster[labels[index]] ++;
            index ++;
        }

        Mat mat(1,k,CV_32S);
        auto ptr = mat.ptr<int>(0);
        mempcpy(ptr,cluster,sizeof(int) * k);

        bof.push_back(mat);
        delete cluster;
    }

就是統計每張圖像中,各個Visual Word的個數。這樣一幅圖像就可以使用一個K維的向量表示。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM