# インスタンスセグメンテーション

インスタンスセグメンテーションは、画像内のオブジェクトを検出し、各オブジェクトをピクセルレベルでセグメンテーションするコンピュータビジョンのタスクです。オブジェクト検出（バウンディングボックスを出力する）とは異なり、インスタンスセグメンテーションは検出された各オブジェクトに対して正確なマスクを生成するため、オブジェクトが重なっていても個々のインスタンスを区別できます。

このガイドでは、次のことを学びます。

 1. Hugging Face Hub からインスタンスセグメンテーションのデータセットを読み込みます。
 2. Transformersの[Trainer](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer)を使用して、Transformerベースのインスタンスセグメンテーションモデルである[RF-DETR-Seg](https://huggingface.co/Roboflow/rf-detr-seg-medium)をファインチューニングします。
 3. 平均IoUでモデルを評価します。
 4. 推論を実行し、予測結果を可視化します。

建物インスタンスマスクでアノテーションされた約9.6kの衛星画像を含む[satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation)データセットを使用します。

> [!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` を持つ、インスタンスごとのアノテーションの dict

```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`辞書が含まれます。各インスタンスには`[x, y, width, height]`形式の`bbox`と、ポリゴンの座標を持つ`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/main/ja/model_doc/auto#transformers.AutoImageProcessor) と [AutoModelForInstanceSegmentation](/docs/transformers/main/ja/model_doc/auto#transformers.AutoModelForInstanceSegmentation) を使用して、RF-DETR-Seg モデルを読み込みます。モデルの読み込み時に、`id2label` と `label2id` のマッピングを渡すことで、単一の "building" クラス用の分類ヘッドを設定します。事前学習済みモデルは COCO（91 クラス）で学習されているため、`ignore_mismatched_sizes=True` を設定し、出力数を正しくした状態で分類ヘッドを再初期化します。

画像プロセッサはすべての前処理を担当します。アスペクト比を維持したまま画像をリサイズし、ImageNet の統計量で正規化し、統一サイズにパディングします。また、インスタンスセグメンテーションにとって極めて重要ですが、ポリゴンのアノテーションをバイナリマスクに変換し、そのマスクをリサイズし、モデルが期待する `[0, 1]` 範囲の `[cx, cy, w, h]` 形式にバウンディングボックスを正規化します。

```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` を作成します

この transform は、データセットの `objects` カラムから、画像プロセッサが期待する COCO 形式のアノテーション辞書を再構築します。

[with_transform](https://huggingface.co/docs/datasets/main/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])

```

## データコラテーター

各画像のオブジェクトインスタンス数が異なるため、ラベルは可変長の辞書となり、単一のテンソルにスタックできません。`pixel_values` と `pixel_mask` は通常どおりスタックし、`labels` は画像ごとの辞書のリストとして保持する、カスタムの collate 関数を定義します。

```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
```

## 評価指標を定義する

学習中のセグメンテーション品質を追跡するために、各評価エポックで*unionベースの平均IoU*を計算します。各画像について:

1. 予測されたすべてのインスタンスマスクを、単一の二値「buildings」マップにマージします（各クエリのマスクロジスティックを`sigmoid > 0.5`で二値化します）
2. すべての正解インスタンスマスクを単一のバイナリマップにマージします
3. 2つのマップ間のIntersection-over-Unionを計算します

RF-DETR-Segのクエリマスクはクラススコアの閾値でゲートされていないため、このメトリックはクラススコアを無視し、クエリごとのマスクロジックを直接結合します。一致しないクエリはほぼ空のマスクを生成するため、結合結果を汚染しません。

これにより、モデルが建物をどれだけカバーしているかを示す画像ごとの指標が得られ、これは検証セット全体で平均化されます。

> [!TIP]
> インスタンスセグメンテーションのベンチマーク（COCO など）では、マスクの平均平均精度（mAP）が報告されることが一般的です。これは、予測された各インスタンスのマスクを、IoU の閾値の範囲にわたって正解データと比較してスコアを付けるため、個々のオブジェクトを正しく分離することを評価します。ここで使用されている Union ベースの平均 IoU は、よりシンプルで高速な代替指標です。インスタンスごとの品質ではなく、全体のピクセルカバレッジを測定するため、トレーニング中の進捗を追跡する際に便利です。標準的なインスタンス認識評価を行う場合は、代わりにマスク mAP を計算してください。例えば、`torchmetrics` の [`MeanAveragePrecision(iou_type="segm")`](https://lightning.ai/docs/torchmetrics/stable/detection/mean_average_precision.html) を使用できます。

サブクラス化せず、[Trainer](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer) に `compute_metrics` 関数として渡します。`eval_do_concat_batches=False`（下の [TrainingArguments](/docs/transformers/main/ja/main_classes/trainer#transformers.TrainingArguments) で設定）の場合、標準的な評価パスで生成される予測とラベルは、バッチごとの出力のリストとして `compute_metrics` に渡されるため、メトリクスがこれらの予測を再利用でき、検証セットに対する2回目のフォワードパスは不要になります。モデル出力のタプルでは、インデックス `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/main/ja/main_classes/trainer#transformers.Trainer) は、返されたキーに自動的に `eval_` というプレフィックスを付与するため、これにより、後述のチェックポイント選択で使用される `eval_mean_iou` メトリクスが生成されます。

## トレーニング

データ、モデル、メトリクスが準備できたので、トレーニングを設定します。[TrainingArguments](/docs/transformers/main/ja/main_classes/trainer#transformers.TrainingArguments)に関する重要な点について、いくつか説明します。

- `remove_unused_columns=False`: 変換が実行される前にカラムが削除されるデフォルトの動作を避けるため、この設定が必要です。
- `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/main/ja/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)

