Kmeans方法概述
- 无监督学习方法
- 分类问题,输入分类数目,初始化中心位置
- 硬分类方法,以距离度量
- 迭代分类为聚类
KMeans 原理介绍
“聚对于"监督学习"(supervisedlea rning),其训练样本是带有标记信息的,并且监督学习的目的是:对带有标记的数据集进行模型学习,从而便于对新的样本进行分类。而在“无监督学习”(unsupervised learning)中,训练样本的标记信息是未知的,目标是通过对无标记训练样本的学习来揭示数据的内在性质及规律,为进一步的数据分析提供基础。对于无监督学习,应用最广的便是"聚类"(clustering)。
“聚类算法”试图将数据集中的样本划分为若干个通常是不相交的子集,每个子集称为一个“簇”(cluster),通过这样的划分,每个簇可能对应于一些潜在的概念或类别。
我们可以通过下面这个图来理解:
上图是未做标记的样本集,通过他们的分布,我们很容易对上图中的样本做出以下几种划分。
当需要将其划分为两个簇时,即 k=2 时:
当需要将其划分为四个簇时,即 k=4 时:
那么计算机是如何进行这样的划分的呢?这就需要聚类算法来进行实现了
大致算法流程原理如下图所示:
相关函数api
随机填充
void RNG::fill( InputOutputArray mat,
int distType,
InputArray a,
InputArray b,
bool saturateRange=false )
函数介绍
- mat:需要填充的矩阵
- distType:随机数产生的方式,有两种
- RNG::UNIFORM:产生均一分布的随机数
- a:表示均匀分布的下限
- b:表示上限
- RNG::NORMAL:产生高斯分布的随机数
- a:表示均值
- b:表示方程
- RNG::UNIFORM:产生均一分布的随机数
- saturateRange:只有当随机数产生方式为均匀分布时才有效,表示的是是否产生的数据要布满整个范围
需要注意的是用来保存随机数的矩阵mat可以是多维的,也可以是多通道的,目前最多只能支持4个通道。
函数api
void randShuffle(InputOutputArray dst, double iterFactor=1., RNG* rng=0 )
函数介绍
该函数表示随机打乱1D数组dst里面的数据,随机打乱的方式由随机数发生器rng决定。iterFactor为随机打乱数据对数的因子,总共打乱的数据对数为:dst.rowsdst.colsiterFactor,因此如果为0,表示没有打乱数据。
函数api
Class TermCriteria
函数介绍
类TermCriteria 一般表示迭代终止的条件,如果为CV_TERMCRIT_ITER,则用最大迭代次数作为终止条件,如果为CV_TERMCRIT_EPS则用精度作为迭代条件,如果为CV_TERMCRIT_ITER+CV_TERMCRIT_EPS则用最大迭代次数或者精度作为迭代条件,看哪个条件先满足。
函数解释
rowRange为指定的行span创建一个新的矩阵头,可取指定行区间元素
colRange为指定的列span创建一个新的矩阵头,可取指定列区间元素
理解:
rowRange(int x, int y) (其中y应小于等于行数,例如一个矩阵最大为5行,那么y最大为4) 的创建矩阵范围为从x行为首行开始,往后数y-x行。
例如:rowRange(0,3) 位 从第0行开始,往后数3行。即 0 、1、2行。
colRange(int x,int y)同理。
函数api
double cv::kmeans ( InputArray data,
int K,
InputOutputArray bestLabels,
TermCriteria criteria,
int attempts,
int flags,
OutputArray centers = noArray()
)
参数介绍
- data:为cv::Mat类型,每行代表一个样本,即特征点,即mat.cols=特征长度,mat.rows=样本数,数据类型仅支持float;
- K:指定聚类时划分为几类;
- bestLabels:为cv::Mat类型,是一个长度为(样本数,1)的矩阵,即mat.cols=1,mat.rows=样本数;为K-Means算法的结果输出,指定每一个样本聚类到哪一个label中;
- criteria:TermCriteria类,算法进行迭代时终止的条件,可以指定最大迭代次数,也可以指定预期的精度,也可以这两种同时指定;
- attempts:指定K-Means算法执行的次数,每次算法执行的结果是不一样的,选择最好的那次结果输出;
- flags:初始化均值点的方法,目前支持三种:KMEANS_RANDOM_CENTERS、KMEANS_PP_CENTERS、KMEANS_USE_INITIAL_LABELS;
- centers:为cv::Mat类型,输出最终的均值点,mat.cols=特征长度,mat.rols=K.
一般当attempts和TermCriteria中迭代次数值越大时,聚类效果越好。
代码演示(kmeans实现聚类演示)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(void)
{
//创建图片
Mat img(500,500,CV_8UC3);
//定义随机数产生器
RNG rng(12345);
//定义5种颜色 最大分类不超过5
Scalar color_tab[]={
Scalar(0,0,255),
Scalar(0,255,0),
Scalar(255,0,0),
Scalar(0,255,255),
Scalar(255,0,255)
};
//定义分类 即函数K值 多少个分类点
int num_cluster = rng.uniform(2,5);
printf("num of num_cluster is %d\n",num_cluster);
//一共产生多少点数供我们测试
int sample_count = rng.uniform(2,1000);
printf("num of sample_count is %d\n",sample_count);
Mat points(sample_count,1,CV_32FC2); //产生的样本数,实际上为2通道的列向量,元素类型为Point2f
Mat lables; //聚类标签 每个点归哪个类
Mat centers;
//生成随机数
for(int i=0;i<num_cluster;i++)
{
Point center;
center.x = rng.uniform(0,img.cols);
center.y = rng.uniform(0,img.rows);
//获取填充区域 只是获取到区域 没有填充数值
Mat point_chunk = points.rowRange(i*sample_count/num_cluster,i == num_cluster-1 ? sample_count:
(i+1)*sample_count/num_cluster);
//向获取到的填充区域随机数填充 高斯分布 满足(均值为Scalar(center.x,center.y) 方程为Scalar(img.cols*0.05,img.rows*0.05))
rng.fill(point_chunk,RNG::NORMAL,Scalar(center.x,center.y),Scalar(img.cols*0.05,img.rows*0.05));
}
//打乱
randShuffle(points,3,&rng);
//使用Kmeans
kmeans(points,num_cluster,lables,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,10,0.1),
3,KMEANS_PP_CENTERS,centers);
//用不同的颜色分类显示
img = Scalar::all(255); //全部初始化为0 白色背景
//给每个点 根据相应分类类不同 进行着色
for(int i=0;i<sample_count;i++)
{
int index = lables.at<int>(i);
Point p = points.at<Point2f>(i);
circle(img,p,2,color_tab[index],-1,8);
}
//每个聚类绘制中心
for(int i=0;i<num_cluster;i++)
{
int x = centers.at<float>(i,0);
int y = centers.at<float>(i,1);
circle(img,Point(x,y),45,color_tab[i],2);
}
imshow("kmeans-data",img);
waitKey(0);
destroyAllWindows();
return 0;
}
效果
代码演示(kmeans实现图片分割)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "kmeans.jpeg"
int main(void)
{
Mat src;
//获取完整的图片路径及名称
string pic = string(PIC_PATH)+string(PIC_NAME);
//打印图片路径
cout << "pic path is :"<<pic<<endl;
//读取图片
src = imread(pic);
//判断图片是否存在
if(src.empty())
{
cout<<"pic is not exist!!!!"<<endl;
return -1;
}
//显示图片
namedWindow("src pic",WINDOW_AUTOSIZE);
imshow("src pic",src);
//定义5种颜色 最大分类不超过5
Scalar color_tab[]={
Scalar(0,0,255),
Scalar(0,255,0),
Scalar(255,0,0),
Scalar(0,255,255),
Scalar(255,0,255)
};
//定义分类 即函数K值 多少个分类点
int num_cluster = 4;
printf("num of num_cluster is %d\n",num_cluster);
int width = src.cols;
int height = src.rows;
int dims = src.channels();
//初始化定义
//一共产生多少点数供我们测试
int sample_count = width*height;
printf("num of sample_count is %d\n",sample_count);
Mat points(sample_count,dims,CV_32F); //产生的样本数,实际上为2通道的列向量,元素类型为Point2f
Mat lables; //聚类标签 每个点归哪个类
Mat centers(num_cluster,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 bgr = src.at<Vec3b>(row,col);
points.at<float>(index,0) = static_cast<int>(bgr[0]);
points.at<float>(index,1) = static_cast<int>(bgr[1]);
points.at<float>(index,2) = static_cast<int>(bgr[2]);
}
//使用Kmeans
kmeans(points,num_cluster,lables,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,10,0.1),
3,KMEANS_PP_CENTERS,centers);
//显示图像分割结果
Mat result = Mat::zeros(src.size(),src.type());
for(int row=0;row<height;row++)
for(int col=0;col<width;col++)
{
index = row*width +col;
int label = lables.at<int>(index,0);
result.at<Vec3b>(row,col)[0] = color_tab[label][0];
result.at<Vec3b>(row,col)[1] = color_tab[label][1];
result.at<Vec3b>(row,col)[2] = color_tab[label][2];
}
imshow("kmeans-data",result);
waitKey(0);
destroyAllWindows();
return 0;
}