# 实例分割

实例分割是一种计算机视觉任务，旨在检测图像中的对象，并在像素级别对每个对象进行分割。与目标检测（输出边界框）不同，实例分割会为每个检测到的对象生成精确的掩码，即使对象相互重叠，也能区分出各个独立的实例。

在本指南中，您将学习如何：

 1. 从 Hugging Face Hub 加载一个实例分割数据集。
 2. 使用 Transformers 的 [Trainer](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.Trainer) 对基于 Transformer 的实例分割模型 [RF-DETR-Seg](https://huggingface.co/Roboflow/rf-detr-seg-medium) 进行微调。
 3. 使用平均 IoU 评估您的模型。
 4. 运行推理并可视化预测结果。

我们将使用 [satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation) 数据集，其中包含约 9.6k 张带有建筑物实例掩码标注的卫星图像。

> [!TIP]
> 要查看与此任务兼容的所有架构和检查点，我们建议查阅[任务页面](https://huggingface.co/tasks/image-segmentation)。

在开始之前，请安装所有必要的库：

```bash
pip install -Uq "transformers>=5.9" datasets torchvision
```

我们鼓励您与社区分享您的模型。训练完成后，请登录您的 Hugging Face 账户，将其上传到 Hub：

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## 加载数据集

[satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation) 数据集采用 Hugging Face Datasets 的原生格式，因此可以直接使用 `load_dataset` 加载。每一行包含：

- `image`：卫星图像（PIL）
- `image_id`：图像的唯一标识符
- `width` / `height`：图像尺寸
- `objects`：一个字典，包含每个实例的标注，包括 `id`、`category`、`bbox`、`area`、`segmentation`（多边形坐标）和 `iscrowd`

```py
>>> from datasets import load_dataset

>>> MODEL_ID = "Roboflow/rf-detr-seg-medium"
>>> DATASET_ID = "merve/satellite-building-segmentation"

>>> ds = load_dataset(DATASET_ID)
>>> train_ds = ds["train"]
>>> valid_ds = ds["validation"]
>>> print(f"Train: {len(train_ds)} images, Valid: {len(valid_ds)} images")
Train: 6764 images, Valid: 1934 images

```

检查单个示例。每条记录包含一个 `image`、一个 `image_id`，以及一个包含逐实例注释的 `objects` 字典。每个实例都有一个 `bbox`，格式为 `[x, y, width, height]`，以及一个包含多边形坐标的 `segmentation` 字段：

```py
>>> sample = train_ds[0]
>>> print(f"Image ID: {sample['image_id']}")
Image ID: 0
>>> print(f"Image size: {sample['image'].size}")
Image size: (512, 512)
>>> print(f"Number of instances: {len(sample['objects']['id'])}")
Number of instances: 7
>>> print(f"\nObjects keys: {list(sample['objects'].keys())}")
<BLANKLINE>
Objects keys: ['id', 'area', 'bbox', 'segmentation', 'category', 'iscrowd']
>>> print(f"First bbox: {sample['objects']['bbox'][0]}")
First bbox: [80.0, 0.0, 51.0, 51.5]
>>> print(f"First category: {sample['objects']['category'][0]}")
First category: 0

```

可视化一个示例及其真实掩码：

```py
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from PIL import Image, ImageDraw

>>> sample = train_ds[0]
>>> image = sample["image"].convert("RGB")

>>> fig, axes = plt.subplots(1, 2, figsize=(14, 6))
>>> _ = axes[0].imshow(image)
>>> _ = axes[0].set_title("Original image")
>>> _ = axes[0].axis("off")

>>> overlay = image.copy()
>>> draw = ImageDraw.Draw(overlay, "RGBA")
>>> objects = sample["objects"]
>>> for seg in objects["segmentation"]:
...     for poly in seg:
...         coords = list(zip(poly[0::2], poly[1::2]))
...         color = tuple(np.random.randint(50, 255, 3)) + (100,)
...         draw.polygon(coords, fill=color, outline="red")

>>> _ = axes[1].imshow(overlay)
>>> _ = axes[1].set_title(f"Ground truth ({len(objects['id'])} buildings)")
>>> _ = axes[1].axis("off")
>>> plt.tight_layout()
>>> plt.show()
```

![Dataset Sample](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/satellite-dataset.png)

## 加载模型和图像处理器

使用 [AutoImageProcessor](/docs/transformers/v5.23.0/zh/model_doc/auto#transformers.AutoImageProcessor) 和 [AutoModelForInstanceSegmentation](/docs/transformers/v5.23.0/zh/model_doc/auto#transformers.AutoModelForInstanceSegmentation) 加载 RF-DETR-Seg 模型。加载模型时，传入 `id2label` 和 `label2id` 映射，以针对单个 "building" 类别配置分类头。由于预训练模型是在 COCO（91 个类别）上训练的，请设置 `ignore_mismatched_sizes=True`，以便使用正确的输出数量重新初始化分类头。

图像处理器负责所有预处理工作：在保持宽高比的同时调整图像大小，使用 ImageNet 统计量进行归一化，填充至统一尺寸，并且——对于实例分割而言至关重要——将多边形标注转换为二值掩码，调整这些掩码的大小，并将边界框归一化为模型所期望的 `[cx, cy, w, h]` 格式，其取值范围为 `[0, 1]`。

```py
>>> from transformers import AutoImageProcessor, AutoModelForInstanceSegmentation

>>> id2label = {0: "building"}
>>> label2id = {"building": 0}

>>> image_processor = AutoImageProcessor.from_pretrained(MODEL_ID)
>>> model = AutoModelForInstanceSegmentation.from_pretrained(
...     MODEL_ID,
...     id2label=id2label,
...     label2id=label2id,
...     ignore_mismatched_sizes=True,
... )
```

## 预处理数据

要对模型进行微调，您必须对数据进行预处理，使其符合模型所期望的格式。当您传入图像和 COCO 格式的标注，并设置 `return_segmentation_masks=True` 时，`RfDetrImageProcessor` 会完成所有繁重的工作：

1. 将多边形分割栅格化为二值掩码
2. 将图像、边界框和掩码调整至模型的输入尺寸
3. 使用 ImageNet 的均值/标准差对像素值进行归一化
4. 将边界框从 `[x, y, w, h]` 转换为归一化的 `[cx, cy, w, h]`
5. 将图像填充为统一尺寸，并创建 `pixel_mask`

该转换函数会根据数据集的 `objects` 列，重建图像处理器所期望的 COCO 风格注释字典。

使用 [with_transform](https://huggingface.co/docs/datasets/v5.2.0/en/package_reference/main_classes#datasets.Dataset.with_transform) 可以延迟应用预处理（在加载样本时即时处理），从而避免将整个处理后的数据集存储在内存中。

```py
>>> from functools import partial
>>> from typing import Any

>>> def transform_batch(examples: dict[str, Any], image_processor) -> dict[str, Any]:
...     """Convert HF dataset rows into COCO-style dicts and pass to the processor."""
...     images, targets = [], []
...     for image, img_id, objects in zip(
...         examples["image"], examples["image_id"], examples["objects"]
...     ):
...         if not objects["id"]:
...             continue
...         annotations = [
...             {
...                 "id": ann_id,
...                 "image_id": img_id,
...                 "category_id": cat,
...                 "bbox": bbox,
...                 "area": area,
...                 "segmentation": seg,
...                 "iscrowd": crowd,
...             }
...             for ann_id, cat, bbox, area, seg, crowd in zip(
...                 objects["id"],
...                 objects["category"],
...                 objects["bbox"],
...                 objects["area"],
...                 objects["segmentation"],
...                 objects["iscrowd"],
...             )
...         ]
...         images.append(image.convert("RGB"))
...         targets.append({"image_id": img_id, "annotations": annotations})
...
...     if not images:
...         return {}
...     return image_processor(
...         images=images, annotations=targets, return_segmentation_masks=True, return_tensors="pt"
...     )

>>> transform = partial(transform_batch, image_processor=image_processor)
>>> train_ds = train_ds.shuffle(seed=42).with_transform(transform)
>>> valid_ds = valid_ds.with_transform(transform)
```

验证一个预处理后的示例。它包含 `pixel_values`（归一化的图像张量）、`pixel_mask`（指示真实像素与填充像素）以及 `labels`（一个字典，包含 `class_labels`、采用归一化中心格式的 `boxes` 以及二值 `masks`）：

```py
>>> example = train_ds[0]
>>> print(f"pixel_values shape: {example['pixel_values'].shape}")
pixel_values shape: torch.Size([3, 432, 432])
>>> print(f"pixel_mask shape: {example['pixel_mask'].shape}")
pixel_mask shape: torch.Size([432, 432])
>>> print(f"labels keys: {list(example['labels'].keys())}")
labels keys: ['size', 'image_id', 'class_labels', 'boxes', 'area', 'iscrowd', 'orig_size', 'masks']
>>> print(f"  class_labels: {example['labels']['class_labels']}")
  class_labels: tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0])
>>> print(f"  boxes shape: {example['labels']['boxes'].shape}")
  boxes shape: torch.Size([26, 4])
>>> print(f"  masks shape: {example['labels']['masks'].shape}")
  masks shape: torch.Size([26, 432, 432])

```

## 数据整理器

由于每张图片包含的对象实例数量不同，标签是变长字典，无法堆叠为单个张量。请定义一个自定义的 collate 函数，正常堆叠 `pixel_values` 和 `pixel_mask`，但将 `labels` 保留为按图片划分的字典列表：

```py
>>> import torch

>>> def collate_fn(batch: list[dict[str, Any]]) -> dict[str, Any]:
...     out = {
...         "pixel_values": torch.stack([x["pixel_values"] for x in batch]),
...         "labels": [x["labels"] for x in batch],
...     }
...     if "pixel_mask" in batch[0]:
...         out["pixel_mask"] = torch.stack([x["pixel_mask"] for x in batch])
...     return out
```

## 定义评估指标

为了在训练过程中跟踪分割质量，请在每个评估 epoch 计算基于并集的 *平均 IoU*。对于每张图像：

1. 将所有预测的实例掩码合并为一张二值化的“建筑物”地图（使用`sigmoid > 0.5`对每个查询的掩码 logits 进行二值化）
2. 将所有真实实例掩码合并为单个二值图
3. 计算两个地图之间的交并比

RF-DETR-Seg 的查询掩码不受类别分数阈值的门控，因此该指标会忽略类别分数，并直接对每个查询的掩码 logits 进行并集运算。未匹配的查询会产生近乎为空的掩码，因此不会污染并集结果。

这给出了一个逐图像的指标，衡量“模型对建筑物的覆盖程度”，并在整个验证集上取平均值。

> [!TIP]
> 实例分割基准（如 COCO）通常报告掩码平均精度均值（mAP），该指标在一系列 IoU 阈值范围内将每个预测的实例掩码与真实值进行比对，因此会奖励正确分离单个对象的行为。此处使用的基于并集的掩码平均 IoU 是一种更简单、更快的替代指标：它衡量的是整体像素覆盖率，而非单个实例的质量，这使得它在训练过程中跟踪进度时非常方便。要进行标准的、感知实例的评估，请改为计算掩码 mAP，例如使用 `torchmetrics` 的 [`MeanAveragePrecision(iou_type="segm")`](https://lightning.ai/docs/torchmetrics/stable/detection/mean_average_precision.html)。

将此函数作为 `compute_metrics` 传递给 [Trainer](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.Trainer)，而不是通过子类化 Trainer 来实现。当设置 `eval_do_concat_batches=False`（在下面的 [TrainingArguments](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.TrainingArguments) 中设置）时，标准评估流程生成的预测和标签会以逐批次输出列表的形式传递给 `compute_metrics`，因此该指标会复用这些预测，无需对验证集进行第二次前向传播。在模型输出元组中，索引 `3` 包含 `pred_masks`：

```py
>>> import torch.nn.functional as F

>>> @torch.no_grad()
... def compute_mean_iou(pred_masks, gt_masks, target_size):
...     """Union-based IoU: merge all instances per image, then compute IoU."""
...     pred_masks = F.interpolate(pred_masks[None], size=target_size, mode="bilinear", align_corners=False)[0]
...     pred_union = (pred_masks.sigmoid() > 0.5).any(dim=0)
...
...     gt_union = gt_masks.any(dim=0).bool()
...
...     intersection = (pred_union & gt_union).sum().float()
...     union = (pred_union | gt_union).sum().float()
...     return (intersection / union.clamp(min=1)).item()

>>> @torch.no_grad()
... def compute_metrics(evaluation_results):
...     predictions, targets = evaluation_results.predictions, evaluation_results.label_ids
...     ious = []
...     for pred_batch, target_batch in zip(predictions, targets):
...         batch_masks = pred_batch[3]
...         for i, gt_label in enumerate(target_batch):
...             gt_masks = torch.as_tensor(gt_label["masks"])
...             if gt_masks.numel() == 0:
...                 continue
...             target_size = gt_masks.shape[-2:]
...             pred_masks = torch.as_tensor(batch_masks[i])
...             ious.append(compute_mean_iou(pred_masks, gt_masks, target_size))
...
...     mean_iou = sum(ious) / len(ious) if ious else 0.0
...     return {"mean_iou": mean_iou}
```

[Trainer](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.Trainer) 会自动为返回的键添加 `eval_` 前缀，因此这会生成下方用于检查点选择的 `eval_mean_iou` 指标。

## 训练

在数据、模型和指标准备就绪后，配置训练。关于 [TrainingArguments](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.TrainingArguments) 的几点重要说明：

- `remove_unused_columns=False`：必须设置，因为默认行为会在我们的 transform 运行之前删除列。
- `eval_do_concat_batches=False`：实例分割标签是变长字典，无法跨批次进行拼接。同时，这也使预测结果按批次分组，以便 `compute_metrics` 将其与对应的标签进行匹配。
- `metric_for_best_model="eval_mean_iou"`：根据分割质量而非仅损失来选择最佳检查点。
- `fp16=True`：混合精度训练可显著加快现代 GPU 上的训练速度。

```py
>>> from transformers import Trainer, TrainingArguments

>>> training_args = TrainingArguments(
...     output_dir="rf-detr-seg-satellite-buildings",
...     num_train_epochs=10,
...     per_device_train_batch_size=16,
...     per_device_eval_batch_size=16,
...     learning_rate=1e-4,
...     weight_decay=1e-4,
...     lr_scheduler_type="cosine",
...     warmup_steps=0.1,
...     fp16=True,
...     dataloader_num_workers=4,
...     eval_strategy="epoch",
...     save_strategy="epoch",
...     save_total_limit=2,
...     load_best_model_at_end=True,
...     metric_for_best_model="eval_mean_iou",
...     greater_is_better=True,
...     remove_unused_columns=False,
...     eval_do_concat_batches=False,
...     push_to_hub=False,
... )

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=train_ds,
...     eval_dataset=valid_ds,
...     processing_class=image_processor,
...     data_collator=collate_fn,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()
```

如果在训练参数中设置 `push_to_hub=True`，训练检查点将被推送到 Hugging Face Hub。训练完成后，调用 [push_to_hub()](/docs/transformers/v5.23.0/zh/main_classes/trainer#transformers.Trainer.push_to_hub) 方法将最终模型也推送到 Hub。

```py
>>> trainer.push_to_hub(
...     dataset=DATASET_ID,
...     tags=["instance-segmentation", "rf-detr-seg", "vision", "satellite", "building"],
... )
```

## 推理

现在，您已拥有一个经过微调的模型，可以使用它对新的卫星图像进行推理。工作流程如下：

1. 使用图像处理器对图像进行预处理
2. 通过模型执行一次前向传播
3. 使用 `post_process_instance_segmentation` 对输出进行后处理，以获取像素级掩码

后处理步骤将原始查询输出（logits + 低分辨率掩码）转换为全分辨率实例分割图，并应用分数阈值处理和掩码二值化。

```py
>>> from datasets import load_dataset

>>> test_ds = load_dataset(DATASET_ID, split="test")
>>> sample = test_ds[0]
>>> image = sample["image"].convert("RGB")

>>> device = next(model.parameters()).device
>>> inputs = image_processor(images=image, return_tensors="pt").to(device)

>>> with torch.no_grad():
...     outputs = model(**inputs)

>>> results = image_processor.post_process_instance_segmentation(
...     outputs, threshold=0.5, target_sizes=[(image.height, image.width)]
... )[0]

>>> print(f"Detected {len(results['segments_info'])} buildings")
Detected 0 buildings
>>> for seg_info in results["segments_info"][:5]:
...     print(f"  Building (score: {seg_info['score']:.3f})")
```

可视化预测结果。分割图为每个像素分配一个分割 ID（背景为 -1）。为每个检测到的建筑物叠加一种随机颜色：

```py
>>> fig, axes = plt.subplots(1, 2, figsize=(14, 6))

>>> _ = axes[0].imshow(image)
>>> _ = axes[0].set_title("Input satellite image")
>>> _ = axes[0].axis("off")

>>> seg_map = results["segmentation"].cpu().numpy()
>>> overlay = np.array(image).copy()
>>> for seg_info in results["segments_info"]:
...     mask = seg_map == seg_info["id"]
...     color = np.random.randint(0, 255, 3)
...     overlay[mask] = (overlay[mask] * 0.4 + color * 0.6).astype(np.uint8)

>>> _ = axes[1].imshow(overlay)
>>> _ = axes[1].set_title(f"Predicted masks ({len(results['segments_info'])} buildings)")
>>> _ = axes[1].axis("off")
>>> plt.tight_layout()
>>> plt.show()
```

![Fine-tuning Result](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/finetuned-results.png)

