cv2.connectedComponentsWithStats 处理不规则连通区域

  • 函数介绍
  • 实例-参数详解
  • 举个栗子


函数介绍

retval, labels, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=8)

输入值:

  • image : 是要处理的图片,官方文档要求是8位单通道的图像。
  • connectivity : 可以选择是4连通还是8连通。

输出值

  • retval : 返回值是连通区域的数量。
  • labels : labels是一个与image一样大小的矩形(labels.shape = image.shape),其中每一个连通区域会有一个唯一标识,标识从0开始。
  • stats :stats会包含5个参数分别为x,y,h,w,s。分别对应每一个连通区域的外接矩形的起始坐标x,y;外接矩形的wide,height;s其实不是外接矩形的面积,实践证明是labels对应的连通区域的像素个数。
  • centroids : 返回的是连通区域的质心。

实例-参数详解

举个栗子

1、 我创建了10x10的图片,其中像素值分别有0和100

python 提取连通域大小大于某个值的块 python 连通区域_原始数据

输入的image就是我们这个10x10的图片
如果我们要分析8连通,就令

connectivity=8

2、用 opencv 将图片读入

image = cv2.imread('test1.tif') # 将上述的10*10图片存为test.tif的图片
img = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) #转灰度
retval, labels, stats, centroids = cv2.connectedComponentsWithStats(img, connectivity=8)

返回值

retval #retval = 3
>>> 3

stats      #我们看出有3个连通区域
            # x   y   w   h  s 
>>> array([[ 0,  0, 10, 10, 76],  # 这代表整个图片,0值也有连通区域
           [ 4,  1,  5,  6, 18],  # 这里18代表有18个像素 下面的6同理
           [ 2,  2,  3,  2,  6]], dtype=int32)
           
labels  # labdels 将背景0值标为0,值为100的标为1 和 2
>>> array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	       [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
	       [0, 0, 2, 2, 2, 0, 1, 0, 0, 0],
	       [0, 0, 2, 2, 2, 0, 1, 1, 1, 0],
	       [0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
	       [0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
	       [0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
	       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
	       
centroids
>>> array([[4.17105263, 4.68421053],
	       [6.38888889, 4.38888889],
	       [3.        , 2.5       ]]

3、我们来详细看一下如何利用stats和labels处理不规则图形

按照 stats返回的外接矩形分别如下两个矩形,其中蓝色的矩形有两个值在红色矩形内,同时可以看到原始数据的连通区域与labels可以相互对应

python 提取连通域大小大于某个值的块 python 连通区域_python_02


python 提取连通域大小大于某个值的块 python 连通区域_参数详解_03

问题来了: 如果我想要将原始图片红色框内的100变为0,如果我们直接令外接矩形框内全部为0,这样不可避免的会伤及无辜将蓝色框内的两个100值都改为0.这个是不允许的。
所以我们可以利用labels的标识

label = labels[y:y + h, x:x + w] #获取要处理的红色方框外接矩形
lab = label.reshape(-1, )  # 二维转一维
lab = np.unique(lab)  # 去掉重复
lab = np.setdiff1d(lab, 0)  # 去掉0值,因为0值是背景的标识,我们不用处理0,只要除去1保留2即可

之后又因为stats中的返回值s是像素值的个数,并不是外接矩形内所有100值的个数。

下面两个图可以看出,红色框内100的值是20个,但构成连通区域的之后18个像素,所以stats返回值中是18不是20;这个很重要!!!!

python 提取连通域大小大于某个值的块 python 连通区域_python_04


这样在红色矩形框内的labels= 2 的数量只有两个,labels = 1的数量有18个,并且与s一致,可以依据这个条件,获取labels = 1 的坐标,并令这些坐标所在值等于0,这样labels = 2 的值并没有受到影像。

for l in lab:
    seeds = np.argwhere(label==l)
    seedlist = list(seeds)
    print(seedlist)
    if len(seedlist) == s:
#         print(seedlist)
        for point in seedlist:
            image[point[0],point[1]] = 0
        img[y:y + h, x:x + w] = image

4 、结果:

python 提取连通域大小大于某个值的块 python 连通区域_原始数据_05


原始图:

python 提取连通域大小大于某个值的块 python 连通区域_灰度_06

在这之前我尝试了一些别的函数,都不能很好的操作,下期会整理一下。