Transformers documentation

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

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

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, 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 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, 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à.

Để 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ụ.

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

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:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

Tải bộ dữ liệu

Bộ dữ liệu 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
>>> 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:

>>> 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ó:

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

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

Sử dụng AutoImageProcessor và 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.

>>> 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 để á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ớ.

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

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

>>> 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ạ cho 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ư rỗ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.

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") của torchmetrics.

Truyền hàm này vào 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 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:

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

  • 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.
>>> 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().

>>> 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ạ.

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

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

Update on GitHub