? ? KMeans算法作為數(shù)據(jù)聚類的重要算法之一,通過(guò)被廣泛的應(yīng)用。本文將調(diào)用OPENCV的工具包來(lái)對(duì)一些隨機(jī)數(shù)進(jìn)行聚類,并將結(jié)果通過(guò)OPENCV進(jìn)行可視化。 本實(shí)驗(yàn)主要是生成一些二維樣本數(shù)據(jù),每個(gè)樣本數(shù)據(jù)都服從高斯分布,我們將通過(guò)KMeans算法來(lái)對(duì)結(jié)果進(jìn)行分類。
? ??KMeans算法的基本流程
? ? 1.確定參數(shù)K,也就是需要將數(shù)據(jù)分成幾個(gè)類別
? ? 2隨機(jī).初始化K個(gè)中心坐標(biāo)點(diǎn)
? ? 3.根據(jù)每個(gè)數(shù)據(jù)樣本與中心點(diǎn)的距離來(lái)判斷,選擇距離最近的中心類別,分配聚類的編號(hào)
? ?4.對(duì)于相同類別的樣本數(shù)據(jù)重新計(jì)算每個(gè)樣本到中心點(diǎn)的距離,取均值來(lái)調(diào)整中心坐標(biāo)點(diǎn)的位置
????5.判斷算法是否到達(dá)了最大迭代次數(shù)或者精度,如果到達(dá)說(shuō)明數(shù)據(jù)聚類完成,沒有到達(dá)條件時(shí)將會(huì)繼續(xù)計(jì)算不停地調(diào)整中心坐標(biāo)點(diǎn)

了解完基本思想后我們將通過(guò)代碼來(lái)演示KMeans算法對(duì)數(shù)據(jù)聚類的效果,同時(shí)對(duì)相關(guān)API函數(shù)進(jìn)行介紹
第一步我們先使用了OPENCV的Mat數(shù)據(jù)結(jié)構(gòu)來(lái)創(chuàng)建了一個(gè)畫板,我們由下圖的代碼可以看到,創(chuàng)建了一個(gè)長(zhǎng)500,寬500的畫板,同時(shí)對(duì)圖像的文件格式做了設(shè)定。在這里簡(jiǎn)單的提一下CvMat矩陣對(duì)應(yīng)的參數(shù)類型。一般的圖像文件格式使用的是 Unsigned 8bits吧,CvMat矩陣對(duì)應(yīng)的參數(shù)類型就是CV_8UC1,CV_8UC2,CV_8UC3。(最后的1、2、3表示通道數(shù),譬如RGB3通道就用CV_8UC3)。在這里我們使用了CV_8UC3創(chuàng)建了一張3通道的圖像,下面的步驟中我們需要對(duì)不同的數(shù)據(jù)類別指定不同的顏色,因此采用了3通道的圖像文件格式,當(dāng)然使用灰度圖也是沒有問(wèn)題的。

第二步生成隨機(jī)樣本,程序中采用RNG隨機(jī)模塊來(lái)進(jìn)行數(shù)據(jù)的隨機(jī)初始化,在此來(lái)介紹幾個(gè)程序中用到的初始化模塊。
(1)void?RNG::fill(InputOutputArray?mat, int?distType, InputArray?a, InputArray?b, bool?saturateRange=false?)
這個(gè)函數(shù)是對(duì)矩陣mat填充隨機(jī)數(shù),隨機(jī)數(shù)的產(chǎn)生方式有參數(shù)2來(lái)決定,如果為參數(shù)2的類型為RNG::UNIFORM,則表示產(chǎn)生均一分布的隨機(jī)數(shù),如果為RNG::NORMAL則表示產(chǎn)生高斯分布的隨機(jī)數(shù)。對(duì)應(yīng)的參數(shù)3和參數(shù)4為上面兩種隨機(jī)數(shù)產(chǎn)生模型的參數(shù)。比如說(shuō)如果隨機(jī)數(shù)產(chǎn)生模型為均勻分布,則參數(shù)a表示均勻分布的下限,參數(shù)b表示上限。如果隨機(jī)數(shù)產(chǎn)生模型為高斯模型,則參數(shù)a表示均值,參數(shù)b表示方程。參數(shù)5只有當(dāng)隨機(jī)數(shù)產(chǎn)生方式為均勻分布時(shí)才有效,表示的是是否產(chǎn)生的數(shù)據(jù)要布滿整個(gè)范圍(沒用過(guò),所以也沒仔細(xì)去研究)。另外,需要注意的是用來(lái)保存隨機(jī)數(shù)的矩陣mat可以是多維的,也可以是多通道的,目前最多只能支持4個(gè)通道。
(2)void?randShuffle(InputOutputArray?dst, double?iterFactor=1.,?RNG*?rng=0?)
該函數(shù)表示隨機(jī)打亂1D數(shù)組dst里面的數(shù)據(jù),隨機(jī)打亂的方式由隨機(jī)數(shù)發(fā)生器rng決定。iterFactor為隨機(jī)打亂數(shù)據(jù)對(duì)數(shù)的因子,總共打亂的數(shù)據(jù)對(duì)數(shù)為:dst.rows*dst.cols*iterFactor,因此如果為0,表示沒有打亂數(shù)據(jù)。
初始化隨機(jī)樣本核心代碼
int numCluster = 5;
int sampleCount = rng.uniform(5, 500);
Mat points(sampleCount, 1, CV_32FC2);
Mat labels;
Mat centers;
for (int k = 0;k < numCluster;k++)
{
Point center;
center.x = rng.uniform(0, img.cols);
center.y = rng.uniform(0,img.rows);
Mat pointDome = points.rowRange(k*sampleCount / numCluster, (k + 1)*sampleCount / numCluster);
rng.fill(points,rng.uniform,0,500);
}
randShuffle(points,1,&rng);
第三步我們將對(duì)構(gòu)建出來(lái)的隨機(jī)樣本進(jìn)行聚類,采用KMeans算法,對(duì)此我們對(duì)KMeans算法用到的API函數(shù)進(jìn)行介紹。
Class TermCriteria
類TermCriteria 一般表示迭代終止的條件,如果為CV_TERMCRIT_ITER,則用最大迭代次數(shù)作為終止條件,如果為CV_TERMCRIT_EPS?則用精度作為迭代條件,如果為CV_TERMCRIT_ITER+CV_TERMCRIT_EPS則用最大迭代次數(shù)或者精度作為迭代條件,看哪個(gè)條件先滿足。
double?kmeans(InputArray?data, int?K, InputOutputArray?bestLabels, TermCriteria?criteria, int?attempts, int?flags, OutputArray?centers=noArray()?)
該函數(shù)為kmeans聚類算法實(shí)現(xiàn)函數(shù)。參數(shù)data表示需要被聚類的原始數(shù)據(jù)集合,一行表示一個(gè)數(shù)據(jù)樣本,每一個(gè)樣本的每一列都是一個(gè)屬性;參數(shù)k表示需要被聚類的個(gè)數(shù);參數(shù)bestLabels表示每一個(gè)樣本的類的標(biāo)簽,是一個(gè)整數(shù),從0開始的索引整數(shù);參數(shù)criteria表示的是算法迭代終止條件;參數(shù)attempts表示運(yùn)行kmeans的次數(shù),取結(jié)果最好的那次聚類為最終的聚類,要配合下一個(gè)參數(shù)flages來(lái)使用;參數(shù)flags表示的是聚類初始化的條件。其取值有3種情況,如果為KMEANS_RANDOM_CENTERS,則表示為隨機(jī)選取初始化中心點(diǎn),如果為KMEANS_PP_CENTERS則表示使用某一種算法來(lái)確定初始聚類的點(diǎn);如果為KMEANS_USE_INITIAL_LABELS,則表示使用用戶自定義的初始點(diǎn),但是如果此時(shí)的attempts大于1,則后面的聚類初始點(diǎn)依舊使用隨機(jī)的方式;參數(shù)centers表示的是聚類后的中心點(diǎn)存放矩陣。該函數(shù)返回的是聚類結(jié)果的緊湊性
第四步將對(duì)分類出來(lái)的進(jìn)行顯示
// 用不同顏色顯示分類
img = Scalar::all(255);
for (int i = 0; i < sampleCount; i++) {
int index = labels.at<int>(i);
Point p = points.at<Point2f>(i);
circle(img, p, 2, colorTab[index], -1, 8);
}
// 每個(gè)聚類的中心來(lái)繪制圓
for (int i = 0; i < centers.rows;? i++) {
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
printf("c.x= %d, c.y=%d", x, y);
circle(img, Point(x, y), 40, colorTab[i], 1, LINE_AA);
}
imshow("KMeans-Data-Demo", img);
最終代碼:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat img(500, 500, CV_8UC3);
RNG rng(12345);
Scalar colorTab[] = {
Scalar(0, 0, 255),
Scalar(0, 255, 0),
Scalar(255, 0, 0),
Scalar(0, 255, 255),
Scalar(255, 0, 255)
};
int numCluster = rng.uniform(2, 5);
printf("number of clusters : %d\n", numCluster);
int sampleCount = rng.uniform(5, 1000);
Mat points(sampleCount, 1, CV_32FC2);
Mat labels;
Mat centers;
// 生成隨機(jī)數(shù)
for (int k = 0; k < numCluster; k++) {
Point center;
center.x = rng.uniform(0, img.cols);
center.y = rng.uniform(0, img.rows);
Mat pointChunk = points.rowRange(k*sampleCount / numCluster,
k == numCluster - 1 ? sampleCount : (k + 1)*sampleCount / numCluster);
rng.fill(pointChunk, RNG::NORMAL, Scalar(center.x, center.y), Scalar(img.cols*0.05, img.rows*0.05));
}
randShuffle(points, 1, &rng);
// 使用KMeans
kmeans(points, numCluster, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1), 3, KMEANS_PP_CENTERS, centers);
// 用不同顏色顯示分類
img = Scalar::all(255);
for (int i = 0; i < sampleCount; i++) {
int index = labels.at<int>(i);
Point p = points.at<Point2f>(i);
circle(img, p, 2, colorTab[index], -1, 8);
}
// 每個(gè)聚類的中心來(lái)繪制圓
for (int i = 0; i < centers.rows;? i++) {
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
printf("c.x= %d, c.y=%d", x, y);
circle(img, Point(x, y), 40, colorTab[i], 1, LINE_AA);
}
imshow("KMeans-Data-Demo", img);
waitKey(0);
return 0;
}
運(yùn)行效果:
