إعداد مجموعة بيانات مخصصة
رتّب الصور والتسميات بتنسيق Ultralytics YOLO، وحوّل مربعات البكسل إلى أسطر تسميات مُطبَّعة، واكتشف التسميات المعطوبة قبل التدريب.
لا يؤدي اسم مجلد خاطئ أو تسمية غير مُطبَّعة إلى إيقاف التدريب. يتجاوز Ultralytics الصور المعطوبة ويتابع العمل، فتدرّب على جزء من بياناتك وتحصل على نموذج متوسط الأداء، ثم تقضي فترة بعد الظهر في البحث عن السبب في المكان الخطأ.
يتناول هذا الدرس الجانب الإجرائي من مجموعة بيانات الكشف: المجلدات، وأسطر التسميات، والتحويل، والفحوصات. ستعمل على مجموعة بيانات الحبوب الطبية الفعلية، التي تضم 115 صورة ويستخدمها باقي هذه الدورة في التدريب. أما تحديد ما ينبغي جمعه وكيفية تسميته، فيتناوله درس أفضل ممارسات الوسم.
عملي
الحصول على مجموعة بيانات التدريب#

التنسيق الموضح أدناه هو أيضًا ما يقبله مربع حوار «مجموعة بيانات جديدة» في منصة Ultralytics كملف zip، لذا سيُرفع دون تغيير أي ملف لمجموعة بيانات تُعدّها هنا. نزّل 92 إطارًا للتدريب و23 إطارًا للتحقق من صحة صور الحبوب في عبوات الشرائط، وفك ضغطها في my_dataset:
curl -L -o medical-pills.zip https://github.com/ultralytics/assets/releases/download/v0.0.0/medical-pills.zip
unzip -q medical-pills.zip -d my_datasetmy_dataset/
├── images/
│ ├── train/ Frame_0.jpg, Frame_100.jpg, ... (92 images)
│ └── val/ Frame_144.jpg, ... (23 images)
└── labels/
├── train/ Frame_0.txt, Frame_100.txt, ... (92 files)
└── val/ Frame_144.txt, ... (23 files)لا يوجد ملف بيان. يعثر Ultralytics على كل تسمية باستبدال /images/ بـ /labels/ في مسار الصورة، واستبدال الامتداد بـ .txt، ولذلك يقرأ images/val/Frame_144.jpg الملف labels/val/Frame_144.txt. وينتج عن ذلك أمران:
- يجب تسمية المجلدين بالضبط
imagesوlabels، ووضعهما جنبًا إلى جنب. - الصورة التي يحتوي ملف تسميتها على محتوى فارغ أو التي لا تملك ملف تسمية هي صورة خلفية: وهذا صالح ومفيد لتعليم النموذج ما لا ينبغي اكتشافه، لكن فقط إذا كان ذلك مقصودًا.
قراءة سطر التسمية#

يمثل كل سطر عنصرًا واحدًا: فهرس فئة صحيح، ثم مركز مربع الإحاطة وحجمه على هيئة كسور من عرض الصورة وارتفاعها. سطر فعلي من labels/train/Frame_0.txt:
0 0.330729 0.816204 0.103125 0.1212960: فهرس الفئة 0، الذي سيُسمّيه ملف YAMLpill.0.330729 0.816204: يقع مركز المربع عند 33% من عرض الصورة و82% من ارتفاعها.0.103125 0.121296: يبلغ عرض المربع نحو 10% من عرض الصورة وارتفاعه نحو 12% من ارتفاعها.
مثال تطبيقي: تحويل مربع بكسل#
غالبًا ما تُصدّر أدوات الوسم إحداثيات الزوايا بالبكسل (x1, y1, x2, y2). اقسمها على أبعاد الصورة. يمتد الشخص الواقع في أقصى اليسار في صورة bus.jpg ذات الأبعاد 810×1080 من (49, 398) إلى (240, 902):
| الحقل | الصيغة | القيمة |
|---|---|---|
x_center | (x1 + x2) / 2 / العرض | (49 + 240) / 2 / 810 = 0.178395 |
y_center | (y1 + y2) / 2 / الارتفاع | (398 + 902) / 2 / 1080 = 0.601852 |
width | (x2 − x1) / العرض | 191 / 810 = 0.235802 |
height | (y2 − y1) / الارتفاع | 504 / 1080 = 0.466667 |
يمكن لمعظم أدوات الوسم، وأدوات مثل Roboflow، التصدير مباشرةً بتنسيق YOLO. بالنسبة إلى ملفات COCO JSON، اتبع دليل التدريب باستخدام COCO JSON؛ ويغطي مستودع JSON2YOLO التنسيقات الأخرى. أما ملفات Pascal VOC XML، فيمكن تطبيق الصيغة أعلاه بنص برمجي قصير:
import xml.etree.ElementTree as ET
from pathlib import Path
CLASSES = ["person", "bus"] # index = class id; must match names in data.yaml
def voc_to_yolo(xml_file: Path, out_dir: Path) -> None:
root = ET.parse(xml_file).getroot()
w, h = int(root.find("size/width").text), int(root.find("size/height").text)
lines = []
for obj in root.iter("object"):
name = obj.find("name").text
if name not in CLASSES:
continue
box = obj.find("bndbox")
x1, y1, x2, y2 = (float(box.find(k).text) for k in ("xmin", "ymin", "xmax", "ymax"))
xc, yc, bw, bh = (x1 + x2) / 2 / w, (y1 + y2) / 2 / h, (x2 - x1) / w, (y2 - y1) / h
lines.append(f"{CLASSES.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / f"{xml_file.stem}.txt").write_text("\n".join(lines) + "\n")
voc_to_yolo(Path("bus.xml"), Path("labels"))
print(Path("labels/bus.txt").read_text())عند تشغيله على bus.xml الذي يحتوي على مربعي الشخص والحافلة من الدرس السابق، يطبع المثال التطبيقي والحافلة:
0 0.178395 0.601852 0.235802 0.466667
1 0.495679 0.454167 0.991358 0.482407تحقّق قبل التدريب#
ارسم بعض ملفات التسميات فوق صورها. فئة مزاحة أو تبديل موضعي x وy أو مربعات حول الظلال أمور واضحة بالعين ولا تظهر في المقاييس:
from ultralytics.data.utils import visualize_image_annotations
visualize_image_annotations(
"my_dataset/images/train/Frame_0.jpg",
"my_dataset/labels/train/Frame_0.txt",
{0: "pill"},
)ثم دع Ultralytics يتحقق من كل ملف. يفحص التسميات عند قراءة مجموعة البيانات للمرة الأولى، ويقرر سطرًا بسطر ما إذا كانت كل صورة صالحة للاستخدام:
يعرض سطر الفحص النتيجة: 23 images, 0 backgrounds, 0 corrupt هو الشكل المتوقع لمجموعة بيانات سليمة. وأي نتيجة أخرى تشير إلى أحد الأخطاء أدناه.
تجنّب هذه الأخطاء#
العَرَض: ignoring corrupt image/label: non-normalized or out of bounds coordinates [634. 881. 198. 163.] وعدد غير صفري في corrupt. السبب: استخدام قيم البكسل بدلًا من الكسور. الحل: اقسم على عرض الصورة وارتفاعها. أي قيمة تسمية أكبر من 1 تعني أن الصندوق لم يُطبّع قط.
العَرَض: يفحص yolo val المسار 0 images, 23 backgrounds ويصدر تحذير No labels found، ثم يتوقف التدريب مع ValueError: train: No labels found — والسبب هنا أن اسم مجلد التسميات كان annotations. الحل: سمِّ المجلدين images وlabels تمامًا، مع تطابق أسماء الملفات.
العَرَض: Label class 1 exceeds dataset class count 1. Possible class labels are 0-0. السبب: رقّمت أداة التصدير الفئات ابتداءً من 1. الحل: تبدأ فئات YOLO من 0؛ اطرح 1 في برنامج التحويل النصي أو أصلح إعداد التصدير.
إذا ظهرت الصناديق مزاحة إلى الأسفل واليمين بمقدار نصف صندوق، فقد كتبت x1 y1 w h بدلًا من x_center y_center w h. يكشف رسم ملف واحد المشكلة خلال ثوانٍ، ولا يستطيع أي فحص للمسح اكتشافها.
لإجراء فحوصات منهجية على مجموعة بيانات كاملة، راجع مراقبة جودة مجموعة البيانات. تُعد المجموعات المضمنة مثل coco8 وcoco128 وafrican-wildlife أمثلة جيدة على التنسيقات التي يمكنك نسخها؛ ويسرد استعراض مجموعات البيانات جميع المجموعات. يضيف المدرّب زيادة البيانات إلى تسمياتك، لذا فإن إجراء تنظيف سريع للبيانات هنا يفيد في كل حقبة تدريب.
عرض الحل
for s in train val; do
echo "$s: $(ls my_dataset/images/$s | wc -l) images, $(ls my_dataset/labels/$s | wc -l) labels"
done
# train: 92 images, 92 labels
# val: 23 images, 23 labelsfrom pathlib import Path
for txt in Path("my_dataset/labels").rglob("*.txt"):
for line in txt.read_text().splitlines():
values = [float(v) for v in line.split()[1:]]
if any(v < 0 or v > 1 for v in values):
print(txt, line)عدم ظهور أي مخرجات من النص البرمجي يعني أن جميع الإحداثيات مُطبَّعة. وتساوي الأعداد في كل تقسيم يعني أن لكل صورة ملف تسمية؛ وسيؤكد سطر الفحص في الدرس التالي عدم وجود ملفات معطوبة.