Ultralytics YOLO27: 加入候补名单。
Get Started
课程中级

语义分割:标注每个像素

用 yolo26n-sem.pt 预测逐像素类别图,将其转换为覆盖率数值,构建 PNG 类别图数据集,并解读 mIoU,避免被像素准确率误导。

有些问题不涉及对象计数:人行道有多少被堵住,田地里有多少杂草,可行驶道路在哪里结束。语义分割通过为每个像素分配一个类别 ID 来回答这些问题。不过,它的核心指标很容易被误读:你在本课中训练的模型像素准确率达到 91%,却一个人都没检测出来。

学习成果

将 yolo26n-sem.pt 类别图转换为画面某个区域的覆盖率数值,在 Cityscapes8 上训练,并报告 mIoU、像素准确率以及模型漏检的类别。

快速通道
如果你已经熟悉相关内容,以下是简要版。
  1. 使用 yolo26n-sem.pt 进行预测并读取 result.semantic_mask.data:一个 H × W 的张量,包含原始尺寸下的类别 ID,255 表示忽略。

  2. 通过比较类别图与 result.names 中的类别 ID,回答覆盖率问题。

  3. 将标签以无损 PNG 类别图的形式存储在 masks/ 下,文件结构与 images/ 一一对应,并在 YAML 中设置 masks_dir: masks。

  4. 以 mIoU 和各类别 IoU 作为发布门槛,绝不要只看像素准确率。

实践操作

实例分割还是语义分割?#

城市街道的语义分割图,每个像素都按类别着色:道路、人行道、汽车、行人、自行车、植被、建筑和天空

实例分割(segment)语义分割(semantic)
输出每个对象一个掩码,另有一个框每个像素一个类别 ID
统计对象数量是不能——两辆相邻的汽车会被视为一个“汽车”区域
处理“stuff”(道路、天空、草地)效果差——不存在自然划分的实例是
标签多边形PNG 类别图或多边形
门控指标掩码 mAP50-95mIoU
预训练权重yolo26n-seg.pt(COCO,80 个类别)yolo26n-sem.pt(Cityscapes,19 个类别)
数多少个,还是量多少?

如果下游问题是“有多少个”,就使用实例分割。如果问题是“有多少”或“在哪里”,语义分割更易标注和读取。语义分割任务页面包含模型表。

预测类别图#

result.semantic_mask.data 是一个 H × W 张量,尺寸与原始图像相同;每个值都是类别 ID,255 表示忽略。result.summary() 可将它转换为各类别的像素比例:

from ultralytics import YOLO

model = YOLO("yolo26n-sem.pt")  # Cityscapes, 19 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

class_map = result.semantic_mask.data  # H x W tensor of class ids, 255 = ignore
print("class map:", tuple(class_map.shape), class_map.dtype)
for row in result.summary():
    if row["pixel_ratio"] >= 0.02:
        print(f"{row['name']:12s} {row['pixel_ratio']:.1%}")
class map: (1080, 810) torch.uint8
road         27.5%
sidewalk     2.0%
building     19.3%
vegetation   4.5%
person       14.6%
train        30.9%

看最后一行。Cityscapes 有一个 bus 类别,但模型却把公交车识别为 train。模型从车载拍摄的影像中学习,而这些影像大多拍摄于德国街道;从人行道近距离拍摄的公交车不属于这种数据分布。与检测器相比,语义模型更严格地继承训练集的视角和类别定义,也就是其数据集偏差,因此在相信类别图之前,请先用你自己的相机数据进行微调。

将类别图转换为答案#

通过简单的张量比较,类别图就能变成产品指标。下面回答“行人会走进的区域中,道路或人行道占多少,人占多少?”:

from ultralytics import YOLO

result = YOLO("yolo26n-sem.pt")("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
class_map = result.semantic_mask.data  # H x W class ids at the original size, 255 = ignore
ids = {name: i for i, name in result.names.items()}

h = class_map.shape[0]
lower = class_map[h // 2 :]  # the half of the frame a pedestrian would walk into
walkable = (lower == ids["road"]) | (lower == ids["sidewalk"])
people = lower == ids["person"]
print(f"lower half: {walkable.float().mean():.0%} road or sidewalk, {people.float().mean():.0%} person")
result.save("semantic_overlay.jpg")  # colored class map over the photo, for a visual check
lower half: 59% road or sidewalk, 23% person

三个习惯能让这些数值更可靠:

  • 按名称查找 ID,从 result.names 获取,绝不要硬编码数字;用你自己的类别训练的模型,其类别编号会不同。
  • 只关注重要区域。整幅画面的比例会混入天空和建筑;应先裁取类别图(或乘以区域掩码)。
  • 打开叠加图查看。semantic_overlay.jpg 能让你一眼看出数值所统计的“道路”是否真的是道路。

数据集格式#

语义数据集将每张图像与一张单通道掩码图像配对,掩码像素值表示类别 ID。语义数据集指南是参考资料;其目录结构与 images/ 逐文件对应:

cityscapes8/
├── images/
│   ├── train/aachen_000000_000019.png
│   └── val/aachen_000004_000019.png
└── masks/
    ├── train/aachen_000000_000019.png
    └── val/aachen_000004_000019.png

YAML 中增加 masks_dir;如果掩码中存储的 ID 与训练 ID 不同,还需要添加 label_mapping 区块:

path: cityscapes8
train: images/train
val: images/val
masks_dir: masks # semantic mask directory

names:
  0: road
  1: sidewalk
  # ... 19 classes in total

label_mapping: # raw mask value -> training id
  0: ignore_label
  7: 0 # road
  8: 1 # sidewalk
  # ...

Cityscapes 掩码存储 -1 到 33 的原始标签 ID;映射会保留其中 19 个,其余都映射到 ignore_label,并存储为 255。值为 255 的像素不会计入损失或指标;对于标注人员无法确定的区域,也使用相同的值。

如果你已有多边形标签,但没有 masks_dir,加载器会为你将多边形栅格化,并为未覆盖的像素添加 background 类别。

绝不要将掩码保存为 JPEG

有损压缩会混合相邻像素值,使类别 7 和类别 8 之间的边界变成由 7、8 以及不存在的 ID 构成的模糊带。所有掩码都应保存为 PNG 或 TIFF。

训练并解读 mIoU#

Cityscapes8包含 8 张城市场景图像,其中 4 张用于训练,4 张用于验证,足以进行 CPU 冒烟测试:

from ultralytics import YOLO

model = YOLO("yolo26n-sem.pt")
model.train(data="cityscapes8.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"mIoU: {metrics.miou:.3f}  pixel accuracy: {metrics.pixel_accuracy:.3f}")
                 Class     Images     Pixels       mIoU     PixAcc
                   all          4     192315      0.415      0.906
                  road          4      78529      0.974      0.996
            vegetation          4      37602      0.844      0.936
                person          3        722          0          0
                 rider          1         21          0          0
mIoU: 0.415  pixel accuracy: 0.906

(已省略部分类别行。)像素准确率显示为 91%,mIoU 显示为 42%;各类别行则表明模型一个人都没检测出来。这些结果都是真的。道路、建筑和植被占据了大部分像素——仅道路就有 78,529 个像素,占总数 192,315 的很大一部分——因此,能很好地标注这些区域的模型,即使漏掉所有小类别,像素准确率仍然很高。mIoU 对各类别的 IoU 取等权平均,因此每个漏检类别都会拉低它。

鸟类的交并比:预测区域与真实标注区域的重叠部分除以两者的并集,适用于框和掩码

判断规则

除了 mIoU,还要根据产品实际处理的类别的 IoU 设置门槛。对于行人安全功能,如果 person IoU 为 0,无论像素准确率如何,都不合格。

试试看

在你自己的街景照片上运行 semantic.py,列出面积最大的三个类别。调整 walkable.py,回答一个关于照片覆盖率的问题,例如下半部分中道路所占的比例。然后在 cityscapes8.yaml 上训练一个 epoch,记录 mIoU、像素准确率,以及 IoU 为 0 的所有类别。

完成条件
满足以下所有条件,即表示你已完成本课程。
  • 你已获得自己图像中各类别的像素比例和一个区域覆盖率数值,并通过保存的叠加图进行了核对。

  • 你已记录训练运行的 mIoU 和像素准确率,并列出了 IoU 为零的类别。

  • 你可以用一句话解释这次运行中像素准确率为何高于 mIoU。

显示解答

在我们的运行中:mIoU 为 0.415,像素准确率为 0.906;墙壁、行人、骑行者、摩托车和自行车的 IoU 为 0,此外,验证集四张图像中没有像素的类别也全部为 0。

像素准确率更高,是因为它按像素计数,而道路、建筑、植被和天空占据了每张 Cityscapes 图像的大部分区域。mIoU 对每个类别赋予相同权重,因此即使五个漏检的小类别只覆盖几百个像素,也会拉低平均值。对于需要对人采取行动的产品,这个模型仅因行人 IoU 不达标就无法通过门槛。

接下来做什么

接下来,我们从区域转向关键点,标注并训练姿态模型。

登录以保存学习进度