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

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

// 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) = 0

if (_binImg.empty() ||

_binImg.type() != CV_8UC1)

{

return ;

}

// 1. first pass

_lableImg.release() ;

_binImg.convertTo(_lableImg, CV_32SC1) ;

int label = 1 ; // start by 2

std::vector<int> labelSet ;

labelSet.push_back(0) ; // background: 0

labelSet.push_back(1) ; // foreground: 1

int 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 label

data_curRow[j] = label ;

labelSet[label] = label ;

}

else

{

std::sort(neighborLabels.begin(), neighborLabels.end()) ;

int smallestLabel = neighborLabels[0] ;

data_curRow[j] = smallestLabel ;

// save equivalence

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 ;

}

}

}

}

}

}

// update equivalent labels

// assigned with the smallest label in each equivalent label set

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 ;

}

// 2. second pass

for (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) = 0

if (_binImg.empty() ||

_binImg.type() != CV_8UC1)

{

return ;

}

_lableImg.release() ;

_binImg.convertTo(_lableImg, CV_32SC1) ;

int label = 1 ; // start by 2

int 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 label

while (!neighborPixels.empty())

{

// get the top pixel on the stack and label it with the same label

std::pair<int,int> curPixel = neighborPixels.top() ;

int curX = curPixel.first ;

int curY = curPixel.second ;

_lableImg.at<int>(curX, curY) = label ;

// pop the top pixel

neighborPixels.pop() ;

// push the 4-neighbors (foreground pixels)

if (_lableImg.at<int>(curX, curY-1) == 1)

{// left pixel

neighborPixels.push(std::pair<int,int>(curX, curY-1)) ;

}

if (_lableImg.at<int>(curX, curY+1) == 1)

{// right pixel

neighborPixels.push(std::pair<int,int>(curX, curY+1)) ;

}

if (_lableImg.at<int>(curX-1, curY) == 1)

{// up pixel

neighborPixels.push(std::pair<int,int>(curX-1, curY)) ;

}

if (_lableImg.at<int>(curX+1, curY) == 1)

{// down pixel

neighborPixels.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 labeling

cv::Mat labelImg ;

icvprCcaByTwoPass(binImage, labelImg) ;

//icvprCcaBySeedFill(binImage, labelImg) ;

// show result

cv::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

c++连通区域处理 种子生成法相关推荐

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

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

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

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

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

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

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

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

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

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

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

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

  7. OpenCV编程案例:使用轮廓函数检测连通区域

    转自:http://www.aiseminar.cn/bbs/thread-617-1-1.html 此案例位于CXCORE中cvDrawContours函数介绍部分给出.此程序首先载入一个二值图像文 ...

  8. OpenCV删除面积小的区域 实现图像二值化分割 标记连通区域

    OpenCV删除面积小的区域 实现图像二值化分割 标记连通区域    [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/781 ...

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

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

最新文章

  1. (转)C# foreach 中获取索引index的方法
  2. 像git一样使用AI数据集!免费数据托管工具上线,让模型用「活」的数据集训练...
  3. Gitblit用户没有push权限,但是已经在team里面配置了
  4. QT开发及实例学习之六控件
  5. 实现机器学习的循序渐进指南XI——DBSCAN
  6. Linux软件包管理神器--YUM
  7. 【算法与数据结构】二叉堆和优先队列 Priority Queue
  8. html5 游戏 算法,JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法完整实例
  9. Spring中都用到了那些设计模式
  10. Java进阶之路,技术要点
  11. 【持续更新】java 指令释疑
  12. java+junit百科_JUnit介绍
  13. 酷派无线升级服务器设置在哪里,酷派手机CDA自助升级线刷工具安装教程
  14. 计算机原理阅读,计算机原理与应用作业
  15. 吹塑模具和注塑模具的区别
  16. html5改变窗口大小,js怎么改变窗口大小?js改变窗口大小方法
  17. Unity 透视相机世界和屏幕坐标系转换
  18. DongDong认亲戚(字符串之间的并查集应用)
  19. Vue官网提供表单验证cnpm i vee-validate@2 --save
  20. Mac终端常用命令及报错处理

热门文章

  1. BUUCTF:被劫持的神秘礼物
  2. python手机编译器怎么编写程序_怎么用手机编写Python程序?
  3. 监测数据处理系统-自动导出监测日报表、周报表及月报表(基坑监测、地铁监测)
  4. 关于PHP工程师职业资格证书的了解
  5. 小飞的电梯调度算法,光影切割问题编程之美
  6. 方案A:vpn-instance to vpn-instance(PE-CE)
  7. antd Table组件增加一个自增序号
  8. 南京工业大学计算机科学与技术研究生调剂,南京工业大学计算机科学与技术学院硕士研究生考试复试名单...
  9. DL: Basic of C/C++(to be continued)
  10. 聊聊编程语言学习之路