图像连通域标记算法
⑴ opencv如何标记连通区域 并且提取连通区域
代码
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 ;
}
⑵ 什么是图像连通域分析
连通区域(connected
component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域(region,blob)。就是像素值相近的同一块区域
⑶ java代码怎么实现计算图像二值连通区域的质心
一:几何距(Geometric Moments)知识与质心寻找原理
1. Image Moments是图像处理中非常有用的算法,可以用来计算区域图像的质心,方向等几何特性,同时Mpq的高阶具有旋转不变性,可以用来实现图像比较分类,正是因为Moments有这些特性,很多手绘油画效果也会基于该算法来模拟实现。它的数学表达为:
它的低阶M00,M01, M10可以用来计算质心,中心化以后M11,M02,M20可以用来计算区域的方向/角度
2. 什么是质心
就是通过该点,区域达到一种质量上的平衡状态,可能物理学上讲的比较多,简单点的说就是规则几何物体的中心,不规则的可以通过挂绳子的方法来寻找。
二:算法流程
1. 输入图像转换为二值图像
2. 通过连通组件标记算法找到所有的连通区域,并分别标记
3. 对每个连通区域运用计算几何距算法得到质心
4. 用不同颜色绘制连通区域与质心,输出处理后图像
三:算法效果
左边为原图, 右边蓝色为连通组件标记算法处理以后结果,白色点为质心
四:关键代码解析
1. 计算几何距算法代码
doublem00 = moments(pixels, width, height, 0, 0);
doublexCr = moments(pixels, width, height, 1, 0) / m00;// row
doubleyCr = moments(pixels, width, height, 0, 1) / m00;// column
return new double[]{xCr, yCr};
⑷ NI Vision:二值图像连通域标记算法
前面说到,要使用Labwindows + NI Vision(IMAQ Vision)这套商用开发框架来做数图课设。很明显,这套虚拟仪器开发平台由NI Instrument(美国国家仪器公司)开发的。大名鼎鼎的Labview软件就是这个公司开发的。相比较而言,Labwindows使用ANSI C开发,但应用场景是差不多的。
在做课程作业的时候,遇到了一个很有趣的应用。输入是米粒,比背景灰度要低,目的是输出米粒的颗数、面积、周长和孔数,这是工业上的一个很常见的应用。具体处理过程是二值化后使用低通滤波,并计算各种性质。
界面设计如下,可以看到米粒的详细情况。
让我感兴趣的,是通过怎样的算法能够得到米粒的数量?之前曾经用过OpenCV中找最大外界矩形这个函数,但没有具体了解算法实现。直觉告诉我原理应该是相似的。
可以看到,每一个米粒之间都是不连通的。这里就就提出了一个概念。 连通区域(Connected Component) 是指图像中相邻并有相同像素值的图像区域。 连通区域分析(Connected Component Analysis,Connected Component Labeling) 是指将图像中的各个连通区域找出并标记。
二值图像分析最重要的方法就是连通区域标记,它是所有二值图像分析的基础,它通过对二值图像中白色像素(目标)的标记,让每个单独的连通区域形成一个被标识的块,进一步的我们就可以获取这些块的轮廓、外接矩形、质心、不变矩等几何参数。如果要得到米粒的数量,那么通过连通区域分析(这里是二值图像的连通区域分析),就可以得到标记的数量,从而得到米粒的数量。
下面这幅图中,如果考虑4邻接,则有3个连通区域,8邻接则是2个。
从连通区域的定义可以知道,一个连通区域是由具有相同像素值的相邻像素组成像素集合,因此,我们就可以通过这两个条件在图像中寻找连通区域,对于找到的每个连通区域,我们赋予其一个唯一的 标识(Label) ,以区别其他连通区域。
连通区域分析的基本算法有两种:1)Two-Pass两便扫描法 2)Seed-Filling种子填充法 。
两遍扫描法(Two-Pass),正如其名,指的就是通过扫描两遍图像,就可以将图像中存在的所有连通区域找出并标记。
说了一堆数学语言,其实用图很好理解
种子填充方法来源于计算机图形学,常用于对某个图形进行填充。它基于区域生长算法。至于区域生长算法是什么,可以参照我的这篇 文章 。
同样的,上动图
NI Vision 中的算子定义如下
OpenCV中也有相应的算子
这里参照其他博客实现一下Two-Pass算法,Seed-Filling算法就偷懒不搞了。
Reference:
OpenCV实现图像连通组件标记与分析
OpenCV-二值图像连通域分析
数字图像处理技术 ——邓继忠(我的任课老师)
⑸ 急~跪求对二值图像连通域分析的Matlab程序,最好后面有注释,方便我看懂,谢谢了~
%最大连通域提取 其作用是找到最大的连通域
if length(size(bw))>2
bw=rgb2gray(bw); %将图像进行二值化处理
end
L=bwlabel(bw); %标记图像
stats=regionprops(L);
Ar=cat(1,stats.Area);
ind=find(Ar==max(Ar));
bw=zeros(size(bw));
bw(L==ind)=1;
⑹ 对图像中连通域进行标记并计算面积matlab
matlab里有连通域标记的函数bwlabel。
至于求连通域面积,stats=regionprops(ibw,'Area');%ibw是标记号的图。
求中值也有这函数的,median(A)。
⑺ 连通区域
连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域,连通区域分析是指将图像中的各个连通区域找出并标记。
连通区域分析是一种在CV和图像分析处理的众多应用领域中较为常用和基本的方法。
例如:OCR识别中字符分割提取(车牌识别、文本识别、字幕识别等)、视觉跟踪中的运动前景目标分割与提取(行人入侵检测、遗留物体检测、基于视觉的车辆检测与跟踪等)、医学图像处理(感兴趣目标区域提取)等。
在需要将前景目标提取出来以便后续进行处理的应用场景中都能够用到连通区域分析方法,通常连通区域分析处理的对象是一张二值化后的图像。
在图像中,最小的单位是像素,每个像素周围有邻接像素,常见的邻接关系有2种:4邻接与8邻接。
如果A与B连通,B与C连通,则A与C连通,在视觉上看来,彼此连通的点形成了一个区域,而不连通的点形成了不同的区域。这样的一个所有的点彼此连通点构成的集合,我们称为一个连通区域。
两遍扫描法( Two-Pass ),正如其名,指的就是通过扫描两遍图像,将图像中存在的所有连通域找出并标记。
第一次扫描:
•从左上角开始遍历像素点,找到第一个像素为255的点,label=1;
•当该像素的左邻像素和上邻像素为无效值时,给该像素置一个新的label值,label ++,记录集合;
•当该像素的左邻像素或者上邻像素有一个为有效值时,将有效值像素的label赋给该像素的label值;
•当该像素的左邻像素和上邻像素都为有效值时,选取其中较小的label值赋给该像素的label值
第二次扫描:
•对每个点的label进行更新,更新为其对于其集合中最小的label
步骤
1、加载原始图像
2、阈值分割,将图像分割为黑白两个部分
3、对图像进行开运算,即先腐蚀在膨胀
4、对开运算的结果再进行 膨胀,得到大部分是背景的区域
5、通过距离变换 Distance Transform 获取前景区域
6、背景区域sure_bg 和前景区域sure_fg相减,得到即有前景又有背景的重合区域
7、连通区域处理
8、最后使用分水岭算法
⑻ 将一张图二值化后,有很多连通区域,我想分别求出每一块连通区域的面积,不知道有什么好一点的算法
图像处理里有一种叫做Labeling处理的算法。
可以把二值图划分区域,标出不同的区域编号。
只要计算每种编号的个数,就是对应区域的面积了。
如果没看懂,不是算法难,是我表达的不好。哈。
⑼ matlab对二值图像标记,求连通区域的周长,面积。matlab具体代码
二值图像中背景标记为0,联通域标记为1,二值图像记为a
面积的求法:
k=regionprops(a,'Area'); %k得到的是一个structure结构
S=k.Area;
周长求法:
L=length(find(bwperim(a,4)==1));
%bwperim是求图像联通域边界区,对标记边界的1进行计数。
ps:可以好好看下regionprops的用法,形态学方面的有一些很有用的处理。
⑽ 谁能给个好用的连通域处理的算法,要8邻域的
楼主要处理的图像是什么样的?黑白图像还是彩色的?我这里有个以前用过的只针对黑白图像的连通域处理,目标是白色,背景是黑色的,不过,如果你的图像不是这样的,那先二值化,然后变换为黑色背景白色目标的图片 CodeGo.net,再调用我这个函数,先看懂我这个函数,然后修改为可以处理白色背景黑色目标 具体函数
其中THRESHOLD_JOINNUM 是个宏定义……
你要先把图像的边缘像素处理下,把边缘1到2个像素宽度的位置,都置为背景色,否则,可能得不到正确的结果,也可能造成访问越界。也就是把四周的边缘像素都置为背景色 然后,如果你仔细看这个函数的实现的话,其实是使用两次连通域处理,一次是用来把黑色的小区域变为白色,也就是填补目标区域中可能出现的小黑洞。然后第二次处理才是用来删除那些小于阈值的白色目标小连通域 这个函数你要看明白才能正确使用。看吧。
2. 楼主要处理的图像是什么样的?黑白图像还是彩色的?我这里有个以前用过的只针对黑白图像的连通域处理,目标是白色,背景是黑色的,不过,如果你的图像不是这样的,那先二值化,然后变换为黑色背景白色目标的图片,再调用我这个函数,先看懂我这个函数,然后修改为可以处理白色背景黑色目标 具体函数