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; }