加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

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

(2015-10-29 19:41:16)
标签:

像素

图像

像素点

相邻

二值图像

分类: OpenCV

编译环境:

操作系统:Win7  64位 

IDE平台:Visual Studio 2010 Ultimate

OpenCV:2.4.10

一、连通域

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

http://img1.tuicool.com/2IFFzm.png

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

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

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

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

http://img0.tuicool.com/Rji2qav.png

二、连通区域的标记

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中所有的连通区域;

三、程序代码


#include "stdafx.h"
#include
#include 
#include 
#include 
#include  #include  #include  #include  using namespace std; void Seed_Filling(const cv::Mat& binImg, cv::Mat& lableImg) //种子填充法 {  // 4邻接方法  if (binImg.empty() ||   binImg.type() != CV_8UC1)  {   return;  }  lableImg.release();  binImg.convertTo(lableImg, CV_32SC1);  int label = 1;  int rows = binImg.rows - 1;  int cols = binImg.cols - 1;  for (int i = 1; i < rows-1; i++)  {   int* data= lableImg.ptr<<span class="keyword" style="font-weight: bold;">int>(i);
    for (int j = 1; j < cols-1; j++)
    {
      if (data[j] == 1)
      {
        std::stack<<span class="built_in" style="color: rgb(0, 134, 179);">std::pair<<span class="keyword" style="font-weight: bold;">int,int>> neighborPixels;   
        neighborPixels.push(std::pair<<span class="keyword" style="font-weight: bold;">int,int>(i,j));     // 像素位置: 
        ++label;  // 没有重复的团,开始新的标签
        while (!neighborPixels.empty())
        {
          std::pair<<span class="keyword" style="font-weight: bold;">int,int> curPixel = neighborPixels.top(); //如果与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它
          int curX = curPixel.first;
          int curY = curPixel.second;
          lableImg.at<<span class="keyword" style="font-weight: bold;">int>(curX, curY) = label;

          neighborPixels.pop();

          if (lableImg.at<<span class="keyword" style="font-weight: bold;">int>(curX, curY-1) == 1)
          {//左边
            neighborPixels.push(std::pair<<span class="keyword" style="font-weight: bold;">int,int>(curX, curY-1));
          }
          if (lableImg.at<<span class="keyword" style="font-weight: bold;">int>(curX, curY+1) == 1)
          {// 右边
            neighborPixels.push(std::pair<<span class="keyword" style="font-weight: bold;">int,int>(curX, curY+1));
          }
          if (lableImg.at<<span class="keyword" style="font-weight: bold;">int>(curX-1, curY) == 1)
          {// 上边
            neighborPixels.push(std::pair<<span class="keyword" style="font-weight: bold;">int,int>(curX-1, curY));
          }
          if (lableImg.at<<span class="keyword" style="font-weight: bold;">int>(curX+1, curY) == 1)
          {// 下边
            neighborPixels.push(std::pair<<span class="keyword" style="font-weight: bold;">int,int>(curX+1, curY));
          }
        }               
      }
    }
  }
  
}

void Two_Pass(const cv::Mat& binImg, cv::Mat& lableImg)    //两遍扫描法
{
  if (binImg.empty() ||
    binImg.type() != CV_8UC1)
  {
    return;
  }

  // 第一个通路

  lableImg.release();
  binImg.convertTo(lableImg, CV_32SC1);

  int label = 1; 
  std::vector<<span class="keyword" style="font-weight: bold;">int> labelSet;
  labelSet.push_back(0);  
  labelSet.push_back(1);  

  int rows = binImg.rows - 1;
  int cols = binImg.cols - 1;
  for (int i = 1; i < rows; i++)
  {
    int* data_preRow = lableImg.ptr<<span class="keyword" style="font-weight: bold;">int>(i-1);
    int* data_curRow = lableImg.ptr<<span class="keyword" style="font-weight: bold;">int>(i);
    for (int j = 1; j < cols; j++)
    {
      if (data_curRow[j] == 1)
      {
        std::vector<<span class="keyword" style="font-weight: bold;">int> neighborLabels;
        neighborLabels.reserve(2);
        int leftPixel = data_curRow[j-1];
        int upPixel = data_preRow[j];
        if ( leftPixel > 1)
        {
          neighborLabels.push_back(leftPixel);
        }
        if (upPixel > 1)
        {
          neighborLabels.push_back(upPixel);
        }

        if (neighborLabels.empty())
        {
          labelSet.push_back(++label);  // 不连通,标签+1
          data_curRow[j] = label;
          labelSet[label] = label;
        }
        else
        {
          std::sort(neighborLabels.begin(), neighborLabels.end());
          int smallestLabel = neighborLabels[0];  
          data_curRow[j] = smallestLabel;

          // 保存最小等价表
          for (size_t k = 1; k < neighborLabels.size(); k++)
          {
            int tempLabel = neighborLabels[k];
            int& oldSmallestLabel = labelSet[tempLabel];
            if (oldSmallestLabel > smallestLabel)
            {                                                   
              labelSet[oldSmallestLabel] = smallestLabel;
              oldSmallestLabel = smallestLabel;
            }                                           
            else if (oldSmallestLabel < smallestLabel)
            {
              labelSet[smallestLabel] = oldSmallestLabel;
            }
          }
        }                               
      }
    }
  }

  // 更新等价对列表
  // 将最小标号给重复区域
  for (size_t i = 2; i < labelSet.size(); i++)
  {
    int curLabel = labelSet[i];
    int preLabel = labelSet[curLabel];
    while (preLabel != curLabel)
    {
      curLabel = preLabel;
      preLabel = labelSet[preLabel];
    }
    labelSet[i] = curLabel;
  }  ;

  for (int i = 0; i < rows; i++)
  {
    int* data = lableImg.ptr<<span class="keyword" style="font-weight: bold;">int>(i);
    for (int j = 0; j < cols; j++)
    {
      int& pixelLabel = data[j];
      pixelLabel = labelSet[pixelLabel];    
    }
  }
}
//彩色显示
cv::Scalar GetRandomColor()
{
  uchar r = 255 * (rand()/(1.0 + RAND_MAX));
  uchar g = 255 * (rand()/(1.0 + RAND_MAX));
  uchar b = 255 * (rand()/(1.0 + RAND_MAX));
  return cv::Scalar(b,g,r);
}


void LabelColor(const cv::Mat& labelImg, cv::Mat& colorLabelImg) 
{
  if (labelImg.empty() ||
    labelImg.type() != CV_32SC1)
  {
    return;
  }

  std::map<<span class="keyword" style="font-weight: bold;">int, cv::Scalar> colors;

  int rows = labelImg.rows;
  int cols = labelImg.cols;

  colorLabelImg.release();
  colorLabelImg.create(rows, cols, CV_8UC3);
  colorLabelImg = cv::Scalar::all(0);

  for (int i = 0; i < rows; i++)
  {
    const int* data_src = (int*)labelImg.ptr<<span class="keyword" style="font-weight: bold;">int>(i);
    uchar* data_dst = colorLabelImg.ptr(i);
    for (int j = 0; j < cols; j++)
    {
      int pixelValue = data_src[j];
      if (pixelValue > 1)
      {
        if (colors.count(pixelValue) <= 0)
        {
          colors[pixelValue] = GetRandomColor();
        }

        cv::Scalar color = colors[pixelValue];
        *data_dst++   = color[0];
        *data_dst++ = color[1];
        *data_dst++ = color[2];
      }
      else
      {
        data_dst++;
        data_dst++;
        data_dst++;
      }
    }
  }
}


int main()
{

  cv::Mat binImage = cv::imread("test.jpg", 0);
  cv::threshold(binImage, binImage, 50, 1, CV_THRESH_BINARY_INV);
  cv::Mat labelImg;
  Two_Pass(binImage, labelImg, num);
  //Seed_Filling(binImage, labelImg);
  //彩色显示
  cv::Mat colorLabelImg;
  LabelColor(labelImg, colorLabelImg);
  cv::imshow("colorImg", colorLabelImg);


  cv::waitKey(0);
  return 0;
}
四、演示结果

原图: 

http://img0.tuicool.com/vEjaq2r.jpg

效果图:                                                                                           

http://img1.tuicool.com/6R3Mnm.jpg

参考文章:

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

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

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有