前面说到,要使用Labwindows + NI Vision(IMAQ Vision)这套商用开发框架来做数图课设。很明显,这套虚拟仪器开发平台由NI Instrument(美国国家仪器公司)开发的。大名鼎鼎的Labview软件就是这个公司开发的。相比较而言,Labwindows使用ANSI C开发,但应用场景是差不多的。
虚拟仪器(VI)的实质是利用计算机的IO接口完成信号的采集、测试与调试,凭借现代PC强大的计算能力来实现信号数据的运算、分析和处理,并使用显示器来模拟传统仪器的控制面板,从而完成各种测试功能的一种计算机仪器系统。
在做课程作业的时候,遇到了一个很有趣的应用。输入是米粒,比背景灰度要低,目的是输出米粒的颗数、面积、周长和孔数,这是工业上的一个很常见的应用。具体处理过程是二值化后使用低通滤波,并计算各种性质。
界面设计如下,可以看到米粒的详细情况。
让我感兴趣的,是通过怎样的算法能够得到米粒的数量?之前曾经用过OpenCV中找最大外界矩形这个函数,但没有具体了解算法实现。直觉告诉我原理应该是相似的。
1.连通区域
可以看到,每一个米粒之间都是不连通的。这里就就提出了一个概念。连通区域(Connected Component)是指图像中相邻并有相同像素值的图像区域。连通区域分析(Connected Component Analysis,Connected Component Labeling)是指将图像中的各个连通区域找出并标记。
二值图像分析最重要的方法就是连通区域标记,它是所有二值图像分析的基础,它通过对二值图像中白色像素(目标)的标记,让每个单独的连通区域形成一个被标识的块,进一步的我们就可以获取这些块的轮廓、外接矩形、质心、不变矩等几何参数。如果要得到米粒的数量,那么通过连通区域分析(这里是二值图像的连通区域分析),就可以得到标记的数量,从而得到米粒的数量。
首先,连通区域的定义一般有两种,分为4邻接和8邻接,上图就知道了。
下面这幅图中,如果考虑4邻接,则有3个连通区域,8邻接则是2个。
从连通区域的定义可以知道,一个连通区域是由具有相同像素值的相邻像素组成像素集合,因此,我们就可以通过这两个条件在图像中寻找连通区域,对于找到的每个连通区域,我们赋予其一个唯一的标识(Label),以区别其他连通区域。
连通区域分析的基本算法有两种:1)Two-Pass两便扫描法 2)Seed-Filling种子填充法 。
2.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);
完成扫描后,图像中具有相同label值的像素就组成了同一个连通区域。
说了一堆数学语言,其实用图很好理解
3.Seed-Filling算法
种子填充方法来源于计算机图形学,常用于对某个图形进行填充。它基于区域生长算法。至于区域生长算法是什么,可以参照我的这篇文章。
下面给出基于种子填充法的连通区域分析方法:
(1)扫描图像,直到当前像素点B(x,y) == 1:
a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;
b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;
c、重复b步骤,直到栈为空;
此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;
(2)重复第(1)步,直到扫描结束;
扫描结束后,就可以得到图像B中所有的连通区域;
同样的,上动图
4.算法实现
NI Vision 中的算子定义如下
//统计标记数量
int imaqLabel(Image* dest, Image* source, int connectivity8, int* particleCount);
//得到粒子的具体信息
ParticleReport* imaqGetParticleInfo(Image* image, int connectivity8,
ParticleInfoMode mode, int* reportCount);
OpenCV中也有相应的算子
//带统计信息
int cv::connectedComponents(
InputArray image, // 输入二值图像,黑色背景
OutputArray labels, // 输出的标记图像,背景index=0
int connectivity = 8, // 连通域,默认是8连通
int ltype = CV_32S // 输出的labels类型,默认是CV_32S
)
//不带统计信息
int cv::connectedComponentsWithStats(
InputArray image, // 输入二值图像,黑色背景
OutputArray labels, // 输出的标记图像,背景index=0
OutputArray stats, // 统计信息,包括每个组件的位置、宽、高与面积
OutputArray centroids, // 每个组件的中心位置坐标cx, cy
int connectivity, // 寻找连通组件算法的连通域,默认是8连通
int ltype, // 输出的labels的Mat类型CV_32S
int ccltype // 连通组件算法
)
这里参照其他博客实现一下Two-Pass算法,Seed-Filling算法就偷懒不搞了。
/********************************************************************
* Created by 杨帮杰 on 10/21/18
* Right to use this code in any way you want without
* warranty, support or any guarantee of it working
* E-mail: [email protected]
* Association: SCAU 华南农业大学
********************************************************************/
#include
#include
#include
#include
#include
Beautiful ~~
Reference:
OpenCV实现图像连通组件标记与分析
OpenCV-二值图像连通域分析
数字图像处理技术 ——邓继忠(我的任课老师)