本文将介绍如何使用VLFeat开源库来进行K-means聚类,关于K-means的介绍可以参考这里。
用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是一个轻量级的库,主要实现了在特征提取和聚类方面的高效算法, 可以用在图像检索和物体识别领域中。
接下来将介绍如何用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)
对一个数据进行量化
还有一些存取函数下文将省略,可以查询文档查看详情。
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; }