EX5连通域与距离场

本文详细介绍了使用并查集实现图像处理中的连通域分析,通过C++代码展示了如何从灰度图转化为二值图像,然后进行连通域标记和合并。实验中还应用了距离变换函数distanceTransform进行距离场计算,并讨论了不同距离类型(L1,L2,C)的效果。最后,文章提到了归一化在处理浮点数图像时的重要性,以及在结果输出时的注意事项。
摘要由CSDN通过智能技术生成

实验E5:图像结构1

实验5.1 连通域

并查集基本算法find,unite
//并查集找到祖先和合并
int find(int x) {
	int root = par[x];
	while (root!=par[root])
	{
		root = par[root];
	}
	
	while (x!=root)
	{
		int tt = par[x];
		par[x] = root;
		x = tt;
	}
	return root;
}
int unite(int x, int y) {
	int rx = find(x);
	int ry = find(y);
	par[ry] = rx;
	return rx;
}

读入图像处理:以单通道图读入imread("D:\\CV\\horse_mask.png",IMREAD_GRAYSCALE);//灰度图读入,然后将灰度图转化成二值图像:

阈值函数直接控制阈值转化成二值图像:threshold(ori, ori, 127, 255, THRESH_BINARY);//按照阈值分为二值图像

创建与原图像相同大小的Mat–mark存储第一次扫描标记的集合,😢注意使用int,uchar溢出;

然后对要扫描的mark加pad防止扫描过程的边界处理;

ptr使用详解:

算法理论个四领域类似:

每当遇到一个白点时进行下面的算法

对于八领域的左、左上、上、右上四个点:

  • 若都是黑色,标记这个像素一个新的类
  • 若有1个非零值,标记成这个非零值
  • 若有2个非零值,任意标记成其中一个,并将这两个合并
	for (int r = 0; r < ori.rows; r++) {
		for (int c = 0; c < ori.cols; c++) {
			r_pad = r + padding;
			c_pad = c + padding;
			if (ori.ptr<uchar>(r, c)[0] != 0) {
				leftp = mark_pad.ptr<int>(r_pad, c_pad - 1), top = mark_pad.ptr<int>(r_pad - 1, c_pad);//mask必须是int型
				//cout << "ptr:" << *leftp << " ";
				if (*leftp == 0 && *top == 0 && *(top - 1) == 0 && *(top + 1) == 0) {//四个领域都是黑的
					cnt++;
					*mark_pad.ptr<int>(r_pad, c_pad)= cnt;
				}
				else {//其他情况由于是二值图像,对应的四个位置最多有两个集合,v1,v2表示可能的两种类
					int v1 = 0, v2 = 0;
					if (*(top - 1) != 0) {
						v1 = *(top - 1);
					}

					if (*top != 0) {
						if (v1 == 0) v1 = *top;
						else {
							if (*top != v1) v2 = *top;
						}
					}
					if (*(top +1) != 0) {
						if (v1 == 0) v1 = *(top + 1);
						else {
							if (*(top +1)!= v1&&v2==0) v2 = *(top + 1);
						}
					}
					if (*leftp != 0) {
						if (v1 == 0) v1 = *leftp;
						else {
							if (*leftp != v1&&v2==0) v2 = *leftp;
						}
					}
					*mark_pad.ptr<int>(r_pad, c_pad) = v1;
					if (v1 && v2) {
						unite(v1, v2);//如果周围存在两种那么就要合并
					}
				}
			}
		}
	}

到这里全部标记完类和并查集,然后再次遍历计算并查集的数目和最大的类,😢par在并查集合并时不一定全部合并了,所以要有find找到最根部的操作,才能找到正确的并查集,否则只显示一部分;

	//第二次遍历找到最大的并查集连通域标注
	int maxclass = 1, maxnum = 0, num = 0;

	for (int r = 0; r < ori.rows; r++) {
		for (int c = 0; c < ori.cols; c++) {
			r_pad = r + padding, c_pad = c + padding;
			int root = find(*mark_pad.ptr<int>(r_pad, c_pad) );//只有通过find才有可能找到最终的祖先,否则par就可能只是上一级||是否过程中有小合并到大的优化?
			if (root != 0) {
				classsize[root]++;
				if (classsize[root] > maxnum) {
					maxnum = classsize[root];
					maxclass = root;
				}
			}
		}
	}

结果:

image-20211023160040305

连通域个数:7324;上图表示出164353个像素;

距离场distanceTransform 距离变换函数

API :

void distanceTransform(InputArray src, OutputArray dst, int distanceType, int maskSize)

参数:

src – 8-bit, 单通道(二值化)输入图片。
dst – 输出结果中包含计算的距离,这是一个32-bit float 单通道的Mat类型数组,大小与输入图片相同。
distanceType – 计算距离的类型那个,可以是 CV_DIST_L1、CV_DIST_L2 、CV_DIST_C。
maskSize – 距离变换掩码矩阵的大小,可以是
3(CV_DIST_L1、 CV_DIST_L2 、CV_DIST_C)
5(CV_DIST_L2 )
CV_DIST_MASK_PRECISE (这个只能在4参数的API中使用)

标记:

image-20211023105824613使用时遍历时对应;

通过该函数得到的是距离的4字节的float表示,超过了255的像素点会以255显示不会可视化,可视化需要nomalize出来:

``void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());`:

使用时:

normalize(dst, dst, 0, 1.0, NORM_MINMAX);alpha对应归一化的最小值,beta对应最大值,后面的模式选择最大最小模式;

imshow()对应小数的图像表示时会自动的x255转换,但是要存储并不会自动转化,需要使用normalize函数min=0,max=255在扩大存储:

	for (auto k : masksize) {
		//cout << k << "--";
		for (int distop = 0; distop < 3; distop++) {
			string cur_file = dists[distop] + "_masksize_" + to_string(k) + ".jpg";
			string outpath = "D:\\CV\\" +cur_file;
			distanceTransform(binary, dst, distoptions[distop], k);//距离转化
			normalize(dst, dst, 0, 1.0, NORM_MINMAX);//越界处理归一化
			imshow(cur_file, dst);//32float自动x255
			normalize(dst, dst,0,255,NORM_MINMAX);//扩大到8U存储
			imwrite(outpath, dst);
		}
	}

imshow:

image-20211023223612725

32位浮点数,imshow内部会转化x255,然后造成越界,展示的图就是原图,所以不能使用浮点数的Mat输出。

结果:

test

image-20211023110739579

DIST_L2_masksize_3

DIST_L1_masksize_3

上图列出了l1,l2,与c的效果,通过右上直线周围的距离场,可以看出是三种不同距离的边界不同,多边形性c>l1>l2,l2最为平滑,c棱角最为清楚;原因:

image-20211023111043910

l1距离对应的是直线边界,l2形式对应曲线边界;和一般的对于l1和l2的认知边界相同。

normalize()

image-20211023163834302

eg

vector<double> positiveData = { 2.0, 8.0, 10.0 };
vector<double> normalizedData_l1, normalizedData_l2, normalizedData_inf, normalizedData_minmax;
// Norm to probability (total count)
// sum(numbers) = 20.0
// 2.0      0.1     (2.0/20.0)
// 8.0      0.4     (8.0/20.0)
// 10.0     0.5     (10.0/20.0)
normalize(positiveData, normalizedData_l1, 1.0, 0.0, NORM_L1);
// Norm to unit vector: ||positiveData|| = 1.0
// 2.0      0.15
// 8.0      0.62
// 10.0     0.77
normalize(positiveData, normalizedData_l2, 1.0, 0.0, NORM_L2);
// Norm to max element
// 2.0      0.2     (2.0/10.0)
// 8.0      0.8     (8.0/10.0)
// 10.0     1.0     (10.0/10.0)
normalize(positiveData, normalizedData_inf, 1.0, 0.0, NORM_INF);
// Norm to range [0.0;1.0]
// 2.0      0.0     (shift to left border)
// 8.0      0.75    (6.0/8.0)
// 10.0     1.0     (shift to right border)
normalize(positiveData, normalizedData_minmax, 1.0, 0.0, NORM_MINMAX);

不使用转化到任意位置;一般处理方式;

使用3或者5模板:颜色表示的距离有微小差异。

结果分析与体会

写并查集的过程中遇到的坑是对祖先的寻找,以及在过程中使用标记集合代表时用uchar导致图像没有变化,以及最后使用opencv自带库函数,查阅官方文档,最后输出需要将0–1再映射到0–255进行保存输出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值