OpenCV4学习笔记(50)——K-Means聚类算法

本次要整理记录的笔记是关于K-Means聚类算法的相关内容。

首先,在我学习这部分内容的时候,总有一个让我疑惑的点,那就是对于K-Means算法的表述方式各有不同,有些资料上称之为分类算法,而有些资料上称之为聚类算法,更有些资料上将该两者混为一谈,时而用分类、时而用聚类。这就让我感到有些疑惑了,到底分类和聚类的概念是如何定义的,于是我先对这两个概念进行了解。

下面是维基百科中关于这两个概念的解释:

聚类分析(英语:Cluster analysis)亦称为群集分析,是对于统计数据分析的一门技术,在许多领域受到广泛应用,包括机器学习,数据挖掘,模式识别,图像分析以及生物信息。聚类是把相似的对象通过静态分类的方法分成不同的组别或者更多的子集(subset),这样让在同一个子集中的成员对象都有相似的一些属性,常见的包括在坐标系中更加短的空间距离等。一般把数据聚类归纳为一种非监督式学习。

分类学(英语:Taxonomy)是一门进行分类的方法与科学,源于希腊文的τάξις(taxis,意指类别),以及νόμος(nomos,意指方法、法则、科学)。不同层级的分类单位之间,有子分类与母分类的关系。举例而言,车子是一种交通工具,因而车子是交通工具的子分类。

看起来还是有那么点摸不着头脑,尤其是其中对于分类学的定义过于空泛。
于是在经过一番查询资料后,大致总结出 分类 和 聚类 这两者的一些区别:

分类:事先定义了各个明确的类别,根据目标是否满足各个类别的特点来划分数据,也就是把不同目标根据不同特点划分到不同类别中去;

聚类:事先只知道要把数据分成几类(如K类),而不知道具体的类别是什么,然后在数据之间按照相关性把相似的数据聚成一类,再根据聚类的结果来分析聚成的这一类有什么特点。

也就是说,分类后的结果是每一个目标属于某一个类别,相当于给该目标贴上来标签。而聚类是把相似的目标先归为一个整体,再根据这个整体所具有的特征来判断这是什么类别。

做个简单的比喻,假设现在有10只猫和10只狗、总共20只宠物放在一起,那么把其中每只宠物进行判断是猫还是狗的过程,就是一个分类。而把所有相似的宠物进行划分,并划分为两类,再看哪一类是猫、哪一类是狗,这就是一个聚类的过程。

而本次要整理记录的K-Means聚类算法就是一种对数据进行聚类的算法,采用的是硬分类方式,是属于非监督学习(没有标注信息)的算法,但是K-Means只能应用于连续型的数据,并且需要在聚类前人工指定要分成几类,也就是指定一个K值。

K-Means聚类算法的大致流程如下:
(1)首先指定要把数据划分为k类,也就是指定初始K值,然后随机从所有数据中选取k个数据作为每一类别的中心数据点;
(2)根据距离度量来计算每个数据点距离每一类别中心数据点的距离,如果当前计算的数据点距离某一个中心数据点最近,那么该数据点就属于该中心数据点所对应的类别,遍历所有数据点实现对所有数据点的聚类;
(3)根据上一轮聚类结果,在每一类中计算所有样本特征的均值,并将该均值作为每个类别的新的聚类中心来重新设置中心数据点;
(4)继续遍历数据集将数据点按照与中心数据点的距离进行聚类;
(5)不断迭代地步骤(2)到步骤(4),直到指定的迭代次数或者前后两次聚类结果差异值小于指定阈值,则停止继续聚类计算,最终得到各个样本数据的类别。

注意我们通过K-Means算法得到的聚类结果,只是每个数据样本属于哪一类,该类有那些特征。如果需要知道这一类别是什么,则需要对这一整个类的特征再进行分析。

在OpenCV中就封装了kmeans()这个API来实现K-Means算法,其参数含义如下:
(1)参数data:输入的样本数据集,必须是按行来组织数据的,且需要是CV_32F类型;
(2)参数K:分类的类别数;
(3)参数bestLabels:每一个样本的标签,为一个Mat()对象,每一行是一个样本的标签(属于第几类别);
(4)参数criteria:聚类迭代的停止条件;
(5)参数attempts:表示运行kmeans的次数,取结果最好的那次聚类为最终的聚类,要配合下一个参数flag来使用;一般和类别数相同;
(6)参数flag:初始化中心点的方式,有三种可选项:
KMEANS_RANDOM_CENTERS,则表示为随机选取初始化中心点,
KMEANS_PP_CENTERS则表示使用某一种算法来确定初始聚类的点;
KMEANS_USE_INITIAL_LABELS,则表示使用用户自定义的初始点;
注意,当attempts大于1时,后续运行kmeans仍然使用随机初始化中心点;
(7)参数centers:表示每个类别的中心数据点,每一行为一个类别的中心数据点。

我们提取两张不同图像的特征点,并利用K-Means算法对这些特征点进行聚类,得到两个不同分类,并将不同分类的点绘制出来,代码演示如下:

	//读取两张不同类型的图像
	Mat cat_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\tem.jpg");
	Mat dog_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\dog.jpg");
	resize(cat_image, cat_image, Size(400, 400));
	resize(dog_image, dog_image, Size(400, 400));
	//使用GFTT特征点检测器获取两张图像各自的特征点集
	auto gftt = GFTTDetector::create(1000, 0.05, 1, 3, false, 0.04);
	vector<KeyPoint> cat_keyPoints, dog_keyPoints;
	gftt->detect(cat_image, cat_keyPoints, Mat());
	gftt->detect(dog_image, dog_keyPoints, Mat());
	drawKeypoints(cat_image, cat_keyPoints, cat_image, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	drawKeypoints(dog_image, dog_keyPoints, dog_image, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	imshow("cat_image", cat_image);
	imshow("dog_image", dog_image);

	//将两张图像的特征点集从KeyPoint类型转变为Point类型
		vector<Point2f>cat_data, dog_data;
	for (int i = 0; i < cat_keyPoints.size(); i++)
	{
		cat_data.push_back(cat_keyPoints[i].pt);
	}
	for (int j = 0; j < dog_keyPoints.size(); j++)
	{
		dog_data.push_back(dog_keyPoints[j].pt);
	}
	//将两张图像的特征点集制作成一个特征点数据集,为一个Mat对象,每一行是一个样本数据点
	int data_size = cat_data.size() + dog_data.size();
	Mat data_points = Mat::zeros(Size(2, data_size), CV_32F);
	for (int k = 0; k < data_size; k++)
	{
		for (int col = 0;col < 32;col++)
		{
			if (k < cat_data.size())
			{
			 	data_points.at<float>(k, 0) = cat_data[k].x;
				data_points.at<float>(k, 1) = cat_data[k].y;
			}
			else
			{
				data_points.at<float>(k, 0) = dog_data[k - cat_data.size()].x;
				data_points.at<float>(k, 1) = dog_data[k - cat_data.size()].y;
			}
		}
		
	}

	//对数据集进行K-Means聚类,并分为两类
	Mat bestLabels, centers;
	TermCriteria criteria = TermCriteria(TermCriteria::Type::COUNT + TermCriteria::Type::EPS, 10, 0.1);
	kmeans(data_points, 2, bestLabels, criteria, 2, KMEANS_PP_CENTERS, centers);

	//将标签为0的数据点用绿色绘制,标签为1的数据点用红色绘制
	Mat dst = Mat::ones(cat_image.size(), cat_image.type());
	dst = Scalar::all(255);
	float x, y;
	for (int n = 0; n < data_points.rows; n++)
	{
		int label = bestLabels.at<int>(n);
		float x = data_points.at<float>(n, 0);
		float y = data_points.at<float>(n, 1);
		Point2f pt(x, y);
		if (0 == label)
		{
			circle(dst, pt, 2, Scalar(0, 255, 0), 1, LINE_AA, 0);
		}
		else if (1 == label)
		{
			circle(dst, pt, 2, Scalar(0, 0, 255), 1, LINE_AA, 0);
		}
	}
	//绘制每个类别的中心点
	for (int m = 0; m < centers.rows; m++)
	{
		Point center = centers.at<Point2f>(m, 0);
		circle(dst, center, 110, Scalar(255, 0, 0), 1, LINE_AA, 0);
	}
	imshow("dst", dst);

下面是我们进行提取特征点后并显示特征点的图像:
OpenCV4学习笔记(50)——K-Means聚类算法_第1张图片
OpenCV4学习笔记(50)——K-Means聚类算法_第2张图片
将两张图像的特征点进行K-Means算法,得到下面结果:
OpenCV4学习笔记(50)——K-Means聚类算法_第3张图片
可见,所有的特征点都被分成了两类,分别用红色和绿色进行绘制,而且不同颜色的特征点也都大致出现在相近的区域,这正是由于K-Means算法将相似的特征点给聚成一类了。当然,其中也会有一些偏离比较大的点出现。

好了,今天对于K-Means算法的记录到此结束,下次再利用K-Means算法来实现一些小应用。

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

你可能感兴趣的:(学习笔记)