7.13. דיכוי לא-מקסימלי (Non-max suppression)¶
רשת זיהוי מפיקה בדרך כלל כמה תיבות מועמדות חופפות סביב אותו עצם בעולם האמיתי: כל עוגן (anchor) קרוב לעצם מופעל עם ציון דומה, ומעבד-הקצה רואה את כולם. דיכוי לא-מקסימלי (NMS) הוא השלב שהופך את האשכול הזה לתיבה אחת.
האלגוריתם קצר: ממיינים את התיבות המועמדות לפי ציון, לוקחים את זו בעלת הציון הגבוה ביותר, מדכאים כל תיבה אחרת שחופפת לה מעבר לסף שנבחר, ואז לוקחים את הגבוהה הבאה מבין הנותרות, וחוזרים על כך. מדד החפיפה הוא intersection-over-union (IoU) – השטח המשותף של שתי תיבות חלקי השטח המאוחד שלהן, ערך בין 0 (אין חפיפה) לבין 1 (תיבות זהות). הארגומנט nms_threshold של הבנאי בכל מעבד-קצה מובנה הוא הסף שמעליו תיבות נחשבות לכפילות של תיבה שכבר נשמרה.
NMS מצמצם אשכול של זיהויים חופפים לזה בעל הציון הגבוה ביותר.¶
7.13.1. Soft-NMS¶
מחלקת ml.utils.NMS המובנית מממשת Soft-NMS, שכלול שמדעיך את ציונה של תיבה חופפת בכמות התלויה במידת החפיפה, במקום להשליך את התיבה לחלוטין. אם הציון המופחת יורד מתחת לסף, התיבה מושלכת; אחרת היא שורדת בציון המופחת ומתחרה בסבב הבא.
הפרמטר nms_sigma שולט בעוצמת הדעיכה. עם nms_sigma קטן (ברירת המחדל המובנית של 0.1) הדעיכה תלולה: תיבה חופפת מאוד מקבלת ציון שמונמך כמעט לאפס ו-Soft-NMS מצטמצם ל-NMS הקלאסי. עם nms_sigma גדול יותר הדעיכה עדינה ותיבות חופפות של עצמים שונים שורדות לעיתים תכופות יותר, מה שחשוב כאשר עצמים בעולם האמיתי מאותה מחלקה אכן חופפים (קהל פנים, אשכול כפות ידיים).
הגדרת nms_sigma לערך <= 0 משביתה את הדעיכה לחלוטין: תיבות חופפות עוברות עם ציוניהן המקוריים, ורק סף הציון מסנן אותן.
7.13.2. בנייה ישירה של אחד¶
כל מעבד-קצה מובנה בונה NMS חדש לכל הסקה, מוסיף אליו כל מועמד, וקורא ל-get_bounding_boxes() בסוף. מעבד-קצה מותאם אישית פועל לפי אותו דפוס:
from ml.utils import NMS
iw = model.input_shape[0][2]
ih = model.input_shape[0][1]
nms = NMS(iw, ih, inputs[0].roi)
for box, score, class_idx in candidates:
nms.add_bounding_box(box.xmin, box.ymin,
box.xmax, box.ymax,
score, class_idx)
result = nms.get_bounding_boxes(threshold=nms_threshold,
sigma=nms_sigma)
הבנאי מקבל את רוחב וגובה הקלט של הרשת בפיקסלים ואת ה-ROI של התמונה המקורית שהמודל הורץ נגדה; add_bounding_box() מקבל קואורדינטות תיבה במרחב הפיקסלים של קלט הרשת, ו-get_bounding_boxes() ממפה מחדש את השורדים בחזרה לקואורדינטות התמונה באמצעות ה-ROI. המיפוי מחדש מתחשב באופן אוטומטי במתיחת Normalization – אותו ROI שהחזאי ראה משמש להטלת התיבות בחזרה – כך שהתיבות המוחזרות מוכנות לציור על הפריים שצולם.
צורת ההחזרה היא רשימה של רשימות לכל מחלקה, מאונדקסת לפי ה-label_index שהועבר ל-add_bounding_box. רשימות מחלקה ריקות נשמרות כך שהאינדקס תואם לאינדקס המחלקה של המודל; enumerate(result) עובר על המחלקות לצד הזיהויים שלהן.