连通域分析之种子填充法

一、连通域

需要了解连通域和两遍扫描法的可以看连通域分析之两遍扫描法(Two-Pass)这篇博文。我在里面已经介绍过了连通域的两种定义,这里就不再介绍。在此文中仍以4-邻域为例。

二、种子填充(区域填充)

1、遍历二值化后待测连通域的图像,当当前访问的像素不为零,将该像素入栈(栈中保存像素点的坐标);
2、访问栈顶元素对应的像素,退栈;
3、访问2中得到的栈顶像素的四个邻域像素点,若像素值不为零,则将对应像素入栈;
4、继续访问栈顶元素,直至栈为空;
5、继续遍历图像,直至完成遍历。
以上就是种子填充法的一个基本步骤,在访问像素的同时,我们可以根据需求做出操作。

三、算法代码

int myBoundaryFill(Mat &src, Mat &dst)
{
	if (src.empty() || src.type() != CV_8UC1)
		return 0;
	stack<Point2i> pts;
	int label = 1;//标注初始为1
	
	int row = src.rows;
	int col = src.cols;
	dst.release();
	dst = cv::Mat::zeros(row, col, CV_32S);
	for (int i = 0;i < row ;i++)
	{
		for (int j = 0;j < col ;j++)
		{
			int curPixel = (int)src.at<uchar>(i,j);
			int curLabel = (int)dst.at<int>(i, j);
			if (curPixel != 0 && curLabel == 0)
			{
				dst.at<int>(i,j)= label;	
				pts.push(Point2i(j, i));
				
				while (!pts.empty())
				{
					Point2i curPt = pts.top();
					int x = curPt.x;
					int y = curPt.y;
					pts.pop();
					if (y > 0)
					{//upPixel
						if (src.at<uchar>(y-1, x) != 0 && dst.at<int>(y-1, x) == 0)
						{
							dst.at<int>(y - 1, x) = label;
							pts.push(Point2i(x, y-1));
						}
					}
					
					if (y < row - 1)
					{//downPixel
						if ((int)src.at<uchar>(y + 1, x) != 0 && dst.at<int>(y + 1, x) == 0)
						{
							dst.at<int>(y + 1, x) = label;
							pts.push(Point2i(x, y+1));
						}
					}
					if (x > 0)
					{//leftPixel
						if ((int)src.at<uchar>(y, x-1) != 0 && dst.at<int>(y, x-1) == 0)
						{
							dst.at<int>(y, x-1) = label;
							pts.push(Point2i(x-1, y));
						}
					}
					if (x < col - 1)
					{//rightPixel
						if (src.at<uchar>(y, x + 1) != 0 && dst.at<int>(y, x + 1) == 0)
						{
							dst.at<int>(y, x + 1) = label;
							pts.push(Point2i(x+1, y));
						}
					}
					
				}
				label++;//自增				
			}
		}
	}
	return label-1;//最后得到的连通域数量
}

四、算法解析

1、不遗漏像素点的访问

for (int i = 0;i < row ;i++)
{
	for (int j = 0;j < col ;j++)
	{
	   .....
	}
}

遍历图像行和宽都从0开始,可以确保每个像素点都被访问;
2、避免入过栈的像素再次入栈

int curPixel = (int)src.at<uchar>(i,j);
int curLabel = (int)dst.at<int>(i, j);
if (curPixel != 0 && curLabel == 0)

curPixel表示当前访问的像素点,curLabel表示该像素点在输出图像dst中对应的标注。输出图像dst是一个像素初始值全为零的图像。当输出图像dst中的某个像素值不为0,说明已经访问过其对应于输入图像中的像素点,即该像素点已经被标注了,无需再次标注。
3、越界访问控制
由于是从(0,0)开始访问的,包含了边界的像素点,但边界像素点并不是四个邻域像素都存在。所以在访问栈顶像素四个邻域像素之前在相应的边界进行一次边界判定,使用if (y > 0)、if (y < row - 1)、if (x > 0)和if (x < col - 1)防止相应边界的越界访问。

算法比较简单,就是一个DFS的算法,只需要在访问元素的过程中注意越界和遗漏边界像素的问题即可。

五、测试效果
测试代码

int main()
{	
	Mat test = (cv::Mat_<uchar>(8, 8) <<
		0, 0, 0, 0, 1, 0, 1, 1, 
		1, 0, 0, 1, 1, 0, 1, 0, 
		1, 0, 1, 0, 1, 0, 0, 1, 
		1, 0, 1, 0, 1, 0, 1, 1, 
		1, 1, 1, 1, 1, 1, 0, 0, 
		0, 0, 0, 0, 0, 1, 0, 1, 
		1, 0, 1, 0, 1, 1, 0, 1, 
		1, 1, 1, 1, 1, 1, 0, 0);
	Mat testResult;
	//Mat preConnect;
	//cout<<"连通域个数为:"<<myTwoPass(test, testResult, preConnect);
	cout << "testResult连通域个数为:" << myBoundaryFill(test, testResult);
    cout << endl;
	
	//Mat img = imread("../coins.png");//原图
	Mat img = imread("../rice.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat rice, riceBW;
	
	//将图像转换成二值图像,用于统计连通域
	cvtColor(img, rice, COLOR_BGR2GRAY);
	threshold(rice, riceBW, 50, 255, THRESH_BINARY);
	
	//用于生成随机颜色,用于区分不同的连通域
	RNG rang(10086);
	int w = img.cols;
	int h = img.rows;

	
	Mat myout,preConnect;
	//int mynumber = myTwoPass(riceBW, myout, preConnect);//自定义函数
	// mynumber = myTwoPass(riceBW, myout);//自定义函数
	int mynumber = myBoundaryFill(riceBW, myout);//自定义函数
	vector<Vec3b> mycolors;


	for (int i = 0;i < mynumber;i++)
	{
		//使用均匀分布的随机数确定颜色
		Vec3b myvec3 = Vec3b(rang.uniform(0, 256), rang.uniform(0, 256), rang.uniform(0, 256));
		mycolors.push_back(myvec3);
	}

	//以不同的颜色标记不同的连通域
	Mat myresult = Mat::zeros(rice.size(), img.type());//自定义输出图像
	//cout << "mycolors number= " << mycolors.size();
	
	for (int i = 0;i < h;i++)
	{
		for (int j = 0;j < w;j++)
		{
			int label = myout.at<int>(i, j);
			if (label == 0)
			{//背景颜色不改变
				continue;
			}
			myresult.at<Vec3b>(i, j) = mycolors[label-1];
		}
	}
	
	Mat out;
	int number = connectedComponents(riceBW, out, 4, CV_16U);//库函数
	vector<Vec3b> colors;
	for (int i = 0;i < number;i++)
	{
		//使用均匀分布的随机数确定颜色
		Vec3b vec3 = Vec3b(rang.uniform(0, 256), rang.uniform(0, 256), rang.uniform(0, 256));
		colors.push_back(vec3);
	}

	Mat result = Mat::zeros(rice.size(), img.type());//库函数输出图像
	for (int i = 0;i < h;i++)
	{
		for (int j = 0;j < w;j++)
		{
			int label = out.at<uint16_t>(i, j);
			if (label == 0)
			{//背景颜色不改变
				continue;
			}
			result.at<Vec3b>(i, j) = colors[label-1];
		}
	}
	
	cout << "库函数得到的连通域数:" << number << endl;
	cout << "自定义函数得到的连通域数:" << mynumber << endl;

	//显示结果
	imshow("原图", img);
	imshow("库函数输出图像", result);
	imshow("自定义函数输出图像", myresult);

	waitKey(0);
	return 0;
}

自定义Mat变量的测试结果,图像被分为1,2,3,4一共4个连通域
在这里插入图片描述
图片的测试
原图
在这里插入图片描述
测试结果对比
在这里插入图片描述
计算出的连通域数量对比,库函数计算了背景所以多1。
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
连通域填充(Connected Component Filling)是一种图像处理算法,用于将相邻像素值相同的区域填充为同一种颜色或标签。在Python中,可以使用OpenCV库来实现连通域填充操作。 下面是一个使用OpenCV进行连通域填充的简单示例代码: ```python import cv2 import numpy as np # 读取图像 image = cv2.imread("input.jpg", cv2.IMREAD_GRAYSCALE) # 阈值化图像 _, thresh_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # 寻找连通域 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(thresh_image, connectivity=8) # 创建一个随机颜色数组 colors = np.random.randint(0, 255, size=(num_labels, 3), dtype=np.uint8) # 将每个连通域填充为不同颜色 output = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.uint8) for label in range(1, num_labels): output[labels == label] = colors[label] # 显示结果图像 cv2.imshow("Output", output) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在上述代码中,我们首先读取输入图像并将其转换为灰度图像。然后,我们使用阈值化操作将图像转换为二值图像。接下来,通过调用`connectedComponentsWithStats`函数寻找连通域,并获得每个连通域的标签信息和统计数据。然后,我们创建一个随机颜色数组,并将每个连通域填充为不同的颜色。最后,显示结果图像。 请注意,上述代码仅是一个基本示例,实际应用中可能需要根据具体需求进行进一步的处理和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zmq1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值