K-means之C++及OpenCV实现

K-means算法算是个著名的聚类算法了,不仅容易实现,并且效果也不错,训练过程不需人工干预,实乃模式识别等领域的居家必备良品啊,今天就拿这个算法练练手。


总结来说,这个算法的步骤如下:

1.随机选取样本中的K个点作为聚类中心
2.计算所有样本到各个聚类中心的距离,将每个样本规划在最近的聚类中
3.计算每个聚类中所有样本的中心,并将新的中心代替原来的中心
4.检查新老聚类中心的距离,如果距离超过规定的阈值,则重复2-4,直到小于阈值


那么,现在,我实现的程序的步骤也是按照上面一步一步来的,

为了方便,我直接在平面上随机产生n个点,选取前K个点作为聚类中心,

距离就定义为平面上的欧式距离,

然后为了形象化地观察过程和结果,我将过程以图像的方式显示。


代码如下:


首先是主体:

int iter_times = 0;//迭代次数
    while(!good_result())//检查是否是需要的聚类中心
    {
        for(int i = 0;i < POINT_NUM;i++)
        {
            double min = 10000;
            int min_k = 0;
            for(int j = 0;j < K;j++)
            {
                double tmp = DIS(c[j].center,s[i].p); 
                if(tmp < min)
                {
                    min = tmp;
                    min_k = j; 
                }
            }
            s[i].cluster = min_k;
            c[min_k].count++;
        }
        update_center();//更新聚类中心
        iter_times++;
        show_outcome();
    }

然后是其他函数的实现:

void update_center()
{
    double x[K],y[K];
    memset(x,0,sizeof(x));
    memset(y,0,sizeof(y));
    for(int i = 0;i < POINT_NUM;i++)
    {
        x[s[i].cluster] += s[i].p.x;
        y[s[i].cluster] += s[i].p.y;
    }
    for(int i = 0;i < K;i++)
    {
        c[i].pre_center = c[i].center;
        c[i].center.x = x[i] / c[i].count;
        c[i].center.y = y[i] / c[i].count;
        c[i].count = 0;
    }
}
判断是否是需要的:
bool good_result()
{
    for(int i = 0;i < K;i++)
    {
        if(DIS(c[i].center,c[i].pre_center) > TH)
            return false;
    }
    return true;
}
显示结果:
void show_outcome()
{
    for(int y = 0;y < HEIGHT;y++)//这里将平面中所有的点都标记,就可以看到平面是怎样被划分的了
        for(int x = 0;x < WIDTH;x++)
        {
            double min = 1000;
            int min_k = 0;
            CvPoint p = cvPoint(x,y);
            for(int i = 0;i < K;i++)
            {
                double tmp = DIS(c[i].center,p); 
                if(tmp < min)
                {
                    min = tmp;
                    min_k = i; 
                }
            }
            IMG_B(img,x,y) = color[min_k].val[0];
            IMG_G(img,x,y) = color[min_k].val[1];
            IMG_R(img,x,y) = color[min_k].val[2];
            IMG_A(img,x,y) = 200;//4通道图像,就是说可以是透明的,纯试验而已,哪知道直接显示没效果,要保存之后才能看出来。
        }
    CvScalar scalar = cvScalar(255,255,255,255);
    for(int i = 0;i < POINT_NUM;i++)//画每个样本点
    {
        int x = s[i].p.x;
        int y = s[i].p.y;
        cvLine(img,cvPoint(x - 5,y),cvPoint(x + 5,y),scalar,2);
        cvLine(img,cvPoint(x,y - 5),cvPoint(x,y + 5),scalar,2);
    }
    for(int i = 0;i < K;i++)//画聚类中心
    {
        int x = c[i].center.x;
        int y = c[i].center.y;
        cvCircle(img,cvPoint(x,y),20,scalar,2);
    }
    cvShowImage("Image",img);
    cvWaitKey(100);//100毫秒是个差不多的数值,可以完整的看到聚类过程
}

看下效果:



而通过几次运行和观察,阈值不必取的很小,首先是迭代次数越来越多,时间越来越长,但结果差别却是越来越小,即,到几次迭代之后就能取得好的效果了,再迭代下去取的结果跟原来相差不大。


然后,Kmeans的缺点:

K值完全是凭经验自己定义,无法自动计算

结果跟初始时取的聚类中心差别很大,即每次计算都将有误差


你可能感兴趣的:(OpenCV,C/C++,Algorithm)