kmeans聚類分析、隨機數生成、源代碼的修改(保證隨機點在500*500矩陣內)(OpenCV案例源碼kmeans.cpp解讀)


官方源代碼中有一點瑕疵,高斯分布產生的隨機點points的坐標可能出現負數或大於500的數。如橫坐標均值是0,方差是25,那么橫坐標隨機值中會出現負數。

修改了兩處:隨機數生成種子是時間、隨機點points坐標保證在500*500以內。

【知識點1】聚類函數

double kmeans( InputArray data, int K, InputOutputArray bestLabels,TermCriteria criteria, int attempts,int flags, OutputArray centers = noArray() );
data——原始數據集,行為樣本列為特征。
K——聚類的類別數。
bestLabels——每個樣本所屬的類別標簽,從0開始,樣本數行1列的矩陣。
criteria——迭代終止條件,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0),10次迭代、期望准確率1.0
attempts——運行kmeans的次數,取結果最好的那次聚類為最終的聚類。
flags——聚類中心初始化
    KMEANS_RANDOM_CENTERS 隨機初始化
    KMEANS_USE_INITIAL_LABELS 第一次初始化使用用戶設定的,之后使用隨機的(random or semi-random centers)。
    KMEANS_PP_CENTERS 算法kmeans++的center
centers——最終最優的聚類的中心。

返回值——點的緊湊程度,越小越緊湊。

【知識點2】隨機數生成

RNG rng(getTickCount());  //隨時間每次產生的隨機數都不同(起始種子不同),如果RNG rng(12345); 每次運行,隨機數都一樣。
rng.uniform(2, 6); //指定范圍內產生隨機數,左閉右開。最多5類。
rng.fill(pointChunk, RNG::NORMAL, Scalar(mean.x, mean.y), Scalar(25, 25)); //高斯分布RNG::NORMAL,等效下句代碼
randn(pointChunk, Scalar(mean.x, mean.y), Scalar(25, 25)); //高斯分布的隨機數,被填充的矩陣、均值、方差
randShuffle(points); //打亂points矩陣中元素(坐標)順序

其他參考https://blog.csdn.net/qq_33485434/article/details/78980587

【難理解點講解】pointChunk等分points,每一份的數據均值隨機[25,475)、方差25

Mat pointChunk = points.rowRange(k*sampleCount / clusterCount, k == clusterCount - 1 ? sampleCount : (k + 1)*sampleCount / clusterCount);

案例中這句難理解,Mat a=b;是淺拷貝,a、b指向同一內存,其一改變,都改變。所以操作pointChunk,就是處理points。

樣本被等分成“sampleCount / clusterCount”份,當然有可能不完美等分,前n-1份一定是個數相同的,最后一份個數會少於其他。

所以前n-1份,points.rowRange(k*sampleCount / clusterCount, (k + 1)*sampleCount / clusterCount); ,即第k份的頭和第k份的尾。

最后一份,points.rowRange(k*sampleCount / clusterCount, sampleCount);,即最后一份的頭和所有樣本的尾。

#include<opencv2\opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

int main()
{
    //5種顏色,因為最多分5類
    Scalar colorTab[] =
    {
        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);//3通道
    RNG rng(getTickCount()); //隨時間每次產生的隨機數都不同(起始種子不同),如果RNG rng(12345); 每次運行,隨機數都一樣。

    for (;;)
    {
        int k, clusterCount = rng.uniform(2, 6);//指定范圍內產生隨機數,左閉右開。最多5類。
        int i, sampleCount = rng.uniform(1, 1001);
        Mat points(sampleCount, 1, CV_32FC2), labels;//樣本點,一列,2通道
        clusterCount = MIN(clusterCount, sampleCount);//分類數與樣本數的最小值
        std::vector<Point2f> centers;//中心坐標容器

        //生成隨機點,pointChunk是points被等分的局部矩陣,每一份均值隨機[25,475),方差25
        for (k = 0; k < clusterCount; k++)
        {
            //淺拷貝,pointChunk與points同一內存
            Mat pointChunk = points.rowRange(k*sampleCount / clusterCount, k == clusterCount - 1 ? sampleCount : (k + 1)*sampleCount / clusterCount);
            Point mean;//均值
            mean.x = rng.uniform(0+25, img.cols-25);//由於randn中方差是25,所以均值隨機范圍增減了25,防止pointChunk點坐標出現負數或大於500的數
            mean.y = rng.uniform(0+25, img.rows-25);
            randn(pointChunk, Scalar(mean.x, mean.y), Scalar(25, 25));//高斯分布的隨機數,被填充的矩陣、均值、方差
            //rng.fill(pointChunk, RNG::NORMAL, Scalar(mean.x, mean.y), Scalar(25, 25));//等效上句代碼
        }
        randShuffle(points);//打亂points內容
        //聚類分析
        double compactness = kmeans(points, clusterCount, labels,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0),3, KMEANS_PP_CENTERS, centers);
        //黑色圖
        img = Scalar::all(0);//img三通道圖,值全是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],FILLED);//畫圓,圖、圓心、半徑、顏色、實心填充
        }
        //畫圓
        for (i = 0; i < (int)centers.size(); ++i)
        {
            Point2f c = centers[i];
            circle(img, c, 40, colorTab[i], 2, LINE_AA);//畫圓,圖、圓心、半徑、顏色、線條粗細與細膩程度(16連通)
        }
        cout << "Compactness: " << compactness << endl;//輸出返回值,緊湊度,越小越緊湊

        imshow("clusters", img);

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

    return 0;
}

 


免責聲明!

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



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