基于OpenCV实现的最大最小距离聚类算法

首先声明本文章部分内容借鉴于OpenCV实现最大最小距离聚类算法_pan_jinquan的博客-CSDN博客_opencv 聚类算法

 对其中的代码做了一定的优化修改,加了很多注释便于理解。

 一、算法基本思想

         最大最小距离法是模式识别中一种基于试探的类聚算法,它以欧式距离为基础,取尽可能远的对象作为聚类中心。因此可以避免K-means法初值选取时可能出现的聚类种子过于临近的情况,它不仅能智能确定初试聚类种子的个数,而且提高了划分初试数据集的效率。
        该算法以欧氏距离为基础,首先初始一个样本对象作为第1个聚类中心,再选择一个与第1个聚类中心最远的样本作为第2个聚类中心,然后确定其他的聚类中心,直到无新的聚类中心产生。最后将样本按最小距离原则归入最近的类。


二、算法实现步骤

测试是在二维平面上选取十个样本点,坐标分别为:{x1(0 0), x2(3 8), x3(2 2), x4(1 1), x5(5 3), x6(4 8), x7(6 3), x8(5 4), x9(6 4), x10(7 5)},其样本分布如图所示(图1):

 最大最小距离聚类算法步骤如下:(图2)

 

 原博客给出图2的算法步骤和代码是能对上的,但是又紧接着配了一个表格,经过仔细分析,个人觉得表格中有些数据错误。在这里就不贴出原博客的表格。 本人将采取自己代码跑出的一套结果来分析。

三、代码实现

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
 
using namespace cv;
using namespace std;

/*
*计算两个数据之间的欧式距离
*cols:数据的维度(这里是二维,自然也可以是更高维)
*/
float calcuDistance(const uchar* ptr,const uchar* ptrCen, int cols) 
{
	float d = 0.0;
	for (size_t j = 0; j < cols; j++)
	{
		d += (double)(ptr[j] - ptrCen[j])*(ptr[j] - ptrCen[j]);
	}
	d = sqrt(d);
	return d;
}
 
/** @brief   最大最小距离算法
@param data:输入样本数据,每一行为一个样本,每个样本可以存在多个特征数据
@param Theta:阈值,一般设置为0.5,阈值越小聚类中心越多
@param centerIndex:聚类中心的下标
@return :返回每个样本的类别,类别从1开始,0表示未分类或者分类失败
*/
cv::Mat  MaxMinDisFun(const cv::Mat& data, float Theta, vector<int>& centerIndex) 
{
	double 	maxDistance = 0;
	int 	k = 0;        //中心点计数,也即是类别
	int 	dataNum = data.rows; //输入的样本数量

	//表示所有样本到当前聚类中心的距离。假设样本总数为N,
	//那么distanceMat、minDistanceMat是N行1列的向量,
	cv::Mat distanceMat = cv::Mat::zeros(cv::Size(1, dataNum), CV_32FC1); 
	//取较小的距离。minDistanceMat是N行1列的向量,假设当前已经找出3个聚类中心,
	//那么minDistanceMat的第4行代表min{第4个样本点分别到3个聚类中心的距离}
	cv::Mat minDistanceMat = cv::Mat::zeros(cv::Size(1, dataNum), CV_32FC1); 
 
	//表示类别,是N行1列的向量,第N行的值表示第N个样本点所属的类别号(本文中类别号是从1开始的)
	cv::Mat classes = cv::Mat::zeros(cv::Size(1, dataNum), CV_32SC1); 
	
	//选取第1个点作为第一个聚类的中心,index指示新中心点的样本索引
	int 	index = 0; 
	centerIndex.push_back(index); 
	printf("\n\n");
	printf("====== 选取第%d个样本做为第%d个聚类中心\n", index + 1,k + 1);
	printf("===========================================\n");
	
	//指向当前聚类中心的指针
	uchar* ptrCen = (uchar*)data.ptr<uchar>(centerIndex.at(0));
	/*
	*经过这一轮遍历,所有的样本点均被归为类别1,并且计算max{所有样本点距离第一个聚类中心(第一个样本)的距离}。
	*
	*/
	for (size_t i = 0; i < dataNum; i++)
	{
		uchar* ptr1 = (uchar*)data.ptr<uchar>(i);
		
		float d= calcuDistance(ptr1, ptrCen, data.cols);
		distanceMat.at<float>(i, 0) = d;
		classes.at<int>(i, 0) = k + 1; //将样本点归类为样本1
		printf("---       第%d个样本点到第%d类聚类中心的距离为:%f\n",i +1, k + 1,d);
		printf("---       第%d个样本点被划分到第%d类\n",i +1, k + 1);
		if (maxDistance < d)
		{
			maxDistance = d;
			index = i; //与第一个聚类中心距离最大的样本
		}
	}
	printf("\n");
	printf("---- 与第%d个聚类中心最远的是第%d个样本点, 距离为%f\n", k + 1, index + 1, maxDistance);
 
	minDistanceMat = distanceMat.clone();
	double minVal; 
	double maxVal; 
	cv::Point minLoc; 
	cv::Point maxLoc;
	maxVal = maxDistance;
	double distThreshold = maxDistance*Theta;
	printf(" ** 产生新聚类中心的距离阈值为distThreshold = maxDistance*Theta = %f*%f = %f\n",maxDistance, Theta, distThreshold);

	while (maxVal > distThreshold) 
	{
		k = k + 1;
		centerIndex.push_back(index); //加入新的聚类中心
		printf("\n\n");
		printf("====== 选取第%d个样本做为第%d个聚类中心\n", index + 1,k + 1);
		printf("===========================================\n");
		
		//遍历每个样本点,计算其到当前聚类中心的欧式距离,假设样本总数为N,
		//那么minDistanceMat是N行1列的向量,他的第m行表示min{第m个样本点分别到现有聚类中心的距离}
		for (size_t i = 0; i < dataNum; i++)
		{
			uchar* ptr1 = (uchar*)data.ptr<uchar>(i);
			ptrCen = (uchar*)data.ptr<uchar>(centerIndex.at(k));//指向当前聚类中心
			//计算到当前聚类中心的欧式距离
			float d = calcuDistance(ptr1, ptrCen, data.cols);
			printf("--- 第%d个样本点到第%d类聚类中心的距离为:%f\n",i +1, k + 1,d);
			//按照当前最近临方式分类,哪个近就分哪个类别
			if (minDistanceMat.at<float>(i, 0) > d)
			{
				minDistanceMat.at<float>(i, 0) = d;
				printf("---      得知第%d个样本点到第%d类聚类中心更近\n",i +1, k + 1);
				classes.at<int>(i, 0) = k + 1;
				printf("---      第%d个样本点被划分到第%d类\n",i +1, k + 1);
			}
		}
		//查找minDistance中最大值,即求max{min{第i个样本点分别到现有每个聚类中心的距离}}
		cv::minMaxLoc(minDistanceMat, &minVal, &maxVal, &minLoc, &maxLoc);
		index = maxLoc.y;
		
		if(maxVal > distThreshold)
		{
			printf(" *max{min{第i个样本点分别到每个聚类中心的距离}},挑出第%d个样本,其距离值为:%f > distThreshold(%f),故将第%d个样本作为新的聚类中心\n", \
				index+1, maxVal,distThreshold,index+1 );
		}
		else
		{
			printf(" *max{min{第i个样本点分别到每个聚类中心的距离}},挑出第%d个样本,其距离值为:%f <= distThreshold(%f),算法迭代结束\n", index+1, maxVal,distThreshold );
		}

	}

	return classes;
}
 
 
 
int main()
{
	cv::Point offset(10,10);//所有样本均加上这个偏移、防止离原点太近影响视觉显示效果
	
	// cv::Mat data = (cv::Mat_<uchar>(16, 2) <<0,   0,
	// 										3,   8,
	// 										2,   2,
	// 										1,   1,
	// 										5,   3,
	// 										4,   8,
	// 										6,   3,
	// 										5,   4,
	// 										6,   4,
	// 										7,   5,//第10个数据
	// 										5,   8,
	// 										12,	5,
	// 										12,	4,
	// 										12,	8,
	// 										13,	8,
	// 										14, 8);
	cv::Mat data = (cv::Mat_<uchar>(10, 2) <<0,   0,
											3,   8,
											2,   2,
											1,   1,
											5,   3,
											4,   8,
											6,   3,
											5,   4,
											6,   4,
											7,   5);



	cout << "original data=\n" << data << endl;
    //画出原始数据分布图
	cv::Mat originalMat = cv::Mat::zeros(cv::Size(300, 300), CV_8UC3); 
	for (size_t i = 0; i <  data.rows; i++)
	{
		cv::Point pt = offset +  cv::Point(data.at<uchar>(i, 0) * 10, data.at<uchar>(i, 1) * 10); 
		cv::circle(originalMat, pt,  2,  cv::Scalar(0,0,255),2);
	}
	cv::imshow("original data",originalMat);


	vector<int> centerIndex;
	float Theta = 0.35;//
	//classes = cv::Mat::zeros(cv::Size(1, dataNum)....)
	cv::Mat classes = MaxMinDisFun(data, Theta, centerIndex);
	cout << "after classes=\n" << classes << endl;

	//设置了6种不同的颜色
	const int colorCnts = 6;
	cv::Scalar colors[] ={cv::Scalar(255,100,80), cv::Scalar(0,255,0), cv::Scalar(0,0,255), cv::Scalar(0,255,255), cv::Scalar(255,0,255), cv::Scalar(255,255,0)}; 

	//画出聚类后数据分布图
	cv::Mat displayMat = cv::Mat::zeros(cv::Size(300, 300), CV_8UC3); 
	//displayMat.setTo(255);//鐧借壊鑳屾櫙
	for (size_t i = 0; i <  data.rows; i++)
	{
		cv::Point pt = offset +  cv::Point(data.at<uchar>(i, 0) * 10, data.at<uchar>(i, 1) * 10); 
		cv::circle(displayMat, pt,  2,  colors[classes.at<int>(i, 0) % colorCnts], 2);
	}
	cv::imshow("result display",displayMat);

	waitKey(0);
	return 0;
}

3.1 代码结果

 3.2 算法流程具体剖析

1)随机选取某个样本点(这里选的是第一个样本点x1)作为聚类中心,分别计算每个样本点到x1的欧式距离,得知第6个样本点x6距离第一个聚类中心x1最远。 经过此轮操作,所有的样本点都被分到第一类。

       此外,还计算了产生新聚类中心的距离阈值distThreshold.

 2)因为第6个样本点x6距离第一个聚类中心x1最远,距离为8.94,大于distThreshold(3.13),所以选择第6个样本点作为第2个聚类中心。计算每个样本点到第2个聚类中心x6的欧式距离,并判断每个点离哪一个聚类中心(目前只有x1,x6)更近,离哪个聚类中心最近,就把当前样本点归到哪个聚类。

 3)按照最大最小距离规则,max{min{第i个样本点分别到每个聚类中心的距离}},其中1 =< i <= 10,挑出第7个样本,其距离值为:5.385165 > distThreshold(3.130495),故将第7个样本作为新的聚类中心。

选取第7个样本x7作为第3个聚类中心。至此共有三个聚类中心了(x1  、 x6   、 x7)。

同理根据max{min{第i个样本点分别到每个聚类中心的距离}},挑出第3个样本,其距离值为:2.828427 <= distThreshold(3.130495),不满足生成新聚类中心的条件,算法迭代结束!!

四、小拓展

刚才例子是三个聚类结果,那么我们试试稍复杂的数据呢,那么在上个演示例子的基础上加几个数据吧。

	cv::Mat data = (cv::Mat_<uchar>(16, 2) <<0,   0,
											3,   8,
											2,   2,
											1,   1,
											5,   3,
											4,   8,
											6,   3,
											5,   4,
											6,   4,
											7,   5,//第10个数据
											12,	4,
											12,	5,
											12,	6,
											13,	4,
											17, 8,
											18, 9
										);

将float Theta = 0.35改为float Theta = 0.20,阈值越小聚类中心越多,分类越多。请根据实际需要合适地进行设置。

其结果为:

 

作为对比,如果 将float Theta = 0.35改为float Theta = 0.20,那么结果为:

显然并不是很理想(上图右侧黄色的点应该被分为2类)。 

 

Guess you like

Origin blog.csdn.net/thequitesunshine007/article/details/121925486
Recommended