上一节地址: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所示),然后让红点和蓝点移动到中心,不断重复,最终成功划分。
一、分析
- 上一节中,我们得到了什么呢?
得到了一张超像素图片(废话),对于每一个像素,都有其对应的超像素。那么我们是不是对超像素分好类就对这张图片分割了呢?
- 貌似是的,那为什么不直接对每个像素分类呢?整那么多花里花俏东西。
因为通过超像素,我们比较完整的保留了像素的各种信息,并减小了问题规模,比如:相邻的像素点颜色有些差距,和不相邻的像素点颜色有些差距,在上一节的处理过程中,会更大可能的将相邻的像素分为一类,这样在这一节的处理过程中可以更好的进行分类。(如果有更好的想法,欢迎评论指出)
- 那么具体步骤是怎样的呢?
- 首先,去他的像素。现在你就想着你只能接触到超像素,我们的任务就是对超像素分类。
- 然后我们观察上面那一张kmeans分类图(b)。我们假设绿点就是我们的超像素,假设我们分割两张图片那么红点和蓝点就是代表我们分割的图片。(准确的说还包括分完类得到的绿点)
- 现在假设一个具体情况,分割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]; // 图像各超像素点归属
然后就是大致框架了,
- 首先我们对每一个聚类中心点(就是图中的蓝点、红点)初始化了三个随机值。
- 然后按照Kmeans步骤不断更新聚类中心点的LAB值,
- 最后根据聚类中心点绘制每一张分割图片。
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++) {
//int index = i * sqrtK + j;
int index = i; // TODO 不知道为什么这样的效果反而比 i * sqrtK + j 好。
clusters[index].row = temp_row;
clusters[index].col = S / 2 + j * S;
// cout << clusters[index].row << "\t" << clusters[index].col
// << "\t" << clusters[index].h << endl;
clusters[index].l = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[0];
clusters[index].a = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[1];
clusters[index].b = lab.at<Vec3b>(clusters[index].row, clusters[index].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;
}