【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]。

扫描二维码关注公众号,回复: 9046569 查看本文章
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].row = temp_row;
			clusters[i].col = S / 2 + j * S;
			// cout << clusters[i].row << "\t" << clusters[i].col 
			// << "\t" << clusters[i].h << endl;
			clusters[i].l = lab.at<Vec3b>(clusters[i].row, clusters[i].col)[0];
			clusters[i].a = lab.at<Vec3b>(clusters[i].row, clusters[i].col)[1];
			clusters[i].b = lab.at<Vec3b>(clusters[i].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;
}
发布了39 篇原创文章 · 获赞 79 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40515692/article/details/102788157