基于 Ultralytics YOLO 的分割对象隔离指南:从推理结果中提取纯净目标图像
2026/9/15 14:44:37 网站建设 项目流程

基于 Ultralytics YOLO 的分割对象隔离指南:从推理结果中提取纯净目标图像

【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10

导读

本指南讲解如何在使用 Ultralytics YOLO 完成分割任务推理之后,将检测到的各个目标对象从原始图像中"抠"出来——生成仅含目标的二值掩码,并输出黑底、透明背景(PNG)或裁剪后的纯净目标图像。这是一份通用的实战配方:读完本文,你将掌握Results结果对象中masksboxes的底层数据结构,能够用 OpenCV + NumPy 自主实现目标提取管线,并了解内置save_crop参数等更省事的替代方案。该能力在电商素材抠图、目标数据集裁剪、图像预处理等场景中非常实用。


1. 前置知识:分割推理的产出物

YOLOv8 分割模型(yolov8n-seg.pt-seg后缀模型)的输出不仅是目标框,还包括每个目标的像素级掩码(mask)。在 Ultralytics 框架中,predict()返回的是Results对象列表,其核心属性定义于 ultralytics/engine/results.py:

属性类型含义
orig_imgnumpy.ndarray原始图像(BGR,未被缩放)
boxesBoxes检测框,支持xyxyxywhconfcls等属性
masksMasks分割掩码,提供xyxyn等轮廓坐标属性
namesdict类别名称映射(如{0: 'person', ...}
pathstr输入图像的文件路径

从源码看,分割推理的后处理由 ultralytics/models/yolo/segment/predict.py 中的SegmentationPredictor.postprocess()完成:先经 NMS 得到预测框,再通过ops.process_mask()将掩码上采样回原始尺寸,最终以Results(orig_img, path, names, boxes=pred[:, :6], masks=masks)的形式打包返回。也就是说,results[i].masks中的掩码数据已经对齐到原始图像尺寸,这正是我们能直接用它做像素级隔离的基础。


2. 逐步配方:目标隔离的完整流程

2.1 导入依赖

from pathlib import Path import cv2 import numpy as np from ultralytics import YOLO
  • cv2(OpenCV):用于绘制轮廓、位运算与保存图像;
  • numpy:用于数组类型转换、形状变换与通道堆叠;
  • YOLO:Ultralytics 推理入口。

2.2 加载模型并执行推理

from ultralytics import YOLO # 加载一个预训练分割模型 model = YOLO('yolov8n-seg.pt') # 运行推理 results = model.predict()
  • 模型选择:仓库支持yolov8n-seg.ptyolov8s-seg.ptyolov8m-seg.ptyolov8l-seg.ptyolov8x-seg.pt等分割模型,详见分割任务文档中的模型性能表。模型权重在首次使用时自动下载。
  • 不传 source 参数时predict()会使用库自带的示例图片ultralytics/assets/bus.jpgultralytics/assets/zidane.jpg(见 assets 目录),非常适合快速验证流程。
  • 如需指定数据源,可传图片路径、目录、URL 或视频流,完整参数见 Predict 模式文档。

2.3 迭代结果与轮廓

# 迭代检测结果(对多张图片同样适用) for r in results: img = np.copy(r.orig_img) img_name = Path(r.path).stem # 源图片的 base-name,后续保存文件时用 # 迭代每个目标轮廓(同一张图可能有多个检测目标) for ci, c in enumerate(r): # 获取检测类别名称 label = c.names[c.boxes.cls.tolist().pop()]

说明:

  • 外层for r in results:每张输入图片对应一个Results
  • 内层for ci, c in enumerate(r):遍历该图上的每个检测目标,ci是该目标的索引(同一类别出现多个实例时用来区分);
  • c.boxes.cls返回类别索引张量(Boxes.cls在 results.py 中定义为self.data[:, -1]),.tolist().pop()取出其中的整型类别号,再用c.names[...]映射为类别名称;
  • 单张图片时外层循环只执行一次;单张图且只有一个检测目标时,两层循环都只执行一次。

2.4 生成二值掩码并绘制填充轮廓

隔离目标的关键一步:从原图尺寸生成一张全零的二值图,把目标轮廓以白色(255)填充进去,其余区域保持 0。

# 创建与原图同尺寸的二值掩码(单通道) b_mask = np.zeros(img.shape[:2], np.uint8) # 提取轮廓坐标点 contour = c.masks.xy.pop() # (x, y) 像素坐标点集 contour = contour.astype(np.int32) # float32 -> int32,兼容 drawContours contour = contour.reshape(-1, 1, 2) # 整理为 [N, 1, 2] 形状 # 将轮廓填充绘制到掩码上 _ = cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED)

contour三步处理的内部原理:

  1. c.masks.xy:返回掩码轮廓点坐标列表,格式为(x, y)像素坐标。底层实现见 results.py 的Masks.xy属性——它调用ops.masks2segments()将每个掩码张量转换为轮廓点序列,再通过ops.scale_coords()缩放到原图坐标。
  2. .pop()masks.xy是只含一个元素的列表(当前目标的轮廓),用pop()取出该元素得到轮廓点数组。注意masks2segments()默认采用"largest"策略(见 ops.py),即单个掩码只保留面积最大的外部轮廓(由cv2.findContoursRETR_EXTERNAL模式提取),因此单目标场景下取单个轮廓是合理的。
  3. .astype(np.int32)masks.xy返回的坐标是float32类型,而 OpenCV 的drawContours()不接受浮点坐标,必须先转为int32
  4. .reshape(-1, 1, 2)drawContours()要求轮廓形状为[N, 1, 2]——N为轮廓点数,每个点用1个条目承载2个坐标值(x, y);-1表示该维度长度自动推断。

drawContours()配置解读:

  • [contour]:把轮廓包一层列表(多个轮廓可同时传入),经测试能正确生成目标轮廓掩码;
  • -1:表示绘制传入的所有轮廓;
  • (255, 255, 255):绘制颜色为白色;
  • cv2.FILLED:将轮廓包围的所有像素统一填充为白色,其余像素保持 0——这正是"二值掩码"的含义,也是后续隔离操作的依据。

3. 两种隔离方案与各自的裁剪子选项

拿到二值掩码b_mask后,根据期望的背景表现有两种主流方案,每种方案又分"保持整图尺寸"与"裁剪到目标区域"两个子选项。

3.1 方案一:黑色背景像素

# 将单通道二值掩码转为三通道 mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR) # 用按位与运算隔离目标 isolated = cv2.bitwise_and(mask3ch, img)

工作原理

  • 二值掩码原本是单通道(灰度)图像,而原图是三通道 BGR。cv2.cvtColor(..., cv2.COLOR_GRAY2BGR)先把掩码广播为三通道,使两张图通道数一致,满足后续混合运算的兼容性要求;
  • cv2.bitwise_and()逐像素按位与:只有两个输入在同一位置像素值都大于 0 时结果才大于 0。掩码只在目标轮廓内部为 255(>0),因此保留下来的恰好是原图中与轮廓区域重叠的像素;轮廓之外全部变为 0,呈现黑色。

子选项 A:保持整图尺寸

无需额外步骤,isolated即为原图尺寸、目标以外区域全黑的图像。

子选项 B:裁剪到目标区域

利用检测框坐标做数组切片,仅保留目标所在矩形区域:

# 获取边框坐标:xmin, ymin, xmax, ymax x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32) # 裁剪到目标区域 iso_crop = isolated[y1:y2, x1:x2]

这行代码的三段式处理:

  • c.boxes.xyxy:返回[xmin, ymin, xmax, ymax]格式的边框张量(Boxes.xyxy在 results.py 中定义为self.data[:, :4]),单位为原始图像像素;
  • .cpu().numpy().squeeze():从 GPU 移至 CPU、转为 NumPy 数组、去掉多余维度(单目标时为形状(1, 4)(4,));
  • .astype(np.int32):浮点坐标转整数,才能用于数组切片;
  • isolated[y1:y2, x1:x2]:按[ymin:ymax, xmin:xmax]切出目标区域。

3.2 方案二:透明背景像素

# 将 BGR 三通道图与单通道二值掩码沿深度方向堆叠为四通道 isolated = np.dstack([img, b_mask])

工作原理np.dstack()沿第三维(通道/深度方向)堆叠数组。原图是 H×W×3,二值掩码是 H×W×1,堆叠后得到 H×W×4 的图像——即 BGRA 四通道。目标区域对应的 alpha 通道值为 255(不透明),轮廓之外的 alpha 为 0(全透明),保存为 PNG 格式时背景即呈现为透明。注意:JPEG 不支持透明通道,务必使用 PNG 保存。

子选项 A:保持整图尺寸

无需额外步骤,保存为 PNG 后整图尺寸、背景透明。

子选项 B:裁剪到目标区域

与方案一完全相同:

# 获取边框坐标 x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32) # 裁剪到目标区域 iso_crop = isolated[y1:y2, x1:x2]

3.3 内置替代:直接使用save_crop

如果你只是想得到"包含背景的裁剪图"(目标周围保留原背景,不做二值隔离),无需手写上述流程——这是 Ultralytics 的内置能力:在predict()中设置save_crop=True即可自动把每个检测目标按框裁剪保存,该参数与完整推理参数列表见 Predict 模式文档。在结果对象层面,Results.save_crop()方法也已内置(见 results.py 的方法列表)。


4. 可选收尾:将结果保存到文件

隔离或裁剪完成后的下一步完全由你的业务决定。一个最基本的收尾是把结果写盘:

# 将隔离出的目标保存为 PNG 文件 _ = cv2.imwrite(f'{img_name}_{label}-{ci}.png', iso_crop)

命名规则:img_name是源图片 base-name(来自Path(r.path).stem),label是检测类别名,ci是同一类下目标的序号,三者组合可保证同一张图、同一类别、多个实例时文件名不冲突。此步骤可选,不需要保存时可跳过。


5. 完整示例代码

以下将全部步骤合并为一个可直接运行的脚本。需要说明:对要保存三通道(BGR)图像时用cv2.imwrite即可;若选择了透明背景方案(四通道),OpenCV 写 PNG 也能保留 alpha 通道。实际使用中把两个for循环体封装为函数会更利于复用,此处留作练习。

from pathlib import Path import cv2 import numpy as np from ultralytics import YOLO model = YOLO('yolov8n-seg.pt') # 加载分割模型 results = model.predict() # 执行推理(默认使用示例图片) # 迭代检测结果(支持多张图片) for r in results: img = np.copy(r.orig_img) # 原始图像副本 img_name = Path(r.path).stem # 源图片 base-name # 迭代每个目标轮廓(多个检测目标) for ci, c in enumerate(r): label = c.names[c.boxes.cls.tolist().pop()] # 类别名称 # 创建二值掩码 b_mask = np.zeros(img.shape[:2], np.uint8) # 提取轮廓并整理为 drawContours 所需格式(合并为一行) contour = c.masks.xy.pop().astype(np.int32).reshape(-1, 1, 2) _ = cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED) # 以下两种方案任选其一: # 方案一:黑色背景 mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR) isolated = cv2.bitwise_and(mask3ch, img) # 方案二:透明背景(需保存为 PNG 才能看到透明效果) isolated = np.dstack([img, b_mask]) # 可选:按检测框裁剪到目标区域(对以上任一方案都适用) x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32) iso_crop = isolated[y1:y2, x1:x2] # TODO: 在这里编写你的后续处理逻辑 # 例如:cv2.imwrite(f'{img_name}_{label}-{ci}.png', iso_crop)

提示:运行前请确认已安装ultralyticsopencv-pythonnumpy。安装引导参见快速开始文档。


6. 源码级原理速查

为便于深入理解与排查问题,下面汇总本指南涉及的关键实现位置:

关注点源码位置关键说明
分割推理后处理ultralytics/models/yolo/segment/predict.pyNMS →process_mask上采样 → 组装Results(boxes, masks)
结果对象结构ultralytics/engine/results.pyorig_imgboxesmasksnamespath等属性
掩码轮廓坐标ultralytics/engine/results.pyMasks.xy(像素坐标)与Masks.xyn(归一化坐标)
掩码转轮廓算法ultralytics/utils/ops.pymasks2segments()cv2.findContours+largest策略
检测框属性ultralytics/engine/results.pyBoxes.xyxyBoxes.confBoxes.cls
内置裁剪保存ultralytics/engine/results.pyResults.save_crop()方法

7. 常见问题与注意事项

  1. 为什么masks.xy取到的轮廓只有一个?因为masks2segments()默认strategy="largest",每个掩码只保留最大外部轮廓(ops.py)。对绝大多数目标(单个连通区域)够用;若目标本身有孔洞或由多个分离区域组成,可关注"concat"策略或自行扩展。
  2. 透明背景必须存 PNGnp.dstack([img, b_mask])产生的四通道图仅在 PNG 等支持 alpha 通道的格式下才保留透明效果。
  3. 坐标系必须一致masks.xyboxes.xyxy都基于原始图像坐标(后处理时已用scale_boxes/scale_coords回缩),因此可以直接与r.orig_img一起使用,无需再次缩放。
  4. 修改推理默认行为:若想过滤低置信度目标或限定类别,可在predict(conf=0.25, classes=[0])中传入参数,完整参数说明见 Predict 模式文档。

【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询