cv2.connectedComponentsWithStats进行连通域检测的详细记录

最近在进行瑕疵检测识别中的连通域处理。主要是使用了cv2.connectedComponentsWithStats函数。本文将进行 函数介绍,使用经验,其他处理的记录。

函数介绍

'''
    num_labels:所有连通域的数目
    labels:图像上每一像素的标记,用数字1、2、3…表示(不同的数字表示不同的连通域)
    stats:每一个标记的统计信息,是一个5列的矩阵,每一行对应每个连通区域的外接矩形的x、y、width、height和面积,示例如下: 0 0 720 720 291805
    centroids:连通域的中心点
'''
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(arr_gray, connectivity=8)

对于输入的参数:

arr_gray : 是要处理的图片,要求是8位单通道的图像。

connectivity:可以选择是4连通还是8连通(不写也没事)

输出值有四个:

num_labels : 所有连通域的数量

labels :labels和你输入图像拥有同样的shape。但是里面的数值标志着属于哪一个连通域,从0开始

stats:包含5个参数,分别是,x,y,w,h,s。分别对应每个连通区域的外接矩形的左上角坐标x和y,以及其宽和高,s是指的连通域中的像素个数(不是指整个外接矩形的面积)

centroids : 是每一个连通区域的质心。

这里有一个地方我之前琢磨了好一会,就是这个输入图像,我在实际的项目中所得到的是一个已经进行完图像处理的numpy矩阵,对瑕疵进行了二值化(0与255),但是他不能作为输入图像。

我的解决方法是,我对这个numpy矩阵先转换成三通道的RGB图像,然后再把RGB图像转成灰度图就行了。

    #height,width是我image的高和宽
    #res_dilation 是图像处理结束后的一个二维numpy矩阵
    arr = np.zeros((height, width, 3))
    arr[:, :, 0] = res_dilation
    arr[:, :, 1] = res_dilation
    arr[:, :, 2] = res_dilation
    arr = arr.astype(np.uint8)
    arr_gray = cv2.cvtColor(arr, cv2.COLOR_BGR2GRAY)

    num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(arr_gray, connectivity=8)

因为我图像已二值化0与255,所以我对于三通道的处理就是复制3份。最后结果仍然是0与255。


然后就是对它输出值的一些分析:

我先构造一个10x10的一个图片,来模拟我之前的流程

具体测试代码如下:

import numpy as np
import cv2

a = np.array([[0,0,255,0,0,0,0,0,0,0],[0,0,255,0,0,0,0,0,0,0],[0,255,255,255,0,0,0,0,0,0],[0,0,255,0,0,0,0,0,0,0],[0,0,255,255,0,0,0,0,0,0],[0,0,255,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,255,0],[0,0,0,0,0,0,255,255,255,0],[0,0,0,0,0,0,0,255,0,0],[0,0,0,0,0,0,0,255,0,0]])
print(a)
arr = np.zeros((10, 10, 3))
arr[:, :, 0] = a
arr[:, :, 1] = a
arr[:, :, 2] = a
arr = arr.astype(np.uint8)
arr_gray = cv2.cvtColor(arr, cv2.COLOR_BGR2GRAY)
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(arr_gray, connectivity=8)
print(num_labels)
print(labels)
print(stats)
print(centroids)

 上图是0与255的二值图,255也就是我项目中的瑕疵,0可以看作是背景。

可以很清楚的发现,是有2个连通域的。但是可以来看一下它的输出

可以看到,num_labels并不是2,而是3。这是因为它会把大背景作为一个大的连通域,作为0号label输出。所以 num_labels - 1才是真正的连通域数目

再看labels的输出,是一个10x10的矩阵。里面一共是0,1,2个标签,分别代表背景,1号连通域和2号连通域的各个点。

stats也是同样的,它每一行的五个参数x,y,w,h,s,上面也介绍过了。第一个是背景(不是很需要)。很明显的看出1号连通域有9个瑕疵像素点,2号连通域有6个瑕疵像素点。

centroids是每个连通域的质心坐标(第一行的是背景的,一般可以忽略)。但在实际中,我们习惯是用整数。

centroids = centroids.astype(int)

刚才说了,我们实际使用中,一般是要避免掉背景对于我们操作的影响的。所以我们可以直接去掉第一行。

stats0 = np.delete(stats, [0], axis=0)

然后对于我所需要的一些要求,我还可以对它们的x和y进行排序。

stats0 = np.delete(stats, [0], axis=0)    #去掉背景的0号连通域的影响

sort_y = stats0[stats0[:, 1].argsort()]    #把连通域按照y轴方向排序
min_y = sort_y[0][1]
max_y = sort_y[num_labels - 2][1]

sort_x = stats0[stats0[:, 0].argsort()]    #把连通域按照x轴方向排序
min_x = sort_x[0][0]
max_x = sort_x[num_labels - 2][0]
print(min_y,min_x,max_y,max_x)  # 0 1 6 6

这样的操作是为了我之后更精细的对图像进行处理。

还有就是在各连通域找到我想要的连通域,将其坐标找到,然后对其像素点进行处理。

for i in range(1,num_labels):
    label_width = stats[i][2]
    label_height = stats[i][3]
    label_x = stats[i][0]
    label_y = stats[i][1]

    label = labels[label_y:label_y + label_height, label_x:label_x + label_width]  # 获取label外接矩形
    lab = label.reshape(-1, )  # 二维转一维
    # lab = np.unique(lab)  # 去掉重复
    lab = np.setdiff1d(lab, 0)  # 去掉0值,该外接矩阵中其余label值
    print("lab")
    print(lab)
    for l in lab:
        seeds = np.argwhere(label == i)  # 找到所需要的label的点
        seedlist = list(seeds)
        print("l:", l)
        print(seedlist)

 这样就找到了具体每一个连通域的点的坐标。

接下来如果要对具体的每个连通域坐标操作,可以:

for point in seedlist:

        xxxxxx具体操作xxxxxx

但有一个注意点,这个point只是指的其连通域外接矩阵中的坐标,并不是整个numpy矩阵图像上的坐标。

arr_gray[label_y + point[0], label_x + point[1]] ,这个才是其真正在矩阵中的坐标

 总的一些关于连通域的应用就这么多了,如果有帮助的话,可以收藏,点赞一下,感谢。

有问题的话可以评论区交流。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>