Transformers documentation

图像分割

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

图像分割

图像分割模型将图像中对应不同感兴趣区域的区域分离出来。这些模型通过为每个像素分配一个标签来工作。分割有几种类型:语义分割、实例分割和全景分割。

在本指南中,我们将:

  1. 了解不同类型的分割。
  2. 提供一个用于语义分割的端到端微调示例。

在开始之前,请确保已安装所有必要的库:

# uncomment to install the necessary libraries
!pip install -q datasets transformers evaluate accelerate trackio

我们建议您登录您的 Hugging Face 账户,以便上传并与社区共享您的模型。当系统提示时,请输入您的令牌以完成登录:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

分割的类型

语义分割为图像中的每个像素分配一个标签或类别。让我们来看一个语义分割模型的输出。它会将图像中同一物体的所有实例分配为相同的类别,例如,所有的猫都会被标记为“cat”,而不是“cat-1”、“cat-2”。 我们可以使用 transformers 的图像分割流水线来快速推理语义分割模型。让我们来看一个示例图像。

from transformers import pipeline
from PIL import Image
import requests

url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/segmentation_input.jpg"
image = Image.open(requests.get(url, stream=True).raw)
image
Segmentation Input

我们将使用 nvidia/segformer-b1-finetuned-cityscapes-1024-1024。

semantic_segmentation = pipeline("image-segmentation", "nvidia/segformer-b1-finetuned-cityscapes-1024-1024")
results = semantic_segmentation(image)
results

分割流水线的输出包含每个预测类别对应的掩码。

[{'score': None,
  'label': 'road',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'sidewalk',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'building',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'wall',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'pole',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'traffic sign',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'vegetation',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'terrain',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'sky',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

观察汽车类别的掩码,我们可以看到每辆汽车都被分配了相同的掩码。

results[-1]["mask"]
Semantic Segmentation Output

在实例分割中,目标不是对每个像素进行分类,而是为给定图像中每个物体实例预测一个掩码。它与目标检测非常相似:目标检测中每个实例都有一个边界框,而这里则使用分割掩码。我们将使用 facebook/mask2former-swin-large-cityscapes-instance 来完成此任务。

instance_segmentation = pipeline("image-segmentation", "facebook/mask2former-swin-large-cityscapes-instance")
results = instance_segmentation(image)
results

如下所示,存在多个被分类的汽车,并且除了属于汽车和行人实例的像素外,其他像素均没有分类。

[{'score': 0.999944,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999945,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999652,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.903529,
  'label': 'person',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

查看下方其中一个汽车掩码。

results[2]["mask"]
Semantic Segmentation Output

全景分割结合了语义分割和实例分割,其中每个像素都被分类到一个类别以及该类别的一个实例,并且每个类别的每个实例都有多个掩码。我们可以使用 facebook/mask2former-swin-large-cityscapes-panoptic 来实现这一点。

panoptic_segmentation = pipeline("image-segmentation", "facebook/mask2former-swin-large-cityscapes-panoptic")
results = panoptic_segmentation(image)
results

如下所示,我们有更多的类别。稍后我们将展示如何对每个像素进行分类,使其归属于其中一个类别。

[{'score': 0.999981,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999958,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.99997,
  'label': 'vegetation',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999575,
  'label': 'pole',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999958,
  'label': 'building',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999634,
  'label': 'road',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.996092,
  'label': 'sidewalk',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999221,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.99987,
  'label': 'sky',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

让我们对所有类型的分割进行并排比较。

Segmentation Maps Compared

在了解了各种类型的分割之后,让我们深入探讨如何针对语义分割微调模型。

语义分割在现实世界中的常见应用包括:训练自动驾驶汽车以识别行人和重要的交通信息、在医学影像中识别细胞和异常,以及通过卫星图像监测环境变化。

用于分割任务的模型微调

我们将执行以下操作:

  1. 在 SceneParse150 数据集上微调 SegFormer。
  2. 使用您微调后的模型进行推理。

要查看与此任务兼容的所有架构和检查点,我们建议查阅 任务页面

加载 SceneParse150 数据集

首先,从 🤗 Datasets 库中加载 SceneParse150 数据集的一个较小子集。这将让你有机会进行实验,确保一切正常运行,然后再花更多时间在完整数据集上进行训练。

>>> from datasets import load_dataset

>>> ds = load_dataset("merve/scene_parse_150", split="train[:50]")

使用 train_test_split 方法将数据集的 train 子集拆分为训练集和测试集:

>>> ds = ds.train_test_split(test_size=0.2)
>>> train_ds = ds["train"]
>>> test_ds = ds["test"]

然后来看一个示例:

>>> train_ds[0]
{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=512x683 at 0x7F9B0C201F90>,
 'annotation': <PIL.PngImagePlugin.PngImageFile image mode=L size=512x683 at 0x7F9B0C201DD0>,
 'scene_category': 368}

# view the image
>>> train_ds[0]["image"]
  • image:场景的 PIL 图像。
  • annotation:分割掩码的 PIL 图像,同时也是模型的目标。
  • scene_category:描述图像场景的类别 ID,例如“kitchen”或“office”。在本指南中,你只需要 image 和 annotation,它们都是 PIL 图像。

您还需要创建一个将标签 ID 映射到标签类别的字典,这在后续设置模型时会很有用。从 Hub 下载映射,并创建 id2label 和 label2id 字典:

>>> import json
>>> from pathlib import Path
>>> from huggingface_hub import hf_hub_download

>>> repo_id = "huggingface/label-files"
>>> filename = "ade20k-id2label.json"
>>> id2label = json.loads(Path(hf_hub_download(repo_id, filename, repo_type="dataset")).read_text())
>>> id2label = {int(k): v for k, v in id2label.items()}
>>> label2id = {v: k for k, v in id2label.items()}
>>> num_labels = len(id2label)

预处理

下一步是加载 SegFormer 图像处理器,以将图像和标注准备好供模型使用。一些数据集(例如本例中的数据集)使用零索引作为背景类别。然而,背景类别实际上并不包含在 150 个类别中,因此您需要设置 do_reduce_labels=True,以便从所有标签中减去一。零索引会被替换为 255,从而被 SegFormer 的损失函数忽略:

>>> from transformers import AutoImageProcessor

>>> checkpoint = "nvidia/mit-b0"
>>> image_processor = AutoImageProcessor.from_pretrained(checkpoint, do_reduce_labels=True)

对图像数据集应用一些数据增强是常见做法,旨在提高模型对过拟合的鲁棒性。在本指南中,你将使用 torchvision 中的 ColorJitter 函数来随机更改图像的颜色属性,但你也可以使用任何你喜欢的图像库。

>>> from torchvision.transforms import ColorJitter

>>> jitter = ColorJitter(brightness=0.25, contrast=0.25, saturation=0.25, hue=0.1)

现在创建两个预处理函数,用于为模型准备图像和标注。这些函数将图像转换为 pixel_values,将标注转换为 labels。对于训练集,在将图像提供给图像处理器之前,会先应用 jitter。对于测试集,图像处理器会对 images 进行裁剪和归一化,而仅对 labels 进行裁剪,因为测试期间不会应用数据增强。

>>> def train_transforms(example_batch):
...     images = [jitter(x) for x in example_batch["image"]]
...     labels = [x for x in example_batch["annotation"]]
...     inputs = image_processor(images, labels)
...     return inputs


>>> def val_transforms(example_batch):
...     images = [x for x in example_batch["image"]]
...     labels = [x for x in example_batch["annotation"]]
...     inputs = image_processor(images, labels)
...     return inputs

要对整个数据集应用 jitter,请使用 🤗 Datasets 的 set_transform 函数。该转换是即时应用的,速度更快且占用更少的磁盘空间:

>>> train_ds.set_transform(train_transforms)
>>> test_ds.set_transform(val_transforms)

评估

在训练过程中加入评估指标通常有助于评估模型的性能。您可以使用 🤗 Evaluate 库快速加载评估方法。对于此任务,请加载 mean Intersection over Union(IoU)指标(有关如何加载和计算指标的更多信息,请参阅 🤗 Evaluate 快速入门):

>>> import evaluate

>>> metric = evaluate.load("mean_iou")

然后创建一个函数来计算指标。你的预测值需要先转换为 logits,然后重塑为与标签相同的尺寸,之后才能调用 compute:

>>> import numpy as np
>>> import torch
>>> from torch import nn

>>> def compute_metrics(eval_pred):
...     with torch.no_grad():
...         logits, labels = eval_pred
...         logits_tensor = torch.from_numpy(logits)
...         logits_tensor = nn.functional.interpolate(
...             logits_tensor,
...             size=labels.shape[-2:],
...             mode="bilinear",
...             align_corners=False,
...         ).argmax(dim=1)

...         pred_labels = logits_tensor.detach().cpu().numpy()
...         metrics = metric.compute(
...             predictions=pred_labels,
...             references=labels,
...             num_labels=num_labels,
...             ignore_index=255,
...             reduce_labels=False,
...         )
...         for key, value in metrics.items():
...             if isinstance(value, np.ndarray):
...                 metrics[key] = value.tolist()
...         return metrics

您的 compute_metrics 函数已准备就绪,在配置训练时您将再次用到它。

训练

如果您不熟悉使用 Trainer 对模型进行微调,请查看此处的基础教程!

现在您可以开始训练模型了!使用 AutoModelForSemanticSegmentation 加载 SegFormer,并将标签 ID 与标签类别之间的映射传递给模型:

>>> from transformers import AutoModelForSemanticSegmentation, TrainingArguments, Trainer

>>> model = AutoModelForSemanticSegmentation.from_pretrained(checkpoint, id2label=id2label, label2id=label2id)

此时,只剩下三个步骤:

  1. 在 TrainingArguments 中定义训练超参数。请注意不要删除未使用的列,因为这会丢弃 image 列。如果没有 image 列,就无法创建 pixel_values。请设置 remove_unused_columns=False 以防止此行为!另一个必需的参数是 output_dir,用于指定模型的保存位置。通过设置 push_to_hub=True,您可以将该模型推送到 Hub(上传模型需要登录 Hugging Face)。在每个 epoch 结束时,Trainer 会评估 IoU 指标并保存训练检查点。
  2. 将训练参数与模型、数据集、分词器、数据整理器以及 compute_metrics 函数一起传递给 Trainer。
  3. 调用 train() 来微调你的模型。
>>> training_args = TrainingArguments(
...     output_dir="segformer-b0-scene-parse-150",
...     learning_rate=6e-5,
...     num_train_epochs=50,
...     per_device_train_batch_size=2,
...     per_device_eval_batch_size=2,
...     save_total_limit=3,
...     eval_strategy="steps",
...     save_strategy="steps",
...     save_steps=20,
...     eval_steps=20,
...     logging_steps=1,
...     report_to="trackio",
...     run_name="scene-parse-150",
...     eval_accumulation_steps=5,
...     remove_unused_columns=False,
...     push_to_hub=True,
... )

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=train_ds,
...     eval_dataset=test_ds,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()

训练完成后,使用 push_to_hub() 方法将您的模型共享到 Hub,以便所有人都能使用您的模型:

>>> trainer.push_to_hub()

推理

很好,现在你已经微调了一个模型,可以使用它进行推理了!

重新加载数据集并加载一张图像用于推理。

>>> from datasets import load_dataset

>>> ds = load_dataset("merve/scene_parse_150", split="train[:50]")
>>> ds = ds.train_test_split(test_size=0.2)
>>> test_ds = ds["test"]
>>> image = ds["test"][0]["image"]
>>> image
Image of bedroom

接下来,我们将了解如何在不使用 pipeline 的情况下进行推理。使用图像处理器处理图像,并将 pixel_values 放到 GPU 上:

>>> from accelerate import Accelerator
>>> device = Accelerator().device
>>> encoding = image_processor(image, return_tensors="pt")
>>> pixel_values = encoding.pixel_values.to(device)

将输入传递给模型并返回 logits:

>>> outputs = model(pixel_values=pixel_values)
>>> logits = outputs.logits.cpu()

接下来,将 logits 重新缩放至原始图像尺寸:

>>> upsampled_logits = nn.functional.interpolate(
...     logits,
...     size=image.size[::-1],
...     mode="bilinear",
...     align_corners=False,
... )

>>> pred_seg = upsampled_logits.argmax(dim=1)[0]

为了可视化结果,请将数据集调色板加载为ade_palette(),它将每个类别映射到其 RGB 值。

def ade_palette():
  return np.asarray([
      [0, 0, 0],
      [120, 120, 120],
      [180, 120, 120],
      [6, 230, 230],
      [80, 50, 50],
      [4, 200, 3],
      [120, 120, 80],
      [140, 140, 140],
      [204, 5, 255],
      [230, 230, 230],
      [4, 250, 7],
      [224, 5, 255],
      [235, 255, 7],
      [150, 5, 61],
      [120, 120, 70],
      [8, 255, 51],
      [255, 6, 82],
      [143, 255, 140],
      [204, 255, 4],
      [255, 51, 7],
      [204, 70, 3],
      [0, 102, 200],
      [61, 230, 250],
      [255, 6, 51],
      [11, 102, 255],
      [255, 7, 71],
      [255, 9, 224],
      [9, 7, 230],
      [220, 220, 220],
      [255, 9, 92],
      [112, 9, 255],
      [8, 255, 214],
      [7, 255, 224],
      [255, 184, 6],
      [10, 255, 71],
      [255, 41, 10],
      [7, 255, 255],
      [224, 255, 8],
      [102, 8, 255],
      [255, 61, 6],
      [255, 194, 7],
      [255, 122, 8],
      [0, 255, 20],
      [255, 8, 41],
      [255, 5, 153],
      [6, 51, 255],
      [235, 12, 255],
      [160, 150, 20],
      [0, 163, 255],
      [140, 140, 140],
      [250, 10, 15],
      [20, 255, 0],
      [31, 255, 0],
      [255, 31, 0],
      [255, 224, 0],
      [153, 255, 0],
      [0, 0, 255],
      [255, 71, 0],
      [0, 235, 255],
      [0, 173, 255],
      [31, 0, 255],
      [11, 200, 200],
      [255, 82, 0],
      [0, 255, 245],
      [0, 61, 255],
      [0, 255, 112],
      [0, 255, 133],
      [255, 0, 0],
      [255, 163, 0],
      [255, 102, 0],
      [194, 255, 0],
      [0, 143, 255],
      [51, 255, 0],
      [0, 82, 255],
      [0, 255, 41],
      [0, 255, 173],
      [10, 0, 255],
      [173, 255, 0],
      [0, 255, 153],
      [255, 92, 0],
      [255, 0, 255],
      [255, 0, 245],
      [255, 0, 102],
      [255, 173, 0],
      [255, 0, 20],
      [255, 184, 184],
      [0, 31, 255],
      [0, 255, 61],
      [0, 71, 255],
      [255, 0, 204],
      [0, 255, 194],
      [0, 255, 82],
      [0, 10, 255],
      [0, 112, 255],
      [51, 0, 255],
      [0, 194, 255],
      [0, 122, 255],
      [0, 255, 163],
      [255, 153, 0],
      [0, 255, 10],
      [255, 112, 0],
      [143, 255, 0],
      [82, 0, 255],
      [163, 255, 0],
      [255, 235, 0],
      [8, 184, 170],
      [133, 0, 255],
      [0, 255, 92],
      [184, 0, 255],
      [255, 0, 31],
      [0, 184, 255],
      [0, 214, 255],
      [255, 0, 112],
      [92, 255, 0],
      [0, 224, 255],
      [112, 224, 255],
      [70, 184, 160],
      [163, 0, 255],
      [153, 0, 255],
      [71, 255, 0],
      [255, 0, 163],
      [255, 204, 0],
      [255, 0, 143],
      [0, 255, 235],
      [133, 255, 0],
      [255, 0, 235],
      [245, 0, 255],
      [255, 0, 122],
      [255, 245, 0],
      [10, 190, 212],
      [214, 255, 0],
      [0, 204, 255],
      [20, 0, 255],
      [255, 255, 0],
      [0, 153, 255],
      [0, 41, 255],
      [0, 255, 204],
      [41, 0, 255],
      [41, 255, 0],
      [173, 0, 255],
      [0, 245, 255],
      [71, 0, 255],
      [122, 0, 255],
      [0, 255, 184],
      [0, 92, 255],
      [184, 255, 0],
      [0, 133, 255],
      [255, 214, 0],
      [25, 194, 194],
      [102, 255, 0],
      [92, 0, 255],
  ])

然后,您可以将图像与预测的分割图进行组合并绘制:

>>> import matplotlib.pyplot as plt
>>> import numpy as np

>>> color_seg = np.zeros((pred_seg.shape[0], pred_seg.shape[1], 3), dtype=np.uint8)
>>> palette = np.array(ade_palette())
>>> for label, color in enumerate(palette):
...     color_seg[pred_seg == label, :] = color
>>> color_seg = color_seg[..., ::-1]  # convert to BGR

>>> img = np.array(image) * 0.5 + color_seg * 0.5  # plot the image with the segmentation map
>>> img = img.astype(np.uint8)

>>> plt.figure(figsize=(15, 10))
>>> plt.imshow(img)
>>> plt.show()
Image of bedroom overlaid with segmentation map
Update on GitHub