جودة التعليقات التوضيحية
تعرّف على محتوى تسمية YOLO، وافحص ملفات التسميات لاكتشاف الصفوف التالفة، وحافظ على اتساق المعلّقين باستخدام إرشادات وجولة معايرة مقيسة.
يتعلم النموذج المدرّب على تسميات غير متسقة عدم الاتساق. خمسون ألف مربع مرسوم وفق قاعدة واضحة واحدة أفضل من نصف مليون مربع مرسوم وفق عشرين تفسيرًا شخصيًا، كما أن إعادة وضع التسميات أقل تكلفة دائمًا تقريبًا من استخدام نموذج أكبر. يزوّدك هذا الدرس بأداة لفحص التسميات، وإرشادات من صفحة واحدة، ومقياس لمدى اتفاق المعلّقين.
عملي
ما التسمية فعليًا؟#
في تنسيق YOLO، لكل صورة ملف مطابق من نوع .txt يتضمن صفًا واحدًا لكل عنصر. تُطبّع الإحداثيات إلى النطاق 0–1 نسبةً إلى عرض الصورة وارتفاعها، لذا تظل التسميات صالحة بعد تغيير الحجم. يعتمد تخطيط الصف على المهمة:
| المهمة | تسمية واحدة | ملاحظات |
|---|---|---|
| الكشف | class x_center y_center width height | مركز المربع وحجمه |
| تجزئة الكائنات | class x1 y1 x2 y2 ... xn yn | محيط المضلع |
| تقدير الوضعية | class x y w h px1 py1 v1 ... pxn pyn vn | مربع، ثم نقاط مفتاحية مع إمكانية تحديد مدى ظهورها |
| OBB | class x1 y1 x2 y2 x3 y3 x4 y4 | الزوايا الأربع للمربع المدور |
| التصنيف | لا يوجد ملف تسميات | صور مصنفة في مجلد واحد لكل فئة |
| التجزئة الدلالية | قناع PNG لكل صورة | قيمة كل بكسل هي فهرس فئة |
| تقدير العمق | ملف عمق PNG أو NPY لكل صورة | قيمة كل بكسل هي مسافة |
تبدأ فهارس الفئات من 0 وتُربط بأسماء في ملف YAML الخاص بمجموعة البيانات. الصورة التي لا تحتوي على عناصر لا تحتاج إلى ملف تسميات — فهذه هي طريقة عمل صور الخلفية. تتضمن صفحات مجموعات بيانات تنسيق الكشف والتجزئة الدلالية والعمق أمثلة عملية.
افحص ملفات التسميات#
قد تمر الصفوف التالفة من دون اكتشاف: إحداثي أكبر من 1، أو معرّف فئة غير معرّف في YAML، أو تحديد الكائن نفسه بمربعين؛ وكلها تتحول إلى ضوضاء يحاول النموذج تعلمها. تكشف أداة فحص قصيرة هذه المشكلات قبل التدريب:
from itertools import combinations
from pathlib import Path
from ultralytics.data.utils import check_det_dataset
def iou(a, b):
"""IoU of two normalized (x_center, y_center, width, height) boxes."""
ax1, ay1, ax2, ay2 = a[0] - a[2] / 2, a[1] - a[3] / 2, a[0] + a[2] / 2, a[1] + a[3] / 2
bx1, by1, bx2, by2 = b[0] - b[2] / 2, b[1] - b[3] / 2, b[0] + b[2] / 2, b[1] + b[3] / 2
inter = max(0, min(ax2, bx2) - max(ax1, bx1)) * max(0, min(ay2, by2) - max(ay1, by1))
return inter / (a[2] * a[3] + b[2] * b[3] - inter)
def check_labels(text, names):
"""Return a list of problems in one YOLO detection label file."""
problems, boxes = [], []
for i, row in enumerate(text.split("\n"), 1):
values = row.split()
if not values:
continue
if len(values) != 5:
problems.append(f"row {i}: {len(values)} values, expected 5")
elif int(values[0]) not in names:
problems.append(f"row {i}: unknown class {values[0]}")
elif not all(0 <= float(v) <= 1 for v in values[1:]):
problems.append(f"row {i}: coordinates outside 0-1")
else:
boxes.append((i, int(values[0]), [float(v) for v in values[1:]]))
for (i, ca, a), (j, cb, b) in combinations(boxes, 2):
if ca == cb and iou(a, b) > 0.8:
problems.append(f"row {j}: duplicate of row {i} (IoU {iou(a, b):.2f})")
return problems
broken = "0 0.50 0.50 0.20 0.30\n0 0.51 0.50 0.20 0.30\n3 0.40 0.40 0.10 0.10\n0 1.20 0.50 0.10 0.10\n0 0.50 0.50 0.10"
print("\n".join(check_labels(broken, {0: "pill"})))
data = check_det_dataset("medical-pills.yaml")
files = sorted(Path(data["path"], "labels").rglob("*.txt"))
found = sum(len(check_labels(f.read_text(), data["names"])) for f in files)
print(f"medical-pills: {found} problems in {len(files)} label files")row 3: unknown class 3
row 4: coordinates outside 0-1
row 5: 4 values, expected 5
row 2: duplicate of row 1 (IoU 0.90)
medical-pills: 0 problems in 115 label filesيعرض المثال المصنوع يدويًا كل خطأ تكتشفه أداة الفحص؛ أما مجموعة البيانات الحقيقية فتجتاز الفحص من دون مشكلات. شغّلها بعد كل دفعة من أعمال وضع التسميات وقبل كل تدريب — فالأمر يستغرق ثوانٍ.
لماذا تنحرف التسميات؟#

انظر إلى لوحة الفئات في هذا المحرر: حُفظت مضلعات الصناديق الكرتونية الأربعة عشر ضمن Grapes، بينما تقع فئة Box أسفلها في القائمة — كما توجد الفئتان plane وPlane منفصلتين. لا تستطيع أداة الفحص اكتشاف أي من المشكلتين، لأن كل صف صحيح، لكن مراجعًا يطابق التسميات مع الإرشادات سيلاحظ المشكلتين في ثوانٍ. معظم أخطاء التسميات من هذا النوع: ملفات صحيحة، وقرارات خاطئة.
نادرًا ما يضع المعلّقون تسميات رديئة بسبب الإهمال. بل يفعلون ذلك بصورة غير متسقة لأن القواعد ملتبسة، فيجتهد كل منهم بتخمين معقول لكنه مختلف:
- أين ينتهي المربع؟ هل يشمل الظل، أو حقيبة محمولة، أو الحمولة على شوكتي رافعة شوكية؟
- الاقتطاع. هل يُصنّف شخص يقع نصفه خارج الإطار على أنه
person؟ وما النسبة الظاهرة التي تبرر ذلك؟ - الحجب. هل يُصنّف ظهور عجلة واحدة فقط من سيارة، أم تُهمل السيارة؟
- حدود الفئات. هل تُصنّف شاحنة صغيرة على أنها
carأمtruck؟ وهل تُصنّف دراجة نارية صغيرة على أنهاbicycleأمother؟ - حالات لا ينبغي وضع تسميات لها. الانعكاسات والملصقات والشاشات التي تعرض العنصر.
كل خيار مقبول ما دام الجميع يختار الخيار نفسه.
إرشادات من صفحة واحدة#
ينبغي أن تكون الإرشادات في صفحة واحدة، وتتضمن صورة مثال لكل قاعدة، وتعرض الحالات الحدية أولًا:
## Class: forklift
Include:
- Forklifts with at least 30% of the body visible, moving or parked, any size or color.
Do not label:
- Pallet jacks (no overhead mast) → class `pallet_jack`.
- Reflections in glass or polished floors, and forklifts on posters or screens.
Box rules:
- Include the mast and the full visible body.
- Exclude the load (pallets, boxes) — those have their own classes.
- For partly occluded forklifts, box the visible part only.قِس الاتفاق في جولة معايرة#
قبل التوسع من معلّق واحد إلى عشرين، يضع الجميع تسميات للصور الخمسين نفسها ثم تقارن النتائج:
الاتفاق هو نسبة العناصر التي رسمها المعلّقان معًا بالفئة نفسها وبـ IoU لا يقل عن 0.5 — وهو المقياس العملي لـموثوقية الاتفاق بين المقيّمين:
def iou(a, b):
"""IoU of two (x1, y1, x2, y2) pixel boxes."""
w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
inter = w * h
return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter)
def agreement(boxes_a, boxes_b, threshold=0.5):
"""Share of objects both annotators drew with the same class and IoU >= threshold."""
unmatched, matched = list(boxes_b), 0
for cls_a, box_a in boxes_a:
best = max(unmatched, key=lambda b: iou(box_a, b[1]) if b[0] == cls_a else -1, default=None)
if best and best[0] == cls_a and iou(box_a, best[1]) >= threshold:
unmatched.remove(best)
matched += 1
return matched / max(len(boxes_a), len(boxes_b))
# Two annotators label the same calibration image
alice = [("forklift", (100, 200, 400, 600)), ("person", (450, 220, 520, 480)), ("person", (600, 230, 660, 470))]
bob = [("forklift", (100, 120, 400, 600)), ("person", (452, 218, 522, 478)), ("pallet_jack", (600, 230, 660, 470))]
print(f"agreement: {agreement(alice, bob):.0%}")agreement: 67%يخفي هذا الرقم، 67%، نوعين من الاختلاف. يبدأ مربع الرافعة الشوكية الذي رسمه Bob أعلى بـ80 بكسل — إذ أدرج طرف السارية — لكنه يطابق مع ذلك عند IoU بقيمة 0.83، لذا يُحتسب اتفاقًا؛ شدّد قاعدة تحديد المربع إذا كان منتجك يقيس الارتفاع. العنصر الثالث يمثل تعارضًا حقيقيًا: تقول Alice إنه person، بينما يقول Bob إنه pallet_jack. يستلزم ذلك قاعدة أوضح وصورة مثال، ثم جولة أخرى. احتفظ بصور المعايرة الخمسين: ستصبح اختبار التهيئة لكل معلّق جديد.
المراجع الذي يصحح التسميات بهدوء يزيل حلقة التغذية الراجعة، ولا يتعلم العاملون على التعليقات التوضيحية أبدًا ما أخطؤوا فيه. ارفض التسميات مع ذكر السبب ودع العامل المسؤول عن التعليقات التوضيحية يصلحها. سيكون العمل أبطأ في أول مئة صورة، لكنه أسرع بكثير بعدها.
ما الذي ينبغي تتبعه؟#
| المقياس | ما الذي يوضحه؟ |
|---|---|
| الإنتاجية (عدد العناصر في الساعة) | القدرة والتكلفة |
| معدل الرفض | ما إذا كانت الجودة تتراجع |
| الاتفاق على مجموعة تداخل مشتركة | ما إذا كان المعلّقون لا يزالون يطبقون القواعد نفسها |
عندما ينخفض الاتفاق، فهذا يعني أن شيئًا ما قد تغير: معلّق جديد، أو كاميرا جديدة، أو دفعة ملتبسة، أو إرهاق. وعندما يخفق النموذج لاحقًا في حالة محددة، أعد إرسال الصور نفسها لوضع التسميات — وهي الحلقة التي تؤتمتها عملية التعلم النشط، والتي يأتي منها معظم التحسن الفعلي في الدقة الواقعية. استعرض حالات الإخفاق وصفِّها في مجموعات البيانات على Ultralytics Platform، وراجع ضبط جودة مجموعات البيانات وأفضل ممارسات التعليقات التوضيحية لمعرفة كيفية تطبيق ذلك على نطاق واسع.
عرض الحل
إرشادات لفئة pill الخاصة بـ medical-pills، كُتبت بعد جولة معايرة على 10 صور:
## Class: pill
Include:
- Every tablet inside a blister cavity, including half-visible ones at the image edge.
- Tablets seen through clear plastic, even when glare covers part of them.
Do not label:
- Empty cavities, torn foil, or tablets outside the pack.
- Reflections of tablets in the foil.
Box rules:
- Box the tablet itself, not the plastic bubble around it.
- If two tablets touch, draw two boxes.كيفية قراءة درجة الاتفاق: لنفترض أن نتيجة الجولة الأولى 82%. صنّف العناصر غير المتطابقة حسب السبب. في عبوات الفقاعات، يتمثل السببان المعتادان في رسم المربعات حول الفقاعة البلاستيكية بدلًا من القرص (يُعالج ذلك بقاعدة المربع الأولى وإضافة صورة مثال) وتجاوز الأقراص التي يحجبها الوهج من قِبل أحد المعلّقين (يُعالج ذلك بقاعدة التضمين الثانية). أعد الاختبار على 10 صور جديدة، ولا تتوسع إلا بعد تجاوز النتيجة حد 90%.