انتقل إلى المحتوى الرئيسي

الوحدة 4 — المراسي وقمع غير الحدّ الأقصى والعتبات

كواشف الوحدة السابقة تُنتج عشرات الآلاف من الصناديق المرشّحة لكلّ صورة. لتحويل هذا الفيض إلى قائمة نظيفة قابلة للاستخدام في مشروع عدّ السيّارات، نحتاج إلى ثلاث آليات دقيقة: المراسي التي تُنظّم فضاء التنبّؤ، وNMS الذي يُزيل التكرارات، والعتبات التي تُوازن بين الدقّة والاسترجاع.

المراسي: قوالب صناديق مُعرَّفة سلفًا

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

طرح Faster R-CNN وYOLOv2 حلًّا: المراسي (anchors). في كلّ موضع من خريطة السمات، نُعرِّف مسبقًا عدّة صناديق مرجعية بأحجام ونسب مختلفة. مثلًا في مشروعنا، يمكن اختيار تسعة مراسي لكلّ نقطة: ثلاثة أحجام (صغير للمُشاة البعيد، متوسّط للسيّارة، كبير للحافلة) × ثلاث نسب عرض/ارتفاع (1:1، 1:2، 2:1).

الشبكة لا تتنبّأ بإحداثيات مطلقة، بل بـإزاحات نسبةً إلى كلّ مرساة. هذا يُبسّط التعلّم كثيرًا: الشبكة تتعلّم أن تقول «هذه السيّارة أكبر بقليل من مرساة الحجم المتوسّط وأزيح 3 بكسلات لليمين»، بدلًا من محاولة تعلّم موضع مطلق.

اختيار أحجام المراسي مهمّ عمليًا. YOLOv2 قدّم استخدام k-means على أحجام الصناديق الحقيقية في مجموعة التدريب لاختيار مراسي تُغطّي التوزيع فعلًا، بدلًا من مراسي عامّة قد لا تُلائم البيانات.

الكواشف بلا مراسي

المراسي تُدخل تعقيدًا حقيقيًا: يجب اختيار عددها وأحجامها لكلّ مجموعة بيانات، ويجب تعيين كلّ جسم مرجعي إلى المرساة «الأنسب» أثناء التدريب (تعيين إشكالي حين يكون جسم واحد قريبًا من عدّة مراسي). هذا ولّد جيلًا من الكواشف بلا مراسي.

FCOS وCenterNet وYOLOX ثم YOLOv8 كلّها تعمل بلا مراسي. الفكرة: كلّ نقطة من خريطة السمات تتنبّأ مباشرة بمسافات من مركز الجسم إلى حواف صندوقه الأربعة، دون قوالب مسبقة. البساطة أكبر، والأداء يُنافس أو يفوق النماذج بالمراسي في معظم المهامّ.

قمع غير الحدّ الأقصى: تنظيف التكرارات

بعد المرور الأمامي، الكاشف ينتج عشرات الصناديق المرشّحة لكلّ جسم فعلي: عدّة مراسي متجاورة تكشف السيّارة نفسها، مع اختلافات طفيفة في الإحداثيات والثقة. قمع غير الحدّ الأقصى (Non-Maximum Suppression، اختصارًا NMS) هو خوارزمية ما بعد المعالجة التي تختار من بين هذه التكرارات صندوقًا واحدًا لكلّ جسم.

الخوارزمية بسيطة بشكل مذهل:

  1. رتّب كلّ الصناديق تنازليًا حسب درجة الثقة
  2. خذ الصندوق الأعلى ثقةً، أضفه إلى القائمة النهائية
  3. احسب IoU بينه وبين جميع الصناديق المتبقّية
  4. أزل الصناديق التي IoU معه أكبر من عتبة (عادةً 0.45 أو 0.5)
  5. كرّر حتّى تفرغ القائمة

IoU (Intersection over Union) نسبة مساحة التقاطع إلى مساحة الاتّحاد بين صندوقين، تقيس مدى تداخلهما.

IoU=ABAB\text{IoU} = \frac{|A \cap B|}{|A \cup B|}

قيمة IoU بين 0 (لا تداخل) و1 (تطابق تامّ). سنُعمّق هذه الصيغة في الوحدة التالية.

تنفيذ NMS من الصفر

import numpy as np

def calculer_iou(box_a, box_b):
"""IoU بين صندوقين بصيغة [x1, y1, x2, y2]"""
x1 = max(box_a[0], box_b[0])
y1 = max(box_a[1], box_b[1])
x2 = min(box_a[2], box_b[2])
y2 = min(box_a[3], box_b[3])

intersection = max(0, x2 - x1) * max(0, y2 - y1)
area_a = (box_a[2] - box_a[0]) * (box_a[3] - box_a[1])
area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1])
union = area_a + area_b - intersection

return intersection / union if union > 0 else 0.0


def nms(boxes, scores, iou_threshold=0.45):
"""يعيد المؤشرات المحفوظة بعد NMS"""
indices = np.argsort(scores)[::-1] # تنازلي
kept = []

while len(indices) > 0:
current = indices[0]
kept.append(current)
rest = indices[1:]

# نبقي الصناديق التي IoU معها اقل من العتبة
keep_mask = np.array([
calculer_iou(boxes[current], boxes[i]) < iou_threshold
for i in rest
])
indices = rest[keep_mask]

return kept


# مثال على كاميرا التقاطع
boxes = np.array([
[100, 200, 200, 300], # سيارة 1
[105, 205, 205, 305], # نسخة قريبة من السيارة 1
[300, 200, 400, 300], # سيارة 2
[500, 100, 550, 200], # مشاة
])
scores = np.array([0.92, 0.88, 0.85, 0.71])

kept = nms(boxes, scores, iou_threshold=0.45)
print(f"المحفوظ بعد NMS: {kept}") # [0, 2, 3]

NMS لكلّ صنف على حدة

نقطة عمليّة أساسية: NMS يُطبَّق لكلّ صنف على حدة. مُشاة وسيّارة قد يتداخلان (مُشاة يعبر أمام سيّارة متوقّفة) دون أن يعني ذلك أنّهما نفس الجسم. تطبيق NMS دون تفريق يُزيل الصنف الأقلّ ثقةً ويُنتج نقصًا في العدّ.

أثر العتبتين على الدقّة والاسترجاع

عتبتان قابلتان للضبط، ولكلّ منهما دور مختلف:

عتبة الثقة (conf): تُحدّد أدنى ثقة نقبلها قبل NMS. رفعها يُقلّل الكشوف الخاطئة (يزيد الدقّة) لكنّه يُفقدنا كشوفًا حقيقية ذات ثقة منخفضة (يخفض الاسترجاع). المُشاة البعيد في زاوية الصورة ثقته منخفضة عادةً؛ عتبة عالية جدًّا تجعله يختفي.

عتبة IoU لـNMS (iou): تُحدّد متى نُعدّ صندوقين تكرارًا. عتبة منخفضة (مثلًا 0.3) عدوانية: تُزيل الكثير من الصناديق، مفيدة حين لا تكون هناك أجسام متداخلة فعلًا. عتبة عالية (مثلًا 0.65) متسامحة: تحفظ صناديق متقاربة، مفيدة حين تُتوقّع أجسام قريبة كسربات المُشاة عند الإشارة.

السيناريوconf مقترحةiou مقترحةالمبرّر
ساحة انتظار خفيفة0.50.45إعداد افتراضي متوازن
ساعة الذروة مكتظّة0.350.55نقبل ثقة أدنى، ونحفظ الصناديق المتقاربة
كشف طبّي دقيق0.70.3نتشدّد على الجودة، والتكرارات نادرة
مراقبة صور جوّية0.250.5الأجسام الصغيرة تحصل على ثقة منخفضة
عتبة NMS ليست عتبة الاعتبار الصحيح

شائع الخلط بين عتبة NMS (تُحدّد التكرار بين تنبّؤين للنموذج) وعتبة تقييم mAP (تُحدّد متى نعدّ التنبّؤ صحيحًا مقابل الصندوق الحقيقي). كلاهما IoU، لكنّهما مختلفان تمامًا في الوظيفة. رفع عتبة NMS لا يجعل النموذج «أدقّ» في نظر التقييم؛ فقد يُدهور mAP بإدخال تكرارات، ولا يُحسّنه بأيّ حال.

في الخلاصة

  • المراسي قوالب صناديق مُعرَّفة سلفًا تُبسّط تعلّم التنبّؤ؛ الكواشف الحديثة بلا مراسي (YOLOv8، FCOS) تُبسّط التصميم دون خسارة أداء.
  • NMS يُنظّف تكرارات التنبّؤ بترتيبها حسب الثقة ثم إزالة كلّ صندوق IoU مع الأعلى ثقةً يتجاوز العتبة.
  • يُطبَّق NMS لكلّ صنف على حدة، وإلّا اختفت الأجسام المتداخلة من أصناف مختلفة.
  • عتبة الثقة تُوازن الدقّة والاسترجاع، وعتبة NMS تُوازن حذف التكرارات وحفظ الأجسام المتقاربة؛ الإعدادات المثلى تتغيّر بحسب السيناريو.

الوحدة التالية: كيف نُقيس فعلًا جودة كاشف؟ IoU وAP وmAP، مع قراءة تقرير COCO كامل.