kmeans爲無監督聚類最重要的算法,本文用kmeans算法對圖像進行分割。算法原理參考:
https://blog.csdn.net/u013719780/article/details/51755124
https://blog.csdn.net/qq_36134318/article/details/80408658
https://blog.csdn.net/xiligey1/article/details/82457271
https://blog.csdn.net/wangxiaopeng0329/article/details/53542606
以上文章對Kmeans解釋得很清楚,這裏我主要說一下實例代碼。
核心思想:kmeans以k爲參數,把樣本分爲k個族(對於圖像,每個像素點灰度值就是樣本),使族內具有較高的相似度,而族與族之間相似度較低。
核心步驟:假如要分爲2類,則
一:隨機定義2個中心點,P1與P2。 並且P1代表A族,P2代表B族。
二:所有像素點值分別與這2箇中心點計算距離(用歐式距離),與哪個中心點最近,則該像素點屬於該中心點所對應的族
三:通過二:把圖像所有像素點都分爲A和B兩類。屬於A類的像素點計算中心位置,該中心位置就是更新的P1。屬於B類的像素點計算中心位置,該中心位置就是更新的P2。
四:回到第二步,知道滿足結束條件,即循環次數大於閾值,或者中心點位置移動的距離小於閾值。
注意:對於數據來說,計算距離就是二維空間X,Y的歐式距離
但是對於圖像而言,計算距離是三維空間的歐式距離,X,Y值代表像素點的位置,Z軸代表該像素點的灰度值。
由此可見,基於kmeans的圖像分割,可以看作爲顏色分割,即把相似顏色(相似灰度值)分爲一類。
下面是opencv C++代碼
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat SrcImage = imread("C:/Users/zhang/Desktop/1.png");
imshow("原圖", SrcImage);
int width = SrcImage.cols;//圖像寬
int height = SrcImage.rows;//圖像高
int dims = SrcImage.channels();//圖像通道數
//kmeans的輸入數據:對於圖像而言,每一個像素點都是一個數據,
int sampleCount = width * height;//圖像數據點的個數
int clusterCount = 3;//分類數目
Mat points(sampleCount, dims, CV_32F, Scalar(10));//輸入數據,與原圖像有着相同通道
Mat labels;//輸出數據,爲各個數據點最終的分類索引
Mat centers(clusterCount, 1, points.type());//每個分類的中心點
Scalar colorbar[] =//每個分類的顏色
{
Scalar(0,0,255),
Scalar(203,192,255),
Scalar(255,0,255),
Scalar(0,255,0)
};
////RGB數據轉換到樣本數據
int index = 0;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
index = row * width + col;//每個像素點
//把RGB圖像的三個通道的各個像素點值分別賦給points的三個通道
Vec3b rgb = SrcImage.at<Vec3b>(row, col);
points.at<float>(index, 0) = static_cast<int>(rgb[0]);
points.at<float>(index, 1) = static_cast<int>(rgb[1]);
points.at<float>(index, 2) = static_cast<int>(rgb[2]);
}
}
TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0);
//10代表最大循環數目,1.0代表閾值
kmeans(points, clusterCount, labels, criteria, 3, KMEANS_PP_CENTERS, centers);
//第一個參數:表示輸入的數據集合,可以一維或者多維數據,類型是Mat類型,
//比如Mat points(count, 2, CV_32F)表示數據集合是二維,浮點數數據集;
//第二個參數:表示分類的數目,K = 2時即表示二分類;
//第三個參數:表示計算之後各個數據點的最終的分類索引,是一個INT類型的Mat對象,類型和長寬與原圖像一致
//第四個參數:表示算法終止的條件,達到最大循環數目或者指定的精度閾值算法就停止繼續分類迭代計算;
//第五個參數:表示爲了獲得最佳的分類效果,算法要不同的初始分類嘗試次數;
//第六個參數:表示表示選擇初始中心點選擇方法用哪一種方法:
//KMEANSRANDOMCENTERS 表示隨機選擇中心點
//KMEANSPPCENTERS 基於中心化算法選擇
//KMEANSUSEINITIAL_LABELS第一次分類中心點用輸入的中心點;
//第七個參數:表示輸出的每個分類的中心點數據;
//顯示圖像分割結果//
Mat result = Mat::zeros(SrcImage.size(), SrcImage.type());
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
index = row * width + col;//每個像素點
int label = labels.at<int>(index, 0);////每個像素點的標籤
//把每個像素點對應的標籤所對應的顏色賦給新圖像
result.at<Vec3b>(row, col)[0] = colorbar[label][0];
result.at<Vec3b>(row, col)[1] = colorbar[label][1];
result.at<Vec3b>(row, col)[2] = colorbar[label][2];
}
}
imshow("聚類結果", result);
imwrite("C:/Users/zhang/Desktop/xiannv5.png", result);
//centers按行保存着每個分類的終點座標,每一行有一個座標
for (int i = 0; i<centers.rows; i++)
{
int x = centers.at<float>(i, 0);
int y = centers.at<float>(i, 1);
cout << "x="<<x << "," << "y=" << y << endl;
}
waitKey(0);
return 0;
}
結果:
kmeans的進階:
1:如何解決 開始隨機定義中心點可能陷入局部最佳解的問題?
2:如何判斷圖片應該分爲幾類?
3:如何評估模型好壞?
以上三個問題,可以參考上面的鏈接
kmeans的缺點:kmeans能把所有樣本都找到歸屬的族,這就造成了過度分割,即不能消除噪聲。