KMeans算法MacQueen在1967年提出的,是最简单与最常见数据分类方法之一并且最为一种常见数据分析技术在机器学习、数据挖掘、模式识别、图像分析等领域都用应用。如果从分类角度看KMeans属于硬分类即需要人为指定分类数目,而MeanSift分类方法则可以根据收敛条件自动决定分类数目。从学习方法上来说KMeans属于非监督学习方法即整个学习过程中不需要人为干预的学习方法,自动完成整个数据集合分类。对于给定的数据集合DS (Data Set)与输入的分类数目K,KMeans的整个工作原理可以描述如下:
从数学的角度来说KMeans就是要找到K个分类而且他们的中心点到各个分类中各个数据的之间差值平方和最小化,而实现这个过程就是要通过上述2~4步不断的迭代执行,直到收敛为止。公式表示如下:
以上是KMeans算法的基本思想,想要实现或者应用该算法有三个注意点值得关注
下图是一个例子,黑色的点代表数据点,十字表示中心点位置,初始输入的分类数目K=2时,KMeans各步执行结果:
KMeans是OpenCV核心模块的一个API函数
参数名称 | 解释 |
---|---|
data | 表示输入的数据集合,可以一维或者多维数据,类型是Mat类型,比如Mat points(count, 2, CV_32F)表示数据集合是二维,浮点数数据集 |
K | 表示分类的数目,最常见的是K=2表示二分类 |
bestLabels | 表示计算之后各个数据点的最终的分类索引,是一个INT类型的Mat对象 |
criteria | 表示算法终止的条件,达到最大循环数目或者指定的精度阈值算法就停止继续分类迭代计算 |
attempts | 表示为了获得最佳的分类效果,算法要不同的初始分类尝试次数 |
flags | 表示表示选择初始中心点选择方法用哪一种KMEANSRANDOMCENTERS 表示随机选择中心点。KMEANSPPCENTERS 基于中心化算法选择。KMEANSUSEINITIAL_LABELS第一次分类中心点用输入的中心点 |
centers | 表示输出的每个分类的中心点数据 |
KMeans在图像处理中经典应用场景就是根据用户输入的分类数目实现图像自动区域分割,本例就是基于OpenCV KMeans函数实现图像的自动分割, 对彩色图像来说,每个像素点都有RGB三个分量,整个图像可以看成是一个3维数据集合,只要把这个三维数据集作为输入参数传给KMeans函数即可,算法执行完毕之后,根据分类标记的索引设置不同的颜色即可。所以演示程序的实现步骤如下:
运行效果如下:
完整的代码实现如下:
#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(int argc, char** argv) { Mat src = imread("D:/vcprojects/images/toux.jpg"); imshow("input", src); int width = src.cols; int height = src.rows; int dims = src.channels(); // 初始化定义 int sampleCount = width*height; int clusterCount = 4; Mat points(sampleCount, dims, CV_32F, Scalar(10)); Mat labels; Mat centers(clusterCount, 1, points.type()); // 图像RGB到数据集转换 int index = 0; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { index = row*width + col; Vec3b rgb = src.at<Vec3b>(row, col); points.at<float>(index, 0) = static_cast<int>(rgb[0]); points.at<float>(index, 1) = static_cast<int>(rgb[1]); points.at<float>(index, 2) = static_cast<int>(rgb[2]); } } // 运行K-Means数据分类 TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0); kmeans(points, clusterCount, labels, criteria, 3, KMEANS_PP_CENTERS, centers); // 显示图像分割结果 Mat result = Mat::zeros(src.size(), CV_8UC3); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { index = row*width + col; int label = labels.at<int>(index, 0); if (label == 1) { result.at<Vec3b>(row, col)[0] = 255; result.at<Vec3b>(row, col)[1] = 0; result.at<Vec3b>(row, col)[2] = 0; } else if (label == 2) { result.at<Vec3b>(row, col)[0] = 0; result.at<Vec3b>(row, col)[1] = 255; result.at<Vec3b>(row, col)[2] = 0; } else if (label == 3) { result.at<Vec3b>(row, col)[0] = 0; result.at<Vec3b>(row, col)[1] = 0; result.at<Vec3b>(row, col)[2] = 255; } else if (label == 0) { result.at<Vec3b>(row, col)[0] = 0; result.at<Vec3b>(row, col)[1] = 255; result.at<Vec3b>(row, col)[2] = 255; } } } imshow("kmeans-demo", result); //imwrite("D:/vcprojects/images/cvtest.png", result); waitKey(0); return 0;}
你以为遥不可及的高度,只是别人开始的起点