前面说到,要使用Labwindows + NI Vision(IMAQ Vision)这套商用开发框架来做数图课设。很明显,这套虚拟仪器开发平台由NI Instrument(美国国家仪器公司)开发的。大名鼎鼎的Labview软件就是这个公司开发的。相比较而言,Labwindows使用ANSI C开发,但应用场景是差不多的。

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

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

原图 - 二值化 - 低通滤波 - 连通域标记

界面设计如下,可以看到米粒的详细情况。

Labwindows界面

让我感兴趣的,是通过怎样的算法能够得到米粒的数量?之前曾经用过OpenCV中找最大外界矩形这个函数,但没有具体了解算法实现。直觉告诉我原理应该是相似的。

1.连通区域

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

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

首先,连通区域的定义一般有两种,分为4邻接和8邻接,上图就知道了。

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值的像素就组成了同一个连通区域。

说了一堆数学语言,其实用图很好理解

Two-Pass动态过程

3.Seed-Filling算法

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

下面给出基于种子填充法的连通区域分析方法:
(1)扫描图像,直到当前像素点B(x,y) == 1:
a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;
b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;
c、重复b步骤,直到栈为空;
此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;
(2)重复第(1)步,直到扫描结束;
扫描结束后,就可以得到图像B中所有的连通区域;

同样的,上动图

Seed-Filling动态过程

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: yangbangjie1998@qq.com* Association: SCAU 华南农业大学********************************************************************/#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>using namespace std;
using namespace cv;/*** @brief TwoPassLabel 对二值图像连通域进行标记* @param bwImg 输入必须为二值图像* @return labImg 已经标记的灰度图像,不同标记灰度值不同*/
Mat TwoPassLabel(const Mat &bwImg)
{assert(bwImg.type() == CV_8UC1 );Mat labImg;bwImg.convertTo( labImg, CV_32SC1 );int rows = bwImg.rows - 1;int cols = bwImg.cols - 1;//二值图像像素值为0或1,为了不冲突,label从2开始int label = 2;vector<int> labelSet;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 < rows; i++ ){data_cur++;data_prev++;for( int j = 1; j < cols; j++, data_cur++, data_prev++ ){if( *data_cur!=1 )//当前点不为1,扫描下一个点continue;left = *(data_cur-1);up = *data_prev;int neighborLabels[2];int cnt = 0;if( left > 1 )neighborLabels[cnt++] = left;if( up > 1)neighborLabels[cnt++] = up;if(!cnt){labelSet.push_back(label);*data_cur = label;label++;continue;}//将当前点标记设为左点和上点的最小值int smallestLabel = neighborLabels[0];if(cnt==2 && neighborLabels[1] < smallestLabel )smallestLabel = neighborLabels[1];*data_cur = smallestLabel;//设置等价表,这里可能有点难理解//左点有可能比上点小,也有可能比上点大,两种情况都要考虑,例如//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的位置设置为2for(int k = 0; k < cnt; k++ ){int neiLabel = neighborLabels[k];int oldSmallestLabel = labelSet[neiLabel];if(oldSmallestLabel > smallestLabel ){labelSet[oldSmallestLabel] = smallestLabel;}else if(oldSmallestLabel<smallestLabel )labelSet[smallestLabel] = oldSmallestLabel;}}data_cur++;data_prev++;}//上面一步中,有的labelSet的位置还未设为最小值,例如//0 0 1 0 1      x x 2 x 3//0 1 1 1 1  ->  x 4 2 2 2//1 1 1 0 1      5 4 2 x 2//上面这波操作中,把labelSet[4]设为2,但labelSet[5]仍为4//这里可以将labelSet[5]设为2for( 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;}//第二次扫描,用labelSet进行更新,最后一列data_cur = (int*)labImg.data;for(int i = 0; i < rows; i++ ){for(int j = 0; j < cols; j++, data_cur++)*data_cur = labelSet[*data_cur];data_cur++;}return labImg;
}/*** @brief LabelColor 对连通域分析得到的图像(矩阵)添加颜色* @param labelImg 二值图像连通域分析得到的图像(每个点32位)* @param num 标记的个数* @return coloerLabelImg 带有颜色的标记图像*/
Mat LabelColor(const Mat& labelImg, int& num)
{num = 0;assert(labelImg.empty() == false);assert(labelImg.type() == CV_32SC1);map<int, Scalar> colors;int rows = labelImg.rows;int cols = labelImg.cols;Mat colorLabelImg = Mat::zeros(rows, cols, 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 < 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] = Scalar(b,g,r);r = 255 * (rand()/(1.0 + RAND_MAX));g = 255 * (rand()/(1.0 + RAND_MAX));b = 255 * (rand()/(1.0 + RAND_MAX));num++;}Scalar color = colors[pixelValue];*data_dst++   = color[0];*data_dst++ = color[1];*data_dst++ = color[2];}else{data_dst++;data_dst++;data_dst++;}}}return colorLabelImg;
}int main()
{Mat binImage = imread("/media/jacob/存储盘2/图像资料/3.3 实验参考图像/rice.bmp", 0);threshold(binImage, binImage, 107, 1, CV_THRESH_BINARY);Mat labelImg;int num; //标记的数量labelImg = TwoPassLabel(binImage);//彩色显示Mat colorLabelImg;colorLabelImg = LabelColor(labelImg, num);cout << "total number of label:" << num << endl;imshow("colorImg", colorLabelImg);//灰度显示Mat grayImg;labelImg.convertTo(grayImg, CV_8UC1);imshow("grayImg", grayImg);waitKey(0);return 0;
}
结果

Beautiful ~~

Reference:
OpenCV实现图像连通组件标记与分析
OpenCV-二值图像连通域分析
数字图像处理技术 ——邓继忠(我的任课老师)

NI Vision:二值图像连通域标记算法相关推荐

  1. 图像连通域标记算法研究

    搬以前写的博客[2014-03-01 08:09] 图像连通域标记算法研究 ConnectedComponent Labeling 最近在研究一篇复杂下背景文字检测的论文. "Detecti ...

  2. Matlab实现连通域标记算法求图像连通域

    Matlab实现连通域标记算法求图像连通域 连通域 连通域标记算法 连通域 连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域(Regi ...

  3. python连通域标记_图像分析:二值图像连通域标记

    一.前言 二值图像,顾名思义就是图像的亮度值只有两个状态:黑(0)和白(255).二值图像在图像分析与识别中有着举足轻重的地位,因为其模式简单,对像素在空间上的关系有着极强的表现力.在实际应用中,很多 ...

  4. 图像分析:二值图像连通域标记

    FROM:http://www.cnblogs.com/ronny/p/img_aly_01.html 一.前言 二值图像,顾名思义就是图像的亮度值只有两个状态:黑(0)和白(255).二值图像在图像 ...

  5. 图像分析:二值图像连通域标记-基于行程的标记方法

    一.前言 二值图像,顾名思义就是图像的亮度值只有两个状态:黑(0)和白(255).二值图像在图像分析与识别中有着举足轻重的地位,因为其模式简单,对像素在空间上的关系有着极强的表现力.在实际应用中,很多 ...

  6. 改进的二值图像像素标记算法及程序实现(含代码)

    笔者实现了一个论文里面的算法程序,论文(可以网上搜索到,实在搜不到可以联系笔者或留下邮箱发给你)讲解比较到位,按照作者的思路写完了代码,测试效果很好,在此分享一下算法思路及实现代码. 此算法优于一般的 ...

  7. python连通域标记_pythonamp;#8212;二值图像连通域标记 - 易采站长站

    # -*- coding: utf-8 -*- from PIL import Image import numpy as np #定位一个种子,返回种子位置 def seed_dirt(img): ...

  8. 寻找连通域算法_FPGA实现的连通域识别算法升级

    代码在这:https://github.com/becomequantum/kryon 还做了个算法演示动画:https://www.bilibili.com/video/av26067000 < ...

  9. MATLAB二值图连通域快速标记算法

    (来点有用的)MATLAB二值图连通域快速标记算法 基本原理 算法实现 0. 变量解释 1.种子点搜索 2 区域生长 完整代码 实验 其他 by HPC_ZY 由于工程需要,用C++实现三维二值图像的 ...

最新文章

  1. 巧用FTP命令进行文件传输
  2. Python基础语法学习(字符串)
  3. 【思维】最大降雨量(解题报告)
  4. 【转】第7篇:Xilium CefGlue 关于 CLR Object 与 JS 交互类库封装报告:全自动注册与反射方法分析...
  5. 前端面试题汇总(jQuery)
  6. 中英文对照 —— 手机 App/PC 端软件(系统)、互联网
  7. 更新小红伞antivirus失败:生成更新结构失败。更新库生成错误 556
  8. java实现菱形的打印java实现菱形的输出
  9. 【MAC】使用CHM查看JDK帮助文档【转载】
  10. 计算机运行命令定时关机,电脑定时关机命令 使用系统命令定时关机 - 云骑士一键重装系统...
  11. 后台开发笔记-在服务器上运行java后台项目
  12. 浅谈非数学类全国大学生数学竞赛
  13. .ico 图标下载网站推荐
  14. java整数最大_Java 整数最大值
  15. Tomcat启动startup.bat一闪而过就消失的原因和解决方法
  16. CTF必备取证神器(volatility、PTF、取证大师、Magnet AXIOM)
  17. android webview 本地视频播放,Android中webview html5 自动播放本地视频
  18. 用Python输出自幂数
  19. 干货:压敏电阻选型和注意事项,必知
  20. NIOS II 内核使用 之 代码保存FLASH(EPCSX芯片)

热门文章

  1. JCL,JES运作流程
  2. Android 4.0模拟器弹出---“谷歌拼音输入法”已停止运行的解决方法
  3. 华硕f540u内存条在哪里_华硕笔记本电脑怎么拆机添加内存条?
  4. 伪代码基本规范~呦呦呦
  5. 小猫爪:i.MX RT1050学习笔记2-下载
  6. cent os 主题安装
  7. 许奔创新社-第25问:创新者的刻意练习怎么做?
  8. 2020年代,中国AI创业公司将走向何方?
  9. DVWA之XSS (Reflected)
  10. 在统计学中_[求助] OR在统计学中指什么?