用种子填充法实现了区域标记,最终用彩色图展示标记结果。进而我们可以做一下图片中的大米计数。
编译环境 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 (该博主还写有序贯标记的区域标记代码,可参考)