OpenCV學習筆記(11)-K均值

K均值聚類算法在cxcoer中,因爲它在ML庫誕生之前就存在了.K均值嘗試找到數據的自然類別.用戶設置類別個數,K均值迅速地找到"好的"類別中心."好的"意味着聚類中心位於數據的自然類別中心.K均值是最常用的聚類計數之一,與高斯混合中的期望最大化算法(在ML庫中實現爲CvEM)很相似,也與均值漂移算法(在CV庫中實現爲cvMeanShift())相似.K均值是一個迭代算法,在OpenCV中採用的是Lloyd算法,也叫Voronoi迭代.算法運行如下.

1.輸入數據集合和類別數K(由用戶指定)

2.隨機分配類別中心點的位置

3.將每個點放入離它最近的類別中心點所在的集合.

4.移動類別中心點到它所在集合的中心

5.轉到第3步,直到收斂.

圖13-5展示了K均值是怎麼工作的.在這個例子中,只用兩次迭代就達到了收斂.在現實數據中,算法經常很快的收斂,但是有的時候需要迭代的次數比較多.


問題和解決方案

K均值是一個及其高效的聚類算法,但是它也有以下3個問題.

1.它不保證能找到定位聚類中心的最佳方案,但是它能保證能收斂到某個解決方案(例如迭代不再無限繼續下去).

2.K均值無法指出應該使用多少類別.在同意數據集中,例如對圖13-5中的例子,選擇兩個類別和選擇四個類別,得到的結果是不一樣的,甚至是不合理的.

3.K均值假設空間的協方差矩陣不會影響結果,或者已經歸一化(參考Mahalanobis距離的討論).

這三個問題都有"解決辦法".這些解決方法中最好的兩種都是基於"劫色數據的方差".在K均值中,每個聚類中線擁有他的數據點,我們計算這些點的方差,最好的聚類在不引起太大的複雜度的情況下使方差達到最小.所以,列出的問題可以改進如下.

1.多進行幾次K均值,每次初始的聚類中心點都不一樣(很容易做到,因爲OpenCV隨機選擇中心點),最後選擇方差最小的那個結果

2.首先將類別數設爲1,然後提高類別數(到達某個上限),每次聚類的時候使用前面提到的方法1.一般情況下,總方差會很快下降,直到到達一個拐點;這意味着再加一個新的聚類中心不會顯著地減少總方差.在拐點處停止,保存此時的類別數.

3.將數據乘以逆協方差矩陣.例如:如果輸入向量是按行排列,沒行一個數據點,則通過計算新的數據向量D*,D* = D∑-1/2來歸一化數據空間.

K均值代碼

K均值函數的調用很簡單:

void cvKMeans2(const CvArr* samples, int cluster_count,CvArr* labels,CvTrtmCriteria termcrit);

數組sample是一個多維的數據樣本矩陣,每行一個數據樣本.這裏有一點點微妙,數據樣本可以是浮點型CV_32FC1向量,也可以是CV_32FC2,CV_32FC3和CV_32FC(k)的多維向量.參數cluster_count指定類別數,返回向量包含每個點最後的類別索引.前面已經討論了terncrit.

#include <QCoreApplication>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
   #define MAX_CLUSTER 5
    CvScalar color_table[MAX_CLUSTER];
    IplImage* img = cvCreateImage(cvSize(500,500),8,3);
    CvRNG rng = cvRNG(0xffffffff);

    color_table[0] = CV_RGB(255,0,0);
    color_table[1] = CV_RGB(0,255,0);
    color_table[2] = CV_RGB(100,100,255);
    color_table[3] = CV_RGB(255,0,255);
    color_table[4] = CV_RGB(255,255,0);

    cvNamedWindow("clusters",1);
    for(;;)
    {
        int k,cluster_count = cvRandInt(&rng)%MAX_CLUSTER +1; //類別個數 1~5
        int i,sample_count = cvRandInt(&rng)%1000+1;  //樣本個數 1~1000
        CvMat* points = cvCreateMat(sample_count,1,CV_32FC2); //創建sample_count行1列的雙通道矩陣 用於存儲數據樣本
        CvMat* clusters = cvCreateMat(sample_count,1,CV_32SC1);//創建sample_count行1列的矩陣 用來存儲數據標籤
        //隨機生成樣本多元高斯分佈
        //樣本總數爲sample_count 類別總數爲cluster_count
        //樣本矩陣爲points,先按類別分成cluster_count份 每一份數據的個數爲sample_count/cluster_count
        //然後按類別隨機矩陣填充樣本矩陣,第一類填充矩陣的0~sample_count/cluster_count行
        //第二類填充樣本矩陣的sample_count/cluster_count~sample_count/cluster_count*2行,以此類推直到填充滿所有矩陣,每一類的樣本個數是一樣的
        for(k=0;k < cluster_count;k++)
        {
            CvPoint center;  //爲圖像中的隨機點
            CvMat point_chunk;
            center.x = cvRandInt(&rng)%img->width;
            center.y = cvRandInt(&rng)%img->height;
            //返回數組在一定跨度的行
            //points爲輸入數組,point_chunk返回數組
            //開始行爲k*sample_count/cluster_count
            //結束行爲 當k== cluster-1時 爲 第samplecount行 否則爲(k+1)*sample_count/cluster_count
            cvGetRows(points,&point_chunk,k*sample_count/cluster_count,
                      k == cluster_count -1? sample_count:
                      (k+1)*sample_count/cluster_count);
            //point_chunk輸出數組,CV_RAND_NORMAL分佈類型爲正態分佈或者高斯分佈
            //cvScalar(center.x,center.y,0,0)隨機數的平均值
            // cvScalar(img->width/6,img->height/6,0,0)如果是正態分佈它是隨機數的標準差
            cvRandArr(&rng,&point_chunk,CV_RAND_NORMAL,
                      cvScalar(center.x,center.y,0,0),
                      cvScalar(img->width/6,img->height/6,0,0));
        }
        //樣本重新排序
        for(i=0;i<sample_count/2;i++)
        {
            //在points樣本矩陣中隨機取兩個樣本交換位置
            CvPoint2D32f* pt1=(CvPoint2D32f*)points->data.fl+cvRandInt(&rng)%sample_count;
            CvPoint2D32f* pt2=(CvPoint2D32f*)points->data.fl+cvRandInt(&rng)%sample_count;
            CvPoint2D32f temp;
            CV_SWAP(*pt1,*pt2,temp);
        }
        //points樣本矩陣,cliuster_count分類數,輸出向量clusters,最後一個參數指定精度
        cvKMeans2(points,cluster_count,clusters,cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER,10,1.0));
        cvZero(img);
        for(i=0;i<sample_count;i++)
        {
            CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i];
            int cluster_idx = clusters->data.i[i];
            cvCircle(img,cvPointFrom32f(pt),2, color_table[cluster_idx],CV_FILLED);
        }
        cvReleaseMat(&points);
        cvReleaseMat(&clusters);
        cvShowImage("clusters",img);
        int key = cvWaitKey(0);
        if(key==27)
            break;
    }

    return a.exec();
}





在這段代碼中,包含highgui來使用窗口輸出,包含cxcore是因爲它包含了Kmean2().在main函數中,我們設置了放回的類別顯示的顏色;設置類別個數上界MAX_CLUSTERS(這是5),類別的個數是隨機產生的,存儲在cluster_count中;設置數據樣本的個數的上界(1000),數據樣本的個數也是隨機產生的,被存儲在sample_count中.在最外層循環for{]中,我們分配了一個浮點數雙通道矩陣poing來存儲sample_count個數據樣本,我們還分配了一個整型矩陣clusters來存儲數據樣本的聚類標籤,從0~cluster_count-1.

然後我們進入數據生成for{}循環,這個循環可以用於其他算法中使用.我們給每個類別填寫sample_count/cluster_count個數據樣本,這些2維數據樣本服從正態分佈,正態分佈的中心是隨機選擇的.

下一個for{}循環僅僅打亂了數據樣本的順序.然後我們使用cvKMean2(),直到聚類中心的最大移動小於1.

最後的for{}循環話出結果,在圖中顯示.然後釋放數組,等待用戶的輸入進入下一次計算或者Esc鍵退出.

發佈了57 篇原創文章 · 獲贊 95 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章