摘要
本文將介紹如何使用VLFeat開源庫來進行K-means聚類,關於K-means的介紹可以參考這里。
什么是VLFeat
用VLFeat官方主頁的話來說,VLFeat 是一個實現了視覺領域諸多算法的開源庫,其包括SIFT, MSER, k-means, hierarchical k-means, agglomerative information bottleneck, quick shift 等等。底層代碼用C語言實現,並提供了MATLAB接口。支持Windows, Mac 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 頭文件,其聲明了如下接口:
數據結構
struct VlIKMFilt
量化器,k-means聚類的核心數據結構。聚類相關的任何函數都與此數據結構有關。
枚舉類型
enum VlIKMAlgorithms { VL_IKM_LLOYD, VL_IKM_ELKAN }
VLFeat 實現了兩種聚類算法,在進行訓練時作為參數傳入給vl_ikm_train 函數。
函數
VlIKMFilt * vl_ikm_new (int method)
創建量化器,傳入參數為VL_IKM_LLOYD或者 VL_IKM_ELKAN
void vl_ikm_delete (VlIKMFilt *f)
刪除量化器
void vl_ikm_init (VlIKMFilt *f, vl_ikm_acc const *centers, int M, int K)
指定聚類的center 對量化器初始化,M為數據的維數,K為聚類數
void vl_ikm_init_rand (VlIKMFilt *f, int M, int K)
隨機生成center,並進行初始化,M為數據的維數,K為聚類數
void vl_ikm_init_rand_data (VlIKMFilt *f, vl_uint8 const *data, int M, int N, int K)
在數據中隨機指定center,對量化器進行初始化,M為維數,N為數據數,K為聚類數
int vl_ikm_train (VlIKMFilt *f, vl_uint8 const *data, int N)
對輸入數據進行訓練, data為數據, N為數據數目。
void vl_ikm_push (VlIKMFilt *f, vl_uint *asgn, vl_uint8 const *data, int N)
將新數據量化到聚類中心,得到每個數據的標記。 asgn為數據的標記數組, data為輸入數據, N為數據數目。
vl_uint vl_ikm_push_one (vl_ikm_acc const *centers, vl_uint8 const *data, int M, int K)
對一個數據進行量化
還有一些存取函數下文將省略,可以查詢文檔查看詳情。
IKM使用步驟
step 1. 創建量化器
用 vl_ikm_new() 函數創建一個IKM 量化器(聚類器)。
step 2. 初始化 IKM量化器
用 vl_ikm_init() 或者其它接口(本文用vl_ikm_init_rand函數)。
step 3. 用 vl_ikm_train() 訓練量化器。
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個點的數據。
圖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; } }
結果如下圖所示:
圖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; }