NI Vision:二值图像连通域标记算法

NI Vision:二值图像连通域标记算法,第1张

前面说到,要使用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-二值图像连通域分析

数字图像处理技术 ——邓继忠(我的任课老师)

代码

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 = 1i <rowsi++)

{

int* data_preRow = _lableImg.ptr<int>(i-1)

int* data_curRow = _lableImg.ptr<int>(i)

for (int j = 1j <colsj++)

{

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 = 1k <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 = 2i <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 = 0i <rowsi++)

{

int* data = _lableImg.ptr<int>(i)

for (int j = 0j <colsj++)

{

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 = 1i <rows-1i++)

{

int* data= _lableImg.ptr<int>(i)

for (int j = 1j <cols-1j++)

{

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 = 0i <rowsi++)

{

const int* data_src = (int*)_labelImg.ptr<int>(i)

uchar* data_dst = _colorLabelImg.ptr<uchar>(i)

for (int j = 0j <colsj++)

{

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

}


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/12560119.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-26
下一篇 2023-05-26

发表评论

登录后才能评论

评论列表(0条)

保存