You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 05:36:07