日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【OpenCv3】 VS C++ (五):SLIC超像素分割算法

發布時間:2023/12/31 c/c++ 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【OpenCv3】 VS C++ (五):SLIC超像素分割算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

超像素SuperPixel),就是把原本多個像素點,組合成一個大的像素。比如,原本的圖片有二十多萬個像素,用超像素處理之后,就只有幾千個像素了。后面做直方圖等處理就會方便許多。經常作為圖像處理的預處理步驟。

這一節講的是用C++實現超像素,下一節講在超像素基礎上用Kmeans分類進行分割,代碼先根據超像素SLIC算法編寫,后參考github的代碼優化了一些地方,然后根據老師說的有更改了一些地方,歡迎大家一起討論。

題目如下:

簡單解法(HSV 直方圖閾值)如下(至于為什么不用Matlab了,因為作為C系程序員,寫c++真滴好爽呀):
https://blog.csdn.net/qq_40515692/article/details/102749271

這一節先講SLIC超像素算法,下一節講在超像素基礎上用Kmeans分類進行分割,參考博客如下:
https://www.jianshu.com/p/d0ef931b3ddf
https://blog.csdn.net/duyue3052/article/details/82149877

效果如下(雖然還是有些可以更好的地方,但是可以看到已經分得很不錯了,當然還有缺陷更少的算法可以更加好的分割比如像素點較少的藍色線等的算法),完整代碼附在下一節了:

一、分析

在寫較復雜的程序時,前期的百度、google參考別人思路、考慮算法的步驟十分關鍵,甚至應該用一半實現代碼以上的時間。

  • 為什么使用超像素?

如果看了之前的顏色閾值分割程序就會發現,之前的閾值分割沒有考慮更高維的顏色數據,更重要的是沒有利用各個像素點的位置信息(比如相鄰的像素點更有可能屬于一張分割圖片),所以我們使用超像素算法,在保留圖像像素的位置、顏色信息的同時,簡化問題。

  • 超像素示意圖:

    這里貼出SLIC的步驟:
  • 撒種子。將K個超像素中心分布到圖像的像素點上。(這里我的實現里面直接先根據圖像大小和超像素的數目,均勻發布)

  • 微調種子的位置。以K為中心的3×3范圍內,移動超像素中心到這9個點中梯度最小的點上。這樣是為了避免超像素點落到噪點或者邊界上。(這里我也進行了實現,但是對于最終結果貌似沒有太大影響,篇幅有限就不進行講解)

  • 初始化數據。取一個數組label保存每一個像素點屬于哪個超像素。dis數組保存像素點到它屬于的那個超像素中心的距離。

  • 對每一個超像素中心x,它2S范圍內的點:如果點到超像素中心x的距離(5維,馬上會講)小于這個點到它原來屬于的超像素中心的距離,那么說明這個點屬于超像素x。更新dis,更新label。

  • 對每一個超像素中心,重新計算它的位置(根據的是屬于該超像素的所有像素的位置中心)以及其LAB值(馬上會講)。

    重復4 5 兩步。

  • 其中關鍵的4,5步其實用到了kmeans算法的思想,如下圖所示,假設有兩個超像素點(紅點、藍點),一系列像素點(綠色),首先對每個像素點計算應該歸屬與哪一個超像素點、分類(如圖片b、c所示)。

    然后進行第五步計算中心,讓超像素移動到中心,不斷重復,最終成功劃分。

    但是應注意實際的SLIC算法和kmeans算法有區別,為了加快計算速度,在進行第4步時只計算了超像素中心有限范圍內的點。(這是我實現算法時的理解,如果有誤希望指出)

    需要注意的是這里的“距離”可以是多維的數據距離,而不一定是比如像素之間的row、col之間的距離(比如RGB的歐式距離等)。

    • 然后就需要考慮如何針對一張圖像,度量”距離“?

    這里先簡單介紹LAB色彩空間。Lab色彩模型是由亮度(L)和有關色彩的a, b三個要素組成。L表示亮度(Luminosity),L的值域由0(黑色)到100(白色)。a表示從洋紅色至綠色的范圍(a為負值指示綠色而正值指示品紅),b表示從黃色至藍色的范圍(b為負值指示藍色而正值指示黃色)。

    Lab色彩模型的絕妙之處還在于它彌補了RGB色彩模型色彩分布不均的不足,因為RGB模型在藍色到綠色之間的過渡色彩過多,而在綠色到紅色之間又缺少黃色和其他色彩。如果我們想在數字圖形的處理中保留盡量寬闊的色域和豐富的色彩,最好選擇Lab。

    然后就是如何計算”距離“,距離計算方法如下,其中,dc代表顏色距離,ds代表空間距離,Ns是類內最大空間距離,Nc為最大的顏色距離:

    二、讀取圖片,完成大致框架

    第一步還是先包含頭文件,還有定義需要用到的變量,需要配置opencv,在VS上的配置可以參考:
    https://blog.csdn.net/qq_40515692/article/details/81042303

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std;#define sqrtK 128 // 超像素個數128*128 #define sqrtN 512 // 圖像格式512*512int label[sqrtN][sqrtN]; // 圖像各像素點歸屬 int dis[sqrtN][sqrtN]; // 圖像各像素點距離struct cluster{int row, col, l, a, b; }; cluster clusters[sqrtK*sqrtK]; // 存儲超像素的像素坐標

    我們先定義好大致框架,首先是讀取圖片,轉換為LAB色彩空間,然后把上面提到的步驟分步定義為函數。

    int main(){// 注意修改文件位置Mat src = imread("C:\\Users\\ttp\\Desktop\\map.bmp"), lab;// resize圖片并濾波resize(src, src, Size(sqrtN, sqrtN));// GaussianBlur(src, src, Size(3, 3), 1, 1);// 得到Lab色彩空間,需要注意的是:// 1.opencv里面默認為BGR排列方式// 2.LAB通道范圍取決于轉換前的通道范圍,這樣其實也方便處理// 例如:開始是0-255,轉換后也是0-255,而不是LAB規定的[127,-128]cvtColor(src, lab, CV_BGR2Lab);int N = sqrtN * sqrtN; // 像素總數 512*512int K = sqrtK * sqrtK; // 超像素個數 128*128int 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.4.初始化數據update_pixel(lab, 2*S);cout << "3-初始化數據-完成\n";// 5.讓超像素位于正中間updaye_clusters(lab);cout << "4-讓超像素位于正中間-完成\n";// -------------------這兩個函數主要是幫助顯示結果的// 6.標識超像素draw_clusters(src.clone());cout << "5-標識超像素-完成\n";// 7.繪制超像素結果圖final_draw(lab, lab.clone());cout << "6-繪制超像素結果圖-完成\n";// opencv的函數,每1000ms更新一下,動態顯示圖片waitKey(1000);// -----------------------------------------------}imshow("原圖", src);waitKey(0); }

    三、各函數實現

    1.init_clusters函數

    init_clusters函數就是我們的第一步了,傳入的參數為lab的色彩空間和S。

    需要注意的是opencv里面Mat類的賦值并不是直接把Mat的數據全部拷貝一份賦值。
    而是類似于C++的引用賦值(比如:Mat a,b; b=a; 改變b也會改變a)。
    如果想賦值得到一個全新的圖像矩陣,可以使用b=a.clone();這種方式。
    所以這里就直接傳lab了,效率應該不會低。

    fill函數用于對一段空間賦值,這里即將矩陣dis賦-1。(在非opencv程序也可以使用)

    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;}}// 初始化每一個像素的label(即屬于哪一個超像素)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;}}// 像素與超像素的距離先假設為-1fill(dis[0], dis[0] + (sqrtN * sqrtN), -1); }

    2.update_pixel函數

    首先我們還是實現距離計算函數吧,這個函數傳入參數為lab,clusters_index表示超像素的索引,
    i,j表示像素的橫縱坐標。

    lab.at(row,col)屬于opencv里面的寫法,用于訪問矩陣lab在坐標(row,col)的值
    Vec3b表示3通道,每個通道為uchar類型(0-255)。為什么是Vec3b,參考完成大致框架里面的代碼注釋。

    代碼和上面的公式幾乎沒區別(權重取得有點隨意)。

    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 * 100 << endl;return h_distance + xy_distance * 100; }

    然后就可以完成update_pixel函數了

    void update_pixel(const Mat lab,int s) {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++) { // 在它周圍-s到s的范圍內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);// 如果為-1(還沒有更新過)或者新的距離更小,就更換當前像素屬于的超像素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;}}}} }

    3. updaye_clusters函數

    這個函數就是根據當前超像素的所有歸屬像素來更新位置。

    需要注意的是C++用new申請空間時后面加上()會自動初始化申請的空間。
    還有就是記得delete

    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; }

    4. 顯示函數

    OK, 到了這一步其實算法已經完成了。我們在實現一下用于顯示的函數吧。draw_clusters函數就是畫出每一個超像素點,final_draw函數就是繪制一張超像素分割圖。

    void draw_clusters(const Mat copy) {for (int index = 0; index < sqrtK * sqrtK; index++) {Point p(clusters[index].row, clusters[index].col);circle(copy, p, 1, Scalar(0, 0, 255), 1); // 畫半徑為1的圓(畫點)}imshow("超像素示意圖", copy); }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); }

    最后效果如下:


    2020 7.3更新:

    完成了超像素框繪制的功能,這里給出匯總代碼:

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std;#define sqrtK 32 // 超像素個數32*32 #define sqrtN 512 // 圖像格式512*512int label[sqrtN][sqrtN]; // 圖像各像素點歸屬 int dis[sqrtN][sqrtN]; // 圖像各像素點距離struct cluster {int row, col, l, a, b; }; cluster clusters[sqrtK * sqrtK]; // 存儲超像素的像素坐標、顏色/*** 初始化每一個超像素的坐標* 初始化每一個像素的label(即屬于哪一個超像素)* 像素與超像素的距離先假設為-1 */ 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;}}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;}}fill(dis[0], dis[0] + (sqrtN * sqrtN), -1); }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 * 100 << endl;return h_distance + xy_distance * 100; }void update_pixel(const Mat lab, int s) {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++) { // 在它周圍-s到s的范圍內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);// 如果為-1(還沒有更新過)或者新的距離更小,就更換當前像素屬于的超像素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;}}}} }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; }void draw_clusters(const Mat copy) {for (int index = 0; index < sqrtK * sqrtK; index++) {Point p(clusters[index].row, clusters[index].col);circle(copy, p, 1, Scalar(0, 0, 255), 1); // 畫半徑為1的圓(畫點)}imshow("超像素示意圖", copy); }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); }void draw_edge(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];}}// 這里的思路是4個方向,一旦有標簽不同,就設置為黑色static int X[] = { 0,0,-1,1 };static int Y[] = { 1,-1,0,0 };cvtColor(copy, copy, CV_Lab2BGR); // 改成BGR,方便后面設置邊框的顏色。for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {int index = label[i][j];for (int k = 0; k < 4; k++) {if (index != label[i + X[k]][j + X[k]])copy.at<Vec3b>(i, j)[0] = copy.at<Vec3b>(i, j)[1] = copy.at<Vec3b>(i, j)[2] = 0;}}}imshow("超像素邊界", copy); }int main() {// 注意修改文件位置Mat src = imread("C:\\Users\\ttp\\Desktop\\map.bmp"), lab;// resize圖片并高斯濾波(可選)resize(src, src, Size(sqrtN, sqrtN));// GaussianBlur(src, src, Size(3, 3), 1, 1);/**得到Lab色彩空間,需要注意的是:1.opencv里面默認為BGR排列方式2.LAB通道范圍取決于轉換前的通道范圍,這樣其實也方便處理例如:開始是0-255,轉換后也是0-255,而不是LAB規定的[127,-128]*/cvtColor(src, lab, CV_BGR2Lab);int N = sqrtN * sqrtN; // 像素總數 512*512int K = sqrtK * sqrtK; // 超像素個數 128*128int S = sqrt(N / K); // 相鄰種子點距離(超像素邊長) 4// 1.初始化像素init_clusters(lab, S);cout << "1-初始化像素-完成\n";// 2.微調種子的位置 貌似好一點,沒有太大區別,所以這里就直接省略了for (int i = 0; i < 5; i++) {// 3.4.初始化數據update_pixel(lab, 2 * S);cout << "3-初始化數據-完成\n";// 5.讓超像素位于正中間updaye_clusters(lab);cout << "4-讓超像素位于正中間-完成\n";// -------------------這兩個函數主要是幫助顯示結果的// 6.標識超像素draw_clusters(src.clone());cout << "5-標識超像素-完成\n";// 7.繪制超像素結果圖final_draw(lab, lab.clone());cout << "6-繪制超像素結果圖-完成\n";draw_edge(lab, lab.clone());// opencv的函數,每1000ms更新一下,動態顯示圖片waitKey(30);// -----------------------------------------------}imshow("原圖", src);waitKey(0); }

    運行結果:

    總結

    以上是生活随笔為你收集整理的【OpenCv3】 VS C++ (五):SLIC超像素分割算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。