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

تجزئة الصور

تُفصل نماذج تجزئة الصور المناطق التي تتوافق مع مناطق الاهتمام المختلفة في الصورة. تعمل هذه النماذج من خلال إسناد تسمية إلى كل بكسل. توجد عدة أنواع من التجزئة: التجزئة الدلالية، والتجزئة على مستوى الكائن، والتجزئة الشاملة.

في هذا الدليل، سنقوم بما يلي:

  1. اطّلع على أنواع التقطيع المختلفة.
  2. يوجد مثال شامل لتدريب نموذج على تقسيم الصور الدلالي من البداية إلى النهاية.

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

# uncomment to install the necessary libraries
!pip install -q datasets transformers evaluate accelerate trackio

ننصحك بتسجيل الدخول إلى حسابك على Hugging Face حتى تتمكن من رفع نموذجك ومشاركته مع المجتمع. عند الطلب، أدخل رمزك (token) لتسجيل الدخول:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

أنواع التقطيع

تُخصّص التقسيم الدلالي (Semantic segmentation) تسميةً أو فئةً لكل بكسل في الصورة. لنلقِ نظرةً على مخرجات نموذج التقسيم الدلالي. سيُخصّص النموذج الفئةَ نفسها لكل حالة من حالات الكائن الذي يظهر في الصورة، فمثلاً، ستُسمّى جميع القطط بـ "قطة" بدلاً من "قطة-1" أو "قطة-2". يمكننا استخدام خط أنابيب تقسيم الصور في مكتبة transformers للاستدلال السريع على نموذج التقسيم الدلالي. لنلقِ نظرةً على الصورة الموضّحية.

from transformers import pipeline
from PIL import Image
import requests

url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/segmentation_input.jpg"
image = Image.open(requests.get(url, stream=True).raw)
image
Segmentation Input

سنستخدم nvidia/segformer-b1-finetuned-cityscapes-1024-1024.

semantic_segmentation = pipeline("image-segmentation", "nvidia/segformer-b1-finetuned-cityscapes-1024-1024")
results = semantic_segmentation(image)
results

يتضمن مخرج خط أنابيب التقسيم قناعًا لكل فئة متوقعة.

[{'score': None,
  'label': 'road',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'sidewalk',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'building',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'wall',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'pole',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'traffic sign',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'vegetation',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'terrain',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'sky',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': None,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

عند النظر إلى القناع الخاص بفئة السيارات، يمكننا ملاحظة أن كل سيارة مصنّفة بنفس القناع.

results[-1]["mask"]
Semantic Segmentation Output

في تجزئة الكائنات، لا يكون الهدف تصنيف كل بكسل، بل التنبؤ بقناع لـكل كائن من الكائنات في صورة معينة. يعمل هذا الأسلوب بشكل مشابه جداً لكشف الكائنات، حيث يوجد مربع محدد لكل كائن، لكن هنا يوجد قناع تجزئة بدلاً من ذلك. سنستخدم facebook/mask2former-swin-large-cityscapes-instance لهذا الغرض.

instance_segmentation = pipeline("image-segmentation", "facebook/mask2former-swin-large-cityscapes-instance")
results = instance_segmentation(image)
results

كما ترون أدناه، توجد عدة سيارات مصنّفة، ولا يوجد تصنيف للبيكسلات الأخرى غير البيكسلات التي تنتمي إلى أمثلة السيارات والأشخاص.

[{'score': 0.999944,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999945,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999652,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.903529,
  'label': 'person',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

نستعرض إحدى أقنعة السيارات أدناه.

results[2]["mask"]
Semantic Segmentation Output

تجمع التقسيم البانورامي (Panoptic segmentation) بين التقسيم الدلالي (semantic segmentation) والتقسيم القائم على الكيانات (instance segmentation)، حيث يُصنَّف كل بكسل إلى فئة وكيان من تلك الفئة، وتوجد أقنعة متعددة لكل كيان من فئة معينة. يمكننا استخدام facebook/mask2former-swin-large-cityscapes-panoptic لهذا الغرض.

panoptic_segmentation = pipeline("image-segmentation", "facebook/mask2former-swin-large-cityscapes-panoptic")
results = panoptic_segmentation(image)
results

كما ترون أدناه، لدينا المزيد من الفئات. سنوضح لاحقًا أن كل بكسل يُصنَّف ضمن إحدى هذه الفئات.

[{'score': 0.999981,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999958,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.99997,
  'label': 'vegetation',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999575,
  'label': 'pole',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999958,
  'label': 'building',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999634,
  'label': 'road',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.996092,
  'label': 'sidewalk',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.999221,
  'label': 'car',
  'mask': <PIL.Image.Image image mode=L size=612x415>},
 {'score': 0.99987,
  'label': 'sky',
  'mask': <PIL.Image.Image image mode=L size=612x415>}]

لنقم بمقارنة جنبًا إلى جنب لجميع أنواع التقطيع.

Segmentation Maps Compared

بعد الاطلاع على جميع أنواع التقطيع، دعونا نتعمق في ضبط نموذج للتقطيع الدلالي.

تشمل التطبيقات الواقعية الشائعة للتجزئة الدلالية تدريب السيارات ذاتية القيادة على تحديد المشاة والمعلومات المرورية المهمة، وتحديد الخلايا والشذوذ في الصور الطبية، ومراقبة التغيرات البيئية من خلال صور الأقمار الصناعية.

ضبط نموذج للتجزئة

سنقوم الآن بما يلي:

  1. قم بضبط SegFormer على مجموعة بيانات SceneParse150.
  2. استخدم النموذج المُحسَّن لديك للاستدلال.

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

تحميل مجموعة بيانات SceneParse150

ابدأ بتحميل مجموعة فرعية أصغر من مجموعة بيانات SceneParse150 من مكتبة 🤗 Datasets. سيمنحك هذا فرصة للتجربة والتأكد من عمل كل شيء بشكل صحيح قبل قضاء مزيد من الوقت في التدريب على المجموعة الكاملة.

>>> from datasets import load_dataset

>>> ds = load_dataset("merve/scene_parse_150", split="train[:50]")

قسّم مجموعة train من مجموعة البيانات إلى مجموعة تدريب ومجموعة اختبار باستخدام طريقة train_test_split:

>>> ds = ds.train_test_split(test_size=0.2)
>>> train_ds = ds["train"]
>>> test_ds = ds["test"]

ثم اطّلع على مثال:

>>> train_ds[0]
{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=512x683 at 0x7F9B0C201F90>,
 'annotation': <PIL.PngImagePlugin.PngImageFile image mode=L size=512x683 at 0x7F9B0C201DD0>,
 'scene_category': 368}

# view the image
>>> train_ds[0]["image"]
  • image: صورة بصيغة PIL للمشهد.
  • annotation: صورة PIL لخريطة التقسيم، وهي أيضاً هدف النموذج.
  • scene_category: معرّف فئة يصف مشهد الصورة مثل “مطبخ” أو “مكتب”. في هذا الدليل، لن تحتاج إلا إلى image وannotation، وكلاهما من صور PIL.

ستحتاج أيضاً إلى إنشاء قاموس يربط معرّف التسمية (label id) بفئة التسمية (label class)، وهو ما سيكون مفيداً عند إعداد النموذج لاحقاً. حمّل خرائط التحويل من Hub وأنشئ قاموسي id2label وlabel2id:

>>> import json
>>> from pathlib import Path
>>> from huggingface_hub import hf_hub_download

>>> repo_id = "huggingface/label-files"
>>> filename = "ade20k-id2label.json"
>>> id2label = json.loads(Path(hf_hub_download(repo_id, filename, repo_type="dataset")).read_text())
>>> id2label = {int(k): v for k, v in id2label.items()}
>>> label2id = {v: k for k, v in id2label.items()}
>>> num_labels = len(id2label)

المعالجة المسبقة

الخطوة التالية هي تحميل معالج صور SegFormer لتحضير الصور والتعليقات التوضيحية للنموذج. بعض مجموعات البيانات، مثل هذه المجموعة، تستخدم الفهرس الصفر كصنف للخلفية. ومع ذلك، فإن صنف الخلفية غير مدرج فعليًا ضمن الأصناف الـ 150، لذا ستحتاج إلى تعيين do_reduce_labels=True لخصم واحد من جميع التسميات. يتم استبدال الفهرس الصفر بـ 255 حتى يتجاهله دالة الخسارة في SegFormer:

>>> from transformers import AutoImageProcessor

>>> checkpoint = "nvidia/mit-b0"
>>> image_processor = AutoImageProcessor.from_pretrained(checkpoint, do_reduce_labels=True)

من الشائع تطبيق بعض عمليات زيادة البيانات على مجموعة بيانات الصور لجعل النموذج أكثر مقاومة للمبالغة في التخصيص. في هذا الدليل، ستستخدم دالة ColorJitter من torchvision لتغيير خصائص اللون في الصورة بشكل عشوائي، لكن يمكنك أيضاً استخدام أي مكتبة صور تفضلها.

>>> from torchvision.transforms import ColorJitter

>>> jitter = ColorJitter(brightness=0.25, contrast=0.25, saturation=0.25, hue=0.1)

الآن أنشئ دالتَي معالجة مسبقة لتحضير الصور والتعليقات التوضيحية للنموذج. تحوّل هذه الدوال الصور إلى pixel_values والتعليقات التوضيحية إلى labels. فيما يتعلق بمجموعة التدريب، يُطبَّق jitter قبل تقديم الصور إلى معالج الصور. أما فيما يتعلق بمجموعة الاختبار، فيقوم معالج الصور بقص الصور images وتطبيعها، ويقوم بقص labels فقط لأن تعظيم البيانات لا يُطبَّق أثناء الاختبار.

>>> def train_transforms(example_batch):
...     images = [jitter(x) for x in example_batch["image"]]
...     labels = [x for x in example_batch["annotation"]]
...     inputs = image_processor(images, labels)
...     return inputs


>>> def val_transforms(example_batch):
...     images = [x for x in example_batch["image"]]
...     labels = [x for x in example_batch["annotation"]]
...     inputs = image_processor(images, labels)
...     return inputs

لتطبيق jitter على كامل مجموعة البيانات، استخدم دالة set_transform في مكتبة 🤗 Datasets. يتم تطبيق التحويل أثناء التشغيل، مما يجعل العملية أسرع ويستهلك مساحة أقل على القرص:

>>> train_ds.set_transform(train_transforms)
>>> test_ds.set_transform(val_transforms)

تقييم

غالباً ما يكون تضمين مقياس أثناء التدريب مفيداً لتقييم أداء نموذجك. يمكنك تحميل طريقة تقييم بسرعة باستخدام مكتبة 🤗 Evaluate. لهذه المهمة، حمّل مقياس mean Intersection over Union (IoU) (راجع quick tour الخاص بـ 🤗 Evaluate لمعرفة المزيد حول كيفية تحميل مقياس وحسابه):

>>> import evaluate

>>> metric = evaluate.load("mean_iou")

ثم أنشئ دالةً لحساب المقاييس. يجب أولاً تحويل توقعاتك إلى logits، ثم إعادة تشكيلها لتطابق حجم التسميات قبل أن تتمكن من استدعاء compute:

>>> import numpy as np
>>> import torch
>>> from torch import nn

>>> def compute_metrics(eval_pred):
...     with torch.no_grad():
...         logits, labels = eval_pred
...         logits_tensor = torch.from_numpy(logits)
...         logits_tensor = nn.functional.interpolate(
...             logits_tensor,
...             size=labels.shape[-2:],
...             mode="bilinear",
...             align_corners=False,
...         ).argmax(dim=1)

...         pred_labels = logits_tensor.detach().cpu().numpy()
...         metrics = metric.compute(
...             predictions=pred_labels,
...             references=labels,
...             num_labels=num_labels,
...             ignore_index=255,
...             reduce_labels=False,
...         )
...         for key, value in metrics.items():
...             if isinstance(value, np.ndarray):
...                 metrics[key] = value.tolist()
...         return metrics

دالة compute_metrics الخاصة بك جاهزة الآن، وستعود إليها عند إعداد التدريب.

تدريب

إذا لم تكن على دراية بتدريب نموذج باستخدام Trainer، فاطّلع على الدرس الأساسي هنا!

أنت مستعد الآن لبدء تدريب نموذجك! حمّل SegFormer باستخدام AutoModelForSemanticSegmentation، وقم بتمرير النموذج إلى الترميز بين معرّفات التسميات وفئات التسميات:

>>> from transformers import AutoModelForSemanticSegmentation, TrainingArguments, Trainer

>>> model = AutoModelForSemanticSegmentation.from_pretrained(checkpoint, id2label=id2label, label2id=label2id)

في هذه المرحلة، تبقى ثلاث خطوات فقط:

  1. حدد معاملات التدريب الفائقة في TrainingArguments. من المهم ألا تحذف الأعمدة غير المستخدمة، لأن ذلك سيؤدي إلى إسقاط عمود image. بدون عمود image، لا يمكنك إنشاء pixel_values. اضبط remove_unused_columns=False لمنع هذا السلوك! المعامل الإلزامي الوحيد الآخر هو output_dir الذي يحدد مكان حفظ النموذج. ستقوم بدفع هذا النموذج إلى Hub عن طريق ضبط push_to_hub=True (يتعين عليك تسجيل الدخول إلى Hugging Face لرفع النموذج). في نهاية كل حقبة، سيقوم Trainer بتقييم مقياس IoU وحفظ نقطة الفحص للتدريب.
  2. مرّر حُجج التدريب إلى Trainer مع النموذج، ومجموعة البيانات، ومُجزّئ الرموز، ومُجمِّع البيانات، ودالة compute_metrics.
  3. استدعِ train() لضبط نموذجك.
>>> training_args = TrainingArguments(
...     output_dir="segformer-b0-scene-parse-150",
...     learning_rate=6e-5,
...     num_train_epochs=50,
...     per_device_train_batch_size=2,
...     per_device_eval_batch_size=2,
...     save_total_limit=3,
...     eval_strategy="steps",
...     save_strategy="steps",
...     save_steps=20,
...     eval_steps=20,
...     logging_steps=1,
...     report_to="trackio",
...     run_name="scene-parse-150",
...     eval_accumulation_steps=5,
...     remove_unused_columns=False,
...     push_to_hub=True,
... )

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=train_ds,
...     eval_dataset=test_ds,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()

بمجرد اكتمال التدريب، شارك نموذجك على Hub باستخدام طريقة push_to_hub() ليتمكن الجميع من استخدام نموذجك:

>>> trainer.push_to_hub()

الاستدلال

رائع، والآن بعد أن قمت بضبط نموذج، يمكنك استخدامه للاستدلال!

أعد تحميل مجموعة البيانات وتحميل صورة للاستدلال.

>>> from datasets import load_dataset

>>> ds = load_dataset("merve/scene_parse_150", split="train[:50]")
>>> ds = ds.train_test_split(test_size=0.2)
>>> test_ds = ds["test"]
>>> image = ds["test"][0]["image"]
>>> image
Image of bedroom

سنرى الآن كيفية الاستدلال دون استخدام خط أنابيب. عالج الصورة بمعالج الصور وضع pixel_values على وحدة معالجة رسومية:

>>> from accelerate import Accelerator
>>> device = Accelerator().device
>>> encoding = image_processor(image, return_tensors="pt")
>>> pixel_values = encoding.pixel_values.to(device)

مرّر مدخلاتك إلى النموذج وأعد logits:

>>> outputs = model(pixel_values=pixel_values)
>>> logits = outputs.logits.cpu()

بعد ذلك، أعد تحجيم القيم المنطقية (logits) إلى حجم الصورة الأصلي:

>>> upsampled_logits = nn.functional.interpolate(
...     logits,
...     size=image.size[::-1],
...     mode="bilinear",
...     align_corners=False,
... )

>>> pred_seg = upsampled_logits.argmax(dim=1)[0]

للتصور البصري للنتائج، حمّل لوحة ألوان مجموعة البيانات على شكل ade_palette() الذي يربط كل فئة بقيم RGB الخاصة بها.

def ade_palette():
  return np.asarray([
      [0, 0, 0],
      [120, 120, 120],
      [180, 120, 120],
      [6, 230, 230],
      [80, 50, 50],
      [4, 200, 3],
      [120, 120, 80],
      [140, 140, 140],
      [204, 5, 255],
      [230, 230, 230],
      [4, 250, 7],
      [224, 5, 255],
      [235, 255, 7],
      [150, 5, 61],
      [120, 120, 70],
      [8, 255, 51],
      [255, 6, 82],
      [143, 255, 140],
      [204, 255, 4],
      [255, 51, 7],
      [204, 70, 3],
      [0, 102, 200],
      [61, 230, 250],
      [255, 6, 51],
      [11, 102, 255],
      [255, 7, 71],
      [255, 9, 224],
      [9, 7, 230],
      [220, 220, 220],
      [255, 9, 92],
      [112, 9, 255],
      [8, 255, 214],
      [7, 255, 224],
      [255, 184, 6],
      [10, 255, 71],
      [255, 41, 10],
      [7, 255, 255],
      [224, 255, 8],
      [102, 8, 255],
      [255, 61, 6],
      [255, 194, 7],
      [255, 122, 8],
      [0, 255, 20],
      [255, 8, 41],
      [255, 5, 153],
      [6, 51, 255],
      [235, 12, 255],
      [160, 150, 20],
      [0, 163, 255],
      [140, 140, 140],
      [250, 10, 15],
      [20, 255, 0],
      [31, 255, 0],
      [255, 31, 0],
      [255, 224, 0],
      [153, 255, 0],
      [0, 0, 255],
      [255, 71, 0],
      [0, 235, 255],
      [0, 173, 255],
      [31, 0, 255],
      [11, 200, 200],
      [255, 82, 0],
      [0, 255, 245],
      [0, 61, 255],
      [0, 255, 112],
      [0, 255, 133],
      [255, 0, 0],
      [255, 163, 0],
      [255, 102, 0],
      [194, 255, 0],
      [0, 143, 255],
      [51, 255, 0],
      [0, 82, 255],
      [0, 255, 41],
      [0, 255, 173],
      [10, 0, 255],
      [173, 255, 0],
      [0, 255, 153],
      [255, 92, 0],
      [255, 0, 255],
      [255, 0, 245],
      [255, 0, 102],
      [255, 173, 0],
      [255, 0, 20],
      [255, 184, 184],
      [0, 31, 255],
      [0, 255, 61],
      [0, 71, 255],
      [255, 0, 204],
      [0, 255, 194],
      [0, 255, 82],
      [0, 10, 255],
      [0, 112, 255],
      [51, 0, 255],
      [0, 194, 255],
      [0, 122, 255],
      [0, 255, 163],
      [255, 153, 0],
      [0, 255, 10],
      [255, 112, 0],
      [143, 255, 0],
      [82, 0, 255],
      [163, 255, 0],
      [255, 235, 0],
      [8, 184, 170],
      [133, 0, 255],
      [0, 255, 92],
      [184, 0, 255],
      [255, 0, 31],
      [0, 184, 255],
      [0, 214, 255],
      [255, 0, 112],
      [92, 255, 0],
      [0, 224, 255],
      [112, 224, 255],
      [70, 184, 160],
      [163, 0, 255],
      [153, 0, 255],
      [71, 255, 0],
      [255, 0, 163],
      [255, 204, 0],
      [255, 0, 143],
      [0, 255, 235],
      [133, 255, 0],
      [255, 0, 235],
      [245, 0, 255],
      [255, 0, 122],
      [255, 245, 0],
      [10, 190, 212],
      [214, 255, 0],
      [0, 204, 255],
      [20, 0, 255],
      [255, 255, 0],
      [0, 153, 255],
      [0, 41, 255],
      [0, 255, 204],
      [41, 0, 255],
      [41, 255, 0],
      [173, 0, 255],
      [0, 245, 255],
      [71, 0, 255],
      [122, 0, 255],
      [0, 255, 184],
      [0, 92, 255],
      [184, 255, 0],
      [0, 133, 255],
      [255, 214, 0],
      [25, 194, 194],
      [102, 255, 0],
      [92, 0, 255],
  ])

ثم يمكنك دمج صورتك وخريطة التقسيم المتوقعة وعرضهما:

>>> import matplotlib.pyplot as plt
>>> import numpy as np

>>> color_seg = np.zeros((pred_seg.shape[0], pred_seg.shape[1], 3), dtype=np.uint8)
>>> palette = np.array(ade_palette())
>>> for label, color in enumerate(palette):
...     color_seg[pred_seg == label, :] = color
>>> color_seg = color_seg[..., ::-1]  # convert to BGR

>>> img = np.array(image) * 0.5 + color_seg * 0.5  # plot the image with the segmentation map
>>> img = img.astype(np.uint8)

>>> plt.figure(figsize=(15, 10))
>>> plt.imshow(img)
>>> plt.show()
Image of bedroom overlaid with segmentation map
Update on GitHub