用种子填充法实现了区域标记,最终用彩色图展示标记结果。进而我们可以做一下图片中的大米计数。
编译环境 OpenCV 4.0.1(v15) + VS2017
源码
1 #include2 #include 3 #include
注解
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. 区域标记
2. 数大米
3.先滤波再数大米
参考资料
【1】https://blog.csdn.net/cooelf/article/details/26581539 (该博主还写有序贯标记的区域标记代码,可参考)