本文来源:转载自贾志刚老师的《opencv学堂》微信公众号(2017年4月6日更新 的文章)
-
KMeans算法的应用范围
KMeans算法是MacQueen在1967年提出的,是最简单与最常见的数据分类方法之一,作为一种常见的数据分析技术在机器学习、数据挖掘、模式识别、图像分析等领域都有广泛应用。
-
KMeans算法的实现原理
如果从分类角度看KMeans属于硬分类即需要人为指定分类数目,而MeanSift分类方法则可以根据收敛条件自动决定分类数目。对于给定的数据集合DS (Data Set)与输入的分类数目K,KMeans的整个工作原理可以描述如下:
1:根据输入的分类数目K定义K个分类,每个分类选择一个中心点
2:对DS中每个数据点做如下操作:
(1)计算它与K个中心点之间的距离
(2)把数据点指定属于K个中心点中距离最近的中心点所属的分类
3:对K个分类中每个数据点计算平均值得到新的K个中心点
4:比较新K个中心点之间与第一步中已经存在的K个中心差值
(1)当两者之间的差值没有变化或者小于指定阈值,结束分类
(2)当两者之间的差值或者条件不满足时候,用新计算的中心点值做为K个分类的新中心点,继续执行2~4步。直到条件满足退出。
从数学的角度来说KMeans就是要找到K个分类而且他们的中心点到各个分类中各个数据之间的差值平方和最小,而实现这个过程就是要通过上述2~4步不断的迭代执行,直到收敛为止。公式表示如下:
下图是一个例子,黑色的点代表数据点,十字表示中心点位置,初始输入的分类数目K=2时,KMeans各步执行结果:
-
opencv实例
- opencv中API函数kmeans介绍:
- 应用案例
KMeans在图像处理中经典应用场景就是根据用户输入的分类数目实现图像自动区域分割,本例就是基于OpenCV KMeans函数实现图像的自动分割, 对彩色图像来说,每个像素点都有RGB三个分量,整个图像可以看成是一个3维数据集合,只要把这个三维数据集作为输入参数传给KMeans函数即可,算法执行完毕之后,根据分类标记的索引设置不同的颜色即可。所以演示程序的实现步骤如下:
1.将输入图像转换为数据集合
2.使用KMeans算法对数据实现分类
3.根据每个数据点的分类索引,对图像重新填充颜色,显示分割后图像
原图及运行效果如下:
完整的代码实现如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
Mat img = imread("1.jpg");
int width = img.cols;
int height = img.rows;
int dims = img.channels();
int simpleCount = width*height; //数据数量
int clusterCount = 4; //聚类个数
Mat point(simpleCount,dims,CV_32F,Scalar(10));//一维数据对象
Mat labels;
Mat centers(clusterCount,1,point.type());
//二维图像转换为一维数据集
int index=0;
for (int row=0; row < height; row++){
for (int col=0; col < width; col++){
index = row*width + col;
Vec3b rgb = img.at<Vec3b>(row,col);
point.at<float>(index,0) = static_cast<int>(rgb[0]);
point.at<float>(index,1) = static_cast<int>(rgb[1]);
point.at<float>(index,2) = static_cast<int>(rgb[2]);
}
}
// 运行K-Means数据分类
//TermCriteria模板类,迭代算法的终止条件,该类变量需要3个参数.
//第一个参数:迭代终止条件类型。
//CV_TERMCRIT_ITER:达到最大迭代次数终止。
//CV_TERMCRIT_EPS:迭代到阈值终止。
//CV_TERMCRIT_ITER+CV_TERMCRIT_EPS:两者都作为迭代终止条件。
//以上的宏对应的c++的版本分别为TermCriteria::COUNT、TermCriteria::EPS,这里的COUNT也可以写成MAX_ITER。
//第二个参数:迭代的最大次数。
//第三个参数:阈值。
TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0);
kmeans(point, clusterCount, labels, criteria, 3, KMEANS_PP_CENTERS, centers);
//显示图像分割结果
Mat result = Mat::zeros(img.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("result",result);
waitKey(0);
return 0;
}