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

来源:互联网 发布:vscode主题 编辑:程序博客网 时间:2024/05/18 01:17

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

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

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

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

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

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

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


问题和解决方案

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.

#include <QCoreApplication>#include <opencv2/highgui/highgui.hpp>#include <opencv2/core/core.hpp>int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);       #define MAX_CLUSTER 5    CvScalar color_table[MAX_CLUSTER];    IplImage* img = cvCreateImage(cvSize(500,500),8,3);    CvRNG rng = cvRNG(0xffffffff);    color_table[0] = CV_RGB(255,0,0);    color_table[1] = CV_RGB(0,255,0);    color_table[2] = CV_RGB(100,100,255);    color_table[3] = CV_RGB(255,0,255);    color_table[4] = CV_RGB(255,255,0);    cvNamedWindow("clusters",1);    for(;;)    {        int k,cluster_count = cvRandInt(&rng)%MAX_CLUSTER +1; //类别个数 1~5        int i,sample_count = cvRandInt(&rng)%1000+1;  //样本个数 1~1000        CvMat* points = cvCreateMat(sample_count,1,CV_32FC2); //创建sample_count行1列的双通道矩阵 用于存储数据样本        CvMat* clusters = cvCreateMat(sample_count,1,CV_32SC1);//创建sample_count行1列的矩阵 用来存储数据标签        //随机生成样本多元高斯分布        //样本总数为sample_count 类别总数为cluster_count        //样本矩阵为points,先按类别分成cluster_count份 每一份数据的个数为sample_count/cluster_count        //然后按类别随机矩阵填充样本矩阵,第一类填充矩阵的0~sample_count/cluster_count行        //第二类填充样本矩阵的sample_count/cluster_count~sample_count/cluster_count*2行,以此类推直到填充满所有矩阵,每一类的样本个数是一样的        for(k=0;k < cluster_count;k++)        {            CvPoint center;  //为图像中的随机点            CvMat point_chunk;            center.x = cvRandInt(&rng)%img->width;            center.y = cvRandInt(&rng)%img->height;            //返回数组在一定跨度的行            //points为输入数组,point_chunk返回数组            //开始行为k*sample_count/cluster_count            //结束行为 当k== cluster-1时 为 第samplecount行 否则为(k+1)*sample_count/cluster_count            cvGetRows(points,&point_chunk,k*sample_count/cluster_count,                      k == cluster_count -1? sample_count:                      (k+1)*sample_count/cluster_count);            //point_chunk输出数组,CV_RAND_NORMAL分布类型为正态分布或者高斯分布            //cvScalar(center.x,center.y,0,0)随机数的平均值            // cvScalar(img->width/6,img->height/6,0,0)如果是正态分布它是随机数的标准差            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++)        {            //在points样本矩阵中随机取两个样本交换位置            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);        }        //points样本矩阵,cliuster_count分类数,输出向量clusters,最后一个参数指定精度        cvKMeans2(points,cluster_count,clusters,cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER,10,1.0));        cvZero(img);        for(i=0;i<sample_count;i++)        {            CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i];            int cluster_idx = clusters->data.i[i];            cvCircle(img,cvPointFrom32f(pt),2, color_table[cluster_idx],CV_FILLED);        }        cvReleaseMat(&points);        cvReleaseMat(&clusters);        cvShowImage("clusters",img);        int key = cvWaitKey(0);        if(key==27)            break;    }    return a.exec();}



在这段代码中,包含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键退出.

原创粉丝点击