基礎學習筆記之opencv(18):kmeans函數使用實例


 

  前言

  一提到聚類算法,必然首先會想到的是kmeans聚類,因為它的名氣實在太大了。既然這樣,OpenCV中這個函數也自然必不可少了。這節內容主要是講講OpenCV中kmeans函數的使用方法。

  開發環境:OpenCV2.4.3+QtCreator2.5.1

 

  實驗基礎

  在使用kmeans之前,必須先了解kmeans算法的2個缺點:第一是必須人為指定所聚的類的個數k;第二是如果使用歐式距離來衡量相似度的話,可能會得到錯誤的結果,因為沒有考慮到屬性的重要性和相關性。為了減少這種錯誤,在使用kmeans距離時,一定要使樣本的每一維數據歸一化,不然的話由於樣本的屬性范圍不同會導致錯誤的結果。

  本次實驗是對隨機產生的sampleCount個二維樣本(共分為clusterCount個類別),每個類別的樣本數據都服從高斯分布,該高斯分布的均值是隨機的,方差是固定的。然后對這sampleCount個樣本數據使用kmeans算法聚類,最后將不同的類用不同的顏色顯示出來。

  下面是程序中使用到的幾個OpenCV函數:

  void RNG::fill(InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false )

  這個函數是對矩陣mat填充隨機數,隨機數的產生方式有參數2來決定,如果為參數2的類型為RNG::UNIFORM,則表示產生均一分布的隨機數,如果為RNG::NORMAL則表示產生高斯分布的隨機數。對應的參數3和參數4為上面兩種隨機數產生模型的參數。比如說如果隨機數產生模型為均勻分布,則參數a表示均勻分布的下限,參數b表示上限。如果隨機數產生模型為高斯模型,則參數a表示均值,參數b表示方程。參數5只有當隨機數產生方式為均勻分布時才有效,表示的是是否產生的數據要布滿整個范圍(沒用過,所以也沒仔細去研究)。另外,需要注意的是用來保存隨機數的矩陣mat可以是多維的,也可以是多通道的,目前最多只能支持4個通道。

  void randShuffle(InputOutputArray dst, double iterFactor=1., RNG* rng=0 )

  該函數表示隨機打亂1D數組dst里面的數據,隨機打亂的方式由隨機數發生器rng決定。iterFactor為隨機打亂數據對數的因子,總共打亂的數據對數為:dst.rows*dst.cols*iterFactor,因此如果為0,表示沒有打亂數據。

  Class TermCriteria

  類TermCriteria 一般表示迭代終止的條件,如果為CV_TERMCRIT_ITER,則用最大迭代次數作為終止條件,如果為CV_TERMCRIT_EPS 則用精度作為迭代條件,如果為CV_TERMCRIT_ITER+CV_TERMCRIT_EPS則用最大迭代次數或者精度作為迭代條件,看哪個條件先滿足。

  double kmeans(InputArray data, int K, InputOutputArray bestLabels, TermCriteria criteria, int attempts, int flags, OutputArray centers=noArray() )

  該函數為kmeans聚類算法實現函數。參數data表示需要被聚類的原始數據集合,一行表示一個數據樣本,每一個樣本的每一列都是一個屬性;參數k表示需要被聚類的個數;參數bestLabels表示每一個樣本的類的標簽,是一個整數,從0開始的索引整數;參數criteria表示的是算法迭代終止條件;參數attempts表示運行kmeans的次數,取結果最好的那次聚類為最終的聚類,要配合下一個參數flages來使用;參數flags表示的是聚類初始化的條件。其取值有3種情況,如果為KMEANS_RANDOM_CENTERS,則表示為隨機選取初始化中心點,如果為KMEANS_PP_CENTERS則表示使用某一種算法來確定初始聚類的點;如果為KMEANS_USE_INITIAL_LABELS,則表示使用用戶自定義的初始點,但是如果此時的attempts大於1,則后面的聚類初始點依舊使用隨機的方式;參數centers表示的是聚類后的中心點存放矩陣。該函數返回的是聚類結果的緊湊性,其計算公式為:

  

 

  實驗結果

  隨機產生的符合高斯分布的數據被聚類的結果一:

  

 

  結果二:

  

 

  實驗代碼及注釋:

  main.cpp:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core.hpp"
#include <iostream>

using namespace cv;
using namespace std;

// static void help()
// {
//     cout << "\nThis program demonstrates kmeans clustering.\n"
//             "It generates an image with random points, then assigns a random number of cluster\n"
//             "centers and uses kmeans to move those cluster centers to their representitive location\n"
//             "Call\n"
//             "./kmeans\n" << endl;
// }

int main( int /*argc*/, char** /*argv*/ )
{
    const int MAX_CLUSTERS = 5;
    Scalar colorTab[] =     //因為最多只有5類,所以最多也就給5個顏色
    {
        Scalar(0, 0, 255),
        Scalar(0,255,0),
        Scalar(255,100,100),
        Scalar(255,0,255),
        Scalar(0,255,255)
    };

    Mat img(500, 500, CV_8UC3);
    RNG rng(12345); //隨機數產生器

    for(;;)
    {
        int k, clusterCount = rng.uniform(2, MAX_CLUSTERS+1);
        int i, sampleCount = rng.uniform(1, 1001);
        Mat points(sampleCount, 1, CV_32FC2), labels;   //產生的樣本數,實際上為2通道的列向量,元素類型為Point2f

        clusterCount = MIN(clusterCount, sampleCount);
        Mat centers(clusterCount, 1, points.type());    //用來存儲聚類后的中心點

        /* generate random sample from multigaussian distribution */
        for( k = 0; k < clusterCount; k++ ) //產生隨機數
        {
            Point center;
            center.x = rng.uniform(0, img.cols);
            center.y = rng.uniform(0, img.rows);
            Mat pointChunk = points.rowRange(k*sampleCount/clusterCount,
                                             k == clusterCount - 1 ? sampleCount :
                                             (k+1)*sampleCount/clusterCount);   //最后一個類的樣本數不一定是平分的,
                                                                                //剩下的一份都給最后一類
            //每一類都是同樣的方差,只是均值不同而已
            rng.fill(pointChunk, CV_RAND_NORMAL, Scalar(center.x, center.y), Scalar(img.cols*0.05, img.rows*0.05));
        }

        randShuffle(points, 1, &rng);   //因為要聚類,所以先隨機打亂points里面的點,注意points和pointChunk是共用數據的。

        kmeans(points, clusterCount, labels,
               TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0),
               3, KMEANS_PP_CENTERS, centers);  //聚類3次,取結果最好的那次,聚類的初始化采用PP特定的隨機算法。

        img = Scalar::all(0);

        for( i = 0; i < sampleCount; i++ )
        {
            int clusterIdx = labels.at<int>(i);
            Point ipt = points.at<Point2f>(i);
            circle( img, ipt, 2, colorTab[clusterIdx], CV_FILLED, CV_AA );
        }

        imshow("clusters", img);

        char key = (char)waitKey();     //無限等待
        if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
            break;
    }

    return 0;
}

 

 

  實驗總結:

  Kmeans算法的速度還是夠快的,在指定類別個數的情況下效果還是可以的。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM