NIVision:高效二值图像连通域标记技术解析

更新:11-17 民间故事 我要投稿 纠错 投诉

大家好,关于NIVision:高效二值图像连通域标记技术解析很多朋友都还不太明白,今天小编就来为大家分享关于的知识,希望对各位有所帮助!

虚拟仪器(VI)的本质是利用计算机的IO接口完成信号采集、测试和调试。它依靠现代PC机强大的计算能力,实现信号数据的计算、分析和处理,并利用显示器模拟传统仪器的控制面板。完成各种测试功能的计算机仪器系统。

在做课程作业时,我遇到了一个非常有趣的应用程序。输入是米粒,其灰度低于背景。目的是输出米粒的个数、面积、周长和孔数。这是工业中非常常见的应用。具体处理过程是二值化后使用低通滤波,计算各种属性。

原始图像-二值化-低通滤波-连通域标注接口设计如下。可以看到米粒的细节。

Labwindows界面

我感兴趣的是用什么算法可以得到米粒的数量?之前用过OpenCV中求最大外矩形的功能,但对算法实现没有具体了解。直觉告诉我,原理应该是相似的。

1.连通区域

可以看出,每颗米粒都是不相连的。这里提出了一个概念。连通区域(Connected Component)是指图像中具有相同像素值的相邻图像区域。连通区域分析(Connected Component Analysis,Connected Component Labeling)是指寻找并标记图像中的每个连通区域。

二值图像分析最重要的方法是连通区域标记,它是所有二值图像分析的基础。它允许每个单独的连接区域通过标记二值图像中的白色像素(目标)来形成标记标记。块,进一步可以获得这些块的轮廓、外接矩形、质心、不变矩等几何参数。如果想要得到米粒的数量,那么通过连通区域分析(这里是二值图像的连通区域分析),就可以得到标记的数量,从而得到米粒的数量。

首先,连通区域一般有两种定义,分为4邻接和8邻接,如上图所示。 4-邻接8-邻接下图中,如果考虑4-邻接,则连通区域有3个,8-邻接为2个。

连通区域的数量与邻接的定义有关。从连通区域的定义可知,连通区域是由具有相同像素值的相邻像素组成的像素集合。因此,我们可以利用这两个条件来查找图像中的连通区域。对于找到的每个连接区域,我们为其分配一个唯一的编号标识(Label),以将其与其他连接区域区分开来。

连通区域分析有两种基本算法:1)两遍扫描法2)种子填充法。

2.Two-Pass算法

Two-Pass,顾名思义,就是通过扫描图像两次,可以找到并标记图像中存在的所有连通区域。

(1) 第一次扫描:

访问当前像素B(x,y),如果B(x,y)==1:

一个。如果B(x,y)字段中的标签值全为0,则给B(x,y)一个新标签:

标签+=1,B(x,y)=标签;

b.如果B(x,y)域中存在像素值为1的像素邻居:

1)将Neighbors中的最小值赋给B(x,y):

B(x,y)=min{邻居}

2)记录Neighbors中的值(标签)之间的相等关系,即这些值(标签)属于同一个连通区域;

labelSet[i]={ label_m, label_n },labelSet[i]中的所有标签都属于同一个连通区域(注:这里可以有多种实现方式,只要这些具有相等关系的标签之间的关系即可记录的关系)

(2) 第二次扫描:

访问当前像素B(x,y),如果B(x,y) 1:

一个。找到与label=B(x,y)具有相同等式关系的最小标签值,并将其赋值给B(x,y);

扫描完成后,图像中具有相同标签值的像素形成相同的连通区域。

我说了很多数学语言,但实际上用图表很容易理解。

Two-Pass 动态过程

3.Seed-Filling算法

种子填充方法来源于计算机图形学,常用于填充某种图形。它基于区域生长算法。至于区域生长算法是什么,可以参考我的这篇文章。

以下是基于种子填充法的连通区域分析方法:

(1) 扫描图像,直到当前像素点B(x,y)==1:

一个。使用B(x,y)作为种子(像素位置),给它一个标签,然后将与种子相邻的所有前景像素压入堆栈;

b.将栈顶像素弹出,赋予其相同的标签,然后将与栈顶像素相邻的所有前景像素压入栈中;

c.重复步骤b,直到栈为空;

此时在图像B中找到一个连通区域,将该区域内的像素值标记为标签;

(2)重复步骤(1)直至扫描完成;

扫描完成后,即可得到B图中所有连通区域;

同样,上面的动画

种子填充动态过程

4.算法实现

NI Vision中的算子定义如下

//统计标记的数量

int imaqLabel(图像* dest, 图像* 源, intconnectivity8, int* 粒子计数);

//获取粒子的具体信息

ParticleReport* imaqGetParticleInfo(Image* 图像, intconnectivity8,

ParticleInfoMode 模式, int* reportCount); OpenCV中也有相应的算子

//带有统计信息

int cv:connectedComponents(

InputArray image, //输入二值图像,黑色背景

OutputArray labels, //输出标签图像,背景索引=0

intconnectivity=8, //连接域,默认为8个连接

int ltype=CV_32S //输出标签类型,默认为CV_32S

//没有统计

int cv:connectedComponentsWithStats(

InputArray image, //输入二值图像,黑色背景

OutputArray labels, //输出标签图像,背景索引=0

OutputArray stats, //统计信息,包括各个组件的位置、宽度、高度和面积

OutputArray centroids, //各分量的中心位置坐标cx,cy

intconnectivity, //查找连通域算法的连通性域,默认为8个连通性

int ltype, //输出标签的Mat 类型CV_32S

int ccltype //连通分量算法

)这里参考其他博客实现Two-Pass算法。种子填充算法懒得实现。

/******************************************************** ***** *********************

* 杨邦杰于10/21/18 创建

* 以任何您想要的方式使用此代码的权利,无需

* 保修、支持或其工作的任何保证

* 邮箱: yangbangjie1998@qq.com

* Association: SCAU 华南农业大学

****************************************************** * ******************/

#include#include#include#include#include#include#include#include#includeusing 命名空间std;

使用命名空间cv;

/**

* @brief TwoPassLabel 标记二值图像的连通域

* @param bwImg 输入必须是二值图像

* @return labImg 标记的灰度图像。不同的标记有不同的灰度值。

*/

Mat TwoPassLabel(const Mat bwImg)

{

断言(bwImg.type()==CV_8UC1);

Mat labImg;

bwImg.convertTo(labImg, CV_32SC1);

int rows=bwImg.rows - 1;

int 列=bwImg.cols - 1;

//二值图像的像素值为0或1,为了不冲突,标签从2开始

整数标签=2;

矢量标签集;

labelSet.push_back(0);

labelSet.push_back(1);

//第一次扫描

int *data_prev=(int*)labImg.data;

int *data_cur=(int*)(labImg.data + labImg.step );

int left, up;//指针所指像素的左上点

for(int i=1; i 行; i++)

{

数据_cur++;

数据_上一个++;

for( int j=1; j 列; j++, data_cur++, data_prev++ )

{

if( *data_cur!=1)//当前点不为1,扫描下一个点

继续;

左=*(data_cur-1);

向上=*data_prev;

int neighborLabels[2];

整数cnt=0;

如果(左1)

neighbourLabels[cnt++]=左;

如果(上1)

neighbourLabels[cnt++]=向上;

如果(!cnt)

{

labelSet.push_back(标签);

*data_cur=标签;

标签++;

继续;

}

//设置当前点标记为左点和上点的最小值

intsmallestLabel=neighborLabels[0];

if(cnt==2 neighborLabels[1]smallestLabel )

最小标签=邻居标签[1];

*data_cur=最小标签;

//设置等价表,这里可能有点难以理解

//左边的点可能比上面的点小,也可能比上面的点大。两种情况都必须考虑,例如

//0 0 1 0 1 0 x x 2 x 3 x

//1 1 1 1 1 1 -4 4 2 2 2 2

//将labelSet中3的位置设置为2

for(int k=0; k cnt; k++ )

{

int neiLabel=neiLabels[k];

int oldSmallestLabel=labelSet[neiLabel];

if(oldSmallestLabel 最小标签)

{

labelSet[oldSmallestLabel]=最小标签;

}

否则if(oldSmallestLabelx 4 2 2 2

//1 1 1 0 1 5 4 2 x 2

//上面的操作中,labelSet[4]被设置为2,但是labelSet[5]仍然是4

//这里可以设置labelSet[5]为2

for( size_t i=2; i labelSet.size(); i++ )

{

int curLabel=labelSet[i];

int prelabel=labelSet[curLabel];

while(预标签!=curLabel)

{

curLabel=预标签;

预标签=labelSet[预标签];

}

labelSet[i]=curLabel;

}

//第二次扫描,用labelSet更新,最后一列

data_cur=(int*)labImg.data;

for(int i=0; i 行; i++)

{

for(int j=0; j 列; j++, data_cur++)

*data_cur=labelSet[*data_cur];

数据_cur++;

}

返回实验室图像;

}

/**

* @brief LabelColor 为连通域分析得到的图像(矩阵)添加颜色

* @param labelImg 二值图像连通域分析得到的图像(每点32位)

* @param num 标签数量

* @return coloerLabelImg 用颜色标记图像

*/

Mat LabelColor(const Mat labelImg, int num)

{

数=0;

断言(labelImg.empty()==false);

断言(labelImg.type()==CV_32SC1);

地图颜色;

int rows=labelImg.rows;

int cols=labelImg.cols;

Mat colorLabelImg=Mat:zeros(行、列、CV_8UC3);

uchar r=255 * (rand()/(1.0 + RAND_MAX));

uchar g=255 * (rand()/(1.0 + RAND_MAX));

uchar b=255 * (rand()/(1.0 + RAND_MAX));

for (int i=0; i 行; i++)

{

const int* data_src=(int*)labelImg.ptr(i);

uchar* data_dst=colorLabelImg.ptr(i);

for (int j=0; j 列; j++)

{

int PixelValue=data_src[j];

if (像素值1)

{

if (colors.count(pixelValue)==0)

{

颜色[像素值]=标量(b,g,r);

r=255 * (rand()/(1.0 + RAND_MAX));

g=255 * (rand()/(1.0 + RAND_MAX));

b=255 * (rand()/(1.0 + RAND_MAX));

数++;

}

标量颜色=颜色[像素值];

*data_dst++=颜色[0];

*data_dst++=颜色[1];

*data_dst++=颜色[2];

}

别的

{

数据_dst++;

数据_dst++;

数据_dst++;

}

}

}

返回颜色标签图像;

}

int main()

{

Mat binImage=imread("/media/jacob/存储盘2/图像数据/3.3实验参考图像/rice.bmp", 0);

阈值(binImage, binImage, 107, 1, CV_THRESH_BINARY);

垫标签图像;

整数; //标记数

labelImg=TwoPassLabel(binImage);

//颜色显示

垫子颜色标签图像;

colorLabelImg=LabelColor(labelImg, num);

cout "标签总数:" num endl;

imshow("colorImg", colorLabelImg);

//灰度显示

马特·格雷图片;

labelImg.convertTo(grayImg, CV_8UC1);

imshow("grayImg",grayImg);

等待键(0);

关于NIVision:高效二值图像连通域标记技术解析和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

用户评论

北染陌人

终于看到一篇真正讲到连通域标记算法的文章了!

    有17位网友表示赞同!

江山策

NI Vision一直是我的心头好,这次更新不错啊。

    有19位网友表示赞同!

沐晴つ

学习一下看能不能用在自己的项目中

    有15位网友表示赞同!

千城暮雪

二值图像一直很常见,标记算法还挺重要的呀

    有6位网友表示赞同!

孤者何惧

这种算法感觉应该挺实用的吧,等我好好研究下

    有17位网友表示赞同!

无寒

NI Vision的文档一般都是写的比较好懂的哈

    有20位网友表示赞同!

抚笙

不知道效果怎么样比较真实的结果还是得实际测试一下啊

    有6位网友表示赞同!

米兰

对于图像处理小白来说,这样的教程真是太棒了

    有12位网友表示赞同!

我绝版了i

之前一直想学习下连通域标记算法,现在终于有资源了!

    有20位网友表示赞同!

有些人,只适合好奇~

期待看到一些详细的代码实现

    有9位网友表示赞同!

蔚蓝的天空〃没有我的翅膀

这样标记算法能提高图像分析的速度吗?

    有17位网友表示赞同!

Edinburgh°南空

有没有什么应用场景可以举例说明一下呢?

    有17位网友表示赞同!

矜暮

感觉还是得配合其他算法使用才能发挥最大的效果

    有5位网友表示赞同!

枫无痕

对于复杂的场景,这种算法是否适用呢?

    有11位网友表示赞同!

颜洛殇

NI Vision支持哪些编程语言实现这个算法呢?

    有12位网友表示赞同!

我就是这样一个人

希望能看到更多关于不同算法比较的介绍!

    有20位网友表示赞同!

雪花ミ飞舞

图像连通域标记算法这个名字起的真不错

    有15位网友表示赞同!

嘲笑!

这种算法能应用到各个层次图像处理吗?

    有20位网友表示赞同!

反正是我

学习完这篇教程,就可以自己开发相应的软件了哈?

    有5位网友表示赞同!

【NIVision:高效二值图像连通域标记技术解析】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:2024赛季中超联赛第十七轮赛事回顾 下一篇:深入解析Windows性能监视器:计数器与阀值设定技巧