图像的Blob分析--连通域分析

图像的blob分析是图像处理中应用非常广泛的一种基础算法,所谓的blob,指的是连通区域,同一像素或者相近像素或者说相近纹理邻接构成的区域叫做blob。blob分析的全部流程是:选取阈值进行二值化(可以进行直方图统计,找两个波峰之的最低值),接着计算连通域(这是重点),然后再对不同的连通域(也就是blob)统计中心、置心、形状、面积、周长等参数;

本文将重点讲述连通域分析;
在此之前,我们先定义两个像素在何种位置情况下会被视为连通,有以下两种(4邻域和8邻域):
在这里插入图片描述
接下来要强调的一点是,为了符合我们视觉上对于连通域的直观感受,要对前景和背景采用不同的连通域定义:
在这里插入图片描述
对于图a,前景中一条线将背景分成两个连通区域,使我们希望得到的结果,但是如果这条线有些旋转,背景就会变成一个连通区域,这是跟我们直觉不符合的,c的话背景也是一个连通区域,但是直接上我们会觉得背景包含一个洞,解决此问题的办法就是,背景采用4邻域定义描述连通域,8领域描述前景,反之亦可。
先介绍一种比较简单的方案:
1,逐行扫描图像,我们把每一行中连续的白色像素组成一个序列称为一个团(run),并记下它的起点start、它的终点end以及它所在的行号。

2,对于除了第一行外的所有行里的团,如果它与前一行中的所有团都没有重合区域,则给它一个新的标号;如果它仅与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它;如果它与上一行的2个以上的团有重叠区域,则给当前团赋一个相连团的最小标号,并将上一行的这几个团的标记写入等价对,说明它们属于一类。

3,将等价对转换为等价序列,每一个序列需要给一相同的标号,因为它们都是等价的。从1开始,给每个等价序列一个标号。

4,遍历开始团的标记,查找等价序列,给予它们新的标记。

5,将每个团的标号填入标记图像中。

6,结束
以下是根据上述思路求解图中连通域个数的代码:

#include<opencv2\opencv.hpp>
#include<vector>
#include<iostream>
using namespace std;
int main()
{
    
    
	int rows, cols;
	//cin >> rows >> cols;
	vector<vector<int>> matrix = {
    
     {
    
     1, 1, 1, 0, 0 }, {
    
     1, 1, 1, 0, 0 }, {
    
     0, 0, 0, 1, 1 }, {
    
     1, 1, 0, 1, 1 } };
	rows = matrix.size();
	cols = matrix[0].size();
	/*
	for (int i = 0; i < rows; i++)
	{
		vector<int>every_single_row;
		for (int j = 0; j < cols; j++)
		{
			int x;
			scanf("%d", &x);
			every_single_row.push_back(x);
		}
		matrix.push_back(every_single_row);
	}*/
	vector<vector<int>>start(rows);
	vector<vector<int>>end(rows);
	vector<vector<int>>label(rows);
	int count = 1;
	for (int i = 0; i < rows; i++)
	{
    
    
		vector<int>every_single_row_ = matrix[i];
		bool search_flag = false;
		for (int j = 0; j < every_single_row_.size(); j++)
		{
    
    
			if (search_flag == false)
			{
    
    
				if (every_single_row_[j] == 1)
				{
    
     
					start[i].push_back(j);
				
				label[i].push_back(count);
				count = count + 1;
				search_flag = true;
				}
			}
			else
			{
    
    
				if (every_single_row_[j] == 0)
				{
    
    
					end[i].push_back(j-1);
				search_flag = false;
				}
			}


		}
		//结束
		if (search_flag == true)
			end[i].push_back(cols - 1);
	}
	int max = 0;
	for (int i = 0; i < label.size(); i++)
		for (int j = 0; j < label[i].size(); j++)
		{
    
    
			if (label[i][j]>max)
				max = label[i][j];
		}
	cout << max << std::endl;
	for (int i = 1; i < rows; i++)
	{
    
    
		for (int m = 0; m < end[i].size(); m++)
		{
    
    
			for (int n = 0; n < end[i - 1].size(); n++)
			{
    
    
				if (((start[i - 1][n] <= end[i][m] + 1) && (start[i - 1][n] >= start[i][m] - 1)) ||
					((end[i - 1][n] <= end[i][m] + 1) && (end[i - 1][n] >= start[i][m] - 1)))
					{
    
    
						int choose_label = min(label[i - 1][n], label[i][m]);
						label[i - 1][n] = choose_label;
						label[i][m] = choose_label;
						max = max - 1;
						cout << max << std::endl;
				}                       
			}
		}
	}

	cout << max << std::endl;
	return 1;
}

本文介绍用行程法+深度优先搜索的方式求取连通域;
1.首先逐行遍历,记录每一行中的相连区域,由小到大进行标记;
2.使用深度优先搜索的搜索树;
什么是深度优先搜索:
**深度优先遍历图算法步骤:
–访问顶点v;
–依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
–若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
3.返回搜索后的连通区域;
在这里插入图片描述
这里提一嘴,这里不同于行程编码,它是一种简单的压缩方法,常用于二值化图像,比如行程编码的基本原理是在给定的数据图像中寻找连续的重复数值,然后用两个字符取代这些连续值。例如,一串字母表示的数据为“aaabbbbccccdddeeddaa”,经过 游程编码处理可表示为“3a4b4c3d2e2d2a”。

关于行程法和深度优先搜索的代码如下图所示:

还有另外一种方法叫做two-pass法:
本质上和行程法也非常相似:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_39326879/article/details/118325004