OpenCV4学习笔记(21)——图像连通域

本次要记录的内容是:连通组件标记算法(connected component labeling algorithm)及其信息统计。
图像的连通组件(或者称为连通域更顺口一点)是针对于二值图像而言的,我们都知道二值图像只有0和255这两种像素值分布,当我们扫描二值图像中的每个像素点,并将像素值相同的而且相互连通的像素点分为相同的连通域, 最终得到图像中所有的像素连通组件。最后得到的结果中,每一个连通域表示着这个区域中的像素点有很大的关联性,可能是属于同一个物体的像素点,也可能是属于同一种颜色的像素点。这样我们就可以通过对二值图像的求取连通域,来获取到图像中某些我们感兴趣的区域,从而来进行进一步的操作。
下面来看怎样对图像进行连通域的划分,给出代码实现:

	Mat connected_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\contours.png");
	imshow("connected_image", connected_image);
	Mat connected_image_gray;
	cvtColor(connected_image, connected_image_gray, COLOR_BGR2GRAY);
	Mat connected_image_gaus;
	GaussianBlur(connected_image_gray, connected_image_gaus, Size(), 1, 1);
	Mat connected_image_binary;
	threshold(connected_image_gaus, connected_image_binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("connected_image_binary", connected_image_binary);
	//对二值图像求连通
	Mat label_indexs;			//用于存放求得的连通区域标记
	int num_labels = connectedComponents(connected_image_binary, label_indexs, 8, CV_32S);
	cout << "连通区域数量:" << num_labels - 1 << endl;			//需要减去背景
	vector<Vec3b> colors;			//用于区分不同连通域的颜色向量
	colors.push_back(Vec3b(0, 0, 0));			//将背景设为黑色
	RNG rng;			//生成一个随机数对象
	for (int i = 1; i < num_labels; i++)			//为前景中每一个连通区域,随机生成一种表示颜色
	{
		int b = rng.uniform(0, 256);
		int g = rng.uniform(0, 256);
		int r = rng.uniform(0, 256);
		colors.push_back(Vec3b(b, g, r));			//按连通域索引顺序,将其代表颜色依次放入向量colors中
	}
	Mat label_image = Mat::zeros(connected_image.size(), CV_8UC3);			//定义显示的连通图
	int height = connected_image.rows;
	int width = connected_image.cols;
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int label_index = label_indexs.at<int>(row, col);			//获取label_indexs中该点处的值,也就是该点所处于的连通区域的索引
			if (0 == label_index)			//如果该点为背景区域,则跳过
			{
				continue;
			}
			//将该点所处于的连通区域的代表颜色,赋值给要显示图像该点的像素值
			label_image.at<Vec3b>(row, col) = colors[label_index];	
		}
	}
	imshow("label_image", label_image);

首先,读取一张图像并进行预处理,如转灰度图、高斯模糊、二值化等操作,最终得到一幅二值图像。然后将得到的二值图像利用connectedComponents(connected_image_binary, label_indexs, 8, CV_32S)这个API来实现连通域的划分,其中参数的含义为:
第一个参数image:输入的二值图像,只适用于输入图像是黑色背景、白色前景的图像;
第二个参数labels:输出的一个Mat对象,size和输入图像相同,像素值为各个像素点所属的连通区域的索引;其中索引0表示的是背景,索引范围 [ 1 , 索引数目-1 ] 表示前景中各个连通域的索引;
第三个参数connectivity:选择像素连通性,默认是八连通
第四个参数ltype:输出的labels图像的深度,由于连通域的最大数量为输入图像像素数的1/2,所以推荐使用CV_32S,防止索引数溢出。
同时,connectedComponents()会有一个返回值,该值为求得的 [ 前景连通区域数 + 1(背景) ],也就是说当需要使用到求得的连通域数量时,必须得将返回值减去1后才是真正的连通域数量。

到这里就实现了连通域的划分,为了便于可视化,上述代码中又定义了一个向量用来随机生成不同的颜色,颜色数就是连通域数量,从而在显示的时候能够以不同颜色来区分不同连通域。效果如下图:
在这里插入图片描述
由上图可见,不同的几何形状被分为了不同的连通域,并使用不同颜色进行表示。有时候,可以通过求取连通域来得到我们需要的图像区域。

OpenCV中除了上面的求取连通域的API,还有一个功能更强大的API也可以用来求取连通域,而且还能在求取连通域的基础上,获得每个连通域的信息。那就是connectedComponentsWithStats(),其参数含义是:
第一个参数image:输入的二值图像,只适用于输入图像是黑色背景、白色前景的图像;
第二个参数labels:输出的一个Mat对象,size和输入图像相同,像素值为各个像素点所属的连通区域的索引;其中索引0表示的是背景,索引范围 [ 1 , 索引数目-1 ] 表示前景中各个连通域的索引;
第三个参数stats:包含相关的统计信息,每个连通组件输出一个stats对象;
第四个参数centroids:包含每个连通域的中心坐标;每行的第一列是x坐标,第二列是y坐标,有多少个连通域就有多少行;
第五个参数connectivity:选择像素连通性,默认是八连通;
第六个参数ltype:输出的labels图像的深度,由于连通域的最大数量为输入图像像素数的1/2,所以推荐使用CV_32S,防止索引数溢出。

下面看代码实现:

	Mat labels, stats, centroids;
	int num_labels = connectedComponentsWithStats(connected_image_binary, labels, stats, centroids, 8, 4);
	vector<Vec3b> colors;
	colors.push_back(Vec3b(0, 0, 0));
	RNG rng;
	for (int i = 1; i < num_labels; i++)
	{
		int b = rng.uniform(0, 256);
		int g = rng.uniform(0, 256);
		int r = rng.uniform(0, 256);
		colors.push_back(Vec3b(b, g, r));
	}
	for (int j = 1; j < num_labels; j++)
	{
		//获取每个连通域的中心坐标
		int cx = centroids.at<double>(j, 0);			
		int cy = centroids.at<double>(j, 1);
		//获取每个连通域的统计信息
		int connected_x = stats.at<int>(j, CC_STAT_LEFT);		//外接矩形左上角的x坐标
		int connected_y = stats.at<int>(j, CC_STAT_TOP);		//外接矩形左上角的y坐标
		int connected_height = stats.at<int>(j, CC_STAT_HEIGHT);		//外接矩形的高度
		int connected_width = stats.at<int>(j, CC_STAT_WIDTH);		//外接矩形的宽度
		int connected_area = stats.at<int>(j, CC_STAT_AREA);			//连通域的面积
		cout << "第" << j << "个" << "连通组件面积: " << connected_area << endl;
		//绘制每个连通域的中心点
		circle(connected_image, Point(cx, cy), 1, Scalar(0, 255, 0), -1, 8, 0);
		//绘制每个连通域的外接矩形
		Rect rect(connected_x, connected_y, connected_width, connected_height);
		rectangle(connected_image, rect, Scalar(colors[j][0], colors[j][1], colors[j][2]), 1, 8, 0);
		//在图像上输出文字
		putText(connected_image, format("index_%d", j), Point(cx, cy), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(colors[j][0], colors[j][1], colors[j][2]), 1, 8, false);
	}
	imshow("connected_image", connected_image);

上述代码中,通过stats对象获取每个连通域的信息,如锚点坐标、高度、宽度、面积等等,在stats对象中每一行包括了一个连通域的信息,有多少个连通域就有多少行信息,而且不同信息具有不同的索引,如CC_STAT_LEFT表示外接矩形左上角的x坐标、CC_STAT_TOP表示外接矩形左上角的y坐标等。
我们通过获取stats对象中保存的连通域外接矩形的左上角坐标以及高、宽等信息,来绘制其外接矩形,输出每个连通域的面积大小,并且在每个连通域上用文字标记出其索引,效果如下:
在这里插入图片描述

总结:根据我们自己的需求来选用不同的API进行二值图像连通域的求取,如果需要到关于连通域的更多信息就使用结合stats对象的API,能够方便我们提取到很多有用的信息,否则就使用基本的连通域求取算法,更为方便快捷。

好的本次记录到此结束,谢谢阅读,下次有空再继续整理笔记啦,现在每天要上学校的网课,还有自学内容,留给整理注释笔记的时间不多啦━━( ̄ー ̄*|||━━,加油加油!!!
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值