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

基于4连通性的轮廓检测问题:OpenCV替代方案咨询

4连通性轮廓检测解决方案

问题背景

需要实现基于4连通性的轮廓检测,但OpenCV的findContours()仅支持8连通性。在测试图中,该函数将目标识别为单个轮廓,而4连通性应识别为两个轮廓。自行编写的朴素Python算法在轮廓计数和嵌套轮廓处理上存在困难,寻求以下解决方案:

  1. OpenCV是否有其他函数可实现4连通性轮廓检测?
  2. 若需自行编写算法,如何完成1、2层级轮廓的识别与计数?

用户提供的测试代码:

import numpy as np
import matplotlib.pyplot as plt
import cv2

def is_4_connected(pixel, neighbor):
    return np.abs(pixel - neighbor) == 1

# uploading file with data
with open('text_data.txt', 'r') as file:
    lines = file.readlines()
    data = [[float(value) for value in line.strip().split()] for line in lines]

# Converting data to NumPy array
image = np.array(data)
max_value = np.amax(image)
threshold = max_value / 2
# Image binarization (threshold value can be adjusted)
binary_image = np.where(image < threshold, 0, 1)
contour_count = np.zeros((255, 255))
c = []
# Creating an image with contours based on 4-connectivity
contour_image = np.zeros_like(binary_image)
binary_image = binary_image.astype(np.uint8)
for y in range(1, binary_image.shape[0] - 1):
        c.append(len(contour_count[y]))
        for x in range(1, binary_image.shape[1] - 1):
                if binary_image[y, x] == 0:
                        if binary_image[y - 1, x] == 1 or binary_image[y + 1, x] == 1 or binary_image[y, x - 1] == 1 or binary_image[y, x + 1] == 1:
                                contour_image[y, x] = 1
                elif binary_image[y, x] == 1:
                # Checking 4-connectivity with neighboring pixels
                        if not is_4_connected(binary_image[y, x], binary_image[y - 1, x]) and \
                        not is_4_connected(binary_image[y, x], binary_image[y + 1, x]) and \
                        not is_4_connected(binary_image[y, x], binary_image[y, x - 1]) and \
                        not is_4_connected(binary_image[y, x], binary_image[y, x + 1]):
                     # If there is no 4-connectivity with any neighboring pixel, it is a new contour
                                        contour_count[y][x] = 1
        
        
        
contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
level1_count = 0
level2_count = 0
for i, cnt in enumerate(contours):
        if hierarchy[0][i][3] == -1:
# First level contour
                level1_count += 1
        else:
# 2nd level contour
                level2_count += 1

# Print result
print("number of 1st level contours:", len(c))
print("number of 2nd level contours:", level2_count)
 
# Displaying an image with outlines
plt.imshow(contour_image, cmap='gray')
plt.show()

解决方案

一、使用OpenCV内置函数实现4连通轮廓检测

OpenCV的findContours()确实不支持4连通,但可以通过连通域分析+轮廓提取的组合实现:

  1. 使用cv2.connectedComponentsWithStats()函数,该函数支持指定4连通性(参数connectivity=4),先标记所有4连通的独立区域。
  2. 对每个连通域,提取其轮廓(对单个连通域掩码调用findContours()即可,此时8连通不影响结果,因为区域已被4连通分割)。
  3. 通过连通域的包围关系判断层级:内层轮廓的质心会被外层连通域的边界框完全包含。

示例代码:

import numpy as np
import cv2
import matplotlib.pyplot as plt

# 读取并二值化图像
with open('text_data.txt', 'r') as file:
    lines = file.readlines()
    data = [[float(value) for value in line.strip().split()] for line in lines]
image = np.array(data)
max_value = np.amax(image)
threshold = max_value / 2
# 转为OpenCV兼容的8位单通道格式(0为背景,255为前景)
binary_image = np.where(image < threshold, 0, 255).astype(np.uint8)

# 4连通域分析
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=4)

# 提取轮廓并区分层级
level1_contours = []
level2_contours = []

# 遍历所有前景连通域(跳过背景label=0)
for label in range(1, num_labels):
    # 生成当前连通域的掩码
    mask = (labels == label).astype(np.uint8) * 255
    # 提取该区域的轮廓
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnt = contours[0]
    
    # 判断是否为内层轮廓:检查质心是否被其他连通域包围
    is_inner = False
    for other_label in range(1, num_labels):
        if other_label == label:
            continue
        # 获取其他连通域的边界框
        x, y, w, h = stats[other_label]
        centroid_x, centroid_y = centroids[label]
        if x < centroid_x < x + w and y < centroid_y < y + h:
            is_inner = True
            break
    
    if is_inner:
        level2_contours.append(cnt)
    else:
        level1_contours.append(cnt)

# 输出结果
print(f"1级轮廓数量: {len(level1_contours)}")
print(f"2级轮廓数量: {len(level2_contours)}")

# 绘制结果
draw_img = cv2.cvtColor(binary_image, cv2.COLOR_GRAY2BGR)
cv2.drawContours(draw_img, level1_contours, -1, (0, 255, 0), 1)
cv2.drawContours(draw_img, level2_contours, -1, (0, 0, 255), 1)
plt.imshow(draw_img)
plt.show()

二、自行编写4连通轮廓检测算法(含层级处理)

如果要自主实现,核心分为三个步骤:

  1. 4连通域标记:用BFS/DFS遍历图像,对每个未标记的前景像素,仅遍历上下左右4个邻域,标记为同一个连通域。
  2. 轮廓提取:对每个连通域,筛选出邻域包含背景的像素(即边界像素)作为轮廓。
  3. 层级判断:通过连通域的包围盒关系区分外层(1级)和内层(2级)轮廓。

修正后的自定义算法示例:

import numpy as np
import matplotlib.pyplot as plt
import cv2

def four_connected_labeling(binary_img):
    """4连通域标记,返回标记矩阵和连通域像素列表"""
    h, w = binary_img.shape
    labels = np.zeros_like(binary_img, dtype=int)
    current_label = 1
    regions = []
    
    for y in range(h):
        for x in range(w):
            if binary_img[y, x] == 1 and labels[y, x] == 0:
                # BFS遍历4连通区域
                queue = [(y, x)]
                labels[y, x] = current_label
                region = [(y, x)]
                while queue:
                    cy, cx = queue.pop(0)
                    # 仅检查上下左右4个邻域
                    for dy, dx in [(-1,0), (1,0), (0,-1), (0,1)]:
                        ny, nx = cy + dy, cx + dx
                        if 0 <= ny < h and 0 <= nx < w:
                            if binary_img[ny, nx] == 1 and labels[ny, nx] == 0:
                                labels[ny, nx] = current_label
                                queue.append((ny, nx))
                                region.append((ny, nx))
                regions.append(region)
                current_label += 1
    return labels, regions

def get_contour_from_region(region, binary_img):
    """从连通域提取轮廓边界像素"""
    h, w = binary_img.shape
    contour = []
    for (y, x) in region:
        # 检查4邻域是否有背景,或处于图像边界
        is_boundary = False
        for dy, dx in [(-1,0), (1,0), (0,-1), (0,1)]:
            ny, nx = y + dy, x + dx
            if ny < 0 or ny >= h or nx <0 or nx >=w:
                is_boundary = True
                break
            if binary_img[ny, nx] == 0:
                is_boundary = True
                break
        if is_boundary:
            contour.append((x, y))
    return np.array(contour, dtype=np.int32).reshape((-1, 1, 2))

def is_region_inside(region_a, region_b):
    """判断region_a是否被region_b包围(简化包围盒判断)"""
    # 获取region_b的包围盒
    ys_b = [y for y, x in region_b]
    xs_b = [x for y, x in region_b]
    min_x_b, max_x_b = min(xs_b), max(xs_b)
    min_y_b, max_y_b = min(ys_b), max(ys_b)
    
    # 取region_a的中心点判断
    ys_a = [y for y, x in region_a]
    xs_a = [x for y, x in region_a]
    centroid_x = (min(xs_a) + max(xs_a)) / 2
    centroid_y = (min(ys_a) + max(ys_a)) / 2
    
    return min_x_b < centroid_x < max_x_b and min_y_b < centroid_y < max_y_b

# 读取并二值化图像
with open('text_data.txt', 'r') as file:
    lines = file.readlines()
    data = [[float(value) for value in line.strip().split()] for line in lines]
image = np.array(data)
max_value = np.amax(image)
threshold = max_value / 2
binary_image = np.where(image < threshold, 0, 1).astype(np.uint8)

# 4连通域标记
labels, regions = four_connected_labeling(binary_image)

# 提取所有轮廓
contours = [get_contour_from_region(reg, binary_image) for reg in regions]

# 判断层级
level1_count = 0
level2_count = 0

for i, reg_a in enumerate(regions):
    is_inner = False
    for j, reg_b in enumerate(regions):
        if i == j:
            continue
        if is_region_inside(reg_a, reg_b):
            is_inner = True
            break
    if is_inner:
        level2_count += 1
    else:
        level1_count += 1

# 输出结果
print(f"1级轮廓数量: {level1_count}")
print(f"2级轮廓数量: {level2_count}")

# 绘制轮廓
contour_img = np.zeros((binary_image.shape[0], binary_image.shape[1], 3), dtype=np.uint8)
for cnt in contours:
    cv2.drawContours(contour_img, [cnt], -1, (0, 255, 0), 1)
plt.imshow(contour_img)
plt.show()

内容的提问来源于stack exchange,提问作者Walrus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:55:58