官方源代碼中有一點瑕疵,高斯分布產生的隨機點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; }