• 基础学习笔记之opencv(18):kmeans函数使用实例


     

      前言

      一提到聚类算法,必然首先会想到的是kmeans聚类,因为它的名气实在太大了。既然这样,OpenCV中这个函数也自然必不可少了。这节内容主要是讲讲OpenCV中kmeans函数的使用方法。

      开发环境:OpenCV2.4.3+QtCreator2.5.1

     

      实验基础

      在使用kmeans之前,必须先了解kmeans算法的2个缺点:第一是必须人为指定所聚的类的个数k;第二是如果使用欧式距离来衡量相似度的话,可能会得到错误的结果,因为没有考虑到属性的重要性和相关性。为了减少这种错误,在使用kmeans距离时,一定要使样本的每一维数据归一化,不然的话由于样本的属性范围不同会导致错误的结果。

      本次实验是对随机产生的sampleCount个二维样本(共分为clusterCount个类别),每个类别的样本数据都服从高斯分布,该高斯分布的均值是随机的,方差是固定的。然后对这sampleCount个样本数据使用kmeans算法聚类,最后将不同的类用不同的颜色显示出来。

      下面是程序中使用到的几个OpenCV函数:

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

      这个函数是对矩阵mat填充随机数,随机数的产生方式有参数2来决定,如果为参数2的类型为RNG::UNIFORM,则表示产生均一分布的随机数,如果为RNG::NORMAL则表示产生高斯分布的随机数。对应的参数3和参数4为上面两种随机数产生模型的参数。比如说如果随机数产生模型为均匀分布,则参数a表示均匀分布的下限,参数b表示上限。如果随机数产生模型为高斯模型,则参数a表示均值,参数b表示方程。参数5只有当随机数产生方式为均匀分布时才有效,表示的是是否产生的数据要布满整个范围(没用过,所以也没仔细去研究)。另外,需要注意的是用来保存随机数的矩阵mat可以是多维的,也可以是多通道的,目前最多只能支持4个通道。

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

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

      Class TermCriteria

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

      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表示的是聚类后的中心点存放矩阵。该函数返回的是聚类结果的紧凑性,其计算公式为:

      

     

      实验结果

      随机产生的符合高斯分布的数据被聚类的结果一:

      

      结果二:

      

     

      实验代码及注释:

      main.cpp:

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/core/core.hpp"
    #include <iostream>
    
    using namespace cv;
    using namespace std;
    
    // static void help()
    // {
    //     cout << "\nThis program demonstrates kmeans clustering.\n"
    //             "It generates an image with random points, then assigns a random number of cluster\n"
    //             "centers and uses kmeans to move those cluster centers to their representitive location\n"
    //             "Call\n"
    //             "./kmeans\n" << endl;
    // }
    
    int main( int /*argc*/, char** /*argv*/ )
    {
        const int MAX_CLUSTERS = 5;
        Scalar colorTab[] =     //因为最多只有5类,所以最多也就给5个颜色
        {
            Scalar(0, 0, 255),
            Scalar(0,255,0),
            Scalar(255,100,100),
            Scalar(255,0,255),
            Scalar(0,255,255)
        };
    
        Mat img(500, 500, CV_8UC3);
        RNG rng(12345); //随机数产生器
    
        for(;;)
        {
            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 || key == 'q' || key == 'Q' ) // 'ESC'
                break;
        }
    
        return 0;
    }

     

      实验总结:

      Kmeans算法的速度还是够快的,在指定类别个数的情况下效果还是可以的。

     

     

     

  • 相关阅读:
    【BZOJ】1552/3506 [Cerc2007]robotic sort
    【BZOJ】1014 [JSOI2008]火星人prefix
    【BZOJ】1500: [NOI2005]维修数列
    【51NOD-0】1046 A^B Mod C
    【51NOD-0】1019 逆序数
    【51NOD-0】1018 排序
    【51NOD-0】1012 最小公倍数LCM
    The Grove(poj 3182)
    Iahub and Permutations(codeforces 314c)
    多边形之战(bzoj 2927)
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2783709.html
Copyright © 2020-2023  润新知