• 聚类


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

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

    kmeans函数使用实例(续)

    #include "opencv2/highgui/highgui.hpp"

    #include "opencv2/core/core.hpp"

    #include <iostream>

    using namespace cv;

    using namespace std;

    // static void help()

    // {

    //     cout << " This program demonstrates kmeans clustering. "

    //             "It generates an image with random points, then assigns a random number of cluster "

    //             "centers and uses kmeans to move those cluster centers to their representitive location "

    //             "Call "

    //             "./kmeans " << 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;

    }

    K-means之C++及OpenCV实现

    K-means算法算是个著名的聚类算法了,不仅容易实现,并且效果也不错,训练过程不需人工干预,实乃模式识别等领域的居家必备良品啊,今天就拿这个算法练练手。

    总结来说,这个算法的步骤如下:

    1.随机选取样本中的K个点作为聚类中心

    2.计算所有样本到各个聚类中心的距离,将每个样本规划在最近的聚类中

    3.计算每个聚类中所有样本的中心,并将新的中心代替原来的中心

    4.检查新老聚类中心的距离,如果距离超过规定的阈值,则重复2-4,直到小于阈值

    那么,现在,我实现的程序的步骤也是按照上面一步一步来的,

    为了方便,我直接在平面上随机产生n个点,选取前K个点作为聚类中心,

    距离就定义为平面上的欧式距离,

    然后为了形象化地观察过程和结果,我将过程以图像的方式显示。

    K-means之C++及OpenCV实现(续)

    代码如下:

    首先是主体:

    int iter_times = 0;//迭代次数 

        while(!good_result())//检查是否是需要的聚类中心 

        { 

            for(int i = 0;i < POINT_NUM;i++) 

            { 

                double min = 10000; 

                int min_k = 0; 

                for(int j = 0;j < K;j++) 

                { 

                    double tmp = DIS(c[j].center,s[i].p);  

                    if(tmp < min) 

                    { 

                        min = tmp; 

                        min_k = j;  

                    } 

                } 

                s[i].cluster = min_k; 

                c[min_k].count++; 

            } 

            update_center();//更新聚类中心 

            iter_times++; 

            show_outcome(); 

        } 

    K-means之C++及OpenCV实现(续)

    然后是其他函数的实现:

    void update_center() 

        double x[K],y[K]; 

        memset(x,0,sizeof(x)); 

        memset(y,0,sizeof(y)); 

        for(int i = 0;i < POINT_NUM;i++) 

        { 

            x[s[i].cluster] += s[i].p.x; 

            y[s[i].cluster] += s[i].p.y; 

        } 

        for(int i = 0;i < K;i++) 

        { 

            c[i].pre_center = c[i].center; 

            c[i].center.x = x[i] / c[i].count; 

            c[i].center.y = y[i] / c[i].count; 

            c[i].count = 0; 

        } 

    }

    K-means之C++及OpenCV实现(续)

    然后是其他函数的实现:

    void update_center() 

        double x[K],y[K]; 

        memset(x,0,sizeof(x)); 

        memset(y,0,sizeof(y)); 

        for(int i = 0;i < POINT_NUM;i++) 

        { 

            x[s[i].cluster] += s[i].p.x; 

            y[s[i].cluster] += s[i].p.y; 

        } 

        for(int i = 0;i < K;i++) 

        { 

            c[i].pre_center = c[i].center; 

            c[i].center.x = x[i] / c[i].count; 

            c[i].center.y = y[i] / c[i].count; 

            c[i].count = 0; 

        } 

    }

    K-means之C++及OpenCV实现(续)

    判断是否是需要的:

    bool good_result() 

        for(int i = 0;i < K;i++) 

        { 

            if(DIS(c[i].center,c[i].pre_center) > TH) 

                return false; 

        } 

        return true; 

    }

    K-means之C++及OpenCV实现(续)

    显示结果:

    void show_outcome() 

        for(int y = 0;y < HEIGHT;y++)//这里将平面中所有的点都标记,就可以看到平面是怎样被划分的了 

            for(int x = 0;x < WIDTH;x++) 

            { 

                double min = 1000; 

                int min_k = 0; 

                CvPoint p = cvPoint(x,y); 

                for(int i = 0;i < K;i++) 

                { 

                    double tmp = DIS(c[i].center,p);  

                    if(tmp < min) 

                    { 

                        min = tmp; 

                        min_k = i;  

                    } 

                } 

                IMG_B(img,x,y) = color[min_k].val[0]; 

                IMG_G(img,x,y) = color[min_k].val[1]; 

                IMG_R(img,x,y) = color[min_k].val[2]; 

                IMG_A(img,x,y) = 200;//4通道图像,就是说可以是透明的,纯试验而已,哪知道直接显示没效果,要保存之后才能看出来。 

            } 

        CvScalar scalar = cvScalar(255,255,255,255); 

        for(int i = 0;i < POINT_NUM;i++)//画每个样本点 

        { 

            int x = s[i].p.x; 

            int y = s[i].p.y; 

            cvLine(img,cvPoint(x - 5,y),cvPoint(x + 5,y),scalar,2); 

            cvLine(img,cvPoint(x,y - 5),cvPoint(x,y + 5),scalar,2); 

        } 

        for(int i = 0;i < K;i++)//画聚类中心 

        { 

            int x = c[i].center.x; 

            int y = c[i].center.y; 

            cvCircle(img,cvPoint(x,y),20,scalar,2); 

        } 

        cvShowImage("Image",img); 

        cvWaitKey(100);//100毫秒是个差不多的数值,可以完整的看到聚类过程 

    }

    K-means之C++及OpenCV实现(续)

    效果:

     

    K-means之C++及OpenCV实现(续)

    而通过几次运行和观察,阈值不必取的很小,首先是迭代次数越来越多,时间越来越长,但结果差别却是越来越小,即,到几次迭代之后就能取得好的效果了,再迭代下去取的结果跟原来相差不大。

    然后,Kmeans的缺点:

    K值完全是凭经验自己定义,无法自动计算

    结果跟初始时取的聚类中心差别很大,即每次计算都将有误差

  • 相关阅读:
    12 KLT算法
    1- js vue.js
    复用代码
    计算两个日期相隔的天数(jodd)
    [转]ORA-00907: 缺失右括号
    [转]sql server 数据库日期格式化函数
    [Oralce]Oralce格式化日期
    myeclipse内存配置
    cookie 编码问题
    [转]Oracle 操作字符串的函数
  • 原文地址:https://www.cnblogs.com/linchenjian/p/6399551.html
Copyright © 2020-2023  润新知