OpenCv Kmeans算法及实现

Kmeans算法:基于划分的聚类方法,适用于数据预处理,但图像分割的消耗的时间太长并且效果不怎么好。

基本思想:以空间中K个点为中心进行聚类,对最靠近他们的对象归类。通过迭代,逐次更新各聚类中心的值,直到得到最好的聚类结果。

算法描述:

1、适当选择C个类的初始中心;

2、在第K次迭代中,对任意一个样本,求其到C个中心的距离,将该样本归到距离最短的中心所在的类;

3、利用均值等方法更新该类的中心值;

4、对于所有的C个类的聚类中心,如果利用2、3的迭代更新后,值保持不变,则迭代结束,否则,继续。

 该算法的最大优势在于简洁、快速。

 关键在于:初始中心的选择和距离公式。

算法流程:

1、从N个数据中任意选择K个对象作为初始聚类中心;

2、对于剩下的,根据他们与这些聚类中心的相似度(距离),分别将其分配给与之最相似的聚类;

3、再计算每个所获新聚类的聚类中心(该聚类内所有对象的均值);

4、重复上述,直到标准测度函数开始收敛为止。

  一般采用均方差作为标准测度函数。

  K个聚类各聚类本身尽可能紧凑,但是各聚类之间尽可能的分散。

算法缺陷:

1、必须人为指定所聚的类的个数k,但实际情况中,往往很难确定图片中的类别数。;

2、如果使用欧式距离来衡量相似度的话,可能会得到错误的结果,因为没有考虑到属性的重要性和相关性。

为了减少这种错误,在使用kmeans距离时,一定要使样本的每一维数据归一化,不然的话由于样本的属性范围不同会导致错误的结果。

3、使用空间信息后,图像的分割后受空间的影响大(同一类的数据如果分布较远,不是高斯型的,就会错分),因为图像分割本身要求数据是呈超球体(高斯类)分布。K-means得到的是线性判决面,因为算法使用的准则函数是最小均方误差,相当于不同类别间求最小二乘直线拟合。

4、在OPENCV里判断聚类误差是由类别中心点的两次迭代结果的差决定的,即当类别中心点都变化不大时或者说不变时,聚类结束。多次运行程序会发现不同的结果,因为程序可能会陷入不同的局部极值,所以如果要找到全局最优,可以多次运行找出误差最小值。

涉及到的函数简介:


  1、void RNG::fill(InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false )

  功能:

对矩阵mat填充随机数,随机数的产生方式有参数“int distType”来决定,如果其类型为RNG::UNIFORM,则表示产生均一分布的随机数,如果为RNG::NORMAL则表示产生高斯分布的随机数。

对应的“InputArray a, InputArray b,”为上面两种随机数产生模型的参数。如果随机数产生模型为均匀分布,则参数a表示均匀分布的下限,参数b表示上限。如果随机数产生模型为高斯模型,则参数a表示均值,参数b表示方程。

bool saturateRange=false”只有当随机数产生方式为均匀分布时才有效,表示是否产生的数据要布满整个范围。

用来保存随机数的矩阵mat可以是多维的,也可以是多通道的,目前最多只能支持4个通道。

  2、void randShuffle(InputOutputArray dst, double iterFactor=1., RNG* rng=0 )

  功能:

随机打乱1D数组dst里面的数据,随机打乱的方式由随机数发生器rng决定。iterFactor为随机打乱数据对数的因子,总共打乱的数据对数为:dst.rows*dst.cols*iterFactor,如果为0,表示没有打乱数据。

  3、Class TermCriteria

  功能:

类TermCriteria 一般表示迭代终止的条件,如果为CV_TERMCRIT_ITER,则用最大迭代次数作为终止条件,如果为CV_TERMCRIT_EPS 则用精度作为迭代条件,如果为CV_TERMCRIT_ITER+CV_TERMCRIT_EPS则用最大迭代次数或者精度作为迭代条件,看哪个条件先满足。

  4、double kmeans(InputArray data, int K, InputOutputArray bestLabels, TermCriteria criteria, int attempts, int flags, OutputArray centers=noArray() )

  功能:

该函数为kmeans聚类算法实现函数。

参数data表示需要被聚类的原始数据集合,一行表示一个数据样本,每一个样本的每一列都是一个属性;

参数k表示需要被聚类的个数;

参数bestLabels表示每一个样本的类的标签,是一个整数,从0开始的索引整数;

参数criteria表示的是算法迭代终止条件;

参数attempts表示运行kmeans的次数,取结果最好的那次聚类为最终的聚类,要配合下一个参数flages来使用;

参数flags表示的是聚类初始化的条件。

其取值有3种情况,如果为KMEANS_RANDOM_CENTERS,则表示为随机选取初始化中心点,如果为KMEANS_PP_CENTERS则表示使用某一种算法来确定初始聚类的点;如果为KMEANS_USE_INITIAL_LABELS,则表示使用用户自定义的初始点,但是如果此时的attempts大于1,则后面的聚类初始点依旧使用随机的方式;

参数centers表示的是聚类后的中心点存放矩阵。

该函数返回的是聚类结果的紧凑性,其计算公式为:

  

在使用K-means函数时,注意输入和输出矩阵的数据类型,是32FC1。输入矩阵的每一行是一个输入向量。OPENCV矩阵的特点是,矩阵的元素本身可以是个向量,即元素的数据通道,这样方便图像处理。所以一个样本向量可以用矩阵的一行表示即单通道多数据,也可以用一个多数据通道的矩阵元素表示。

代码实现:

#include <highgui.h>
#include <cv.h>
#include <iostream>
using namespace cv;
using namespace std;
int main( )
{
    const int MAX_CLUSTERS = 6;//最多6类
    Scalar colorTab[] =     //6个颜色
    {
        Scalar(0, 0, 255),
        Scalar(0,255,0),
        Scalar(255,0,0),
        Scalar(255,255,0),
        Scalar(255,0,255),
        Scalar(0,255,255)
    };
    Mat img(500, 500, CV_8UC3);
    RNG rng(123456); //随机数产生器
    while(1)
    {
        int k, clusterCount = rng.uniform(2, MAX_CLUSTERS+1);
        int i, sampleCount = rng.uniform(1, 1001);
        Mat points(sampleCount, 1, CV_32FC2), labels;
        //产生的样本数,实际上为2通道的列向量,元素类型为Point2f
        clusterCount = MIN(clusterCount, sampleCount);
        Mat centers(clusterCount, 1, points.type());    //用来存储聚类后的中心点
        /* generate random sample from multigaussian distribution */
        for( k = 0; k < clusterCount; k++ ) //产生随机数
        {
            Point center;
            center.x = rng.uniform(0, img.cols);
            center.y = rng.uniform(0, img.rows);
            Mat pointChunk = points.rowRange(k*sampleCount/clusterCount,
                                             k == clusterCount - 1 ? sampleCount :
                                             (k+1)*sampleCount/clusterCount);   //最后一个类的样本数不一定是平分的,
                                                                                //剩下的一份都给最后一类
            //每一类都是同样的方差,只是均值不同而已
            rng.fill(pointChunk, CV_RAND_NORMAL, Scalar(center.x, center.y), Scalar(img.cols*0.05, img.rows*0.05));
        }
        randShuffle(points, 1, &rng);
        //因为要聚类,所以先随机打乱points里面的点,注意points和pointChunk是共用数据的。
        kmeans(points, clusterCount, labels,
               TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0),
               3, KMEANS_PP_CENTERS, centers);
        //聚类3次,取结果最好的那次,聚类的初始化采用PP特定的随机算法。
        img = Scalar::all(0);
        for( i = 0; i < sampleCount; i++ )
        {
            int clusterIdx = labels.at<int>(i);
            Point ipt = points.at<Point2f>(i);
            circle( img, ipt, 2, colorTab[clusterIdx], CV_FILLED, CV_AA );
        }
        imshow("clusters", img);
        char key = (char)
                waitKey();     //等待
        if( key == 27  ) // 'ESC'
            break;
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值