OpenCV:二值图像连通区域分析与标记算法实现
http://blog.csdn.net/cooelf/article/details/26581539?utm_source=tuicool&utm_medium=referral
版权声明:本文为博主原创文章,未经博主允许不得转载。
编译环境:
操作系统:Win8.1 64位
IDE平台:Visual Studio 2013 Ultimate
OpenCV:2.4.8
一、连通域
在图像中,最小的单位是像素,每个像素周围有8个邻接像素,常见的邻接关系有2种:4邻接与8邻接。4邻接一共4个点,即上下左右,如下左图所示。8邻接的点一共有8个,包括了对角线位置的点,如下右图所示。
如果像素点A与B邻接,我们称A与B连通,于是我们不加证明的有如下的结论:
如果A与B连通,B与C连通,则A与C连通。
在视觉上看来,彼此连通的点形成了一个区域,而不连通的点形成了不同的区域。这样的一个所有的点彼此连通点构成的集合,我们称为一个连通区域。
下面这符图中,如果考虑4邻接,则有3个连通区域;如果考虑8邻接,则有2个连通区域。(注:图像是被放大的效果,图像正方形实际只有4个像素)。
二、连通区域的标记
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<iostream>
- #include <string>
- #include <list>
- #include <vector>
- #include <map>
- #include <stack>
- #include <opencv2/imgproc/imgproc.hpp>
- #include <opencv2/highgui/highgui.hpp>
- 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<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)); // 像素位置: <i,j>
- ++label; // 没有重复的团,开始新的标签
- while (!neighborPixels.empty())
- {
- std::pair<int,int> curPixel = neighborPixels.top(); //如果与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它
- int curX = curPixel.first;
- int curY = curPixel.second;
- lableImg.at<int>(curX, curY) = label;
- neighborPixels.pop();
- if (lableImg.at<int>(curX, curY-1) == 1)
- {//左边
- neighborPixels.push(std::pair<int,int>(curX, curY-1));
- }
- if (lableImg.at<int>(curX, curY+1) == 1)
- {// 右边
- neighborPixels.push(std::pair<int,int>(curX, curY+1));
- }
- if (lableImg.at<int>(curX-1, curY) == 1)
- {// 上边
- neighborPixels.push(std::pair<int,int>(curX-1, curY));
- }
- if (lableImg.at<int>(curX+1, curY) == 1)
- {// 下边
- neighborPixels.push(std::pair<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<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<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); // 不连通,标签+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<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<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] = 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::Mat grayImg;
- labelImg *= 10;
- labelImg.convertTo(grayImg, CV_8UC1);
- cv::imshow("labelImg", grayImg);
- */
- cv::waitKey(0);
- return 0;
- }
四、演示结果
原图:
效果图:
参考文章:
http://www.cnblogs.com/ronny/p/img_aly_01.html
http://blog.csdn.net/icvpr/article/details/10259577
OpenCV:二值图像连通区域分析与标记算法实现的更多相关文章
- OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波
http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...
- 使用OpenCV查找二值图中最大连通区域
http://blog.csdn.net/shaoxiaohu1/article/details/40272875 使用OpenCV查找二值图中最大连通区域 标签: OpenCVfindCoutour ...
- OpenCV二值图像孔洞填充的一个简单方法
在Matlab下,使用imfill可以很容易的完成孔洞填充操作,感觉这是一个极为常用的方法,然而不知道为什么OpenCV里面却没有集成这个函数.在网上查了好多关于Opencv下的孔洞填充方法,大部分使 ...
- Opencv2系列学习笔记10(提取连通区域轮廓)
连通区域指的是二值图像中相连像素组成的形状.而内.外轮廓的概念及opencv1中如何提取二值图像的轮廓见我的这篇博客:http://blog.csdn.net/lu597203933/article/ ...
- Opencv2系列学习笔记10(提取连通区域轮廓) 另一个
http://blog.csdn.net/lu597203933/article/details/17362457 连通区域指的是二值图像中相连像素组成的形状.而内.外轮廓的概念及opencv1中如何 ...
- JVM GC-----3、垃圾标记算法(二)
在上一篇文章中,介绍了在GC机制中,GC是以什么标准判定对象可以被标记的,以及最有效最常用的可达性分析法.今天介绍另外一种非常常用的标记算法,它的应用面也相当广泛.这就是:引用计数法 Referenc ...
- Java虚拟机(三)垃圾标记算法与Java对象的生命周期
前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的生命周期. 1.垃圾收集器概述 垃圾收集器( ...
- [LeetCode] Number of Connected Components in an Undirected Graph 无向图中的连通区域的个数
Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), ...
- opencv的实用研究--分析轮廓并寻找边界点
opencv的实用研究--分析轮廓并寻找边界点 轮廓是图像处理中非常常见的.对现实中的图像进行采样.色彩变化.灰度变化之后,能够处理得到的是“轮廓”.它直接地反应你了需要分析对象的边界特 ...
随机推荐
- HTML,JS禁止鼠标右键、禁止全选、复制、粘贴的方法
禁止鼠标右键.禁止全选.复制.粘贴: oncontextmenu事件禁用右键菜单: js代码: document.oncontextmenu = function(){ event.returnVal ...
- Nginx下$_POST获取不到数据的解决方法
运行环境:windows+phpstorm+Nginx 步骤1:找到php.ini 配置文件,查找enable_post_data_reading变量,把Off改为On,确保其打开状态: 步骤2:将p ...
- 分布式版本控制系统Git-----9.Git 使用的小技巧
1. git push -u orgin master[后面push的时候可简写] 第一次push的时候-u后面加上<本地分支名><远程分支>,第二次push的时候就只需要写g ...
- Xcode-之Code Snippets Library
一.说明 Code Snippets Library 为代码片段库,在开发项目过程中经常会遇到一些重复的代码块,创建代码片段库可以减少我们开发的工作量,而且非常方便调用.Xcode系统中也为我们提供了 ...
- 设置phpMyAdmin本地自动登陆
一般配置本地测试用的 phpMyAdmin 可以不用每次输入帐号密码,打开后自动登陆就行了. 版本: phpMyAdmin 3.5.3 打开: phpMyAdmin 根目录 复制: config.sa ...
- 第一百零九节,JavaScript面向对象与原型
JavaScript面向对象与原型 学习要点: 1.学习条件 2.创建对象 3.原型 4.继承 ECMAScript有两种开发模式:1.函数式(过程化),2.面向对象(OOP).面向对象的语言有一个标 ...
- html5权威指南:定制input元素
第十三章:定制Inpur元素,http://www.cnblogs.com/polk6/p/5417921.html#Menu3-New input标签最全面的type属性:http://blog.s ...
- 【Python@Thread】thread模块
一.关于Python多线程 Python解释器中可以同时运行多个线程,但是再任意时刻只能有一个线程在解释器运行. Python虚拟机的访问是由全局解锁器(GIL)控制的,由GIL保证同时只有一个线程的 ...
- jQuery Post 提交内容中有标签报错
Post编辑一点内容要传后台数据库: var html = editor2.html() console.log(encodeURIComponent(html)); //console.log(&q ...
- 图的连通性:有向图强连通分量-Tarjan算法
参考资料:http://blog.csdn.net/lezg_bkbj/article/details/11538359 上面的资料,把强连通讲的很好很清楚,值得学习. 在一个有向图G中,若两顶点间至 ...