vl_feat库中的k-means聚类

最近准备好好看一下vl_feat库。

首先简单介绍一下vl_feat库(http://www.vlfeat.org/index.html)。vl_feat库是用C语言开发的一个开源的计算机视觉的库,它比opencv要小,但是实现了一些比较常见的视觉方面的算法(包括HOG,SIFTMSERk-meanshierarchical k-meansagglomerative information bottleneckSLIC superpixels, and quick shift)。它不仅提供了C的API还提供了Matlab的API和toolbox,这也可能是与opencv最大的不同吧。它的主要作者是Andrea Vedaldi(http://www.robots.ox.ac.uk/~vedaldi/),他现在是牛津大学的老师,是计算机视觉方面的一个牛人。

vl_feat库中的聚类主要包括了两种:k-mean clustering和integer k-means(还有层次化的整型k-means,HIKM)。

采用vl_feat进行聚类,一般包括以下步骤:

  1. 创建一个k-means聚类对象,设置具体聚类使用的方法(vl_feat库实现了3种聚类方法:Lloyd、Elkan和ANN,其中,Elkan是Lloyd的加速版本,ANN (approximate nearest neighbor) 对integer k-means无效)
  2. 初始化种子点
  3. 进行聚类

首先介绍一下integer k-means clustering (ikmeans)。整型k-means内容主要来自http://www.cnblogs.com/smyb000/archive/2012/08/28/k-means_cluster_via_vlfeat.html,作者也给出了一个为什么定义整型k-means的解释“IKmeans聚类数据的类型是unsigned char型。虽然这看上去有局限性,但对于图像的特征聚类,算法有很高的准确性,因为在高维空间中(例如SIFT特征,128维),UCHAR型已经足够。”。

针对上面一般步骤,ikmeans实现时具体如下:

  1. 利用 vl_ikm_new(VL_IKM_ELKAN) 创建一个ikmeans聚类对象,里面定义了聚类所需的一些数据结构(例如聚类中心,数据维度,聚类中心数等)
  2. 利用 vl_ikm_init_rand() 随机初始化种子点
  3. 利用 vl_ikm_train() 进行聚类
得到聚类结果的时候,利用 vl_ikm_push() 得到聚类结果,即聚类后原数据集中每个点所对应的类标签,从0开始。这个函数还可将任意满足要求的(维度、类型等)数据点进行映射,得到所属类。利用 vl_ikm_get_centers() 可以得到聚类中心。还有其他一些操作,可以具体查看vl_feat官方文档。

实现的时候,先通过随机数产生随机点(2维的),然后进行聚类。为了将结果显示出来,定义了两个显示的图片,show1和show2。

具体代码如下:
void vlikmeans()
{
	int row = 255;
	int col = 255;
	Mat show1 = Mat::zeros(row, col, CV_8UC3);
	Mat show2 = show1.clone();
	
	int data_num = 5000;
	int data_dim = 2;

	vl_uint8 * data = new vl_uint8[data_dim * data_num];

	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(show1, Point(x, y), 2, Scalar(255, 255, 255));
	}


	namedWindow("random_points");
	imshow("random_points", show1);
	waitKey(0);

	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;
		}
	}

	const vl_ikm_acc *centers = vl_ikm_get_centers(kmeans);
	circle(show2, Point(centers[0], centers[1]), 4, Scalar(255, 255, 0), 4);
	circle(show2, Point(centers[2], centers[3]), 4, Scalar(255, 255, 0), 4);
	circle(show2, Point(centers[4], centers[5]), 4, Scalar(255, 255, 0), 4);

	namedWindow("vlikmeans_result");
	imshow("vlikmeans_result", show2);
	waitKey(0);

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

在函数退出之前,要养成将创建的数据结构进行释放的习惯,vl_feat也定义了释放的函数。
执行结果如下:
vl_feat库中的k-means聚类_第1张图片
聚类中心的位置看起来有点怪,可能是数据的密度导致的,但是我们肉眼从图中看不出来。

vl_feat中一般情况下的k-means聚类的步骤是:
  1. 利用 vl_kmeans_new() 创建一个聚类的对象,它包含了两个参数:数据类型和具体比较的相似度计算方法(距离函数)
  2. 利用 vl_kmeans_set_algorithm() 设定需要使用的聚类方法(Lloyd、Elkan还是ANN)
  3. 利用 vl_kmeans_init_centers_plus_plus() 或者 vl_kmeans_set_centers() 或者 vl_kmeans_init_centers_with_rand_data() 初始化聚类中心 (由于要与matlab的聚类结果进行对比,实现时采用的是 vl_kmeans_set_centers())
  4. 利用 vl_kmeans_cluster() 进行聚类
可以看出vl_feat中integer k-means和一般的k-means操作不是很统一,可能是由于由不同的作者实现的,他们之间没有统一,这也是个人开源代码难以避免的。在使用过程中要详细的看官方文档,但是vl_feat库的官方文档也较为简单,有时一个函数的作用就一句话,有时候还会有点错误,可能还要在实际使用中进行尝试。

具体代码如下:
void vlkmeans()
{
	int data_num = 10;
	int data_dim = 2;
	int k = 2;

	float *data = new float[data_dim * data_num];
	
	cout << "Points to clustering: " << endl;
	for (int i = 0; i < data_num; i++)
	{
		data[i * data_dim] = (float)rand()/3.0;
		data[i * data_dim + 1] = (float)rand()/3.0;
		cout << data[i * data_dim] << "\t" << data[i * data_dim + 1] << endl;
	}

	float * init_centers = new float[data_dim * k];
	cout << "Initial centers: " << endl;
	for (int i = 0; i < k; i++)
	{
		init_centers[i * data_dim] = (float)rand()/3.0;
		init_centers[i * data_dim + 1] = (float)rand()/3.0;
		cout << init_centers[i * data_dim] << "\t" << init_centers[i * data_dim + 1] << endl;
	}

	VlKMeans * fkmeans = vl_kmeans_new(VL_TYPE_FLOAT, VlDistanceL2);
	vl_kmeans_set_algorithm(fkmeans, VlKMeansElkan);
	
	// vl_kmeans_init_centers_plus_plus(fkmeans, data, data_dim, data_num, k);
	vl_kmeans_set_centers(fkmeans, (void *)init_centers, data_dim, k);
	 vl_kmeans_cluster(fkmeans, data, data_dim, data_num, k);
	// vl_kmeans_set_max_num_iterations(fkmeans, 100);
	// vl_kmeans_refine_centers(fkmeans, data, data_num);
	// vl_kmeans_cluster(fkmeans, data, data_dim, data_num, k);

	const float * centers = (float *)vl_kmeans_get_centers(fkmeans);

	cout << "Clustering Centers: " << endl;
	for (int i = 0; i < k; i++)
	{
		cout << centers[i * data_dim] << "\t" << centers[i * data_dim + 1] << endl;
	}
}

聚类结果如下:
vl_feat库中的k-means聚类_第2张图片
采用matlab聚类的结果是:
2706 1419.88666666667
4213.95242857143 6829.14285714286

可以看出聚类效果是差不多的。

附上整个程序的其他部分:
#include <ikmeans.h>
#include <kmeans.h>

#include <opencv/cv.h>
#include <opencv/cxcore.h>
#include <opencv/highgui.h>

using namespace std;
using namespace cv;

void vlikmeans();
void vlkmeans();

int main()
{

	rand();
	vlikmeans();
	vlkmeans();
	
	system("pause");
	return 0;
}


你可能感兴趣的:(clustering,k-means,vl_feat)