kmeans聚类分析、随机数生成、源代码的修改(保证随机点在500*500矩阵内)(OpenCV案例源码kmeans.cpp解读)

官方源代码中有一点瑕疵,高斯分布产生的随机点points的坐标可能出现负数或大于500的数。如横坐标均值是0,方差是25,那么横坐标随机值中会出现负数。

修改了两处:随机数生成种子是时间、随机点points坐标保证在500*500以内。

【知识点1】聚类函数

double kmeans( InputArray data, int K, InputOutputArray bestLabels,TermCriteria criteria, int attempts,int flags, OutputArray centers = noArray() );
data——原始数据集,行为样本列为特征。
K——聚类的类别数。
bestLabels——每个样本所属的类别标签,从0开始,样本数行1列的矩阵。
criteria——迭代终止条件,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0),10次迭代、期望准确率1.0
attempts——运行kmeans的次数,取结果最好的那次聚类为最终的聚类。
flags——聚类中心初始化
    KMEANS_RANDOM_CENTERS 随机初始化
    KMEANS_USE_INITIAL_LABELS 第一次初始化使用用户设定的,之后使用随机的(random or semi-random centers)。
    KMEANS_PP_CENTERS 算法kmeans++的center
centers——最终最优的聚类的中心。

返回值——点的紧凑程度,越小越紧凑。

【知识点2】随机数生成

RNG rng(getTickCount());  //随时间每次产生的随机数都不同(起始种子不同),如果RNG rng(12345); 每次运行,随机数都一样。
rng.uniform(2, 6); //指定范围内产生随机数,左闭右开。最多5类。
rng.fill(pointChunk, RNG::NORMAL, Scalar(mean.x, mean.y), Scalar(25, 25)); //高斯分布RNG::NORMAL,等效下句代码
randn(pointChunk, Scalar(mean.x, mean.y), Scalar(25, 25)); //高斯分布的随机数,被填充的矩阵、均值、方差
randShuffle(points); //打乱points矩阵中元素(坐标)顺序

其他参考https://blog.csdn.net/qq_33485434/article/details/78980587

【难理解点讲解】pointChunk等分points,每一份的数据均值随机[25,475)、方差25

Mat pointChunk = points.rowRange(k*sampleCount / clusterCount, k == clusterCount - 1 ? sampleCount : (k + 1)*sampleCount / clusterCount);

案例中这句难理解,Mat a=b;是浅拷贝,a、b指向同一内存,其一改变,都改变。所以操作pointChunk,就是处理points。

样本被等分成“sampleCount / clusterCount”份,当然有可能不完美等分,前n-1份一定是个数相同的,最后一份个数会少于其他。

所以前n-1份,points.rowRange(k*sampleCount / clusterCount, (k + 1)*sampleCount / clusterCount); ,即第k份的头和第k份的尾。

最后一份,points.rowRange(k*sampleCount / clusterCount, sampleCount);,即最后一份的头和所有样本的尾。

#include<opencv2\opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

int main()
{
    //5种颜色,因为最多分5类
    Scalar colorTab[] =
    {
        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);//3通道
    RNG rng(getTickCount()); //随时间每次产生的随机数都不同(起始种子不同),如果RNG rng(12345); 每次运行,随机数都一样。

    for (;;)
    {
        int k, clusterCount = rng.uniform(2, 6);//指定范围内产生随机数,左闭右开。最多5类。
        int i, sampleCount = rng.uniform(1, 1001);
        Mat points(sampleCount, 1, CV_32FC2), labels;//样本点,一列,2通道
        clusterCount = MIN(clusterCount, sampleCount);//分类数与样本数的最小值
        std::vector<Point2f> centers;//中心坐标容器

        //生成随机点,pointChunk是points被等分的局部矩阵,每一份均值随机[25,475),方差25
        for (k = 0; k < clusterCount; k++)
        {
            //浅拷贝,pointChunk与points同一内存
            Mat pointChunk = points.rowRange(k*sampleCount / clusterCount, k == clusterCount - 1 ? sampleCount : (k + 1)*sampleCount / clusterCount);
            Point mean;//均值
            mean.x = rng.uniform(0+25, img.cols-25);//由于randn中方差是25,所以均值随机范围增减了25,防止pointChunk点坐标出现负数或大于500的数
            mean.y = rng.uniform(0+25, img.rows-25);
            randn(pointChunk, Scalar(mean.x, mean.y), Scalar(25, 25));//高斯分布的随机数,被填充的矩阵、均值、方差
            //rng.fill(pointChunk, RNG::NORMAL, Scalar(mean.x, mean.y), Scalar(25, 25));//等效上句代码
        }
        randShuffle(points);//打乱points内容
        //聚类分析
        double compactness = kmeans(points, clusterCount, labels,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0),3, KMEANS_PP_CENTERS, centers);
        //黑色图
        img = Scalar::all(0);//img三通道图,值全是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],FILLED);//画圆,图、圆心、半径、颜色、实心填充
        }
        //画圆
        for (i = 0; i < (int)centers.size(); ++i)
        {
            Point2f c = centers[i];
            circle(img, c, 40, colorTab[i], 2, LINE_AA);//画圆,图、圆心、半径、颜色、线条粗细与细腻程度(16连通)
        }
        cout << "Compactness: " << compactness << endl;//输出返回值,紧凑度,越小越紧凑

        imshow("clusters", img);

        char key = (char)waitKey();
        if (key == 27 || key == 'q' || key == 'Q') // 'ESC'
            break;
    }

    return 0;
}

 

posted @ 2020-03-10 14:01  夕西行  阅读(1081)  评论(0编辑  收藏  举报