YOLO8实例分割单物体检测出现多轮廓的原因及筛选方法
问题:YOLO8分割Mask对应多个轮廓的原因及解决方法
经过进一步实验后,我已修改问题!
我正在进行YOLO8相关实验,编写了如下测试脚本以复现问题:
# # test script to explore multi contours for 1 found object. import numpy as np import cv2 img = cv2.imread('demo.jpg') imgcopy = img.copy() from ultralytics import YOLO # Load a model model = YOLO('yolov8n-seg.pt') # load an official model #model = YOLO('path/to/best.pt') # load a custom model # Predict with the model results = model(img, retina_masks=True, save=True, imgsz = 640, conf=0.5, boxes=False, show_labels=False, show_conf=False, save_crop=True, save_txt=True, save_conf=True) # predict on an image for result in results: masks = result.masks for mask in masks.data: mask_np= mask.cpu().numpy().astype(np.uint8) #contours, _ = cv2.findContours(mask_np, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours, _ = cv2.findContours(mask_np, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) print ('We found ' + str(len(contours)) + ' objects') # i know i get 3 contours so this works ! imgcopy = cv2.drawContours(imgcopy, contours=contours[0], contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA) imgcopy = cv2.drawContours(imgcopy, contours=contours[1], contourIdx=-1, color=(255, 255, 0), thickness=2, lineType=cv2.LINE_AA) imgcopy = cv2.drawContours(imgcopy, contours=contours[2], contourIdx=-1, color=(255,0, 255), thickness=2, lineType=cv2.LINE_AA) cv2.imwrite('result.jpg', imgcopy)
我预期单个检测到的物体对应1个轮廓,但在该示例中却得到了3个轮廓。
输入图片:
输出图片:
即单个检测物体对应的mask存在3个轮廓,请问为何会出现这种情况?应如何筛选出正确的轮廓?我发现首个轮廓并非总是正确的。
解答
一、出现多个轮廓的原因
- 轮廓检索模式的影响:你使用的
cv2.RETR_TREE模式会提取所有层级的轮廓,包括物体的外部轮廓、内部孔洞轮廓(比如鞋子的鞋带孔、鞋头镂空部分),因此会出现多个轮廓结果。 - Mask的精细度:YOLO8的
retina_masks=True会输出高精度分割掩码,保留了物体的细微镂空结构,这些结构在OpenCV轮廓检测时会被识别为独立的内部轮廓。
二、筛选正确轮廓的方法
方法1:改用外部轮廓检索模式
直接替换为cv2.RETR_EXTERNAL模式,该模式仅提取最外层的外部轮廓,忽略所有内部孔洞的轮廓,代码修改如下:
contours, _ = cv2.findContours(mask_np, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
这是最直接的解决方式,能直接得到单个主物体轮廓。
方法2:通过轮廓面积筛选
如果需要保留内部轮廓但只关注主物体轮廓,可以通过计算轮廓面积排序,选择面积最大的轮廓(主物体外部轮廓面积远大于内部孔洞轮廓):
contours, _ = cv2.findContours(mask_np, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 按轮廓面积降序排序 contours_sorted = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True) # 取面积最大的轮廓作为主物体轮廓 main_contour = contours_sorted[0] # 绘制主轮廓 imgcopy = cv2.drawContours(imgcopy, [main_contour], -1, (0,255,0), 2, cv2.LINE_AA)
这种方法能避免依赖轮廓顺序,解决“首个轮廓并非总是正确”的问题。
方法3:直接使用YOLO8内置轮廓
Ultralytics YOLO8的mask对象已经提供了xy属性,可以直接获取处理好的轮廓点,无需再用OpenCV提取:
for result in results: masks = result.masks for mask in masks: # 直接获取轮廓点,转换为OpenCV需要的整数格式 contour = mask.xy[0].astype(np.int32) imgcopy = cv2.drawContours(imgcopy, [contour], -1, (0,255,0), 2, cv2.LINE_AA)
这种方法更高效,且能避免手动提取轮廓带来的多轮廓问题。
内容的提问来源于stack exchange,提问作者me1960
相关产品推荐
相关产品推荐

