标注并训练实例分割模型
编写多边形标签、用代码检查标签、在 COCO8-seg 上训练 yolo26n-seg.pt,并根据框指标与掩码指标之间的差距诊断模型。
实例分割会为每个对象生成独立轮廓,适用于面积、重叠或边界会影响决策的场景。多边形是大多数团队采购成本最高的标签,而且格式错误的行不会导致训练崩溃——它只会悄悄地让模型用错误的形状进行训练。本课会讲解正确的行格式、用代码检查标签集,并训练第一个模型,让你了解额外标注带来的收益:新增一项评估轮廓而非仅评估位置的指标。
实践操作
多边形标签行#

分割标签文件与检测标签类似,每个对象占一行,但不再使用框,而是以归一化顶点对列出对象的轮廓。以下是 COCO8-seg 中的一条真实标签行,经过了删减:
45 0.782016 0.986521 0.937078 0.874167 0.957297 0.782021 0.950562 0.739333 0.825844 0.561792 ...| 位置 | 检测行 | 分割行 |
|---|---|---|
| 1 | 类别索引(45 表示 bowl) | 类别索引 |
| 2–5 | x_center y_center width height | 前两个顶点,x1 y1 x2 y2 |
| 6+ | — | 更多顶点,数量取决于轮廓需要 |
| 单位 | 归一化到 0–1 | 归一化到 0–1(像素 x ÷ 图像宽度,像素 y ÷ 图像高度) |
最重要的是三条规则:每个对象至少有三个顶点;坐标要除以图像尺寸;即使两个同类别对象相接,也要为每个实例单独写一行。分割数据集指南提供了完整规范。
多边形是边界框标签的超集。同一个标签文件夹既能训练 yolo26n.pt(Ultralytics 根据多边形推导出每个边界框),也能训练 yolo26n-sem.pt(将多边形栅格化为类别图)——我们在 coco8-seg.yaml 上训练过这两种模型。标注完成后即使计划有变,投入也不会白费。
训练前检查标签#
数值数量为奇数、坐标以像素为单位,或轮廓描错对象,都不会让训练停止。这个检查器通过 Ultralytics 自带的加载器读取数据集,拒绝格式错误的行,并列出每个多边形的面积以及 Ultralytics 将据此生成的框:
from pathlib import Path
import numpy as np
from ultralytics.data.utils import check_det_dataset
data = check_det_dataset("coco8-seg.yaml") # resolves paths, downloads if needed
for label in sorted(Path(data["path"], "labels", "train").glob("*.txt"))[:2]:
for n, row in enumerate(label.read_text().splitlines(), 1):
cls, *coords = row.split()
if len(coords) % 2 or len(coords) < 6:
print(f"{label.name}:{n} BAD: needs an even number of values and at least 3 vertices")
continue
xy = np.array(coords, dtype=float).reshape(-1, 2)
if xy.min() < 0 or xy.max() > 1:
print(f"{label.name}:{n} BAD: coordinates must be normalized to 0-1")
continue
x, y = xy[:, 0], xy[:, 1]
area = 0.5 * abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) # shoelace formula
w, h = xy.max(0) - xy.min(0) # the box Ultralytics derives from the polygon
name = data["names"][int(cls)]
print(f"{label.stem} {name:9s} {len(xy):3d} vertices polygon {area:.3f} box {w * h:.3f} fill {area / (w * h):.2f}")000000000009 bowl 12 vertices polygon 0.391 box 0.569 fill 0.69
000000000009 bowl 14 vertices polygon 0.145 box 0.238 fill 0.61
000000000009 broccoli 50 vertices polygon 0.161 box 0.252 fill 0.64
000000000009 bowl 55 vertices polygon 0.079 box 0.531 fill 0.15
000000000009 orange 13 vertices polygon 0.007 box 0.011 fill 0.64
000000000009 orange 13 vertices polygon 0.005 box 0.009 fill 0.61
000000000009 orange 18 vertices polygon 0.012 box 0.019 fill 0.61
000000000009 orange 12 vertices polygon 0.010 box 0.022 fill 0.44
000000000025 giraffe 82 vertices polygon 0.072 box 0.234 fill 0.31
000000000025 giraffe 19 vertices polygon 0.010 box 0.027 fill 0.38面积是图像面积的比例,因此第一个碗占照片的 39%。填充率是多边形面积除以框面积,是你需要重点查看的一列:
- 大多数形状紧凑的对象(碗、橙子、西兰花)的填充率约为 0.6–0.7。
- 长颈鹿的 0.31 是合理的:它细长的腿和脖子让框内大部分区域留空——这正是该对象需要掩码的原因。
- 第三个碗的填充率为 0.15,审核人员应该打开对应标签行仔细查看。碗的大部分被食物挡住,只露出一圈窄窄的边缘,因此轮廓是在大框内的一条窄带。这里的情况是正确的;但在你的数据中,如此低的填充率往往也意味着轮廓可能描错了对象。
训练前修复每一行 BAD 数据。然后按填充率排序,在标注工具中打开每个类别中填充率最低的几行:填充率低可能是因为物体细长或被遮挡(保留),也可能是描绘错误(修正)。
无需逐边描摹也能生成多边形#
手动描摹轮廓是数据标注中最耗时的部分。开始之前,先看看哪种快捷方式适用:
| 你已经有 | 工具 |
|---|---|
| 带分割标注的 COCO JSON | 来自 ultralytics.data.converter 的 convert_coco(labels_dir, use_segments=True) |
| PNG 掩码(像素值 = 类别索引 + 1,0 = 背景) | convert_segment_masks_to_yolo_seg(masks_dir, output_dir, classes)——同一类别中相接的对象会合并成一个多边形,因此需要在审核时拆分 |
| 只有框标签 | yolo_bbox2segment(im_dir)——使用每个框提示 SAM 生成多边形 |
| 还没有任何数据 | 智能标注和批量标注——点击一次,再审核多边形 |
在 Ultralytics Platform 上,智能模式会运行 SAM:在对象上单击一次作为正向提示,再在要排除的区域上单击一次作为负向提示,生成掩码供你审核。

如果掩码蔓延到背景,模型也会学着这么做。根据边界框与掩码中的质量标准检查机器生成的多边形,然后重新运行上面的标签检查。
在 COCO8-seg 上训练#

训练流程与检测相同,只需使用 -seg 模型和分割数据集。在笔记本电脑 CPU 上以 imgsz=320 训练一个周期只需几秒钟,足以验证整个流程:
from ultralytics import YOLO
model = YOLO("yolo26n-seg.pt")
model.train(data="coco8-seg.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"box mAP50-95: {metrics.box.map:.3f}")
print(f"mask mAP50-95: {metrics.seg.map:.3f}") Class Images Instances Box(P R mAP50 mAP50-95) Mask(P R mAP50 mAP50-95)
all 4 17 0.909 0.449 0.661 0.482 0.685 0.409 0.557 0.377
horse 1 2 1 0.993 0.995 0.564 0.619 0.857 0.663 0.199
box mAP50-95: 0.482
mask mAP50-95: 0.377只有四张验证图像,因此这些数字没有统计意义;关键在于训练顺利完成,并在 runs/segment/ 下生成 weights/best.pt。对于真实数据,请遵循 Segment 任务指南,并在 GPU 上完成完整训练计划。
并排查看框指标和掩码指标#
验证表包含两个部分。Box 根据每个预测框与标签对应框的匹配程度进行评分;Mask 根据预测像素与标签多边形的匹配程度进行评分。两者使用相同的 mAP 计算方法,区别仅在于使用的 IoU 不同。
掩码 mAP50-95 几乎总是低于框 mAP50-95,因为像素 IoU 更严格:框偏差几个像素仍能保持较高重叠度,而掩码漏掉手臂或延伸到路面后,在 0.75–0.95 阈值下 IoU 会迅速下降。horse 这一行最能体现这种差异——框 mAP50-95 为 0.564,掩码为 0.199。模型找到了两匹马,框的位置也很好,但没有描出马腿和马尾。根据差距判断要修正什么:
- 两者都低:问题在于没有找到对象,而不是轮廓描得不好。先为遗漏的情况补充数据。
- 框指标好,掩码指标低很多:模型能找到对象,但轮廓效果不好。运行标签检查、尝试更大的模型,或使用更高的
imgsz进行训练和验证。 - 掩码指标在提升,框指标停滞:这没问题——对于分割产品,掩码 mAP50-95 才是发布门槛。
训练时,overlap_mask=True 会将实例掩码合并到一张图像中,而 mask_ratio=4 会对训练掩码进行下采样。这两者都不会改变预测掩码的分辨率——那由下一课中的 retina_masks 决定。除非有测量结果支持调整,否则请保留默认值。
显示解答
要检查所有数据划分,请将循环中的文件夹替换为 Path(data["path"], "labels").rglob("*.txt")。然后训练:
from ultralytics import YOLO
model = YOLO("yolo26n-seg.pt")
model.train(data="coco8-seg.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"box mAP50-95: {metrics.box.map:.3f}") # 0.482 in our run
print(f"mask mAP50-95: {metrics.seg.map:.3f}") # 0.377 in our run0.1 的差距是预期之中的:掩码 IoU 会惩罚每个边界像素,而框 IoU 只关注四条边。在我们的运行中,差距最大的类别是 horse(框 0.564,掩码 0.199)。框 mAP 较好,而掩码 mAP 低很多,因此决策图指向轮廓质量:先检查这些多边形,再尝试更大的模型或更高的 imgsz。