【OpenCv3】 VS C++ (六):Kmeans分類算法

上一節地址:https://blog.csdn.net/qq_40515692/article/details/102750516
OpenCv專欄:https://blog.csdn.net/qq_40515692/article/details/102885061

網上Kmeans分類應用在超像素基礎上圖像的代碼比較少,也有一些人在問。

這一節講的是在超像素基礎上用C++實現Kmeans分類,代碼根據Kmeans算法編寫,供大家參考,技術水平有限,歡迎大家一起討論。

還是上一節的圖,這一次這個圖就十分符合了(除了維度不同)。

如下圖所示,假設有兩個隨機點(紅點、藍點),一系列像素點(綠色),首先對每個像素點計算應該歸屬與哪一個隨機點、分類(如圖片b、c所示),然後讓紅點和藍點移動到中心不斷重複,最終成功劃分。
在這裏插入圖片描述

一、分析

  • 上一節中,我們得到了什麼呢?

得到了一張超像素圖片(廢話),對於每一個像素,都有其對應的超像素。那麼我們是不是對超像素分好類就對這張圖片分割了呢?

  • 貌似是的,那爲什麼不直接對每個像素分類呢?整那麼多花裏花俏東西。

因爲通過超像素,我們比較完整的保留了像素的各種信息,並減小了問題規模,比如:相鄰的像素點顏色有些差距,和不相鄰的像素點顏色有些差距,在上一節的處理過程中,會更大可能的將相鄰的像素分爲一類,這樣在這一節的處理過程中可以更好的進行分類。(如果有更好的想法,歡迎評論指出)

  • 那麼具體步驟是怎樣的呢?
  1. 首先,去他的像素。現在你就想着你只能接觸到超像素,我們的任務就是對超像素分類。
  2. 然後我們觀察上面那一張kmeans分類圖(b)。我們假設綠點就是我們的超像素,假設我們分割兩張圖片那麼紅點和藍點就是代表我們分割的圖片。(準確的說還包括分完類得到的綠點)
  3. 現在假設一個具體情況,分割5張圖片,那麼就需要初始化隨機的5個點,把上一節得到的超像素按照屬性(顏色、位置等)分到空間內。根據Kmeans算法,劃分這些超像素。是不是比較符合呀

二、初始化,完成大致框架

定義好需要的變量,這裏我們的聚類中心點(就是圖中的藍點、紅點)保存了三個值(三維),LAB三個通道,因爲這次分割我打算只根據超像素點的顏色分割。

#define wash_pic 5									// 期望分割數
Mat pic[wash_pic];									// 保存每張分割圖片
struct cluster2 {
	int l, a, b;
};
cluster2 clusters_pic[wash_pic];					// 存儲聚類中心點的lab
int label2[sqrtK * sqrtK];							// 圖像各超像素點歸屬

然後就是大致框架了,

  1. 首先我們對每一個聚類中心點(就是圖中的藍點、紅點)初始化了三個隨機值。
  2. 然後按照Kmeans步驟不斷更新聚類中心點的LAB值,
  3. 最後根據聚類中心點繪製每一張分割圖片。
int main() {
	//////////// 上面還有上一節的代碼,記得去掉上一句 waitKey(0); ////////////
	Mat temp_mat(src.size(), src.type(), Scalar(255, 255, 255));
	cvtColor(temp_mat, temp_mat, COLOR_BGR2Lab);
	for (int i = 0; i < wash_pic; i++) {
		pic[i] = temp_mat.clone();		// 初始化每一張保存分割的Mat爲白色空白圖片
		clusters_pic[i].l = rand() % (256);
		clusters_pic[i].a = rand() % (256);
		clusters_pic[i].b = rand() % (256);
	}
	
	for (int i = 0; i < 5; i++) {
		for (int i = 0; i < wash_pic; i++) {
			cout << clusters_pic[i].l << " " << clusters_pic[i].a << " " << clusters_pic[i].b << " " << endl;;
		}
		cout << endl;
	
		// 1.初始化數據
		update_pixel2(lab);
		cout << "1-初始化數據-完成\n";
	
		// 2.讓超像素位於正中間
		updaye_clusters2(lab);
		cout << "2-讓超像素位於正中間-完成\n";
	}
	
	// 3.顯示分割結果
	draw_segment(lab);
	waitKey(0);
}

三、各函數實現

1.update_pixel2函數

首先我們還是先根據上面說的實現距離計算函數吧,這次就更簡單了。

inline int get_distance2(const Mat lab, int clusters_index, int l, int a, int b) {
	int dl = clusters[clusters_index].l - l;
	int da = clusters[clusters_index].a - a;
	int db = clusters[clusters_index].b - b;
	return dl * dl + da * da + db * db;;
}

然後就是實現update_pixel2函數,它根據”距離“更新label2[i]。

void update_pixel2(const Mat lab) {
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		int min_dis = 99999999;
		for (int k = 0; k < wash_pic; k++) {
			int new_dis = get_distance2(lab, i, clusters_pic[k].l, clusters_pic[k].a, clusters_pic[k].b);
			if (min_dis > new_dis) {
				min_dis = new_dis;
				label2[i] = k;
			}
		}
	}
}

2.updaye_clusters2函數

然後是實現使聚類中心點(就是圖中的藍點、紅點)位於中心的函數。就是計算所屬的點的LAB的平均值。

void updaye_clusters2(const Mat lab) {
	int* sum_count = new int[wash_pic]();
	int* sum_l = new int[wash_pic]();
	int* sum_a = new int[wash_pic]();
	int* sum_b = new int[wash_pic]();
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		sum_count[label2[i]]++;
		int row = clusters[i].row;
		int col = clusters[i].col;
		sum_l[label2[i]] += lab.at<Vec3b>(row, col)[0];
		sum_a[label2[i]] += lab.at<Vec3b>(row, col)[1];
		sum_b[label2[i]] += lab.at<Vec3b>(row, col)[2];
	}
	for (int i = 0; i < wash_pic; i++) {
		if (sum_count[i] == 0)
			continue;
		clusters_pic[i].l = sum_l[i] / sum_count[i];
		clusters_pic[i].a = sum_a[i] / sum_count[i];
		clusters_pic[i].b = sum_b[i] / sum_count[i];
	}
	delete[] sum_count;
	delete[] sum_l;
	delete[] sum_a;
	delete[] sum_b;
}

3. draw_segment函數

最後就是把結果畫出來了。

void draw_segment(const Mat lab){
	for (int i = 0; i < sqrtN; i++) {					// 對於每一個像素
		for (int j = 0; j < sqrtN; j++) {
			int k = label2[label[i][j]];
			// 繪製實際的圖像
			pic[k].at<Vec3b>(i, j)[0] = lab.at<Vec3b>(i, j)[0];
			pic[k].at<Vec3b>(i, j)[1] = lab.at<Vec3b>(i, j)[1];
			pic[k].at<Vec3b>(i, j)[2] = lab.at<Vec3b>(i, j)[2];
			// 根據聚類中心點的LAB來畫
			/*pic[k].at<Vec3b>(i, j)[0] = clusters_pic[k].l;
			pic[k].at<Vec3b>(i, j)[1] = clusters_pic[k].a;
			pic[k].at<Vec3b>(i, j)[2] = clusters_pic[k].b;*/
		}
	}
	
	for (int i = 0; i < wash_pic; i++) {
		cvtColor(pic[i], pic[i], COLOR_Lab2BGR);
		imshow(to_string(i), pic[i]);
	}
}

三、和上一節一起的完整代碼

這是我這裏的完整代碼,微調中心點的代碼沒講,還有一些參考別人github改進算法前的代碼的註釋,和博客裏面的代碼運行基本沒區別。

#include <opencv2/opencv.hpp>
#include <iostream>  

using namespace cv;
using namespace std;

#define wash_pic 5		// 期望分割數
#define sqrtK 256		// 超像素個數sqrt
#define weight 100		// 距離佔比
#define sqrtN 512		// resize格式512*512
#define DEBUG_			// 註釋後不觀察中間結果

int label[sqrtN][sqrtN];		// 圖像各像素點歸屬
int dis[sqrtN][sqrtN];			// 圖像各像素點距離

struct cluster{
	int row, col, l, a, b;
};
cluster clusters[sqrtK*sqrtK];		// 存儲超像素的像素座標

// 獲取距離,可變更權重
// clusters_index clusters超像素索引
// i,j表示像素索引
inline int get_distance(const Mat lab,int clusters_index,int i,int j) {
	int dl = clusters[clusters_index].l - lab.at<Vec3b>(i, j)[0];
	int da = clusters[clusters_index].a - lab.at<Vec3b>(i, j)[1];
	int db = clusters[clusters_index].b - lab.at<Vec3b>(i, j)[2];
	int dx = clusters[clusters_index].row - i;
	int dy = clusters[clusters_index].col - j;

	int h_distance = dl * dl + da * da + db * db;
	int xy_distance = dx * dx + dy * dy;
	//cout << h_distance << "\t" << xy_distance * weight << endl;
	return h_distance + xy_distance * weight;
}

// 1.初始化像素
// clusters存儲超像素的像素座標和h值
// 數組label保存每一個像素點屬於哪個超像素。
// dis數組保存像素點到它屬於的那個超像素中心的距離。
void init_clusters(const Mat lab,int S) {
	for (int i = 0; i < sqrtK; i++) {
		int temp_row = S / 2 + i * S;
		for (int j = 0; j < sqrtK; j++) {
			clusters[i * sqrtK + j].row = temp_row;
			clusters[i * sqrtK + j].col = S / 2 + j * S;
			// cout << clusters[i * sqrtK + j].row << "\t" << clusters[i * sqrtK + j].col 
			// << "\t" << clusters[i * sqrtK + j].h << endl;
			clusters[i * sqrtK + j].l = lab.at<Vec3b>(clusters[i * sqrtK + j].row, clusters[i].col)[0];
			clusters[i * sqrtK + j].a = lab.at<Vec3b>(clusters[i * sqrtK + j].row, clusters[i].col)[1];
			clusters[i * sqrtK + j].b = lab.at<Vec3b>(clusters[i * sqrtK + j].row, clusters[i].col)[2];
		}
	}

	for (int i = 0; i < sqrtN; i++) {
		int cluster_row = i / S;
		for (int j = 0; j < sqrtN; j++) {
			label[i][j] = cluster_row * sqrtK + j / S;
			// cout << cluster_row * sqrtK + j / S << endl;
		}
	}

	fill(dis[0], dis[0] + (sqrtN * sqrtN), -1);
}

inline int gradient(const Mat lab,int i,int j) {
	if (i < 0 || j < 0 || i >= sqrtN - 1 || j >= sqrtN - 1)
		return 99999999;

	int dxl = lab.at<Vec3b>(i, j)[0] - lab.at<Vec3b>(i + 1, j)[0];
	int dxa = lab.at<Vec3b>(i, j)[1] - lab.at<Vec3b>(i + 1, j)[1];
	int dxb = lab.at<Vec3b>(i, j)[2] - lab.at<Vec3b>(i + 1, j)[2];

	int dyl = lab.at<Vec3b>(i, j)[0] - lab.at<Vec3b>(i, j + 1)[0];
	int dya = lab.at<Vec3b>(i, j)[1] - lab.at<Vec3b>(i, j + 1)[1];
	int dyb = lab.at<Vec3b>(i, j)[2] - lab.at<Vec3b>(i, j + 1)[2];

	int dx = dxl * dxl + dxa * dxa + dxb * dxb;
	int dy = dyl * dyl + dya * dya + dyb * dyb;
	return dx + dy;
}

// 2.微調種子的位置
void move_clusters(const Mat lab) {
	static int directx[] = { -1,1,0,0,1,1,-1,-1 };
	static int directy[] = { 0,0,-1,1,1,-1,1,-1 };
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		int min_grd = gradient(lab, clusters[i].row, clusters[i].col);
		for (int j = 0; j < 8; j++) {
			int new_row = clusters[i].row + directx[j];
			int new_col = clusters[i].col + directy[j];
			int new_grd = gradient(lab, new_row, new_col);
			if (new_grd < min_grd) {
				min_grd = new_grd;
				clusters[i].row = new_row;
				clusters[i].col = new_col;
			}
		}
	}
}

// 3.初始化數據
// 數組label保存每一個像素點屬於哪個超像素。
// dis數組保存像素點到它屬於的那個超像素中心的距離。
void update_pixel(const Mat lab,int s) {
	//static int direct[] = { sqrtK,-sqrtK,1,-1,sqrtK + 1,sqrtK - 1,-sqrtK + 1,-sqrtK - 1 };
	//for (int i = 0; i < sqrtN; i++) {
	//	for (int j = 0; j < sqrtN; j++) {
	//		int min_label = label[i][j];
	//		int min_dis = get_distance(lab, label[i][j], i, j);	// TODO 是否直接 dis[i][j],考慮到4微調位置
	//		// 周圍8個clusters
	//		for (int k = 0; k < 8; k++) {
	//			int clusters_index = label[i][j] + direct[k];
	//			if (clusters_index < 0 || clusters_index >= sqrtK * sqrtK)
	//				continue;
	//			int new_dis = get_distance(lab, clusters_index, i, j);
	//			if (min_dis > new_dis) {
	//				min_dis = new_dis;
	//				min_label = clusters_index;
	//			}
	//		}
	//		label[i][j] = min_label;
	//	}
	//}
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		int clusters_x = clusters[i].row;
		int clusters_y = clusters[i].col;
		for (int x = -s; x <= s; x++) {
			for (int y = -s; y <= s; y++) {
				int now_x = clusters_x + x;
				int now_y = clusters_y + y;
				if (now_x < 0 || now_x >= sqrtN || now_y < 0 || now_y >= sqrtN)
					continue;
				int new_dis = get_distance(lab, i, now_x, now_y);
				if (dis[now_x][now_y] > new_dis || dis[now_x][now_y] == -1) {
					dis[now_x][now_y] = new_dis;
					label[now_x][now_y] = i;
				}
			}
		}
	}
}

// 4.對於每個超像素,遍歷所有屬於這個超像素的像素點,讓超像素位於正中間。
void updaye_clusters(const Mat lab) {
	int *sum_count = new int[sqrtK * sqrtK]();
	int *sum_i = new int[sqrtK * sqrtK]();
	int *sum_j = new int[sqrtK * sqrtK](); 
	int* sum_l = new int[sqrtK * sqrtK]();
	int* sum_a = new int[sqrtK * sqrtK]();
	int* sum_b = new int[sqrtK * sqrtK]();
	for (int i = 0; i < sqrtN; i++) {
		for (int j = 0; j < sqrtN; j++) {
			sum_count[label[i][j]]++;
			sum_i[label[i][j]] += i;
			sum_j[label[i][j]] += j; 
			sum_l[label[i][j]] += lab.at<Vec3b>(i, j)[0];
			sum_a[label[i][j]] += lab.at<Vec3b>(i, j)[1];
			sum_b[label[i][j]] += lab.at<Vec3b>(i, j)[2];
		}
	}
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		if (sum_count[i] == 0) {
			continue;
		}
		clusters[i].row = round(sum_i[i] / sum_count[i]);
		clusters[i].col = round(sum_j[i] / sum_count[i]); 
		clusters[i].l = round(sum_l[i] / sum_count[i]);
		clusters[i].a = round(sum_a[i] / sum_count[i]);
		clusters[i].b = round(sum_b[i] / sum_count[i]);
	}
	delete[] sum_count;
	delete[] sum_i;
	delete[] sum_j;
	delete[] sum_l;
	delete[] sum_a;
	delete[] sum_b;
}

// 5.標識超像素
void draw_clusters(const Mat copy) {
	for (int index = 0; index < sqrtK * sqrtK; index++) {
		Point p(clusters[index].col, clusters[index].row);
		circle(copy, p, 1, Scalar(clusters[index].l, clusters[index].a, clusters[index].b), 3);  // 畫半徑爲1的圓(畫點)
	}
	cvtColor(copy, copy, COLOR_Lab2BGR);
	imshow("超像素示意圖", copy);
}

// 6.繪製超像素結果圖
void final_draw(const Mat lab,Mat copy) {
	for (int i = 0; i < sqrtN; i++) {
		for (int j = 0; j < sqrtN; j++) {
			int index = label[i][j];
			copy.at<Vec3b>(i, j)[0] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[0];
			copy.at<Vec3b>(i, j)[1] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[1];
			copy.at<Vec3b>(i, j)[2] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[2];
		}
	}
	cvtColor(copy, copy, CV_Lab2BGR);
	imshow("分割圖", copy);
	//imwrite("C:\\Users\\ttp\\Desktop\\map5.bmp",copy);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Mat pic[wash_pic];									// 保存每張分割圖片
struct cluster2 {
	int l, a, b;
};
cluster2 clusters_pic[wash_pic];					// 存儲聚類中心點的lab
int label2[sqrtK * sqrtK];							// 圖像各超像素點歸屬

inline int get_distance2(const Mat lab, int clusters_index, int l, int a, int b) {
	int dl = clusters[clusters_index].l - l;
	int da = clusters[clusters_index].a - a;
	int db = clusters[clusters_index].b - b;
	return dl * dl + da * da + db * db;;
}

void update_pixel2(const Mat lab) {
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		int min_dis = 99999999;
		for (int k = 0; k < wash_pic; k++) {
			int new_dis = get_distance2(lab, i, clusters_pic[k].l, clusters_pic[k].a, clusters_pic[k].b);
			if (min_dis > new_dis) {
				min_dis = new_dis;
				label2[i] = k;
			}
		}
	}
}

void updaye_clusters2(const Mat lab) {
	int* sum_count = new int[wash_pic]();
	int* sum_l = new int[wash_pic]();
	int* sum_a = new int[wash_pic]();
	int* sum_b = new int[wash_pic]();
	for (int i = 0; i < sqrtK * sqrtK; i++) {
		sum_count[label2[i]]++;
		int row = clusters[i].row;
		int col = clusters[i].col;
		sum_l[label2[i]] += lab.at<Vec3b>(row, col)[0];
		sum_a[label2[i]] += lab.at<Vec3b>(row, col)[1];
		sum_b[label2[i]] += lab.at<Vec3b>(row, col)[2];
	}
	for (int i = 0; i < wash_pic; i++) {
		if (sum_count[i] == 0) {
			// 當前像素值不適合
			clusters_pic[i].l = rand() % (256);
			clusters_pic[i].a = rand() % (256);
			clusters_pic[i].b = rand() % (256);
			continue;
		}
		clusters_pic[i].l = sum_l[i] / sum_count[i];
		clusters_pic[i].a = sum_a[i] / sum_count[i];
		clusters_pic[i].b = sum_b[i] / sum_count[i];
	}
	delete[] sum_count;
	delete[] sum_l;
	delete[] sum_a;
	delete[] sum_b;
}

int sum_res[wash_pic];

void draw_segment(const Mat lab){
	for (int i = 0; i < sqrtN; i++) {					// 對於每一個像素
		for (int j = 0; j < sqrtN; j++) {
			int k = label2[label[i][j]];
			pic[k].at<Vec3b>(i, j)[0] = lab.at<Vec3b>(i, j)[0];
			pic[k].at<Vec3b>(i, j)[1] = lab.at<Vec3b>(i, j)[1];
			pic[k].at<Vec3b>(i, j)[2] = lab.at<Vec3b>(i, j)[2];
			/*pic[k].at<Vec3b>(i, j)[0] = clusters_pic[k].l;
			pic[k].at<Vec3b>(i, j)[1] = clusters_pic[k].a;
			pic[k].at<Vec3b>(i, j)[2] = clusters_pic[k].b;*/
			sum_res[k]++;
		}
	}

	for (int i = 0; i < wash_pic; i++) {
		cvtColor(pic[i], pic[i], COLOR_Lab2BGR);
		imshow(to_string(i), pic[i]);
	}
}


int main(){
	// 一、------------------------------------------------------------超像素
	Mat src = imread("C:\\Users\\ttp\\Desktop\\map.bmp"), lab;
	resize(src, src, Size(sqrtN, sqrtN));
	//GaussianBlur(src, src, Size(3, 3), 1, 1);
	cvtColor(src, lab, CV_BGR2Lab);

	Mat temp_mat(src.size(), src.type(), Scalar(255, 255, 255));
	cvtColor(temp_mat, temp_mat, COLOR_BGR2Lab);

	int N = sqrtN * sqrtN;			// 像素總數 512*512
	int K = sqrtK * sqrtK;			// 超像素個數 128*128 16
	int S = sqrt(N / K);			// 相鄰種子點距離(邊長) 4

	// 1.初始化像素
	init_clusters(lab,S);
	cout << "1-初始化像素-完成\n";

	// 2.微調種子的位置 貌似好一點,沒有太大區別
	move_clusters(lab);
	cout << "2-微調種子的位置-完成\n";

	for (int i = 0; i < 5; i++) {
		// 3.初始化數據
		update_pixel(lab, 2*S);
		cout << "3-初始化數據-完成\n";

		// 4.讓超像素位於正中間
		updaye_clusters(lab);
		cout << "4-讓超像素位於正中間-完成\n";

		#ifdef DEBUG_
		// 5.標識超像素
		draw_clusters(temp_mat.clone());
		cout << "5-標識超像素-完成\n";

		// 6.繪製超像素結果圖
		final_draw(lab, lab.clone());
		cout << "6-繪製超像素結果圖-完成\n";

		waitKey(1000);
		#endif // DEBUG_
	}
	imshow("原圖", src);

	// 二、--------------------------------------------------------------Kmeans
	for (int i = 0; i < wash_pic; i++) {
		pic[i] = temp_mat.clone();
		clusters_pic[i].l = rand() % (256);
		clusters_pic[i].a = rand() % (256);
		clusters_pic[i].b = rand() % (256);
	}

	for (int i = 0; i < 20; i++) {
		for (int i = 0; i < wash_pic; i++) {
			cout << clusters_pic[i].l << " " << clusters_pic[i].a << " " << clusters_pic[i].b << " " << endl;;
		}
		cout << endl;

		// 1.初始化數據
		update_pixel2(lab);
		cout << "1-初始化數據-完成\n";

		// 2.讓超像素位於正中間
		updaye_clusters2(lab);
		cout << "2-讓超像素位於正中間-完成\n";
	}

	// 3.顯示分割結果
	draw_segment(lab);

	for (int i = 0; i < wash_pic; i++) {
		cout << "圖片" << i << " " << sum_res[i] << "\n";
	}

	waitKey(0);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章