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.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

تجزئة الكائن

تُعدّ تقسيم الكيانات (Instance segmentation) مهمة في مجال رؤية الحاسوب تهدف إلى اكتشاف الكائنات في صورة وتقسيم كل كيان على مستوى البكسل. وعلى عكس اكتشاف الكائنات (الذي يُخرج صناديق محيطة)، يُنتج تقسيم الكيانات قناعًا دقيقًا لكل كائن مكتشف، مما يتيح لك التمييز بين الكيانات الفردية حتى عندما تتداخل.

في هذا الدليل، ستتعلم كيفية:

  1. حمّل مجموعة بيانات تقسيم الكائنات (instance segmentation) من Hugging Face Hub.
  2. قم بضبط RF-DETR-Seg، وهو نموذج لتجزئة الكائنات يعتمد على المحولات، باستخدام Trainer في مكتبة Transformers.
  3. قيّم نموذجك باستخدام متوسط IoU.
  4. شغّل الاستدلال (Inference) وبيّن التنبؤات.

سنستخدم مجموعة بيانات satellite-building-segmentation، التي تحتوي على حوالي 9.6k صورة قمرية مصحوبة بأقنعة كائنات المباني.

للاطلاع على جميع البنى المعمارية ونقاط التفتيش المتوافقة مع هذه المهمة، ننصح بالاطلاع على صفحة المهمة.

قبل أن تبدأ، ثبّت جميع المكتبات اللازمة:

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
>>> 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 يحتوي على تعليقات لكل حالة. تحتوي كل حالة على bbox بصيغة [x, y, width, height] وحقل 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

تصوير مثال مع أقنعة الحقيقة الأرضية الخاصة به:

>>> 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، وتعبئة الصور إلى حجم موحد، والأهم من ذلك لتجزئة الكائنات — تحويل التسميات متعددة الأضلاع إلى أقنعة ثنائية، وتغيير حجم تلك الأقنعة، وتطبيع صناديق الحدود إلى صيغة [cx, cy, w, h] ضمن نطاق [0, 1] الذي يتوقعه النموذج.

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

معالجة البيانات مسبقًا

لضبط النموذج، يجب عليك معالجة البيانات مسبقًا لتتوافق مع الصيغة التي يتوقعها النموذج. يقوم RfDetrImageProcessor بكل العمل الشاق عند تمرير الصور وتعليقات COCO بصيغة return_segmentation_masks=True إليه:

  1. يحوّل تجزئات المضلعات إلى أقنعة ثنائية
  2. يعيد تحجيم الصور وصناديق الحدود والأقنعة إلى حجم مدخلات النموذج
  3. يقوم بتطبيع قيم البكسلات باستخدام متوسط/انحراف معيار ImageNet
  4. يحوّل صناديق الحدود من [x, y, w, h] إلى [cx, cy, w, h] مُطبَّعة
  5. يُضيف حشواً للصور لجعلها بحجم موحّد ويُنشئ pixel_mask

يعيد التحويل بناء قواميس التسمية بنمط COCO التي يتوقعها معالج الصور من عمود objects في مجموعة البيانات.

استخدم 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])

منسّق البيانات

بما أن كل صورة تحتوي على عدد مختلف من حالات الكائنات، فإن التسميات هي قواميس بطول متغير ولا يمكن تجميعها في متجه واحد. عرّف دالة تجميع مخصصة تقوم بتجميع pixel_values وpixel_mask بشكل طبيعي، مع إبقاء labels كقائمة من القواميس الخاصة بكل صورة:

>>> 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)
  2. دمج جميع أقنعة الكائنات الصحيحة (ground-truth) في خريطة ثنائية واحدة
  3. احسب نسبة التداخل بين الخريطتين

لا تُقيَّد أقنعة الاستعلام في RF-DETR-Seg بعتبة حدّية لدرجات الفئات، لذا يتجاهل المقياس درجات الفئات ويجمع منطق أقنعة الاستعلام لكل استعلام مباشرةً. وتنتج الاستعلامات غير المطابقة أقنعة شبه فارغة، فلا تُلوِّث عملية الاتحاد.

يُوفّر هذا مقياسًا لكل صورة يقيس مدى تغطية النموذج للمباني، ويُحسب متوسطه على مجموعة التحقق الكاملة.

تُبلّغ معايير تقسيم الكائنات (مثل COCO) عادةً عن متوسط دقة المتوسط (mAP) للأقنعة، حيث تُقيّم كل قناع كائن متنبأ به مقابل الحقيقة الأرضية عبر نطاق من عتبات IoU، مما يكافئ الفصل الصحيح بين الكائنات الفردية. متوسط IoU القائم على الاتحاد المستخدم هنا هو بديل أبسط وأسرع: فهو يقيس التغطية الكلية للبيكسلات بدلاً من جودة كل كائن على حدة، مما يجعله مناسبًا لتتبع التقدم أثناء التدريب. للحصول على تقييم قياسي يأخذ الكائنات في الاعتبار، احسب mAP للأقنعة بدلاً من ذلك، على سبيل المثال باستخدام MeanAveragePrecision(iou_type="segm") من torchmetrics.

مرّر هذا إلى 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: مطلوب لأن السلوك الافتراضي يؤدي إلى حذف الأعمدة قبل تنفيذ التحويل الخاص بنا.
  • eval_do_concat_batches=False: تسميات تجزئة الكائنات (Instance segmentation) هي قواميس بطول متغير، ولا يمكن دمجها عبر الدفعات. كما يحافظ هذا على تجميع التنبؤات لكل دفعة على حدة، مما يتيح لـcompute_metrics مطابقتها مع تسمياتها.
  • metric_for_best_model="eval_mean_iou": اختيار أفضل نقطة تحقق بناءً على جودة التقسيم، وليس فقط على قيمة الخسارة.
  • fp16=True: يُسرّع التدريب بدقة مختلطة بشكل كبير عملية التدريب على وحدات معالجة الرسومات الحديثة.
>>> 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. وعند اكتمال التدريب، قم بدفع النموذج النهائي إلى Hub أيضاً عن طريق استدعاء طريقة push_to_hub().

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

الاستدلال

الآن، بما أنك تمتلك نموذجًا مُعدّلًا، استخدمه للاستدلال على صور أقمار صناعية جديدة. سير العمل هو:

  1. قم بمعالجة الصورة مسبقًا باستخدام معالج الصور
  2. نفّذ تمريرةً أمامية عبر النموذج
  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})")

قم بتصور التنبؤات. يخصص خرائط التقسيم لكل بكسل معرّفًا للتقسيم (-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