opencv 十六 python下各种连通域处理方法(按面积阈值筛选连通域、按面积排序筛选连通域、连通域分割等方法)

本博文基于python-opencv实现了按照面积阈值筛选连通域、按照面积排序筛选topK连通域、 连通域细化(连通域骨架提取)、连通域分割(基于分水岭算法使连通域在细小处断开)、按照面积排序赛选topK轮廓等常见的连通域处理代码。并将代码封装为shapeUtils类,在自己的python代码中import shapeUtil后即可使用相应的连通域处理方法。

1、背景知识

1.1 轮廓

轮廓(Contour )由连续的点组成,以线条的形式聚集在一起,通常是一个有x,y组成的点集,形式为N x 2(N表示轮廓中有n个点)。其是空心的,通常所统计的轮廓面积是那一圈线所包含的面积。在opencv中使用cv2.findContours来查找轮廓,使用cv2.contourArea来统计轮廓包含的面积,使用cv2.drawContours绘制轮廓。如下图就包含了2个轮廓

1.2 连通域

连通域(Connection)由在空间上连续(相邻)的像素点组成,是一个图形区域。相邻的标准有4连通域和8连通域,具体可以参考https://zhuanlan.zhihu.com/p/394073982。对二值图统计完连通域后,得到一个labels图,具体如下右图所示,其背景区域被标记为0,每个联通域的值都从原来的255更改为连通域序号。下图是按照8连通域的方式进行统计的,如果按照4连通域进行统计,那么标记为2的连通域就会被断开为两个(总共会有5个连通域,标签从1~5)。

 

1.3 连通域与轮廓的转换

连通域信息与轮廓存在本质的区别,联通域是一个形状(Mat),轮廓是一个闭合的线条点集(list,元素为坐标)。我们可以使用cv2.drawContours将轮廓绘制为连通域,也可以使用cv2.findContours统计连通域的轮廓信息。在某些情况下,一个轮廓就可以对应一个连通域;当连通域中存在孔洞的时候,则需要多个轮廓才能表示一个连通域。
具体如下图所示,当连通域没有孔洞时,可以转换为一个轮廓;当前存在一个孔洞时,则需要转换2个轮廓。

2、连通域处理方法

2.1 按照面积阈值筛选连通域

通过cv2.connectedComponentsWithStats函数统计出联通域的信息,labels为连通域标记图(具体参考1.2中的描述),stats为联通域统计信息(可见代码中的注释,其包含连通域的xywhs信息),通过对联通域统计信息stats的判断(将连通域面积与阈值threshold进行比较),修改连通域标记图labels将小于阈值的连通域标签值修改为0)。最后通过二值化方法,将联通域标记图转换为二值图。

import cv2
import numpy as np 
class shapeUtils:   
	def find_big_areo(img,threshold=1000):
        #https://blog.csdn.net/weixin_44599604/article/details/111687531
        retval, labels, stats, centroids = cv2.connectedComponentsWithStats(img, connectivity=8)
        #stats的格式为二维数组,其中每一个元素为 x,y,w,h,s的格式,s为联通域面积
        '''
        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)
        '''
        for i in range(1,stats.shape[0]):
            conj=i#获取联通域的标记值
            areo=stats[i,4]
            if areo<threshold:
                labels[labels==conj]=0
        labels=labels.astype(np.uint8)
        ret,labels=cv2.threshold(labels,0,255,cv2.THRESH_BINARY)# 大于0即为255
        return labels
img=cv2.imread("res.png",0)
ret,img=cv2.threshold(img,64,255,cv2.THRESH_BINARY)
im2=shapeUtils.find_big_areo(img,5000)
cv2.imshow("img",img)
cv2.imshow("labels",im2)
cv2.waitKey()

 

2.2 按照面积排序筛选topK连通域

按照面积排序筛选topK连通域。先使用connectedComponentsWithStats统计出labels和stats,然后创建一个行号(其实就是labels中连通域的标签值),并使其与stats的shape相同并将其与stats拼接在一起(在原始的stats中,第i个信息对应着标签值为i的联通域,对stats按面积排序后则会无法正常对应,故需要进行拼接),然后使用np.argsort对stats进行排序,在根据排序结果将topk个连通域后的标签值全部修改为0(将topk后的连通域删除),最后通过二值化方法,将联通域标记图转换为二值图。


import cv2
import numpy as np 
class shapeUtils:   
        def find_topK_areo(img,k=1):
        #https://blog.csdn.net/weixin_44599604/article/details/111687531
        retval, labels, stats, centroids = cv2.connectedComponentsWithStats(img, connectivity=8)
        #stats的格式为二维数组,其中每一个元素为 x,y,w,h,s的格式,s为联通域面积
        '''
        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)
        '''
        #创建一个行号,并使其与stats的shape相同
        rows_num=[x for x in range(stats.shape[0])]
        rows_num=np.array(rows_num)#shape (3)
        rows_num=rows_num.reshape((-1,1)) #shape (3,1)

        print(rows_num.shape,stats.shape)
        #数据维度变化:(3, 1) (3, 5)=>(3, 6)
        stats=np.concatenate((rows_num,stats),axis=1)#拼接时要仅有一个维度不同,才能拼接
        #此时的stats的格式为二维数组,其中每一个元素为 x,y,w,h,s的格式,s为联通域面积
        '''
        拼接后的 stats 如下所示
                  #row x   y   w   h  s 
        >>> array([[0, 0,  0, 10, 10, 76],  # 这代表整个图片,0值也有连通区域
                   [1, 4,  1,  5,  6, 18],  # 这里18代表有18个像素 下面的6同理
                   [2, 2,  2,  3,  2,  6]], dtype=int32)
        '''

        #安装面积对连通域进行排序
        sortId=np.argsort(stats[:,-1])#生成一个排序好的下标,从小到大排序
        sortId=sortId[::-1]#对下标进行逆序,使其变为从大到小的排序
        stats=stats[sortId]#根据序号重新取数据
        #stats=stats[np.argsort(stats[:,-1])[::-1] ]
        print(stats)

        #将第k面积个后的连通域label设置为0
        for i in range(k+1,stats.shape[0]):
            conj=stats[i][0]#获取联通域的标记值
            labels[labels==conj]=0
        print

 

 

 

 

 

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
获取图像的连通域可以使用`cv2.connectedComponentsWithStats()`方法,该方法返回每个连通域的标签、面积、宽度、高度和左上角坐标等信息。然后可以根据面积大小筛选出需要保留的连通域,并使用`cv2.rectangle()`方法框出这些连通域。 下面是一个示例代码: ```python import cv2 # 读取图像 img = cv2.imread('image.png', cv2.IMREAD_GRAYSCALE) # 二值化处理 _, thresh = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY) # 获取连通域信息 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(thresh) # 筛选出需要保留的连通域 min_area = 100 # 最小面积阈值 keep_labels = [] for i in range(1, num_labels): area = stats[i, cv2.CC_STAT_AREA] if area >= min_area: keep_labels.append(i) # 框出保留的连通域 for label in keep_labels: x, y, w, h = stats[label, cv2.CC_STAT_LEFT], stats[label, cv2.CC_STAT_TOP], stats[label, cv2.CC_STAT_WIDTH], stats[label, cv2.CC_STAT_HEIGHT] cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2) # 显示图像 cv2.imshow('image', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在上面的代码中,我们首先读取图像并进行二值化处理,然后使用`cv2.connectedComponentsWithStats()`方法获取连通域信息。接着,我们根据面积大小筛选出需要保留的连通域,并使用`cv2.rectangle()`方法框出这些连通域。最后,我们显示处理后的图像。 注意,上面的代码中我们设定了一个最小面积阈值`min_area`,面积小于该阈值连通域将被删除。您可以根据自己的需求调整该阈值

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FL1768317420

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值