OpenCV:二值图像连通区域分析与标记算法实现

http://blog.csdn.net/cooelf/article/details/26581539?utm_source=tuicool&utm_medium=referral

 

OpenCV:二值图像连通区域分析与标记算法实现

标签: OpenCV连通图两边扫描法种子填充法形成标记算法
 分类:
 
 

目录(?)[+]

 

编译环境:

操作系统:Win8.1  64位

IDE平台:Visual Studio 2013 Ultimate

OpenCV:2.4.8

一、连通域

    在图像中,最小的单位是像素,每个像素周围有8个邻接像素,常见的邻接关系有2种:4邻接与8邻接。4邻接一共4个点,即上下左右,如下左图所示。8邻接的点一共有8个,包括了对角线位置的点,如下右图所示。

image       image

   如果像素点A与B邻接,我们称A与B连通,于是我们不加证明的有如下的结论:

   如果A与B连通,B与C连通,则A与C连通。

   在视觉上看来,彼此连通的点形成了一个区域,而不连通的点形成了不同的区域。这样的一个所有的点彼此连通点构成的集合,我们称为一个连通区域。

   下面这符图中,如果考虑4邻接,则有3个连通区域;如果考虑8邻接,则有2个连通区域。(注:图像是被放大的效果,图像正方形实际只有4个像素)。

image

二、连通区域的标记

1)Two-Pass(两遍扫描法)

下面给出Two-Pass算法的简单步骤:

(1)第一次扫描:

访问当前像素B(x,y),如果B(x,y) == 1:

a、如果B(x,y)的领域中像素值都为0,则赋予B(x,y)一个新的label:

label += 1, B(x,y) = label;

b、如果B(x,y)的领域中有像素值 > 1的像素Neighbors:

1)将Neighbors中的最小值赋予给B(x,y):

B(x,y) = min{Neighbors}

2)记录Neighbors中各个值(label)之间的相等关系,即这些值(label)同属同一个连通区域;

 labelSet[i] = { label_m, .., label_n },labelSet[i]中的所有label都属于同一个连通区域(注:这里可以有多种实现方式,只要能够记录这些具有相等关系的label之间的关系即可)

(2)第二次扫描:

访问当前像素B(x,y),如果B(x,y) > 1:

a、找到与label = B(x,y)同属相等关系的一个最小label值,赋予给B(x,y);

b、完成扫描后,图像中具有相同label值的像素就组成了同一个连通区域。

2)Seed Filling(种子填充法)

     种子填充方法来源于计算机图形学,常用于对某个图形进行填充。思路:选取一个前景像素点作为种子,然后根据连通区域的两个基本条件(像素值相同、位置相邻)将与种子相邻的前景像素合并到同一个像素集合中,最后得到的该像素集合则为一个连通区域。

下面给出基于种子填充法的连通区域分析方法:

(1)扫描图像,直到当前像素点B(x,y) == 1:

a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;

b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;

c、重复b步骤,直到栈为空;

此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;

(2)重复第(1)步,直到扫描结束;

扫描结束后,就可以得到图像B中所有的连通区域;

三、程序代码

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #include "stdafx.h"  
  2. #include<iostream>  
  3. #include <string>  
  4. #include <list>  
  5. #include <vector>  
  6. #include <map>  
  7. #include <stack>  
  8. #include <opencv2/imgproc/imgproc.hpp>  
  9. #include <opencv2/highgui/highgui.hpp>  
  10. using namespace std;  
  11.   
  12. void Seed_Filling(const cv::Mat& binImg, cv::Mat& lableImg)   //种子填充法  
  13. {  
  14.     // 4邻接方法  
  15.   
  16.   
  17.     if (binImg.empty() ||  
  18.         binImg.type() != CV_8UC1)  
  19.     {  
  20.         return;  
  21.     }  
  22.   
  23.     lableImg.release();  
  24.     binImg.convertTo(lableImg, CV_32SC1);  
  25.   
  26.     int label = 1;    
  27.   
  28.     int rows = binImg.rows - 1;    
  29.     int cols = binImg.cols - 1;  
  30.     for (int i = 1; i < rows-1; i++)  
  31.     {  
  32.         int* data= lableImg.ptr<int>(i);  
  33.         for (int j = 1; j < cols-1; j++)  
  34.         {  
  35.             if (data[j] == 1)  
  36.             {  
  37.                 std::stack<std::pair<int,int>> neighborPixels;     
  38.                 neighborPixels.push(std::pair<int,int>(i,j));     // 像素位置: <i,j>  
  39.                 ++label;  // 没有重复的团,开始新的标签  
  40.                 while (!neighborPixels.empty())  
  41.                 {  
  42.                     std::pair<int,int> curPixel = neighborPixels.top(); //如果与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它  
  43.                     int curX = curPixel.first;  
  44.                     int curY = curPixel.second;  
  45.                     lableImg.at<int>(curX, curY) = label;  
  46.   
  47.                     neighborPixels.pop();  
  48.   
  49.                     if (lableImg.at<int>(curX, curY-1) == 1)  
  50.                     {//左边  
  51.                         neighborPixels.push(std::pair<int,int>(curX, curY-1));  
  52.                     }  
  53.                     if (lableImg.at<int>(curX, curY+1) == 1)  
  54.                     {// 右边  
  55.                         neighborPixels.push(std::pair<int,int>(curX, curY+1));  
  56.                     }  
  57.                     if (lableImg.at<int>(curX-1, curY) == 1)  
  58.                     {// 上边  
  59.                         neighborPixels.push(std::pair<int,int>(curX-1, curY));  
  60.                     }  
  61.                     if (lableImg.at<int>(curX+1, curY) == 1)  
  62.                     {// 下边  
  63.                         neighborPixels.push(std::pair<int,int>(curX+1, curY));  
  64.                     }  
  65.                 }         
  66.             }  
  67.         }  
  68.     }  
  69.       
  70. }  
  71.   
  72. void Two_Pass(const cv::Mat& binImg, cv::Mat& lableImg)    //两遍扫描法  
  73. {  
  74.     if (binImg.empty() ||  
  75.         binImg.type() != CV_8UC1)  
  76.     {  
  77.         return;  
  78.     }  
  79.   
  80.     // 第一个通路  
  81.   
  82.     lableImg.release();  
  83.     binImg.convertTo(lableImg, CV_32SC1);  
  84.   
  85.     int label = 1;   
  86.     std::vector<int> labelSet;  
  87.     labelSet.push_back(0);    
  88.     labelSet.push_back(1);    
  89.   
  90.     int rows = binImg.rows - 1;  
  91.     int cols = binImg.cols - 1;  
  92.     for (int i = 1; i < rows; i++)  
  93.     {  
  94.         int* data_preRow = lableImg.ptr<int>(i-1);  
  95.         int* data_curRow = lableImg.ptr<int>(i);  
  96.         for (int j = 1; j < cols; j++)  
  97.         {  
  98.             if (data_curRow[j] == 1)  
  99.             {  
  100.                 std::vector<int> neighborLabels;  
  101.                 neighborLabels.reserve(2);  
  102.                 int leftPixel = data_curRow[j-1];  
  103.                 int upPixel = data_preRow[j];  
  104.                 if ( leftPixel > 1)  
  105.                 {  
  106.                     neighborLabels.push_back(leftPixel);  
  107.                 }  
  108.                 if (upPixel > 1)  
  109.                 {  
  110.                     neighborLabels.push_back(upPixel);  
  111.                 }  
  112.   
  113.                 if (neighborLabels.empty())  
  114.                 {  
  115.                     labelSet.push_back(++label);  // 不连通,标签+1  
  116.                     data_curRow[j] = label;  
  117.                     labelSet[label] = label;  
  118.                 }  
  119.                 else  
  120.                 {  
  121.                     std::sort(neighborLabels.begin(), neighborLabels.end());  
  122.                     int smallestLabel = neighborLabels[0];    
  123.                     data_curRow[j] = smallestLabel;  
  124.   
  125.                     // 保存最小等价表  
  126.                     for (size_t k = 1; k < neighborLabels.size(); k++)  
  127.                     {  
  128.                         int tempLabel = neighborLabels[k];  
  129.                         int& oldSmallestLabel = labelSet[tempLabel];  
  130.                         if (oldSmallestLabel > smallestLabel)  
  131.                         {                             
  132.                             labelSet[oldSmallestLabel] = smallestLabel;  
  133.                             oldSmallestLabel = smallestLabel;  
  134.                         }                         
  135.                         else if (oldSmallestLabel < smallestLabel)  
  136.                         {  
  137.                             labelSet[smallestLabel] = oldSmallestLabel;  
  138.                         }  
  139.                     }  
  140.                 }                 
  141.             }  
  142.         }  
  143.     }  
  144.   
  145.     // 更新等价对列表  
  146.     // 将最小标号给重复区域  
  147.     for (size_t i = 2; i < labelSet.size(); i++)  
  148.     {  
  149.         int curLabel = labelSet[i];  
  150.         int preLabel = labelSet[curLabel];  
  151.         while (preLabel != curLabel)  
  152.         {  
  153.             curLabel = preLabel;  
  154.             preLabel = labelSet[preLabel];  
  155.         }  
  156.         labelSet[i] = curLabel;  
  157.     }  ;  
  158.   
  159.     for (int i = 0; i < rows; i++)  
  160.     {  
  161.         int* data = lableImg.ptr<int>(i);  
  162.         for (int j = 0; j < cols; j++)  
  163.         {  
  164.             int& pixelLabel = data[j];  
  165.             pixelLabel = labelSet[pixelLabel];    
  166.         }  
  167.     }  
  168. }  
  169. //彩色显示  
  170. cv::Scalar GetRandomColor()  
  171. {  
  172.     uchar r = 255 * (rand()/(1.0 + RAND_MAX));  
  173.     uchar g = 255 * (rand()/(1.0 + RAND_MAX));  
  174.     uchar b = 255 * (rand()/(1.0 + RAND_MAX));  
  175.     return cv::Scalar(b,g,r);  
  176. }  
  177.   
  178.   
  179. void LabelColor(const cv::Mat& labelImg, cv::Mat& colorLabelImg)   
  180. {  
  181.     if (labelImg.empty() ||  
  182.         labelImg.type() != CV_32SC1)  
  183.     {  
  184.         return;  
  185.     }  
  186.   
  187.     std::map<int, cv::Scalar> colors;  
  188.   
  189.     int rows = labelImg.rows;  
  190.     int cols = labelImg.cols;  
  191.   
  192.     colorLabelImg.release();  
  193.     colorLabelImg.create(rows, cols, CV_8UC3);  
  194.     colorLabelImg = cv::Scalar::all(0);  
  195.   
  196.     for (int i = 0; i < rows; i++)  
  197.     {  
  198.         const int* data_src = (int*)labelImg.ptr<int>(i);  
  199.         uchar* data_dst = colorLabelImg.ptr<uchar>(i);  
  200.         for (int j = 0; j < cols; j++)  
  201.         {  
  202.             int pixelValue = data_src[j];  
  203.             if (pixelValue > 1)  
  204.             {  
  205.                 if (colors.count(pixelValue) <= 0)  
  206.                 {  
  207.                     colors[pixelValue] = GetRandomColor();  
  208.                 }  
  209.   
  210.                 cv::Scalar color = colors[pixelValue];  
  211.                 *data_dst++   = color[0];  
  212.                 *data_dst++ = color[1];  
  213.                 *data_dst++ = color[2];  
  214.             }  
  215.             else  
  216.             {  
  217.                 data_dst++;  
  218.                 data_dst++;  
  219.                 data_dst++;  
  220.             }  
  221.         }  
  222.     }  
  223. }  
  224.   
  225.   
  226. int main()  
  227. {  
  228.   
  229.     cv::Mat binImage = cv::imread("test.jpg", 0);  
  230.     cv::threshold(binImage, binImage, 50, 1, CV_THRESH_BINARY_INV);  
  231.     cv::Mat labelImg;  
  232.     Two_Pass(binImage, labelImg, num);  
  233.     //Seed_Filling(binImage, labelImg);  
  234.     //彩色显示  
  235.     cv::Mat colorLabelImg;  
  236.     LabelColor(labelImg, colorLabelImg);  
  237.     cv::imshow("colorImg", colorLabelImg);  
  238. /*  //灰度显示 
  239.     cv::Mat grayImg; 
  240.     labelImg *= 10; 
  241.     labelImg.convertTo(grayImg, CV_8UC1); 
  242.     cv::imshow("labelImg", grayImg); 
  243. */  
  244.   
  245.     cv::waitKey(0);  
  246.     return 0;  
  247. }  
四、演示结果

原图:

效果图:                                                                                         

参考文章:

http://www.cnblogs.com/ronny/p/img_aly_01.html

http://blog.csdn.net/icvpr/article/details/10259577

原文地址:https://www.cnblogs.com/donaldlee2008/p/5229975.html