如何使用OpenCV计算图像中连通组件的周长
问题描述
我有如下矩阵:
m = np.asarray( [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], dtype=np.uint8 )
我对其执行了如下形态学梯度变换操作:
gradient = cv2.morphologyEx(m, cv2.MORPH_GRADIENT, np.ones((2,2),np.uint8)) gradient = cv2.morphologyEx(m, cv2.MORPH_GRADIENT, np.ones((3,3),np.uint8))
两种操作分别使用2x2、3x3卷积核得到对应运行结果,但实际输出的像素显示效果和预期相反,请问是否有简单可靠的方法可以正确获取图像中组件的周长?
解答
首先你当前的代码存在一个基础错误:两次调用cv2.morphologyEx都输入了原始矩阵m,第二次计算的3x3梯度结果会直接覆盖第一次的2x2梯度结果,要保留两个结果需要换不同的变量名存储。
针对边缘效果反向和周长计算的需求,有以下几种可靠的解决方案:
- 方案1:修正形态学梯度用法
形态学梯度的计算逻辑是膨胀结果减腐蚀结果,如果输出边缘和预期明暗相反,直接对结果做翻转即可:二值场景用1 - gradient,8位灰度场景用255 - gradient。梯度结果的非零像素总数就是组件周长的近似值,日常使用优先选3x3卷积核,边缘提取的稳定性更好。 - 方案2:调用OpenCV原生轮廓周长接口(最推荐)
不用手动处理核选择、边缘翻转的问题,精度更高,还支持区分统计外轮廓、内部孔洞的周长,示例代码如下:
import cv2 import numpy as np # 原始矩阵 m = np.asarray( [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], dtype=np.uint8 ) # 转成0/255格式适配cv2接口 m_255 = m * 255 # RETR_TREE会提取所有层级轮廓(包含外框、内部孔洞、内部矩形),只需外轮廓就换成RETR_EXTERNAL contours, hierarchy = cv2.findContours(m_255, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 统计总周长 total_perimeter = 0 for cnt in contours: # closed=True表示轮廓是闭合的 total_perimeter += cv2.arcLength(cnt, closed=True) print(f"组件总周长:{total_perimeter}")
- 方案3:手动统计边缘像素
不需要引入复杂的形态学操作,直接遍历所有值为1的像素,只要它上下左右四个相邻像素里有值为0的,就判定为边缘像素,统计这类像素的总数就是周长的近似值,逻辑简单不会出现反向问题。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

