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均值是怎么工作的.在这个例子中,只用两次迭代就达到了收敛.在现实数据中,算法经常很快的收敛,但是有的时候需要迭代的次数比较多.


问题和解决方案

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键退出.

你可能感兴趣的:(算法,opencv,k均值)