用VLFeat庫進行k-means聚類
摘要
本文將介紹如何使用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 標頭檔案,其聲明瞭如下介面:
資料結構
量化器,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個點的資料。
圖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; }