基于4连通性的轮廓检测问题:OpenCV替代方案咨询
4连通性轮廓检测解决方案
问题背景
需要实现基于4连通性的轮廓检测,但OpenCV的findContours()仅支持8连通性。在测试图中,该函数将目标识别为单个轮廓,而4连通性应识别为两个轮廓。自行编写的朴素Python算法在轮廓计数和嵌套轮廓处理上存在困难,寻求以下解决方案:
- OpenCV是否有其他函数可实现4连通性轮廓检测?
- 若需自行编写算法,如何完成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连通,但可以通过连通域分析+轮廓提取的组合实现:
- 使用
cv2.connectedComponentsWithStats()函数,该函数支持指定4连通性(参数connectivity=4),先标记所有4连通的独立区域。 - 对每个连通域,提取其轮廓(对单个连通域掩码调用
findContours()即可,此时8连通不影响结果,因为区域已被4连通分割)。 - 通过连通域的包围关系判断层级:内层轮廓的质心会被外层连通域的边界框完全包含。
示例代码:
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连通轮廓检测算法(含层级处理)
如果要自主实现,核心分为三个步骤:
- 4连通域标记:用BFS/DFS遍历图像,对每个未标记的前景像素,仅遍历上下左右4个邻域,标记为同一个连通域。
- 轮廓提取:对每个连通域,筛选出邻域包含背景的像素(即边界像素)作为轮廓。
- 层级判断:通过连通域的包围盒关系区分外层(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
相关产品推荐
相关产品推荐

