Transformers documentation

인스턴스 분할

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

인스턴스 분할

Instance segmentation은 이미지 내의 객체를 감지하고 각 객체를 픽셀 수준에서 분할하는 컴퓨터 비전 작업입니다. 객체 감지(바운딩 박스를 출력)와 달리, instance segmentation은 감지된 각 객체에 대해 정밀한 마스크를 생성하여 겹치는 경우에도 개별 인스턴스를 구별할 수 있게 합니다.

이 가이드에서는 다음 내용을 배웁니다:

  1. Hugging Face Hub에서 인스턴스 분할 데이터셋을 로드합니다.
  2. Transformers Trainer를 사용하여 트랜스포머 기반 인스턴스 분할 모델인 RF-DETR-Seg를 파인튜닝합니다.
  3. mean IoU로 모델을 평가합니다.
  4. 추론을 실행하고 예측을 시각화합니다.

건물 인스턴스 마스크로 주석이 달린 위성 이미지 약 9.6개체를 포함하는 satellite-building-segmentation 데이터셋을 사용합니다.

이 작업과 호환되는 모든 아키텍처와 체크포인트를 확인하려면 task-page를 참고하는 것을 권장합니다.

시작하기 전에 필요한 라이브러리를 모두 설치하세요:

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

모델을 커뮤니티와 공유하시도록 권장합니다. 학습이 완료되면 Hugging Face 계정에 로그인하여 Hub에 업로드하세요:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

데이터셋 로드

satellite-building-segmentation 데이터셋은 Hugging Face Datasets의 네이티브 형식입니다. 따라서 load_dataset로 직접 로드할 수 있습니다. 각 행에는 다음이 포함됩니다:

  • image: 위성 이미지 (PIL)
  • image_id: 이미지의 고유 식별자
  • width / height: 이미지 크기
  • objects: id, category, bbox, area, segmentation(다각형 좌표), iscrowd를 포함하는 인스턴스별 어노테이션 dict
>>> 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 필드를 포함합니다:

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

ground-truth 마스크가 포함된 예시를 시각화합니다:

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

모델과 이미지 프로세서 로드

AutoImageProcessor와 AutoModelForInstanceSegmentation를 사용하여 RF-DETR-Seg 모델을 로드합니다. 모델을 로드할 때 id2label과 label2id 매핑을 전달하여 단일 “building” 클래스에 대한 분류 헤드를 구성합니다. 사전 학습된 모델은 COCO(91개 클래스)로 학습되었으므로, ignore_mismatched_sizes=True를 설정하여 분류 헤드를 올바른 출력 수로 다시 초기화합니다.

이미지 프로세서가 모든 전처리 작업을 처리합니다. 가로세로 비율을 유지하면서 이미지를 리사이즈하고, ImageNet 통계값으로 정규화하며, 균일한 크기로 패딩합니다. 그리고 인스턴스 분할에 있어 매우 중요한 부분으로, 다각형 주석을 바이너리 마스크로 변환하고 해당 마스크를 리사이즈하며, 모델이 기대하는 [0, 1] 범위의 [cx, cy, w, h] 형식으로 바운딩 박스를 정규화합니다.

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

데이터 전처리

모델을 fine-tune하려면 모델이 기대하는 형식에 맞게 데이터를 전처리해야 합니다. RfDetrImageProcessor에 이미지를 return_segmentation_masks=True와 함께 COCO 형식의 어노테이션과 함께 전달하면 모든 복잡한 작업을 처리합니다:

  1. 다각형 분할을 바이너리 마스크로 래스터화합니다
  2. 이미지, 바운딩 박스, 마스크를 모델의 입력 크기로 리사이즈합니다
  3. ImageNet의 평균/표준편차로 픽셀 값을 정규화합니다
  4. 바운딩 박스를 [x, y, w, h]에서 정규화된 [cx, cy, w, h]로 변환합니다
  5. 이미지를 균일한 크기로 패딩하고 pixel_mask를 생성합니다

이 transform은 데이터셋의 objects 컬럼에서 이미지 프로세서가 기대하는 COCO 스타일의 annotation dict를 재구성합니다.

with_transform를 사용하여 전처리를 지연 실행(샘플이 로드될 때 즉시 처리)하면, 처리된 전체 데이터셋을 메모리에 저장할 필요가 없습니다.

>>> 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가 포함된 딕셔너리)가 포함되어 있습니다:

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

데이터 콜레이터

각 이미지의 객체 인스턴스 수가 다르므로, labels는 가변 길이의 사전이며 단일 텐서로 스택할 수 없습니다. pixel_values와 pixel_mask는 정상적으로 스택하되, labels는 이미지별 사전의 리스트로 유지하는 커스텀 collate 함수를 정의합니다.

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

평가 지표 정의

학습 중 분할 품질을 추적하려면, 각 평가 에포크마다 합집합 기반 평균 IoU를 계산합니다. 각 이미지에 대해:

  1. 각 쿼리의 마스크 로짓을 sigmoid > 0.5로 이진화하여 모든 예측된 인스턴스 마스크를 단일 이진 “buildings” 맵으로 병합합니다
  2. 모든 ground-truth instance mask를 하나의 이진 맵으로 병합
  3. 두 맵 간의 Intersection-over-Union을 계산합니다

RF-DETR-Seg의 query masks는 클래스 점수 임계값으로 게이트되지 않으므로, 해당 메트릭은 클래스 점수를 무시하고 query별 mask logits를 직접 합집합합니다. 매칭되지 않은 query는 거의 빈 mask를 생성하므로, 합집합에 노이즈를 유발하지 않습니다.

이미지별 지표로 “모델이 건물을 얼마나 잘 커버하는지”를 나타내며, 이는 전체 검증 세트에 대해 평균화됩니다.

인스턴스 분할 벤치마크(COCO 등)는 일반적으로 mask mAP를 보고합니다. mask mAP는 각 예측된 인스턴스 마스크를 다양한 IoU 임계값에 걸쳐 ground truth와 비교하여 점수를 매기므로, 개별 객체를 올바르게 분리하는 것을 보상합니다. 여기서 사용되는 union 기반 평균 IoU는 더 단순하고 빠른 대안 지표입니다. 인스턴스별 품질이 아닌 전체 픽셀 커버리지를 측정하므로, 학습 진행 상황을 추적하는 데 편리합니다. 표준적인 인스턴스 인식 평가가 필요할 경우, torchmetrics의 MeanAveragePrecision(iou_type="segm")를 사용하여 mask mAP를 계산하는 것이 좋습니다.

이 함수를 Trainer의 하위 클래스를 만드는 대신 compute_metrics 함수로 전달합니다. eval_do_concat_batches=False를 TrainingArguments에 설정하면, 표준 평가 패스에서 생성된 예측값과 라벨이 compute_metrics에 배치별 출력 목록으로 전달됩니다. 따라서 메트릭이 해당 예측값을 재사용하여 검증 세트에 대한 두 번째 포워드 패스가 필요하지 않습니다. 모델 출력 튜플에서 인덱스 3에는 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는 반환된 키에 자동으로 eval_ 접두사를 붙이므로, 이를 통해 아래 체크포인트 선택에 사용되는 eval_mean_iou 지표가 생성됩니다.

학습

데이터, 모델, 지표가 준비되면 학습을 설정합니다. TrainingArguments에 대한 몇 가지 중요한 참고 사항:

  • remove_unused_columns=False: transform가 실행되기 전에 컬럼이 제거되는 기본 동작을 방지하기 위해 필요합니다.
  • eval_do_concat_batches=False: 인스턴스 분할 라벨은 가변 길이의 dict이므로 배치 간에 연결할 수 없습니다. 이 설정은 예측 결과를 배치별로 그룹화하여 compute_metrics가 해당 라벨과 일치시킬 수 있도록 합니다.
  • metric_for_best_model="eval_mean_iou": 손실값이 아닌 분할 품질 기준으로 최적의 체크포인트를 선택합니다.
  • fp16=True: 혼합 정밀도 학습은 최신 GPU에서 학습 속도를 크게 높입니다.
>>> 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() 메서드를 호출하여 최종 모델도 Hub에 업로드하세요.

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

추론

이제 fine-tuning된 모델을 가지고 새로운 위성 이미지에서 추론을 수행할 수 있습니다. 워크플로우는 다음과 같습니다:

  1. 이미지 프로세서로 이미지를 전처리합니다
  2. 모델을 통해 forward pass를 실행합니다
  3. post_process_instance_segmentation로 후처리하여 픽셀 수준의 마스크를 얻습니다

포스트 프로세싱 단계는 원시 쿼리 출력(로짓 + 저해상도 마스크)을 전체 해상도의 인스턴스 분할 맵으로 변환하며, 점수 임계값 적용과 마스크 이진화를 수행합니다.

>>> 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). 각 감지된 건물에 무작위 색상을 겹쳐 표시합니다:

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