上一節地址: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++) {
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;
}