OpenCV提供了专门的函数标记连通区域connectedComponents,它有四种形式:

1. int connectedComponents(InputArray image, OutputArray labels, int connectivity, int ltype, int ccltype);

参数说明:

image : 输入原始图像,8bit单通道,二值图像

labels : 输出标记图像

connectivity : 邻域类型,4连通或8连通,所以这个值通常为两个值(4或8)

ltype : 输出标记的类型,只支持 CV_32S和CV_16U

ccltype : 连通标记的算法,这个值有三个选择CCL_WU(8连通和4连通均用SAUF算法),CCL_DEFAULT(8连通用BBDT算法,4连通用SAUF算法)和CCL_GRANA(8连通用BBDT算法,4连通用SAUF算法)。(后两种方法是一样的,这里有疑问)

2. int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S);

这个函数实际上就是第一个的简化版

这两个函数的返回值是标记的个数[0,N-1],0表示背景区域

3. int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids,  int connectivity, int ltype, int ccltype);

参数说明:

image : 输入原始图像,8bit单通道,二值图像

labels : 输出标记图像

stats : 每个标记的统计输出,类型为CV_32S,使用格式为stats(label, COLUMN),其中COLUMN为连通类型,包括CC_STAT_LEFT、CC_STAT_TOP、CC_STAT_WIDTH、CC_STAT_HEIGHT、CC_STAT_AREA

centroids : 每个标记区域的质心,类型为CV_64F,使用方法为x = centroids(label,0), y = centroids(label, 1)

connectivity : 邻域类型,4连通或8连通,所以这个值通常为两个值(4或8)

ltype : 输出标记的类型,只支持 CV_32S和CV_16U

4. int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids,  int connectivity = 8, int ltype = CV_32S);

第三个函数的简化版

----------------------华丽的分界线------------------------

以下转自:https://blog.csdn.net/icvpr/article/details/10259577

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方法实现上)。

[cpp] view plaincopy
  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-领域。

[cpp] view plaincopy
  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)颜色标记(用于显示)

[cpp] view plaincopy
  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)测试程序

[cpp] view plaincopy
  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. }

图像分割-连通区域分析相关推荐

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

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

  2. VTK修炼之道51:图形基本操作进阶_连通区域分析

    1.连通区域分析 许多图形数据中,并非只包含一个对象(连通区域).而在处理这些图形数据时,有时需要对每一个对象单独处理或者让其单独显示.比如,利用MarchingCube方法提取三维图像中的等值面,得 ...

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

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

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

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

  5. OpenCV二值图像处理——阈值,连通区域分析(C++)

    阈值 阈值又叫临界值,是指一个效应能够产生的最低值或最高值 对于图像的直方图存在明显边界的图像,我们可以很容易找到这个阈值,但是如果图像直方图分界不明显,那么这个阈值的寻找将变得十分困难.因此我们存在 ...

  6. opencv(十三)-快速连通区域分析

    索引目录 1.连通区域标记算法 2.剔除小连通区域 参考 1.连通区域标记算法 连接区域标记算法(connected component labeling algorithm)是图像分析中最常用的算法 ...

  7. 深度学习,分割后处理之通过连通成分分析去除假阳性区域,提高分割准确度

    用深度学习方法得到的分割结果,会有一些假阳性区域.通过去除这些假阳性区域,可以提高分割结果. 比如说做肾分割,大家都知道,肾只有左右两边有,如果分割结果出现了三个区域,则可以根据常识,去除那个假阳性区 ...

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

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

  9. c++连通区域处理 种子生成法

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

  10. python 连通区域_python skimage 连通性区域检测方法

    python skimage 连通性区域检测方法 涉及到的函数为 import matplotlib.pyplot as plt from skimage import measure, color ...

最新文章

  1. 报Java面授班有哪些优势
  2. Java Web学习总结(30)——Service层在MVC框架中的意义和职责
  3. zabbix客户端一键安装脚本(主动模式监控)
  4. linux考试基础知识测验
  5. linux查看非注释行_Linux筛除空行和注释行的技巧
  6. 微软要干掉ADOBE?
  7. shell读取mysql_shell读取mysql数据库
  8. PCL之Ubuntu16.04下编译libfreenect2和PCL以支持KinectV2点云处理
  9. 中科大计算机考研录取分数线_中科大计算机考研 | 跨考CS上岸经验分享!
  10. MFC使用ADO对象开发数据库应用程序
  11. javascript自动分号补齐带来的坑
  12. 【毕业设计】深度学习 python opencv 实现人脸年龄性别识别
  13. day18-正则表达式
  14. 【FND】EBS中Java并发程序开发
  15. windows7彻底删除流氓软件的操作方法
  16. mysql索引用trie树_数据结构与算法之美【完整版】
  17. 【Paraview教程】第一章安装与基础介绍
  18. HDU 6082 度度熊与邪恶大魔王 (完全背包)
  19. C语言程序对夏令时的处理
  20. vsto clickonce 发布setup.exe失败

热门文章

  1. xss.haozi靶场通关
  2. IE浏览器url中出现中文访问404问题解决
  3. PythonProgramming.net Python 金融教程(转)
  4. 现有存储系统技术架构
  5. 苹果支付Java后台总结
  6. python spss modeler 比较_非常值得收藏的 IBM SPSS Modeler 算法简介
  7. python对京东评论的爬取_python爬取京东评论(三)
  8. 大数据时代的“拼图者”
  9. 论文:YOLOX: Exceeding YOLO Series in 2021
  10. 手机重装android系统,安卓手机系统怎么重装(刷机)