cvKMeans2在做聚类时,一般需要知道分类以后的类别中心。而在教材《学习opencv》和opencv中文文档中并没有给出完整的cvKMeans2的使用方法。
如在文档中的说明如下:
void cvKMeans2( const CvArr* samples, int cluster_count,
CvArr* labels, CvTermCriteria termcrit );
samples
输入样本的浮点矩阵,每个样本一行。
cluster_count
所给定的聚类数目
labels
输出整数向量:每个样本对应的类别标识
termcrit
指定聚类的最大迭代次数和/或精度(两次迭代引起的聚类中心的移动距离)
函数 cvKMeans2 执行 k-means 算法 搜索 cluster_count 个类别的中心并对样本进行分类,输出 labels(i) 为样本 i 的类别标识。
而在cxcore.h文件中cvKMeans2的声明如下:
cvKMeans2( const CvArr* samples, int cluster_count, CvArr* labels,
CvTermCriteria termcrit, int attempts CV_DEFAULT(1),
CvRNG* rng CV_DEFAULT(0), int flags CV_DEFAULT(0),
CvArr* _centers CV_DEFAULT(0), double* compactness CV_DEFAULT(0) );
从声明中可以看出,cvKMeans2函数是可以得到类别中心的。只是被默认设置为0.
在文档中的代码进行适当的修改,可以获得类别中心。
#include "cv.h"
#include <iostream>
#include <time.h>
#include "highgui.h"
using namespace std;
int main( int argc, char** argv )
{
#define MAX_CLUSTERS 5
CvScalar color_tab[MAX_CLUSTERS];
IplImage* img = cvCreateImage( cvSize( 500, 500 ), 8, 3 );
srand((unsigned)time(NULL)); //使用时间作为种子函数
CvRNG rng = cvRNG(rand());
// CvRNG rng = cvRNG(0xffffffff); //文档中的方式
color_tab[0] = CV_RGB(255,0,0);
color_tab[1] = CV_RGB(0,255,0);
color_tab[2] = CV_RGB(100,100,255);
color_tab[3] = CV_RGB(255,0,255);
color_tab[4] = CV_RGB(255,255,0);
cvNamedWindow( "clusters", 1 );
// for(;;) //将死循环去掉,如果用死循环,关闭clusters窗口后,重新显示将出现错误
{
int k;// cluster_count = cvRandInt(&rng)%MAX_CLUSTERS + 1;
int cluster_count = 5;
int i, sample_count = cvRandInt(&rng)00 + 1;
CvMat* points = cvCreateMat( sample_count, 1, CV_32FC2 );
CvMat* clusters = cvCreateMat( sample_count, 1, CV_32SC1 );
CvMat* centers = cvCreateMat(cluster_count, 1, CV_32FC2);
//类别中心数目换成sample_count后得到的结果不正确
for( k = 0; k < cluster_count; k++ )
{
CvPoint center;
CvMat point_chunk;
center.x = cvRandInt(&rng)%img->width;
center.y = cvRandInt(&rng)%img->height;
cvGetRows( points, &point_chunk, k*sample_count/cluster_count,
k == cluster_count - 1 ? sample_count : (k+1)*sample_count/cluster_count );
cvRandArr( &rng, &point_chunk, CV_RAND_NORMAL,
cvScalar(center.x,center.y,0,0),
cvScalar(img->width/6, img->height/6,0,0) );
}
for( i = 0; i < sample_count/2; i++ )
{
CvPoint2D32f* pt1 = (CvPoint2D32f*)points->data.fl + cvRandInt(&rng)%sample_count;
CvPoint2D32f* pt2 = (CvPoint2D32f*)points->data.fl + cvRandInt(&rng)%sample_count;
CvPoint2D32f temp;
CV_SWAP( *pt1, *pt2, temp );
}
//上面的代码,对point赋值做的很复杂。可以做的简单,不知道这样做有什么好处。
cvKMeans2( points, cluster_count, clusters,cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 2.0 ),
0, 0, 0,centers
); //只是简单的加入类别中心的数组
cvZero( img );
//测试centers大小是否被改变,测试结果是没有
cout<<centers->cols<<" "<<centers->rows<<" "<<sample_count<<endl;
double center_x[5]={0.0}; //自己算分类以后各个类的中心
double center_y[5]={0.0}; //用于判断centers得到的结果是否正确
int c_num[5]={0,0,0,0,0};
for( i = 0; i < sample_count; i++ )
{
CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i];
CvPoint2D32f pt1 = ((CvPoint2D32f*)centers->data.fl)[i]; //不知道源代码中centers是怎么赋值的
//这里不是cluster_count大小的数目
if (pt1.x>1&&pt1.y>1&&pt1.x<img->width&&pt1.y<img->height )
{
cout<<i<<" "<<pt1.x<<" "<<pt1.y<<endl;
}
int cluster_idx = clusters->data.i[i];
center_x[cluster_idx] += pt.x;
center_y[cluster_idx] += pt.y;
c_num[cluster_idx]++;
cvCircle( img, cvPointFrom32f(pt), 2, color_tab[cluster_idx], CV_FILLED );
cvCircle(img,cvPointFrom32f(pt1), 5, color_tab[cluster_idx], CV_FILLED );
}
for (i=0; i<cluster_count; i++)
{
if (c_num[i] != 0)
{
center_x[i] = center_x[i]/c_num[i];
center_y[i] = center_y[i]/c_num[i];
cvCircle(img,cvPoint(center_x[i],center_y[i]),7, CV_RGB(255,255,255),1);
}
}
//
cvReleaseMat( &points );
cvReleaseMat( &clusters );
cvReleaseMat(¢ers);
cvShowImage( "clusters", img );
int key = cvWaitKey(10000000);//等待时间,显示cluster窗口的时间。
// if( key == 27 ) // 'ESC'
// break;
}
cvReleaseImage(&img);
cvDestroyWindow("clusters");
}
测试的结果如下:
从图片中,我们可以看出,计算的中心和centers给出的图像中心基本吻合。
但是在不知道源码的实现原理的条件下,最好不要使用centers的方法。