本文主要介绍在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方法实现上)

//  Connected Component Analysis/Labeling By Two-Pass Algorithm
//  Author:  www.icvpr.com
//  Blog  :  http://blog.csdn.net/icvpr
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>void icvprCcaByTwoPass(const cv::Mat& _binImg, cv::Mat& _lableImg)
{// connected component analysis (4-component)// use two-pass algorithm// 1. first pass: label each foreground pixel with a label// 2. second pass: visit each labeled pixel and merge neighbor labels// // foreground pixel: _binImg(x,y) = 1// background pixel: _binImg(x,y) = 0if (_binImg.empty() ||_binImg.type() != CV_8UC1){return ;}// 1. first pass_lableImg.release() ;_binImg.convertTo(_lableImg, CV_32SC1) ;int label = 1 ;  // start by 2std::vector<int> labelSet ;labelSet.push_back(0) ;   // background: 0labelSet.push_back(1) ;   // foreground: 1int rows = _binImg.rows - 1 ;int cols = _binImg.cols - 1 ;for (int i = 1; i < rows; i++){int* data_preRow = _lableImg.ptr<int>(i-1) ;int* data_curRow = _lableImg.ptr<int>(i) ;for (int j = 1; j < cols; j++){if (data_curRow[j] == 1){std::vector<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) ;  // assign to a new labeldata_curRow[j] = label ;labelSet[label] = label ;}else{std::sort(neighborLabels.begin(), neighborLabels.end()) ;int smallestLabel = neighborLabels[0] ;  data_curRow[j] = smallestLabel ;// save equivalencefor (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 ;}}}              }}}// update equivalent labels// assigned with the smallest label in each equivalent label setfor (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 ;}// 2. second passfor (int i = 0; i < rows; i++){int* data = _lableImg.ptr<int>(i) ;for (int j = 0; j < cols; j++){int& pixelLabel = data[j] ;pixelLabel = labelSet[pixelLabel] ;  }}
}

2)Seed-Filling种子填充方法

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

//  Connected Component Analysis/Labeling By Seed-Filling Algorithm
//  Author:  www.icvpr.com
//  Blog  :  http://blog.csdn.net/icvpr
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>void icvprCcaBySeedFill(const cv::Mat& _binImg, cv::Mat& _lableImg)
{// connected component analysis (4-component)// use seed filling algorithm// 1. begin with a foreground pixel and push its foreground neighbors into a stack;// 2. pop the top pixel on the stack and label it with the same label until the stack is empty// // foreground pixel: _binImg(x,y) = 1// background pixel: _binImg(x,y) = 0if (_binImg.empty() ||_binImg.type() != CV_8UC1){return ;}_lableImg.release() ;_binImg.convertTo(_lableImg, CV_32SC1) ;int label = 1 ;  // start by 2int rows = _binImg.rows - 1 ;int cols = _binImg.cols - 1 ;for (int i = 1; i < rows-1; i++){int* data= _lableImg.ptr<int>(i) ;for (int j = 1; j < cols-1; j++){if (data[j] == 1){std::stack<std::pair<int,int>> neighborPixels ;   neighborPixels.push(std::pair<int,int>(i,j)) ;     // pixel position: <i,j>++label ;  // begin with a new labelwhile (!neighborPixels.empty()){// get the top pixel on the stack and label it with the same labelstd::pair<int,int> curPixel = neighborPixels.top() ;int curX = curPixel.first ;int curY = curPixel.second ;_lableImg.at<int>(curX, curY) = label ;// pop the top pixelneighborPixels.pop() ;// push the 4-neighbors (foreground pixels)if (_lableImg.at<int>(curX, curY-1) == 1){// left pixelneighborPixels.push(std::pair<int,int>(curX, curY-1)) ;}if (_lableImg.at<int>(curX, curY+1) == 1){// right pixelneighborPixels.push(std::pair<int,int>(curX, curY+1)) ;}if (_lableImg.at<int>(curX-1, curY) == 1){// up pixelneighborPixels.push(std::pair<int,int>(curX-1, curY)) ;}if (_lableImg.at<int>(curX+1, curY) == 1){// down pixelneighborPixels.push(std::pair<int,int>(curX+1, curY)) ;}}       }}}
}

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

//  Connected Component Analysis/Labeling -- Color Labeling
//  Author:  www.icvpr.com
//  Blog  :  http://blog.csdn.net/icvpr
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>cv::Scalar icvprGetRandomColor()
{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 icvprLabelColor(const cv::Mat& _labelImg, cv::Mat& _colorLabelImg)
{if (_labelImg.empty() ||_labelImg.type() != CV_32SC1){return ;}std::map<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<int>(i) ;uchar* data_dst = _colorLabelImg.ptr<uchar>(i) ;for (int j = 0; j < cols; j++){int pixelValue = data_src[j] ;if (pixelValue > 1){if (colors.count(pixelValue) <= 0){colors[pixelValue] = icvprGetRandomColor() ;}cv::Scalar color = colors[pixelValue] ;*data_dst++   = color[0] ;*data_dst++ = color[1] ;*data_dst++ = color[2] ;}else{data_dst++ ;data_dst++ ;data_dst++ ;}}}
}

4)测试程序

//  Connected Component Analysis/Labeling -- Test code
//  Author:  www.icvpr.com
//  Blog  :  http://blog.csdn.net/icvpr
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>int main(int argc, char** argv)
{cv::Mat binImage = cv::imread("../icvpr.com.jpg", 0) ;cv::threshold(binImage, binImage, 50, 1, CV_THRESH_BINARY_INV) ;// connected component labelingcv::Mat labelImg ;icvprCcaByTwoPass(binImage, labelImg) ;//icvprCcaBySeedFill(binImage, labelImg) ;// show resultcv::Mat grayImg ;labelImg *= 10 ;labelImg.convertTo(grayImg, CV_8UC1) ;cv::imshow("labelImg", grayImg) ;cv::Mat colorLabelImg ;icvprLabelColor(labelImg, colorLabelImg) ;cv::imshow("colorImg", colorLabelImg) ;cv::waitKey(0) ;return 0 ;
}

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

https://www.tuicool.com/articles/Z3IVruJ

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

Matlab版

https://blog.csdn.net/yuejuan0207/article/details/49645399

opencv笔记(十八)——连通区域分析相关推荐

  1. Opencv笔记(十八)——轮廓的更多函数及其层次结构

    凸缺陷 前面我们已经学习了轮廓的凸包,对象上的任何凹陷都被成为凸缺陷.OpenCV 中有一个函数 cv.convexityDefect() 可以帮助我们找到凸缺陷.函数调用如下: hull = cv2 ...

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

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

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

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

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

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

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

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

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

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

  7. Apache Nutch 1.3 学习笔记十(插件机制分析)

    1. 一些对象说明 PluginRepository:这是一个用于存储所有插件描述对象(PluginDescriptor),插件扩展点(ExtensionPoint)和被激活的插件. PluginDe ...

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

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

  9. python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置...

    python3.4学习笔记(十八) pycharm 安装使用.注册码.显示行号和字体大小等常用设置 Download JetBrains Python IDE :: PyCharm http://ww ...

最新文章

  1. 召回:是塔,是塔,但不是双塔!
  2. mysql 5.0存储过程学习总结
  3. JavaScript常见面试题和答案
  4. (转:htk中命令行参数的读取)
  5. 【bug:鳄梨】【上线前修改其他bug急着提交造成的bug】
  6. Python科学计算系列2—不等式和不等式组
  7. python微信语音转发方法_【高逼格技巧系列】如何在微信中转发语音
  8. php imagick 取得psd缩略图,PHP中使用Imagick操作PSD文件实例
  9. matlab | imcrop手动截图演示
  10. iPhoneSE3变化却提高了价格,安卓手机获得了喘息的空间
  11. mysql数据库知识
  12. 歌声美化歌声转换方法与方案
  13. Sphinx 简单应用
  14. DHCP服务以及配置DHCP服务器
  15. pcf8591简明教程 及 51单片机最大只能读到127问题解决
  16. Outlook打开工享Calendar
  17. 水深注记采用渐变颜色的编程实现
  18. 【基础版】大学计算机-计算思维导论
  19. 2015 年最热门的国人开发开源软件 TOP 50
  20. html图片不能拖动,关于html5图片拖动的代码的问题?

热门文章

  1. windows访问控制列表ACL
  2. ​数据科学家必须了解的事:中心极限定理
  3. Alpine Linux(初)
  4. 前端(Jinja2)
  5. C++ 制作简易音乐播放器
  6. MySQL时间戳与时间格式的转换
  7. 利用python进行数据分析学习笔记
  8. Matlab绘制特殊图形------散点图
  9. 恒烁M0+系列CX32L003单片机及其GPIO的操作
  10. python 网络通讯 plc_Python TCP通信网络编程