• OpenCV——图像细化算法


    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
    #include 
    
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179

    4.实验结果

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

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

  • 相关阅读:
    【PytorchLearning】Dropout | 内部机制及代码复现
    就业班 第四阶段(k8s) 2401--6.4 day2 Dashboard+国产kuboard(好用)+简单命令
    Mysql的优化<showprofile>
    Unity截图生成图片 图片生成器 一键生成图片
    浅谈 AOP 什么是 AOP ?
    RHCE---作业2
    分布式数据库Apache Doris HA集群部署
    React:我们即将和后端 API 告别?
    Python3 面向对象
    7.1-WY22 Fibonacci数列
  • 原文地址:https://blog.csdn.net/wfengzi5/article/details/126064398