# Phân đoạn đối tượng

Phân đoạn instance là tác vụ thị giác máy tính nhằm phát hiện các đối tượng trong ảnh và phân đoạn từng đối tượng ở cấp độ pixel. Khác với phát hiện đối tượng (chỉ xuất ra các khung bao), phân đoạn instance tạo ra một mặt nạ chính xác cho mỗi đối tượng được phát hiện, cho phép bạn phân biệt các instance riêng lẻ ngay cả khi chúng chồng lấp lên nhau.

Trong hướng dẫn này, bạn sẽ học cách:

 1. Tải bộ dữ liệu phân đoạn thực thể từ Hugging Face Hub.
 2. Tinh chỉnh [RF-DETR-Seg](https://huggingface.co/Roboflow/rf-detr-seg-medium), một mô hình phân đoạn đối tượng dựa trên transformer, bằng cách sử dụng [Trainer](/docs/transformers/main/vi/main_classes/trainer#transformers.Trainer) của Transformers.
 3. Đánh giá mô hình của bạn bằng mean IoU.
 4. Chạy suy luận và trực quan hóa các dự đoán.

Chúng ta sẽ sử dụng bộ dữ liệu [satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation), bao gồm khoảng 9,6k hình ảnh vệ tinh được chú thích bằng các mặt nạ (mask) thể hiện các tòa nhà.

> [!TIP]
> Để xem tất cả các kiến trúc và checkpoint tương thích với tác vụ này, chúng tôi khuyến nghị bạn kiểm tra [trang tác vụ](https://huggingface.co/tasks/image-segmentation).

Trước khi bắt đầu, hãy cài đặt tất cả các thư viện cần thiết:

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

Chúng tôi khuyến khích bạn chia sẻ mô hình của mình với cộng đồng. Đăng nhập vào tài khoản Hugging Face của bạn để tải lên Hub khi quá trình huấn luyện hoàn tất:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Tải bộ dữ liệu

Bộ dữ liệu [satellite-building-segmentation](https://huggingface.co/datasets/merve/satellite-building-segmentation) ở định dạng Hugging Face Datasets gốc, vì vậy hãy tải trực tiếp bằng `load_dataset`. Mỗi dòng chứa:

- `image`: ảnh vệ tinh (PIL)
- `image_id`: định danh duy nhất cho hình ảnh
- `width` / `height`: kích thước ảnh
- `objects`: một dict chứa chú thích cho từng đối tượng, gồm `id`, `category`, `bbox`, `area`, `segmentation` (tọa độ đa giác) và `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

```

Kiểm tra một ví dụ đơn lẻ. Mỗi bản ghi có một `image`, một `image_id` và một dict `objects` chứa các chú thích cho từng instance. Mỗi instance có một `bbox` ở định dạng `[x, y, width, height]` và một trường `segmentation` chứa tọa độ đa giác:

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

```

Trực quan hóa một ví dụ cùng với các mặt nạ chuẩn (ground-truth masks) của nó:

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

## Tải mô hình và bộ xử lý ảnh

Sử dụng [AutoImageProcessor](/docs/transformers/main/vi/model_doc/auto#transformers.AutoImageProcessor) và [AutoModelForInstanceSegmentation](/docs/transformers/main/vi/model_doc/auto#transformers.AutoModelForInstanceSegmentation) để tải mô hình RF-DETR-Seg. Khi tải mô hình, truyền các ánh xạ `id2label` và `label2id` để cấu hình đầu phân loại cho lớp duy nhất "building". Vì mô hình được huấn luyện trước trên COCO (91 lớp), hãy đặt `ignore_mismatched_sizes=True` để khởi tạo lại đầu phân loại với số lượng đầu ra chính xác.

Bộ xử lý ảnh đảm nhận toàn bộ tiền xử lý: điều chỉnh kích thước ảnh trong khi giữ nguyên tỷ lệ khung hình, chuẩn hóa bằng thống kê ImageNet, đệm để đạt kích thước đồng nhất và — đặc biệt quan trọng đối với phân đoạn đối tượng — chuyển chú thích đa giác thành mặt nạ nhị phân, điều chỉnh kích thước các mặt nạ đó và chuẩn hóa các hộp bao quanh về định dạng `[cx, cy, w, h]` trong khoảng `[0, 1]` mà mô hình mong đợi.

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

## Tiền xử lý dữ liệu

Để tinh chỉnh mô hình, bạn phải tiền xử lý dữ liệu cho khớp với định dạng mà mô hình mong đợi. `RfDetrImageProcessor` đảm nhận toàn bộ công việc nặng nhọc khi bạn truyền vào ảnh và chú thích định dạng COCO với `return_segmentation_masks=True`:

1. Raster hóa các phân đoạn đa giác thành các mặt nạ nhị phân
2. Đổi kích thước ảnh, hộp bao quanh và mặt nạ thành kích thước đầu vào của mô hình
3. Chuẩn hóa giá trị pixel với mean/std của ImageNet
4. Chuyển đổi hộp bao từ `[x, y, w, h]` sang `[cx, cy, w, h]` đã chuẩn hóa
5. Đệm ảnh để có kích thước đồng nhất và tạo ra `pixel_mask`

Biến đổi này tái tạo các dict chú thích theo phong cách COCO mà bộ xử lý hình ảnh mong đợi từ cột `objects` của tập dữ liệu.

Sử dụng [with_transform](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.with_transform) để áp dụng tiền xử lý một cách lười (khi tải mẫu theo thời gian thực), giúp tránh việc lưu trữ toàn bộ bộ dữ liệu đã xử lý trong bộ nhớ.

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

Kiểm tra một ví dụ đã được tiền xử lý. Nó chứa `pixel_values` (tensor ảnh đã được chuẩn hóa), `pixel_mask` (chỉ ra các pixel thực so với phần đệm), và `labels` (một dict chứa `class_labels`, `boxes` ở định dạng tâm đã chuẩn hóa, và `masks` nhị phân):

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

```

## Trình tổng hợp dữ liệu

Vì mỗi hình ảnh có số lượng instance đối tượng khác nhau, nhãn là các từ điển có độ dài thay đổi và không thể được xếp chồng thành một tensor duy nhất. Hãy định nghĩa một hàm collate tùy chỉnh để xếp chồng `pixel_values` và `pixel_mask` bình thường, nhưng giữ `labels` dưới dạng danh sách các từ điển cho từng hình ảnh:

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

## Định nghĩa chỉ số đánh giá

Để theo dõi chất lượng phân đoạn trong quá trình huấn luyện, hãy tính *mean IoU dựa trên union* ở mỗi epoch đánh giá. Với mỗi ảnh:

1. Gộp tất cả các mặt nạ (mask) của các đối tượng dự đoán thành một bản đồ nhị phân "buildings" duy nhất (nhị phân hóa logits của mặt nạ cho mỗi truy vấn bằng `sigmoid > 0.5`)
2. Gộp tất cả các mặt nạ (mask) của các đối tượng thực tế (ground-truth) thành một bản đồ nhị phân duy nhất
3. Tính Intersection-over-Union giữa hai bản đồ

Mặt nạ truy vấn của RF-DETR-Seg không được kiểm soát bởi ngưỡng điểm lớp, do đó chỉ số này bỏ qua điểm lớp và trực tiếp lấy hợp của logit mặt nạ theo từng truy vấn. Các truy vấn không khớp tạo ra các mặt nạ gần như trống, nên chúng không làm nhiễu hợp.

Điều này cho ra một chỉ số theo từng hình ảnh về “mức độ bao phủ các tòa nhà của mô hình”, được trung bình hóa trên toàn bộ tập kiểm định.

> [!TIP]
> Các benchmark phân đoạn instance (chẳng hạn như COCO) thường báo cáo mask mean average precision (mAP), trong đó chấm điểm mỗi mask instance dự đoán so với ground truth trên một dải ngưỡng IoU, do đó thưởng cho việc tách biệt chính xác các đối tượng riêng lẻ. Chỉ số mean IoU dựa trên union được sử dụng ở đây là một proxy đơn giản và nhanh hơn: nó đo lường mức độ bao phủ pixel tổng thể thay vì chất lượng theo từng instance, điều này làm cho nó tiện lợi để theo dõi tiến độ trong quá trình huấn luyện. Để đánh giá chuẩn, nhận thức instance, hãy tính mask mAP thay thế, ví dụ bằng [`MeanAveragePrecision(iou_type="segm")`](https://lightning.ai/docs/torchmetrics/stable/detection/mean_average_precision.html) của `torchmetrics`.

Truyền hàm này vào [Trainer](/docs/transformers/main/vi/main_classes/trainer#transformers.Trainer) dưới dạng hàm `compute_metrics` thay vì kế thừa trainer. Với `eval_do_concat_batches=False` (được đặt trong [TrainingArguments](/docs/transformers/main/vi/main_classes/trainer#transformers.TrainingArguments) bên dưới), các dự đoán và nhãn do bước đánh giá tiêu chuẩn tạo ra sẽ được truyền vào `compute_metrics` dưới dạng danh sách các đầu ra theo từng lô, do đó chỉ số sẽ tái sử dụng các dự đoán đó và không cần một lần truyền tiến thứ hai trên tập kiểm tra. Trong bộ đầu ra của mô hình, chỉ số `3` chứa `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/vi/main_classes/trainer#transformers.Trainer) tự động thêm tiền tố `eval_` cho các khóa được trả về, do đó điều này tạo ra chỉ số `eval_mean_iou` được sử dụng bên dưới để chọn checkpoint.

## Huấn luyện

Khi dữ liệu, mô hình và các chỉ số đã sẵn sàng, hãy thiết lập huấn luyện. Một vài lưu ý quan trọng về [TrainingArguments](/docs/transformers/main/vi/main_classes/trainer#transformers.TrainingArguments):

- `remove_unused_columns=False`: Bắt buộc vì hành vi mặc định sẽ loại bỏ các cột trước khi transform của chúng ta chạy.
- `eval_do_concat_batches=False`: Nhãn phân đoạn instance là các dict có độ dài thay đổi, không thể nối chúng lại giữa các batch. Điều này cũng giữ cho các dự đoán được nhóm theo từng batch để `compute_metrics` có thể khớp chúng với nhãn tương ứng.
- `metric_for_best_model="eval_mean_iou"`: Chọn checkpoint tốt nhất dựa trên chất lượng phân đoạn, không chỉ dựa trên loss.
- `fp16=True`: Huấn luyện độ chính xác hỗn hợp giúp tăng tốc đáng kể quá trình huấn luyện trên các GPU hiện đại.

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

Nếu bạn đặt `push_to_hub=True` trong các đối số huấn luyện, các checkpoint huấn luyện sẽ được đẩy lên Hugging Face Hub. Khi hoàn tất huấn luyện, hãy đẩy mô hình cuối cùng lên Hub bằng cách gọi phương thức [push_to_hub()](/docs/transformers/main/vi/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"],
... )
```

## Suy luận

Giờ đây, khi đã có một mô hình được tinh chỉnh, hãy sử dụng nó để suy luận trên các hình ảnh vệ tinh mới. Quy trình làm việc là:

1. Tiền xử lý ảnh bằng trình xử lý ảnh
2. Chạy một bước truyền trực tiếp (forward pass) qua mô hình
3. Xử lý hậu kỳ đầu ra bằng `post_process_instance_segmentation` để lấy các mặt nạ ở cấp độ pixel

Bước xử lý hậu kỳ chuyển đổi các đầu ra truy vấn thô (logits + mặt nạ độ phân giải thấp) thành các bản đồ phân đoạn đối tượng độ phân giải đầy đủ, áp dụng ngưỡng điểm và nhị phân hóa mặt nạ.

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

Trực quan hóa các dự đoán. Bản đồ phân đoạn gán cho mỗi pixel một ID phân đoạn (-1 cho nền). Chồng mỗi tòa nhà được phát hiện lên với một màu ngẫu nhiên:

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

