# Segmentação de instâncias

A segmentação de instâncias é a tarefa de visão computacional que consiste em detectar objetos em uma imagem e segmentar cada um deles no nível de pixel. Diferentemente da detecção de objetos (que produz caixas delimitadoras), a segmentação de instâncias gera uma máscara precisa para cada objeto detectado, permitindo distinguir instâncias individuais mesmo quando elas se sobrepõem.

Neste guia, você aprenderá como:

 1. Carregue um conjunto de dados de segmentação de instâncias do Hugging Face Hub.
 2. Ajuste finamente o [RF-DETR-Seg](https://huggingface.co/Roboflow/rf-detr-seg-medium), um modelo de segmentação de instâncias baseado em transformers, usando o [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) do Transformers.
 3. Avalie seu modelo com IoU médio.
 4. Execute a inferência e visualize as previsões.

Usaremos o conjunto de dados [satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation), que contém cerca de 9,6 mil imagens de satélite anotadas com máscaras de instâncias de edifícios.

> [!TIP]
> Para ver todas as arquiteturas e checkpoints compatíveis com esta tarefa, recomendamos consultar a [página da tarefa](https://huggingface.co/tasks/image-segmentation).

Antes de começar, instale todas as bibliotecas necessárias:

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

Incentivamos você a compartilhar seu modelo com a comunidade. Faça login na sua conta do Hugging Face para enviá-lo ao Hub quando o treinamento estiver concluído:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Carregue o conjunto de dados

O conjunto de dados [satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation) está no formato nativo do Hugging Face Datasets, portanto, carregue-o diretamente com `load_dataset`. Cada linha contém:

- `image`: a imagem de satélite (PIL)
- `image_id`: identificador único da imagem
- `width` / `height`: dimensões da imagem
- `objects`: um dicionário de anotações por instância com `id`, `category`, `bbox`, `area`, `segmentation` (coordenadas do polígono) e `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

```

Inspecione um exemplo individual. Cada registro possui uma `image`, um `image_id` e um dicionário `objects` contendo anotações por instância. Cada instância tem um `bbox` no formato `[x, y, width, height]` e um campo `segmentation` com coordenadas de polígono:

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

```

Visualize um exemplo com suas máscaras de referência:

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

## Carregue o modelo e o processador de imagem

Use [AutoImageProcessor](/docs/transformers/main/pt/model_doc/auto#transformers.AutoImageProcessor) e [AutoModelForInstanceSegmentation](/docs/transformers/main/pt/model_doc/auto#transformers.AutoModelForInstanceSegmentation) para carregar o modelo RF-DETR-Seg. Ao carregar o modelo, passe os mapeamentos `id2label` e `label2id` para configurar a cabeça de classificação para a única classe "building". Como o modelo pré-treinado foi treinado no COCO (91 classes), defina `ignore_mismatched_sizes=True` para reinitializar a cabeça de classificação com o número correto de saídas.

O processador de imagens trata todo o pré-processamento: redimensiona as imagens mantendo a proporção, normaliza com as estatísticas do ImageNet, aplica padding para um tamanho uniforme e — crucialmente para a segmentação de instâncias — converte anotações de polígonos em máscaras binárias, redimensiona essas máscaras e normaliza as caixas delimitadoras para o formato `[cx, cy, w, h]` no intervalo `[0, 1]` que o modelo espera.

```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,
... )
```

## Pré-processar os dados

Para ajustar o modelo, você deve pré-processar os dados para corresponder ao formato que o modelo espera. O `RfDetrImageProcessor` faz todo o trabalho pesado quando você passa imagens e anotações no formato COCO com `return_segmentation_masks=True`:

1. Rasteriza segmentações de polígonos em máscaras binárias
2. Redimensiona imagens, caixas delimitadoras e máscaras para o tamanho de entrada do modelo
3. Normaliza os valores de pixels com a média/desvio padrão do ImageNet
4. Converte caixas delimitadoras de `[x, y, w, h]` para `[cx, cy, w, h]` normalizadas
5. Preenche as imagens até um tamanho uniforme e cria uma `pixel_mask`

A transformação reconstrói os dicionários de anotação no estilo COCO que o processador de imagens espera da coluna `objects` do conjunto de dados.

Use [with_transform](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.with_transform) para aplicar o pré-processamento de forma preguiçosa (no momento em que as amostras são carregadas), o que evita armazenar o conjunto de dados processado inteiro na memória.

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

Verifique um exemplo pré-processado. Ele contém `pixel_values` (o tensor de imagem normalizado), `pixel_mask` (indicando pixels reais em relação ao preenchimento) e `labels` (um dicionário com `class_labels`, `boxes` em formato de centro normalizado e `masks` binárias):

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

```

## Colaborador de dados

Como cada imagem possui um número diferente de instâncias de objetos, os rótulos são dicionários de comprimento variável e não podem ser empilhados em um único tensor. Defina uma função de colagem personalizada que empilha `pixel_values` e `pixel_mask` normalmente, mas mantém `labels` como uma lista de dicionários por imagem:

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

## Definir métrica de avaliação

Para acompanhar a qualidade da segmentação durante o treinamento, calcule um *mean IoU baseado em união* em cada época de avaliação. Para cada imagem:

1. Mescle todas as máscaras de instâncias previstas em um único mapa binário de "buildings" (binarize os logits de máscara de cada consulta com `sigmoid > 0.5`)
2. Mesclar todas as máscaras de instâncias de referência em um único mapa binário
3. Calcule a Interseção sobre a União entre os dois mapas

As máscaras de consulta do RF-DETR-Seg não são filtradas por um limiar de pontuação de classe, portanto a métrica ignora as pontuações de classe e une diretamente as logits de máscara por consulta. Consultas não correspondidas produzem máscaras quase vazias, de modo que não contaminam a união.

Isso fornece uma métrica por imagem de "o quão bem o modelo cobre os prédios", que é média sobre o conjunto de validação completo.

> [!TIP]
> Benchmarks de segmentação de instâncias (como o COCO) geralmente relatam a precisão média média de máscaras (mAP), que pontua cada máscara de instância prevista em relação ao ground truth em uma faixa de limiares de IoU e, portanto, recompensa a separação correta de objetos individuais. A média de IoU baseada em união usada aqui é um substituto mais simples e rápido: ela mede a cobertura geral de pixels em vez da qualidade por instância, o que a torna conveniente para acompanhar o progresso durante o treinamento. Para uma avaliação padrão, consciente de instâncias, calcule o mAP de máscaras em vez disso, por exemplo, com [`MeanAveragePrecision(iou_type="segm")`](https://lightning.ai/docs/torchmetrics/stable/detection/mean_average_precision.html) do `torchmetrics`.

Passe isso para o [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) como uma função `compute_metrics` em vez de criar uma subclasse do trainer. Com `eval_do_concat_batches=False` (definido no [TrainingArguments](/docs/transformers/main/pt/main_classes/trainer#transformers.TrainingArguments) abaixo), as previsões e os rótulos gerados pela passagem de avaliação padrão são passados para `compute_metrics` como uma lista de saídas por lote, de modo que a métrica reutiliza essas previsões e não é necessária uma segunda passagem de forward no conjunto de validação. Na tupla de saída do modelo, o índice `3` contém `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}
```

O [Trainer](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer) adiciona automaticamente o prefixo `eval_` às chaves retornadas, de modo que isso gera a métrica `eval_mean_iou` usada abaixo para a seleção de checkpoints.

## Treinamento

Com os dados, o modelo e as métricas prontos, configure o treinamento. Algumas observações importantes sobre o [TrainingArguments](/docs/transformers/main/pt/main_classes/trainer#transformers.TrainingArguments):

- `remove_unused_columns=False`: Necessário porque o comportamento padrão removeria as colunas antes da execução do nosso transform.
- `eval_do_concat_batches=False`: Os rótulos de segmentação de instância são dicionários de comprimento variável e não podem ser concatenados entre lotes. Isso também mantém as previsões agrupadas por lote, permitindo que `compute_metrics` as corresponda aos seus rótulos.
- `metric_for_best_model="eval_mean_iou"`: Selecione o melhor checkpoint com base na qualidade da segmentação, e não apenas na perda.
- `fp16=True`: O treinamento com precisão mista acelera significativamente o treinamento em GPUs modernas.

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

Se você definir `push_to_hub=True` nos argumentos de treinamento, os checkpoints de treinamento serão enviados para o Hugging Face Hub. Ao concluir o treinamento, envie também o modelo final para o Hub chamando o método [push_to_hub()](/docs/transformers/main/pt/main_classes/trainer#transformers.Trainer.push_to_hub).

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

## Inferência

Agora que você tem um modelo ajustado, use-o para inferência em novas imagens de satélite. O fluxo de trabalho é:

1. Pré-processa a imagem com o processador de imagens
2. Execute uma passagem direta pelo modelo
3. Pós-processe as saídas com `post_process_instance_segmentation` para obter máscaras em nível de pixel

A etapa de pós-processamento converte as saídas brutas da consulta (logits + máscaras de baixa resolução) em mapas de segmentação de instâncias em resolução completa, aplicando o limite de pontuação e a binarização das máscaras.

```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})")
```

Visualize as previsões. O mapa de segmentação atribui a cada pixel um ID de segmento (-1 para o fundo). Sobreponha cada edifício detectado com uma cor aleatória:

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

