图像处理 区域标记(种子填充栈法) 数大米

 用种子填充法实现了区域标记,最终用彩色图展示标记结果。进而我们可以做一下图片中的大米计数

编译环境 OpenCV 4.0.1(v15) + VS2017

源码

  1 #include  
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  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                 stackint, 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(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                 stackint, 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(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. 区域标记

图像处理 区域标记(种子填充栈法) 数大米_第1张图片

图像处理 区域标记(种子填充栈法) 数大米_第2张图片

图像处理 区域标记(种子填充栈法) 数大米_第3张图片

2. 数大米

图像处理 区域标记(种子填充栈法) 数大米_第4张图片

 图像处理 区域标记(种子填充栈法) 数大米_第5张图片

3.先滤波再数大米

图像处理 区域标记(种子填充栈法) 数大米_第6张图片

图像处理 区域标记(种子填充栈法) 数大米_第7张图片


 

参考资料

【1】https://blog.csdn.net/cooelf/article/details/26581539 (该博主还写有序贯标记的区域标记代码,可参考)

你可能感兴趣的:(图像处理 区域标记(种子填充栈法) 数大米)