1. 程式人生 > >用VLFeat庫進行k-means聚類

用VLFeat庫進行k-means聚類

摘要

本文將介紹如何使用VLFeat開源庫來進行K-means聚類,關於K-means的介紹可以參考這裡

什麼是VLFeat

VLFeat官方主頁的話來說,VLFeat 是一個實現了視覺領域諸多演算法的開源庫,其包括SIFTMSER,  k-means,hierarchical k-meansagglomerative information bottleneck, quick shift 等等。底層程式碼用C語言實現,並提供了MATLAB介面。支援WindowsMac OS X, 和 Linux。最新版本為 VLFeat 0.9.14

和OpenCV相比,VLFeat是一個輕量級的庫,主要實現了在特徵提取和聚類方面的高效演算法, 可以用在影象檢索和物體識別領域中。

Integer K-means (IKM) 介紹

接下來將介紹如何用VLFeat庫來進行k-means聚類。

VLFeat 提供了k-means 聚類和分層k-means聚類的輕量級的實現。需要注意的是,IKmeans聚類資料的型別是unsigned char型。雖然這看上去有侷限性,但對於影象的特徵聚類,演算法有很高的準確性,因為在高維空間中(例如SIFT特徵,128維),UCHAR型已經足夠。C語言的Integer K-means的介面文件請看這裡

Integer K-means (IKM) 實現了整型資料的 K-means 聚類 (或者叫向量量化)。在影象檢索、識別領域,經常會用到Bag-of-words(BOW)模型,該模型對訓練集影象提取特徵並進行聚類,得到固定數量的代表性特徵集(dictionary), 將測試集中提取出來的特徵進行量化,用之前得到代表性特徵集中的特徵(word)來表示,這樣每幅影象就可以表示成bag-of-words。該功能可以用VLFeat庫輕鬆實現。

如何使用介面?

用VLFeat進行K-means 聚類,需要包含 ikmeans.h 標頭檔案,其聲明瞭如下介面:

資料結構

量化器,k-means聚類的核心資料結構。聚類相關的任何函式都與此資料結構有關。

列舉型別

函式

指定聚類的center 對量化器初始化,M為資料的維數,K為聚類數

隨機生成center,並進行初始化,M為資料的維數,K為聚類數

在資料中隨機指定center,對量化器進行初始化,M為維數,N為資料數,K為聚類數

對輸入資料進行訓練, data為資料, N為資料數目。

將新資料量化到聚類中心,得到每個資料的標記。 asgn為資料的標記陣列, data為輸入資料, N為資料數目。

還有一些存取函式下文將省略,可以查詢文件檢視詳情。

IKM使用步驟

step 1. 建立量化器

        用 vl_ikm_new() 函式建立一個IKM 量化器(聚類器)。

step 2. 初始化 IKM量化器

step 4. 用vl_ikm_push() 函式或者 vl_ikm_push_one() 對新的特徵進行量化(如只需要聚類,可以在這一步重複使用step 3. 的訓練資料)。

開始聚類吧

準備工作就緒,開始聚類吧!

我們將隨機產生值為[0,255)的2維資料點來進行k-means聚類,這樣可以很直觀方便地在影象中畫出來看到聚類結果。

在這裡用OpenCV的函式來顯示二維資料點以及分類結果。

首先包含必須的標頭檔案 ikmeans.h。

extern "C" {
#include "ikmeans.h"
}

用OpenCV建立矩陣來顯示隨機生成的資料以及聚類後的結果。

複製程式碼
int row = 255;
int col = 255;
Mat show = Mat::zeros(row, col, CV_8UC3);
Mat show2 = show.clone();
複製程式碼

建立隨機訓練資料,用200組2維資料進行訓練。並在影象中繪製資料點。

複製程式碼
int data_num = 200;
int data_dim = 2;
vl_uint8 *data = new vl_uint8[data_num * data_dim];

for( int i=0; i<data_num; ++i)
{
    vl_uint8 x = data[i*data_dim] = rand()% col;
    vl_uint8 y = data[i*data_dim+1] = rand()% row;
    circle(show,Point(x,y),2,Scalar(255,255,255));
}
複製程式碼

如下圖所示,生成了200個點的資料。

show

                                                                                 圖1.隨機生成的資料點

接下來的幾行程式碼將建立量化器,訓練,並得到量化結果,在這裡,用訓練資料作為新資料傳給量化器,這樣就可以得到訓練資料的聚類結果,聚類數目為K=3。

VlIKMFilt *kmeans = vl_ikm_new(VL_IKM_ELKAN);
vl_uint K = 3;
vl_ikm_init_rand(kmeans, data_dim, K);
vl_ikm_train(kmeans, data, data_num);
vl_uint * label = new vl_uint[data_num];
vl_ikm_push(kmeans, label, data, data_num);

在上面的程式碼中,label陣列存放的就是量化結果,量化的序號為0,1,2...,K-1。

最後,畫圖,顯示結果。

複製程式碼
    for( int i=0;i<data_num; ++i)
    {
        vl_uint8 x = data[i*data_dim];
        vl_uint8 y = data[i*data_dim+1];
        switch(label[i])
        {
        case 0:
                circle(show2,Point(x,y),2,Scalar(255,0,0));
                break;
        case 1:
                circle(show2,Point(x,y),2,Scalar(0,255,0));
                break;
        case 2:
                circle(show2,Point(x,y),2,Scalar(0,0,255));
                break;
        }
    }
複製程式碼

結果如下圖所示:

show2

                                                                                    圖2.聚類(量化)結果

最後別忘了刪除聚類器,以及清空陣列。

vl_ikm_delete(kmeans);
delete []label;
label = NULL;
delete []data;
data = NULL;

整個程式的原始碼如下:

複製程式碼
#include "stdafx.h"

extern "C" {

#include "ikmeans.h"
}

#include "global_header.h"


int main()
{
    /*initialize data point*/
    int row = 255;
    int col = 255;
    Mat show = Mat::zeros(row, col, CV_8UC3);
     Mat show2 = show.clone();

    int data_num = 200;
    int data_dim = 2;
    vl_uint8 *data = new vl_uint8[data_num * data_dim];

    for( int i=0; i<data_num; ++i)
    {
        vl_uint8 x = data[i*data_dim] = rand()% col;
        vl_uint8 y = data[i*data_dim+1] = rand()% row;
        circle(show,Point(x,y),2,Scalar(255,255,255));
    }

    VlIKMFilt *kmeans = vl_ikm_new(VL_IKM_ELKAN);
    vl_uint K = 3;
    vl_ikm_init_rand(kmeans, data_dim, K);
    vl_ikm_train(kmeans, data, data_num);

    vl_uint * label = new vl_uint[data_num];

    vl_ikm_push(kmeans, label, data, data_num);


    for( int i=0;i<data_num; ++i)
    {
        vl_uint8 x = data[i*data_dim];
        vl_uint8 y = data[i*data_dim+1];
        switch(label[i])
        {
        case 0:
                circle(show2,Point(x,y),2,Scalar(255,0,0));
                break;
        case 1:
                circle(show2,Point(x,y),2,Scalar(0,255,0));
                break;
        case 2:
                circle(show2,Point(x,y),2,Scalar(0,0,255));
                break;
        }
    }


    imwrite("show.jpg",show);
    imwrite("show2.jpg",show2);

    vl_ikm_delete(kmeans);
    
    delete []label;
    label = NULL;

    delete []data;
    data = NULL;


    return 0;
}
複製程式碼