K-Means聚类算法原理参考以下链接:
https://www.cnblogs.com/pinard/p/6164214.html
2. 传统K-Means算法流程
在上一节我们对K-Means的原理做了初步的探讨,这里我们对K-Means的算法做一个总结。
首先我们看看K-Means算法的一些要点。
1)对于K-Means算法,首先要注意的是k值的选择,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通过交叉验证选择一个合适的k值。
2)在确定了k的个数后,我们需要选择k个初始化的质心,就像上图b中的随机质心。由于我们是启发式方法,k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心,最好这些质心不能太近。
好了,现在我们来总结下传统的K-Means算法流程。
输入是样本集D={x1,x2,...xm},聚类的簇树k,最大迭代次数N
输出是簇划分C={C1,C2,...Ck}
1) 从数据集D中随机选择k个样本作为初始的k个质心向量: {μ1,μ2,...,μk}
2)对于n=1,2,...,N
a) 将簇划分C初始化为Ct=∅ t=1,2...k
b) 对于i=1,2...m,计算样本xixi和各个质心向量μj(j=1,2,...k)的距离:dij=||xi−μj||2,将xixi标记最小的为dij所对应的类别λi。此时更新Cλi=Cλi∪{xi}Cλi=Cλi∪{xi}
c) 对于j=1,2,...,k,对CjCj中所有的样本点重新计算新的质心μj=1|Cj|∑x∈Cjxμj=1|Cj|∑x∈Cjx
e) 如果所有的k个质心向量都没有发生变化,则转到步骤3)
3) 输出簇划分C={C1,C2,...Ck}
程序代码如下:
//在矩阵img中随机分布白点
void myRandFill(Mat&img,int numPoint)
{
Vec3b white(255,255,255);
for(int i=0;i<numPoint;i++)
{
int x=rand()%100+1;
int y=rand()%100+1;
img.at<Vec3b>(y,x)=white;
}
}
Point getCentroid(std::vector<Point> C)
{
Point newPc(0,0);
for(int i=0;i<(int)C.size();i++)
{
newPc.x += C[i].x;
newPc.y +=C[i].y;
}
newPc.x /=C.size();
newPc.y /=C.size();
return newPc;
}
void getCluster(const Mat&img,Mat&imgCopy,
vector<Point>C[],Point Pc[],
Vec3b color[],int numOfCluster)
{
for(int i=0;i<img.rows;i++)
{
for(int j=0;j<img.cols;j++)
{
Vec3b X=img.at<Vec3b>(i,j);
vector<float> d;//当前像素与所有质心距离组成的数组
if(X[0]||X[1]||X[2])
{
for(int k=0;k<numOfCluster;k++)
{
float dist=(Pc[k].x-j)*(Pc[k].x-j)+(Pc[k].y-i)*(Pc[k].y-i);
d.push_back(dist);
}
//获取数组d[]中最小值的位置
int minPos=(int)(min_element(d.begin(),d.end())-d.begin());
//将距离最小质心 对应颜色赋值给当前像素
imgCopy.at<Vec3b>(i,j)=color[minPos];
//将当前像素并入对应类的集合
C[minPos].push_back(Point(j,i));
}
}
}
}
void myKmeans(const Mat&img,//输入原图像
Mat&imgCopy,//分类后的图像输出
std::vector<Point>*C,//类别标记数组,
//数组中每个元素C[i]表示一类像素
//从图像中找到符合该类准则的像素
//保存在C[i]中,i=1,2,3...k。
Point*Pc,//初始质心坐标
Vec3b*color,//不同类别的像素,赋予不同的颜色
int numOfclusters)//预设的类别数
{
//将距离Pc1近的点染成红色,距离Pc2点距离近的染成蓝色
//
int stop=0;
int countIte=0;
while(!stop)
{
getCluster(img,imgCopy,C,Pc,color,numOfclusters);
//计算新的C1、C2质心,并在imgCopy上绘制黄色和青色新质心点
Point* newPc=new Point[numOfclusters];
for(int nc=0;nc<numOfclusters;nc++)
{
newPc[nc]=getCentroid(C[nc]);
circle(imgCopy,newPc[nc],5,Scalar(0,255,255),1);
}
vector<float>dc;
float sumDistance;
for(int k=0;k<2;k++)
{
float dp=(newPc[k].x-Pc[k].x)*(newPc[k].x-Pc[k].x)
+(newPc[k].y-Pc[k].y)*(newPc[k].y-Pc[k].y);
dc.push_back(dp);
}
sumDistance=accumulate(dc.begin(),dc.end(),0);
cout<<"sum distance="<<sumDistance<<endl;
countIte++;
for(int nc=0;nc<numOfclusters;nc++)
{
Pc[nc]=newPc[nc];
}
if(sumDistance==0)
{
stop=1;
}
}
cout<<"count of iteration="<<countIte<<endl;
}
int main()
{
Mat img(500,500,CV_8UC3,Scalar::all(0));
Mat imgCopy=img.clone();
//创建两组随机分布的聚类点,一共200个白点
Mat rm1=img(Rect(20,100,100,100));
Mat rm2=img(Rect(120,220,100,100));
myRandFill(rm1,200);
myRandFill(rm2,200);
//设有两个聚类颜色标记,分别用红、蓝两色表示
Vec3b color[2]={ Vec3b(0,0,255),Vec3b(255,0,0)};
//与red和blue两种颜色对应的族类集合为C[0]和C[1]
std::vector<cv::Point> C[2];
//随便定义两个坐标点,作为两个聚类质心的起始位置
Point *Pc=new Point[2];
Pc[0]=Point(100,100);//C1类像素的初始质心坐标
Pc[1]=Point(200,350);//C2类像素的初始质心坐标
cv::circle(imgCopy,Pc[0],5,Scalar(0,0,255),-1);
cv::circle(imgCopy,Pc[1],5,Scalar(255,0,0),-1);
imshow("original img",img);
myKmeans(img,imgCopy,C,Pc,color,2);
imshow("new centroid position",imgCopy);
waitKey();
return 0;
}