OpenCV——图像细化算法

本文详细介绍了图像细化的基本概念,包括细化的目的和应用场景,特别讨论了ZS算法和查表法这两种细化方法。代码示例展示了如何使用OpenCV实现查表法细化道路图像,并给出了细化前后的对比结果,强调了细化在位图矢量化预处理中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.基础概念

图像细化(Image Thinning),一般指二值图像的骨架化(Image Skeletonization)的一种操作运算。细化是将图像的线条从多像素宽度减少到单位像素宽度过程的简称,一些文章经常将细化结果描述为“骨架化”、“中轴转换”和“对称轴转换”。
细化技术的一个主要应用领域是位图矢量化的预处理阶段,相关研究表明,利用细化技术生成的位图的骨架质量受到多种因素的影响,其中包括图像自身的噪声、线条粗细不均匀、端点的确定以及线条交叉点选定等,因而对线划图像进行细化从而生成高质量骨架的方法进行研究具有现实意义。

根据算法处理步骤的不同,细化算法分为迭代细化算法非迭代细化算法。根据检查像素方法的不同,迭代细化算法又分为串行细化算法和并行细化算法。
迭代算法:即重复删除图像边缘满足一定条件的像素,最终得到单像素宽带骨架。
迭代方法依据其检查像素的方法又可以再分成:
串行算法:在串行算法中,通过在每次迭代中用固定的次序检查像素来判断是否删除像素,在第n次迭代中像素p的删除取决于到执行过的所有操作,也就是必须在第(n-1)次迭代结果和第n次检测像素的基础之上进行像素删除操作;即是否删除像素在每次迭代的执行中是固定顺序的,它不仅取决于前次迭代的结果,也取决于本次迭代中已处理过像素点分布情况。
并行算法:在并行算法中,第n次迭代中像素的删除只取决于(n-1)次迭代后留下的结果,因此所有像素能在每次迭代中以并行的方式独立的被检测;即像素点删除与否与像素值图像中的顺序无关,仅取决于前次迭代效果。

2.细化过程

细化算法有ZS算法和查表法。ZS细化算法是一种基于8领域的并行细化算法,通过对目标像素8领域进行分布的算术逻辑运算,来确定该像素是否能删除。八领域如下图所示。
在这里插入图片描述
细化判断依据为:内部点不能删除、孤立不能删除、直线端点不能删除。
ZS细化过程如下:
第一次迭代,若P1满足以下四个条件,说明P1为边界点,可以删除,将P1值设为0:
(1)2 小于等于 Pi从i=2到i=9的和 小于等于6
(2)S(P1)=1;
(3)P2×P4×P6=0;
(4)P4×P6×P8=0;
条件(1)中若P2至P9的和在2至6之间,说明P1为边界点。S(P1)表示目标像素P1的8邻域中,顺时针变化一周像素由0变1的次数。在目标点8邻域P2-P9的范围内,像素值由0变1的次数只能为1次。条件(2)保证了图像细化后的连通性。
第二次迭代中,像素点如果满足第一次迭代中的条件(1)和(2)及以下条件,则移除该像素点:
(5)P2×P4×P8=0;
(6)P2×P6×P8=0;
重复以上迭代过程,直至处理完所有像素点,此时细化完成。
查表法中,由于输入的图像是一张二值图,将其归一化为像素值只有0和1的图像,然后对其进行卷积操作。具体卷积操作为:将目标点的八领域和卷积进行点乘,接着将所有值相加即可得表的索引M,下一步用索引值M去找表中对应的值,对应的值为0或1,就把目标点的像素值修改为0或1,其中1为不可删除点,0位可删除点。重复上述步骤,遍历完所有像素点,对目标点进行查表、修改目标像素值,最后得到细化结果。

3.代码实现

#include<iostream>
#include <opencv2\opencv.hpp>

using namespace std;
using namespace cv;

//查表法//
Mat lookUpTable(Mat& mat, int lut[])
{
	Mat mat_in;
	mat.convertTo(mat_in, CV_16UC1);		 //8 转 16
	int MatX = mat_in.rows;
	int MatY = mat_in.cols;
	int num = 512;
	//表的维数和卷积核中的数据有关,小矩阵初始化按行赋值
	Mat kern = (Mat_<int>(3, 3) << 1, 8, 64, 2, 16, 128, 4, 32, 256);		//卷积核
	Mat mat_out = Mat::zeros(MatX, MatY, CV_16UC1);
	Mat mat_expend = Mat::zeros(MatX + 2, MatY + 2, CV_16UC1);

	Rect Roi(1, 1, MatY, MatX);				//(列,行,列,行)

	Mat mat_expend_Roi(mat_expend, Roi);	//确定扩展矩阵的Roi区域
	mat_in.copyTo(mat_expend_Roi);			//将传入矩阵赋给Roi区域

	Mat Mat_conv;

	//实用卷积核和和每一个八邻域进行点乘再相加,其结果为表的索引,对应值为0能去掉,为1则不能去掉
	filter2D(mat_expend, Mat_conv, mat_expend.depth(), kern);				//卷积
	Mat mat_index = Mat_conv(Rect(1, 1, MatY, MatX));
	for (int i = 0; i < MatX; i++)
	{
		for (int j = 0; j < MatY; j++)
		{
			int matindex = mat_index.at<short>(i, j);

			if ((matindex < num) && (matindex > 0))
			{
				mat_out.at<short>(i, j) = lut[matindex];
			}
			else if (matindex > num)
			{
				mat_out.at<short>(i, j) = lut[num - 1];
			}
		}
	}
	return mat_out;
}

//道路细化查表法//
Mat img_bone(Mat& mat)
{
	// mat 为细化后的图像
	Mat mat_in = mat;

	//在数字图像处理时,只有单通道、三通道 8bit 和 16bit 无符号(即CV_16U)的 mat 才能被保存为图像
	mat.convertTo(mat_in, CV_16UC1);

	int lut_1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };

	int lut_2[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1 };

	Mat mat_bool;

	threshold(mat_in, mat_bool, 0, 1, THRESH_BINARY);	//二值图像归一化

	Mat mat_out;

	Mat image_iters;

	while (true)
	{
		mat_out = mat_bool;

		//查表:水平、垂直
		image_iters = lookUpTable(mat_bool, lut_1);
		mat_bool = lookUpTable(image_iters, lut_2);

		Mat diff = mat_out != mat_bool;

		//countNonZero函数返回灰度值不为0的像素数
		bool mat_equal = countNonZero(diff) == 0;		//判断图像是否全黑

		if (mat_equal)
		{
			break;
		}
	}
	Mat Matout;

	mat_bool.convertTo(Matout, CV_8UC1);

	return Matout;
}

//主函数
int main()
{
	Mat src_img, src_imgBool;

	//输入道路二值图,参数 0 是指imread按单通道的方式读入图像,即灰白图像
	src_img = imread("......png", 0);
	
	//去掉噪,例如过滤很小或很大像素值的图像点
	//threshold(src_img, src_imgBool, 0, 255, THRESH_OTSU);
	//threshold(src_img, src_imgBool, 0, 155, THRESH_OTSU);
	//imshow("Binary Image", src_imgBool);

	Mat imgbone = img_bone(src_img);

	//保存结果
	imwrite("D:\\Desktop\\......\\细化222.png", imgbone * 255);
	
	waitKey();
	system("pause");
	return 0;
}

4.实验结果

细化前
在这里插入图片描述

细化后

在这里插入图片描述

### OpenCV4 图像细化处理方法 #### 使用形态学操作进行图像细化OpenCV中,图像细化通常通过形态学操作来完成。具体来说,“腐蚀”是一种常见的用于细化二值图像的技术。腐蚀会移除对象边界上的像素层,从而减小对象的尺寸。对于更复杂的细化需求,可以使用自定义结构化元素来进行多次迭代。 以下是基于OpenCV4实现图像细化的一个基本示例: ```python import cv2 import numpy as np # 加载二值图像 image = cv2.imread('binary_image.png', cv2.IMREAD_GRAYSCALE) # 定义结构化元素 (3x3 的矩形核) kernel = np.ones((3, 3), np.uint8) # 进行腐蚀操作 eroded_image = cv2.erode(image, kernel, iterations=1) # 显示结果 cv2.imshow('Original Image', image) cv2.imshow('Eroded Image', eroded_image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 上述代码展示了如何利用`cv2.erode()`函数对输入图像执行腐蚀操作[^1]。此过程可以通过调整内核大小和迭代次数进一步优化以满足特定的应用场景。 #### 自定义细化算法 如果标准的腐蚀操作无法达到理想的细化效果,则可能需要采用更加高级的细化技术。例如,在某些情况下,可以设计专门针对目标形状特征的细化逻辑。这通常涉及逐像素分析以及条件判断,确保仅保留骨架部分而不会丢失重要细节[^2]。 下面给出了一种简单的手动细化方案伪代码框架: ```python def custom_thinning(binary_img): changed = True while changed: changed = False # 创建临时副本 temp = binary_img.copy() # 遍历每一个像素点... for i in range(1, height-1): for j in range(1, width-1): p2,p3,p4,p5,p6,p7,p8,p9 = neighbors = get_neighbors(i,j,binary_img) # 计算P条件和其他约束条件 A = sum([p2==0 and q!=0 for p,q in zip(neighbors+[neighbors[0]], [0]+list(neighbors))]) B = sum(neighbors[:-1]) if all([ binary_img[i][j]==1, 2<=B<=6, A==1, p2*p4*p6==0, p4*p6*p8==0]): temp[i][j]=0 changed=True binary_img=temp return binary_img ``` 该函数实现了Zhang-Suen细化算法的核心思想之一——逐步去除符合条件的边缘像素直到收敛为止。 #### 总结 无论是借助内置的形态学运算还是开发专属解决方案,OpenCV都提供了丰富的工具支持各种类型的图像细化任务。实际应用时应根据具体的业务背景选取最合适的策略组合。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值