OpenCV学习笔记(11)-K均值

K均值聚类算法在cxcoer中,因为它在ML库诞生之前就存在了.K均值尝试找到数据的自然类别.用户设置类别个数,K均值迅速地找到"好的"类别中心."好的"意味着聚类中心位于数据的自然类别中心.K均值是最常用的聚类计数之一,与高斯混合中的期望最大化算法(在ML库中实现为CvEM)很相似,也与均值漂移算法(在CV库中实现为cvMeanShift())相似.K均值是一个迭代算法,在OpenCV中采用的是Lloyd算法,也叫Voronoi迭代.算法运行如下.

1.输入数据集合和类别数K(由用户指定)

2.随机分配类别中心点的位置

3.将每个点放入离它最近的类别中心点所在的集合.

4.移动类别中心点到它所在集合的中心

5.转到第3步,直到收敛.

图13-5展示了K均值是怎么工作的.在这个例子中,只用两次迭代就达到了收敛.在现实数据中,算法经常很快的收敛,但是有的时候需要迭代的次数比较多.

OpenCV学习笔记(11)-K均值_第1张图片

问题和解决方案

K均值是一个及其高效的聚类算法,但是它也有以下3个问题.

1.它不保证能找到定位聚类中心的最佳方案,但是它能保证能收敛到某个解决方案(例如迭代不再无限继续下去).

2.K均值无法指出应该使用多少类别.在同意数据集中,例如对图13-5中的例子,选择两个类别和选择四个类别,得到的结果是不一样的,甚至是不合理的.

3.K均值假设空间的协方差矩阵不会影响结果,或者已经归一化(参考Mahalanobis距离的讨论).

这三个问题都有"解决办法".这些解决方法中最好的两种都是基于"劫色数据的方差".在K均值中,每个聚类中线拥有他的数据点,我们计算这些点的方差,最好的聚类在不引起太大的复杂度的情况下使方差达到最小.所以,列出的问题可以改进如下.

1.多进行几次K均值,每次初始的聚类中心点都不一样(很容易做到,因为OpenCV随机选择中心点),最后选择方差最小的那个结果

2.首先将类别数设为1,然后提高类别数(到达某个上限),每次聚类的时候使用前面提到的方法1.一般情况下,总方差会很快下降,直到到达一个拐点;这意味着再加一个新的聚类中心不会显著地减少总方差.在拐点处停止,保存此时的类别数.

3.将数据乘以逆协方差矩阵.例如:如果输入向量是按行排列,没行一个数据点,则通过计算新的数据向量D*,D* = D∑-1/2来归一化数据空间.

K均值代码

K均值函数的调用很简单:

void cvKMeans2(const CvArr* samples, int cluster_count,CvArr* labels,CvTrtmCriteria termcrit);

数组sample是一个多维的数据样本矩阵,每行一个数据样本.这里有一点点微妙,数据样本可以是浮点型CV_32FC1向量,也可以是CV_32FC2,CV_32FC3和CV_32FC(k)的多维向量.参数cluster_count指定类别数,返回向量包含每个点最后的类别索引.前面已经讨论了terncrit.

[cpp]  view plain copy
  1. #include <QCoreApplication>  
  2. #include <opencv2/highgui/highgui.hpp>  
  3. #include <opencv2/core/core.hpp>  
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     QCoreApplication a(argc, argv);  
  8.       
  9.    #define MAX_CLUSTER 5  
  10.     CvScalar color_table[MAX_CLUSTER];  
  11.     IplImage* img = cvCreateImage(cvSize(500,500),8,3);  
  12.     CvRNG rng = cvRNG(0xffffffff);  
  13.   
  14.     color_table[0] = CV_RGB(255,0,0);  
  15.     color_table[1] = CV_RGB(0,255,0);  
  16.     color_table[2] = CV_RGB(100,100,255);  
  17.     color_table[3] = CV_RGB(255,0,255);  
  18.     color_table[4] = CV_RGB(255,255,0);  
  19.   
  20.     cvNamedWindow("clusters",1);  
  21.     for(;;)  
  22.     {  
  23.         int k,cluster_count = cvRandInt(&rng)%MAX_CLUSTER +1; //类别个数 1~5  
  24.         int i,sample_count = cvRandInt(&rng)%1000+1;  //样本个数 1~1000  
  25.         CvMat* points = cvCreateMat(sample_count,1,CV_32FC2); //创建sample_count行1列的双通道矩阵 用于存储数据样本  
  26.         CvMat* clusters = cvCreateMat(sample_count,1,CV_32SC1);//创建sample_count行1列的矩阵 用来存储数据标签  
  27.         //随机生成样本多元高斯分布  
  28.         //样本总数为sample_count 类别总数为cluster_count  
  29.         //样本矩阵为points,先按类别分成cluster_count份 每一份数据的个数为sample_count/cluster_count  
  30.         //然后按类别随机矩阵填充样本矩阵,第一类填充矩阵的0~sample_count/cluster_count行  
  31.         //第二类填充样本矩阵的sample_count/cluster_count~sample_count/cluster_count*2行,以此类推直到填充满所有矩阵,每一类的样本个数是一样的  
  32.         for(k=0;k < cluster_count;k++)  
  33.         {  
  34.             CvPoint center;  //为图像中的随机点  
  35.             CvMat point_chunk;  
  36.             center.x = cvRandInt(&rng)%img->width;  
  37.             center.y = cvRandInt(&rng)%img->height;  
  38.             //返回数组在一定跨度的行  
  39.             //points为输入数组,point_chunk返回数组  
  40.             //开始行为k*sample_count/cluster_count  
  41.             //结束行为 当k== cluster-1时 为 第samplecount行 否则为(k+1)*sample_count/cluster_count  
  42.             cvGetRows(points,&point_chunk,k*sample_count/cluster_count,  
  43.                       k == cluster_count -1? sample_count:  
  44.                       (k+1)*sample_count/cluster_count);  
  45.             //point_chunk输出数组,CV_RAND_NORMAL分布类型为正态分布或者高斯分布  
  46.             //cvScalar(center.x,center.y,0,0)随机数的平均值  
  47.             // cvScalar(img->width/6,img->height/6,0,0)如果是正态分布它是随机数的标准差  
  48.             cvRandArr(&rng,&point_chunk,CV_RAND_NORMAL,  
  49.                       cvScalar(center.x,center.y,0,0),  
  50.                       cvScalar(img->width/6,img->height/6,0,0));  
  51.         }  
  52.         //样本重新排序  
  53.         for(i=0;i<sample_count/2;i++)  
  54.         {  
  55.             //在points样本矩阵中随机取两个样本交换位置  
  56.             CvPoint2D32f* pt1=(CvPoint2D32f*)points->data.fl+cvRandInt(&rng)%sample_count;  
  57.             CvPoint2D32f* pt2=(CvPoint2D32f*)points->data.fl+cvRandInt(&rng)%sample_count;  
  58.             CvPoint2D32f temp;  
  59.             CV_SWAP(*pt1,*pt2,temp);  
  60.         }  
  61.         //points样本矩阵,cliuster_count分类数,输出向量clusters,最后一个参数指定精度  
  62.         cvKMeans2(points,cluster_count,clusters,cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER,10,1.0));  
  63.         cvZero(img);  
  64.         for(i=0;i<sample_count;i++)  
  65.         {  
  66.             CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i];  
  67.             int cluster_idx = clusters->data.i[i];  
  68.             cvCircle(img,cvPointFrom32f(pt),2, color_table[cluster_idx],CV_FILLED);  
  69.         }  
  70.         cvReleaseMat(&points);  
  71.         cvReleaseMat(&clusters);  
  72.         cvShowImage("clusters",img);  
  73.         int key = cvWaitKey(0);  
  74.         if(key==27)  
  75.             break;  
  76.     }  
  77.   
  78.     return a.exec();  
  79. }  


 
 
 
 
在这段代码中,包含highgui来使用窗口输出,包含cxcore是因为它包含了Kmean2().在main函数中,我们设置了放回的类别显示的颜色;设置类别个数上界MAX_CLUSTERS(这是5),类别的个数是随机产生的,存储在cluster_count中;设置数据样本的个数的上界(1000),数据样本的个数也是随机产生的,被存储在sample_count中.在最外层循环for{]中,我们分配了一个浮点数双通道矩阵poing来存储sample_count个数据样本,我们还分配了一个整型矩阵clusters来存储数据样本的聚类标签,从0~cluster_count-1.

然后我们进入数据生成for{}循环,这个循环可以用于其他算法中使用.我们给每个类别填写sample_count/cluster_count个数据样本,这些2维数据样本服从正态分布,正态分布的中心是随机选择的.

下一个for{}循环仅仅打乱了数据样本的顺序.然后我们使用cvKMean2(),直到聚类中心的最大移动小于1.

最后的for{}循环话出结果,在图中显示.然后释放数组,等待用户的输入进入下一次计算或者Esc键退出.

你可能感兴趣的:(opencv)