用種子填充法實現了區域標記,最終用彩色圖展示標記結果。進而我們可以做一下圖片中的大米計數。
編譯環境 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 (該博主還寫有序貫標記的區域標記代碼,可參考)