OpenCV_连通区域分析(Connected Component Analysis/Labeling)

【摘要】

本文主要介绍在CVPR和图像处理领域中较为常用的一种图像区域(Blob)提取的方法——连通性分析法(连通区域标记法)。文中介绍了两种常见的连通性分析的算法:1)Two-pass;2)Seed-Filling种子填充,并给出了两个算法的基于OpenCV的C++实现代码。

一、连通区域分析

连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域(Region,Blob)。连通区域分析(Connected Component Analysis,Connected Component Labeling)是指将图像中的各个连通区域找出并标记。

连通区域分析是一种在CVPR和图像分析处理的众多应用领域中较为常用和基本的方法。例如:OCR识别中字符分割提取(车牌识别、文本识别、字幕识别等)、视觉跟踪中的运动前景目标分割与提取(行人入侵检测、遗留物体检测、基于视觉的车辆检测与跟踪等)、医学图像处理(感兴趣目标区域提取)、等等。也就是说,在需要将前景目标提取出来以便后续进行处理的应用场景中都能够用到连通区域分析方法,通常连通区域分析处理的对象是一张二值化后的图像。

二、连通区域分析的算法

从连通区域的定义可以知道,一个连通区域是由具有相同像素值的相邻像素组成像素集合,因此,我们就可以通过这两个条件在图像中寻找连通区域,对于找到的每个连通区域,我们赋予其一个唯一的标识(Label),以区别其他连通区域。

连通区域分析有基本的算法,也有其改进算法,本文介绍其中的两种常见算法:

1)Two-Pass法;2)Seed-Filling种子填充法;

Note:

a、这里的扫描指的是按行或按列访问以便图像的所有像素,本文算法采用的是按行扫描方式;

b、图像记为B,为二值图像:前景像素(pixel value = 1),背景像素(pixel value = 0)

c、label从2开始计数;

d、像素相邻关系:4-领域、8-领域,本文算法采用4-邻域;

                                    

4—领域图例                                                     8—领域图例

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

两遍扫描法,正如其名,指的就是通过扫描两遍图像,就可以将图像中存在的所有连通区域找出并标记。思路:第一遍扫描时赋予每个像素位置一个label,扫描过程中同一个连通区域内的像素集合中可能会被赋予一个或多个不同label,因此需要将这些属于同一个连通区域但具有不同值的label合并,也就是记录它们之间的相等关系;第二遍扫描就是将具有相等关系的equal_labels所标记的像素归为一个连通区域并赋予一个相同的label(通常这个label是equal_labels中的最小值)。

下面给出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);

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

下面这张图动态地演示了Two-pass算法:


2)Seed Filling(种子填充法)

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

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

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

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

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

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

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

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

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

下面这张图动态地演示了Seed-Filling算法:

三、实验演示

1)前景二值图像

2)连通区域分析方法标记后得到的label图像

Two-pass算法:

Seed-filling算法:

注:为了显示方便,将像素值乘以了一个整数进行放大。

3)color后的label图像

Two-pass算法:

Seed-filling算法:

注:颜色是随机生成的。

四、代码

1)Two-pass算法的一种实现

说明:

基于OpenCV和C++实现,领域:4-领域。实现与算法描述稍有差别(具体为记录具有相等关系的label方法实现上)。

  1. // Connected Component Analysis/Labeling By Two-Pass Algorithm
  2. // Author: www.icvpr.com
  3. // Blog : http://blog.csdn.net/icvpr
  4. #include <iostream>
  5. #include <string>
  6. #include <list>
  7. #include <vector>
  8. #include <map>
  9. #include <opencv2/imgproc/imgproc.hpp>
  10. #include <opencv2/highgui/highgui.hpp>
  11. void icvprCcaByTwoPass(const cv::Mat& _binImg, cv::Mat& _lableImg)
  12. {
  13. // connected component analysis (4-component)
  14. // use two-pass algorithm
  15. // 1. first pass: label each foreground pixel with a label
  16. // 2. second pass: visit each labeled pixel and merge neighbor labels
  17. //
  18. // foreground pixel: _binImg(x,y) = 1
  19. // background pixel: _binImg(x,y) = 0
  20. if (_binImg.empty() ||
  21. _binImg.type() != CV_8UC1)
  22. {
  23. return ;
  24. }
  25. // 1. first pass
  26. _lableImg.release() ;
  27. _binImg.convertTo(_lableImg, CV_32SC1) ;
  28. int label = 1 ; // start by 2
  29. std::vector<int> labelSet ;
  30. labelSet.push_back(0) ; // background: 0
  31. labelSet.push_back(1) ; // foreground: 1
  32. int rows = _binImg.rows - 1 ;
  33. int cols = _binImg.cols - 1 ;
  34. for (int i = 1; i < rows; i++)
  35. {
  36. int* data_preRow = _lableImg.ptr<int>(i-1) ;
  37. int* data_curRow = _lableImg.ptr<int>(i) ;
  38. for (int j = 1; j < cols; j++)
  39. {
  40. if (data_curRow[j] == 1)
  41. {
  42. std::vector<int> neighborLabels ;
  43. neighborLabels.reserve(2) ;
  44. int leftPixel = data_curRow[j-1] ;
  45. int upPixel = data_preRow[j] ;
  46. if ( leftPixel > 1)
  47. {
  48. neighborLabels.push_back(leftPixel) ;
  49. }
  50. if (upPixel > 1)
  51. {
  52. neighborLabels.push_back(upPixel) ;
  53. }
  54. if (neighborLabels.empty())
  55. {
  56. labelSet.push_back(++label) ; // assign to a new label
  57. data_curRow[j] = label ;
  58. labelSet[label] = label ;
  59. }
  60. else
  61. {
  62. std::sort(neighborLabels.begin(), neighborLabels.end()) ;
  63. int smallestLabel = neighborLabels[0] ;
  64. data_curRow[j] = smallestLabel ;
  65. // save equivalence
  66. for (size_t k = 1; k < neighborLabels.size(); k++)
  67. {
  68. int tempLabel = neighborLabels[k] ;
  69. int& oldSmallestLabel = labelSet[tempLabel] ;
  70. if (oldSmallestLabel > smallestLabel)
  71. {
  72. labelSet[oldSmallestLabel] = smallestLabel ;
  73. oldSmallestLabel = smallestLabel ;
  74. }
  75. else if (oldSmallestLabel < smallestLabel)
  76. {
  77. labelSet[smallestLabel] = oldSmallestLabel ;
  78. }
  79. }
  80. }
  81. }
  82. }
  83. }
  84. // update equivalent labels
  85. // assigned with the smallest label in each equivalent label set
  86. for (size_t i = 2; i < labelSet.size(); i++)
  87. {
  88. int curLabel = labelSet[i] ;
  89. int preLabel = labelSet[curLabel] ;
  90. while (preLabel != curLabel)
  91. {
  92. curLabel = preLabel ;
  93. preLabel = labelSet[preLabel] ;
  94. }
  95. labelSet[i] = curLabel ;
  96. }
  97. // 2. second pass
  98. for (int i = 0; i < rows; i++)
  99. {
  100. int* data = _lableImg.ptr<int>(i) ;
  101. for (int j = 0; j < cols; j++)
  102. {
  103. int& pixelLabel = data[j] ;
  104. pixelLabel = labelSet[pixelLabel] ;
  105. }
  106. }
  107. }

2)Seed-Filling种子填充方法

说明:

基于OpenCV和C++实现;领域:4-领域。

  1. // Connected Component Analysis/Labeling By Seed-Filling Algorithm
  2. // Author: www.icvpr.com
  3. // Blog : http://blog.csdn.net/icvpr
  4. #include <iostream>
  5. #include <string>
  6. #include <list>
  7. #include <vector>
  8. #include <map>
  9. #include <stack>
  10. #include <opencv2/imgproc/imgproc.hpp>
  11. #include <opencv2/highgui/highgui.hpp>
  12. void icvprCcaBySeedFill(const cv::Mat& _binImg, cv::Mat& _lableImg)
  13. {
  14. // connected component analysis (4-component)
  15. // use seed filling algorithm
  16. // 1. begin with a foreground pixel and push its foreground neighbors into a stack;
  17. // 2. pop the top pixel on the stack and label it with the same label until the stack is empty
  18. //
  19. // foreground pixel: _binImg(x,y) = 1
  20. // background pixel: _binImg(x,y) = 0
  21. if (_binImg.empty() ||
  22. _binImg.type() != CV_8UC1)
  23. {
  24. return ;
  25. }
  26. _lableImg.release() ;
  27. _binImg.convertTo(_lableImg, CV_32SC1) ;
  28. int label = 1 ; // start by 2
  29. int rows = _binImg.rows - 1 ;
  30. int cols = _binImg.cols - 1 ;
  31. for (int i = 1; i < rows-1; i++)
  32. {
  33. int* data= _lableImg.ptr<int>(i) ;
  34. for (int j = 1; j < cols-1; j++)
  35. {
  36. if (data[j] == 1)
  37. {
  38. std::stack<std::pair<int,int>> neighborPixels ;
  39. neighborPixels.push(std::pair<int,int>(i,j)) ; // pixel position: <i,j>
  40. ++label ; // begin with a new label
  41. while (!neighborPixels.empty())
  42. {
  43. // get the top pixel on the stack and label it with the same label
  44. std::pair<int,int> curPixel = neighborPixels.top() ;
  45. int curX = curPixel.first ;
  46. int curY = curPixel.second ;
  47. _lableImg.at<int>(curX, curY) = label ;
  48. // pop the top pixel
  49. neighborPixels.pop() ;
  50. // push the 4-neighbors (foreground pixels)
  51. if (_lableImg.at<int>(curX, curY-1) == 1)
  52. {// left pixel
  53. neighborPixels.push(std::pair<int,int>(curX, curY-1)) ;
  54. }
  55. if (_lableImg.at<int>(curX, curY+1) == 1)
  56. {// right pixel
  57. neighborPixels.push(std::pair<int,int>(curX, curY+1)) ;
  58. }
  59. if (_lableImg.at<int>(curX-1, curY) == 1)
  60. {// up pixel
  61. neighborPixels.push(std::pair<int,int>(curX-1, curY)) ;
  62. }
  63. if (_lableImg.at<int>(curX+1, curY) == 1)
  64. {// down pixel
  65. neighborPixels.push(std::pair<int,int>(curX+1, curY)) ;
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }

3)颜色标记(用于显示)

  1. // Connected Component Analysis/Labeling -- Color Labeling
  2. // Author: www.icvpr.com
  3. // Blog : http://blog.csdn.net/icvpr
  4. #include <iostream>
  5. #include <string>
  6. #include <list>
  7. #include <vector>
  8. #include <map>
  9. #include <stack>
  10. #include <opencv2/imgproc/imgproc.hpp>
  11. #include <opencv2/highgui/highgui.hpp>
  12. cv::Scalar icvprGetRandomColor()
  13. {
  14. uchar r = 255 * (rand()/(1.0 + RAND_MAX));
  15. uchar g = 255 * (rand()/(1.0 + RAND_MAX));
  16. uchar b = 255 * (rand()/(1.0 + RAND_MAX));
  17. return cv::Scalar(b,g,r) ;
  18. }
  19. void icvprLabelColor(const cv::Mat& _labelImg, cv::Mat& _colorLabelImg)
  20. {
  21. if (_labelImg.empty() ||
  22. _labelImg.type() != CV_32SC1)
  23. {
  24. return ;
  25. }
  26. std::map<int, cv::Scalar> colors ;
  27. int rows = _labelImg.rows ;
  28. int cols = _labelImg.cols ;
  29. _colorLabelImg.release() ;
  30. _colorLabelImg.create(rows, cols, CV_8UC3) ;
  31. _colorLabelImg = cv::Scalar::all(0) ;
  32. for (int i = 0; i < rows; i++)
  33. {
  34. const int* data_src = (int*)_labelImg.ptr<int>(i) ;
  35. uchar* data_dst = _colorLabelImg.ptr<uchar>(i) ;
  36. for (int j = 0; j < cols; j++)
  37. {
  38. int pixelValue = data_src[j] ;
  39. if (pixelValue > 1)
  40. {
  41. if (colors.count(pixelValue) <= 0)
  42. {
  43. colors[pixelValue] = icvprGetRandomColor() ;
  44. }
  45. cv::Scalar color = colors[pixelValue] ;
  46. *data_dst++ = color[0] ;
  47. *data_dst++ = color[1] ;
  48. *data_dst++ = color[2] ;
  49. }
  50. else
  51. {
  52. data_dst++ ;
  53. data_dst++ ;
  54. data_dst++ ;
  55. }
  56. }
  57. }
  58. }

4)测试程序

  1. // Connected Component Analysis/Labeling -- Test code
  2. // Author: www.icvpr.com
  3. // Blog : http://blog.csdn.net/icvpr
  4. #include <iostream>
  5. #include <string>
  6. #include <list>
  7. #include <vector>
  8. #include <map>
  9. #include <stack>
  10. #include <opencv2/imgproc/imgproc.hpp>
  11. #include <opencv2/highgui/highgui.hpp>
  12. int main(int argc, char** argv)
  13. {
  14. cv::Mat binImage = cv::imread("../icvpr.com.jpg", 0) ;
  15. cv::threshold(binImage, binImage, 50, 1, CV_THRESH_BINARY_INV) ;
  16. // connected component labeling
  17. cv::Mat labelImg ;
  18. icvprCcaByTwoPass(binImage, labelImg) ;
  19. //icvprCcaBySeedFill(binImage, labelImg) ;
  20. // show result
  21. cv::Mat grayImg ;
  22. labelImg *= 10 ;
  23. labelImg.convertTo(grayImg, CV_8UC1) ;
  24. cv::imshow("labelImg", grayImg) ;
  25. cv::Mat colorLabelImg ;
  26. icvprLabelColor(labelImg, colorLabelImg) ;
  27. cv::imshow("colorImg", colorLabelImg) ;
  28. cv::waitKey(0) ;
  29. return 0 ;
  30. }

Reference

[1] http://en.wikipedia.org/wiki/Connected-component_labeling

[2] http://homepages.inf.ed.ac.uk/rbf/HIPR2/label.htm

[3] http://www.codeproject.com/Articles/336915/Connected-Component-Labeling-Algorithm

声明:

作者:icvpr  | blog.csdn.net/icvpr

图像处理:连通区域算法相关推荐

  1. 通过并查集进行图像的连通区域实现

    通过"并查集"进行图像的连通区域实现 并查集是个什么概念也是通过刷题刷到的,然后就稍微了解了下 小白记录并查集自己实现图像连通区域问题 完整代码放在最后面 并查集可以自己谷歌,目前 ...

  2. OpenCV与图像处理学习九——连通区域分析算法(含代码)

    OpenCV与图像处理学习九--连通区域分析算法(含代码) 一.连通区域概要 二.Two-Pass算法 三.代码实现 一.连通区域概要 连通区域(Connected Component)一般是指图像中 ...

  3. 【机器视觉学习笔记】二值图像连通区域提取算法(C++)

    目录 原理 二值图像 连通区域(Connected Component) 连通区域分析(Connected Component Analysis,Connected Component Labelin ...

  4. matlab连通区边界_Matlab图像处理学习笔记(一):二值化、开操作、连通区域提取、重心、ROI...

    本博客主要记录我学习运用matlab进行一些基本的图像处理的一些笔记,如果有不当的地方,欢迎批评指正,一起学习,一起进步. 本篇是第一篇,只涉及到一些基本的操作,涉及到的知识点如下: 1.二值化 2. ...

  5. Matlab形态学图像处理:二值图像分割 标记连通区域和重心位置 删除连通区域

    Matlab形态学图像处理:二值图像分割 标记连通区域和重心位置 删除连通区域 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/detail ...

  6. 保留已经定位区域MATLAB,基于HSV肤色提取与连通区域过滤的人脸定位算法(Matlab)...

    概述 本篇记录了利用基于HSV颜色模型的肤色提取与连通区域过滤实现图片的人脸定位,附带知识的介绍略烦多,附带Matlab相关程序代码. 一.预备知识 图像预处理 1.1 灰度化 在RGB模型中,如果R ...

  7. 两种连通区域标记算法

    一. One-Pass对应的标记算法(Label.h) 使用: unsigned char label = (unsigned char )fspace_2d(imgMask2.row,imgMask ...

  8. OpenCv连通区域分析——Two-Pass 算法区域生长算法

    连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域.连通区域分析是指将图像中的各个连通区域找出并标记,通常连通区域分析处理的对象是一张二 ...

  9. OPENCV C++图像提取,图像处理,roi,阈值分割,连通区域筛选,边缘检测(以箱子边缘框选为例)

    本周有机会接触了一点opnev, 在此做一下记录, 最终以框选出下图箱子为目的(图片箱子为相机实拍结果,曝光有点低,会有亿点点暗 ), 本文会拆解步骤并附上图片, 完整的源码在最后.PS:本文参考了好 ...

  10. opencv笔记(十八)——连通区域分析

    本文主要介绍在CVPR和图像处理领域中较为常用的一种图像区域(Blob)提取的方法--连通性分析法(连通区域标记法).文中介绍了两种常见的连通性分析的算法:1)Two-pass:2)Seed-Fill ...

最新文章

  1. 企业融入租时代 谋求轻资产化高效运营
  2. 【学习/模板】tarjan割点
  3. Scala类的继承和抽象类
  4. node mysql 连接池创建_Node.js使用MySQL连接池的方法实例
  5. java封装demo_java封装
  6. 高级应用-路由协议配置
  7. 中考英语听说计算机考试满分,中考英语听说机考在即,这些考生可免试计满分...
  8. Kubernetes 搭建 ES 集群(存储使用 local pv)
  9. ZBrush for Mac的插图技巧
  10. 记忆测试系统c语言,c语言重点回忆
  11. Java基础练习题---this
  12. matlab数控加工,关于MATLAB复杂型面数控加工编程.pdf
  13. JS动态添加Html
  14. Maven依赖冲突详解
  15. 【JAVA学习】六、设计模式
  16. windows hotkey
  17. 撩人飙新意,美汁源首次转战“AR微电影”,要你变身当“导演”
  18. Linux这些年经历了什么?
  19. 不良资产证券化中信用评级国外发展史
  20. 苹果自带输入法怎么换行_Iphone手机原生输入法的5个技巧,学会了,才知道这么牛...

热门文章

  1. XSS线上靶场---haozi
  2. android代码禁用软键盘,Android 禁用软键盘
  3. 照着这本“书”,3年量产自动驾驶卡车
  4. 为什么会出现双摄像头手机?
  5. PyCharm的cache缓存文件清理
  6. mysql中%3c%3e和=_如何巧妙去除隐藏“站长统计”文字链接?
  7. debian 7 网络安装后无法联接wifi
  8. 通俗易懂spring之singleton和prototype
  9. 【Python|Kaggle】机器学习系列之Pandas基础练习题(二)
  10. android 指纹识别 怎么使用方法,android实现指纹识别