圖像處理 區域標記(種子填充棧法) 數大米


 用種子填充法實現了區域標記,最終用彩色圖展示標記結果。進而我們可以做一下圖片中的大米計數

編譯環境 OpenCV 4.0.1(v15) + VS2017

源碼

  1 #include <iostream> 
  2 #include <stack>
  3 #include <map>
  4 #include <windows.h>
  5 #include <opencv2/core.hpp>
  6 #include <opencv2/highgui.hpp>
  7 #include <opencv2/opencv.hpp>
  8 #include <opencv2/imgproc.hpp>
  9 using namespace std;
 10 using namespace cv;
 11 
 12 void areaLabeling(Mat &src, Mat&res)
 13 {
 14     if (src.empty() || src.type() != CV_8UC1)
 15     {
 16         cout << "圖象未讀取或者是多通道" << endl;
 17         return;
 18     }
 19     res.release();
 20     src.convertTo(res, CV_32SC1);//CV_32SC1,32位單通道
 21 
 22     int label = 1;//標記值
 23     int row = src.rows-1, col = src.cols-1;
 24 
 25     for (int i = 1; i < row - 1; i++)
 26     {
 27         int* data = res.ptr<int>(i);//獲取一行值
 28         for (int j = 1; j < col - 1; j++)
 29         {
 30             if (data[j] == 1)//目標區域點:未被處理過
 31             {
 32                 //放置種子
 33                 stack<pair<int, int>> labelPixel;
 34                 labelPixel.push(pair<int, int>(i, j));
 35                 ++label;
 36                 printf("第 %d 顆種子入棧, 位置 ( %d , %d )\n", label - 1, i, j);
 37 
 38                 while (!labelPixel.empty())
 39                 {
 40                     pair<int, int> curPixel = labelPixel.top();
 41                     int x = curPixel.first;
 42                     int y = curPixel.second;
 43                     res.at<int>(x, y) = label;//種子標記
 44                     labelPixel.pop();
 45 
 46                     //領域像素位置入棧
 47                     if(res.at<int>(x, y-1) == 1)
 48                         labelPixel.push(pair<int, int>(x, y - 1));
 49                     if (res.at<int>(x, y+1) == 1)
 50                         labelPixel.push(pair<int, int>(x, y + 1));
 51                     if (res.at<int>(x-1, y) == 1)
 52                         labelPixel.push(pair<int, int>(x - 1, y));
 53                     if (res.at<int>(x+1, y) == 1)
 54                         labelPixel.push(pair<int, int>(x + 1, y));
 55                 }
 56             }
 57         }
 58     }
 59     //輸出統計數字
 60     cout << "總計:" << label-1 << endl;
 61 }
 62 
 63 //生成彩色Scalar
 64 Scalar formatRandom()
 65 {
 66     uchar r = rand() % 255;
 67     uchar g = rand() % 255;
 68     uchar b = rand() % 255;
 69     return Scalar(r, g, b);
 70 }
 71 
 72 void Bin2BGR(Mat &src, Mat &res)
 73 {
 74     if (!src.data || src.type() != CV_32SC1)
 75     {
 76         cout << "圖像錯誤" << endl;
 77         return ;
 78     }
 79     int row = src.rows;
 80     int col = src.cols;
 81     map<int, Scalar> colorMp;
 82 
 83     res.release();
 84     res.create(row, col, CV_8UC3);
 85     res = Scalar::all(0);
 86     for (int i = 0; i < row; i++)
 87     {
 88         int* data_bin = src.ptr<int>(i);//提取二值圖像一行
 89         uchar* data_bgr = res.ptr<uchar>(i);//提取彩色圖象一行
 90         for (int j = 0; j < col; j++)
 91         {
 92             if (data_bin[j] > 1)
 93             {
 94                 if (colorMp.count(data_bin[j]) <= 0)//還未生成顏色
 95                 {
 96                     //隨機生成顏色
 97                     colorMp[data_bin[j]] = formatRandom();
 98                 }
 99                 //賦值顏色
100                 Scalar c = colorMp[data_bin[j]];
101                 *data_bgr++ = c[0];
102                 *data_bgr++ = c[1];
103                 *data_bgr++ = c[2];
104             }
105             else
106             {
107                 data_bgr++;
108                 data_bgr++;
109                 data_bgr++;
110             }    
111         }
112     }
113 }
114 
115 int main()
116 {
117     cout << "輸入操作:0-數大米(未濾波),1-區域標記, 2-數大米(濾波) />";
118     int ans;
119     cin >> ans;
120     if (ans == 1)
121     {
122         Mat src = imread("D:\\trashBox\\testImg\\circle.png");
123         imshow("原圖像", src);
124         //轉二值圖像
125         cvtColor(src, src, COLOR_BGR2GRAY);
126         imshow("灰度圖象", src);
127         threshold(src, src, 50, 1, THRESH_BINARY_INV);//背景為白,目標為黑
128         imshow("二值圖像", src);
129         Mat res, rgbRes;
130         //圖象標記
131         areaLabeling(src, res);
132         //轉換彩色圖象並顯示
133         Bin2BGR(res, rgbRes);
134         imshow("標記圖彩色", rgbRes);
135     }
136     else if (ans == 2)
137     {
138         Mat src = imread("D:\\trashBox\\testImg\\rice.jpg");
139         imshow("原圖像", src);
140         //中值濾波去除椒鹽噪聲
141         medianBlur(src, src, 3);
142         imshow("中值濾波", src);
143         //轉二值圖像
144         cvtColor(src, src, COLOR_BGR2GRAY);
145         imshow("灰度圖象", src);
146         threshold(src, src, 22, 1, THRESH_BINARY);//背景為白,目標為黑
147         imshow("二值圖像", src);
148         Mat res, rgbRes;
149         //圖象標記
150         areaLabeling(src, res);
151         //轉換彩色圖象並顯示
152         Bin2BGR(res, rgbRes);
153         imshow("標記圖彩色", rgbRes);
154     }
155     else if (ans == 0)
156     {
157         Mat src = imread("D:\\trashBox\\testImg\\rice.jpg");
158         imshow("原圖像", src);
159         //轉二值圖像
160         cvtColor(src, src, COLOR_BGR2GRAY);
161         imshow("灰度圖象", src);
162         threshold(src, src, 20, 1, THRESH_BINARY);//背景為白,目標為黑
163         imshow("二值圖像", src);
164         Mat res, rgbRes;
165         //圖象標記
166         areaLabeling(src, res);
167         //轉換彩色圖象並顯示
168         Bin2BGR(res, rgbRes);
169         imshow("標記圖彩色", rgbRes);
170     }
171     else
172     {
173         cout << "輸入錯誤, 退出" << endl;
174         return 0;
175     }
176     
177 
178     waitKey(0);
179 }
區域標記完整代碼

注解

1. 核心函數 :areaLabeling

功能:實現將傳入的src(Mat)進行標記,並輸出區域個數。(我們數大米也是借助它)

算法

我們在main函數中已經事先對圖象進行了灰度->二值化處理,結果是目標像素灰度全變成1,而背景全變成0

1. 遍歷Mat src(注意范圍左右上下都要空一個像素出來), 建立一個棧用以存儲當前目標像素所在區域的所有像素位置,找到第一個灰度為1的像素,將其位置壓棧;

2. 棧不為空(本區域還未處理完),while循環,棧頂出棧,賦值標簽label(一個int值,每次while循環結束遞增,表示本區域結束),然后把棧頂周圍的四/八個方向像素值為1的像素位置壓棧(是一個區域的);

3. 所有元素處理完成退出

 1 void areaLabeling(Mat &src, Mat&res)
 2 {
 3     if (src.empty() || src.type() != CV_8UC1)
 4     {
 5         cout << "圖象未讀取或者是多通道" << endl;
 6         return;
 7     }
 8     res.release();
 9     src.convertTo(res, CV_32SC1);//CV_32SC1,32位單通道
10 
11     int label = 1;//標記值
12     int row = src.rows-1, col = src.cols-1;
13 
14     for (int i = 1; i < row - 1; i++)
15     {
16         int* data = res.ptr<int>(i);//獲取一行值
17         for (int j = 1; j < col - 1; j++)
18         {
19             if (data[j] == 1)//目標區域點:未被處理過
20             {
21                 //放置種子
22                 stack<pair<int, int>> labelPixel;
23                 labelPixel.push(pair<int, int>(i, j));
24                 ++label;
25                 printf("第 %d 顆種子入棧, 位置 ( %d , %d )\n", label - 1, i, j);
26 
27                 while (!labelPixel.empty())
28                 {
29                     pair<int, int> curPixel = labelPixel.top();
30                     int x = curPixel.first;
31                     int y = curPixel.second;
32                     res.at<int>(x, y) = label;//種子標記
33                     labelPixel.pop();
34 
35                     //領域像素位置入棧
36                     if(res.at<int>(x, y-1) == 1)
37                         labelPixel.push(pair<int, int>(x, y - 1));
38                     if (res.at<int>(x, y+1) == 1)
39                         labelPixel.push(pair<int, int>(x, y + 1));
40                     if (res.at<int>(x-1, y) == 1)
41                         labelPixel.push(pair<int, int>(x - 1, y));
42                     if (res.at<int>(x+1, y) == 1)
43                         labelPixel.push(pair<int, int>(x + 1, y));
44                 }
45             }
46         }
47     }
48     //輸出統計數字
49     cout << "總計:" << label-1 << endl;
50 }

2. 灰度結果圖轉換為彩色圖

👆上面函數處理結果是背景是0, 目標根據區域不同標簽label也不同,不能直接顯示(看不出效果),所以我們有必要將其轉換為三通道彩色圖象。

算法

1. 新建map映射,從標記值---顏色scalar

2. 遍歷傳入的src (其實是上面函數的處理結果res), 如果當前像素值大於1,說明是標記區域,在map中查找(O(logn))該標記,如果沒有那么就給他生成一個隨機顏色(通過生成三個隨機數r,g,b),並賦值對應它的三通道圖象的三個通道;如果,找到了,說明之前已經對這個標簽生成過顏色了,直接賦值即可;

3. 遍歷完成退出。

 1 void Bin2BGR(Mat &src, Mat &res)
 2 {
 3     if (!src.data || src.type() != CV_32SC1)
 4     {
 5         cout << "圖像錯誤" << endl;
 6         return ;
 7     }
 8     int row = src.rows;
 9     int col = src.cols;
10     map<int, Scalar> colorMp;
11 
12     res.release();
13     res.create(row, col, CV_8UC3);
14     res = Scalar::all(0);
15     for (int i = 0; i < row; i++)
16     {
17         int* data_bin = src.ptr<int>(i);//提取二值圖像一行
18         uchar* data_bgr = res.ptr<uchar>(i);//提取彩色圖象一行
19         for (int j = 0; j < col; j++)
20         {
21             if (data_bin[j] > 1)
22             {
23                 if (colorMp.count(data_bin[j]) <= 0)//還未生成顏色
24                 {
25                     //隨機生成顏色
26                     colorMp[data_bin[j]] = formatRandom();
27                 }
28                 //賦值顏色
29                 Scalar c = colorMp[data_bin[j]];
30                 *data_bgr++ = c[0];
31                 *data_bgr++ = c[1];
32                 *data_bgr++ = c[2];
33             }
34             else
35             {
36                 data_bgr++;
37                 data_bgr++;
38                 data_bgr++;
39             }    
40         }
41     }
42 }

3.隨機顏色生成

算法:利用為隨機函數生成三個隨機數對應r,g,b分量,構建Scalar返回。

1 //生成彩色Scalar
2 Scalar formatRandom()
3 {
4     uchar r = rand() % 255;
5     uchar g = rand() % 255;
6     uchar b = rand() % 255;
7     return Scalar(r, g, b);
8 }

4. 主函數注釋較為完善,不多說。

效果圖

1. 區域標記

2. 數大米

 

3.先濾波再數大米


 

參考資料

【1】https://blog.csdn.net/cooelf/article/details/26581539 (該博主還寫有序貫標記的區域標記代碼,可參考)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM