5.13. מסננים לינאריים ומסנני שכנות

פעולות חשבון הפיקסלים שתוארו קודם בפרק שילבו שתי תמונות נקודה אחר נקודה. מסננים מבצעים עבודה דומה בדרך אחרת: הם מחשבים את הערך של כל פיקסל פלט מתוך שכנות קטנה של פיקסלי קלט שמקיפים את המיקום המקביל. הפלט ב-(x, y) הוא איזושהי תכונה סטטיסטית – הממוצע, החציון, הערך השכיח ביותר – של פיקסלי הקלט בתיבה קטנה שמרכזה ב-(x, y).

אותו שינוי קטן במסגור – המעבר מטיפול בפיקסל אחד בכל פעם לטיפול בחלון של פיקסלים בכל פעם – הוא מה שמאפשר למשפחה שלמה של פעולות שימושיות לעבוד. ממוצע פשוט על פני חלון קטן מחליק את רעש החיישן. החציון על פני אותו חלון מסיר נקודות רעש בודדות מבלי לרכך קצוות באותה מידה. ממוצע דו-צדדי (bilateral) נמנע מהחלקה לרוחב גבולות ניגודיות חזקים, ובכך משמר את קצוות העצמים תוך ניקוי המרקמים שבתוכם. השכנות היא יחידת העבודה; הבחירה בתכונה הסטטיסטית קובעת מה המסנן עושה.

5.13.1. גודל הליבה (kernel)

כל מסנן שכנות מקבל פרמטר size שמגדיר את הרדיוס של החלון בפיקסלים. החלון עצמו ריבועי ומכסה (2 * size + 1) פיקסלים בכל צד – כך ש-size=1 משמעו שכנות של 3 על 3, size=2 משמעו 5 על 5, size=3 משמעו 7 על 7, וכן הלאה.

A small image grid with a highlighted 3-by-3 sub-grid representing the filter's neighbourhood. An arrow shows the neighbourhood sliding one pixel to the right. A second arrow shows it sliding down to the next row at the end of the row. The output pixel for each position is drawn under the neighbourhood, with a small note saying that the output is some statistic of the input neighbourhood.

השכנות מחליקה על פני התמונה פיקסל אחד בכל פעם, משמאל למעלה לימין למטה. כל פיקסל פלט הוא תוצאה של החלת התכונה הסטטיסטית של המסנן על שכנות הקלט שמרכזה בו.

גדלים גדולים יותר משמעם שכנויות גדולות יותר, שמשמען סינון חלק יותר (או אגרסיבי יותר). העלות גדלה עם שטח החלון, כך שמסנן size=3 מבצע בערך פי תשעה עבודה לכל פיקסל בהשוואה למסנן size=1. ברירת המחדל המעשית לרוב עבודות הניקוי היא size=1 או size=2; פנו לגדלים גדולים יותר רק כששכנויות קטנות אינן מספיקות כדי לדכא את המאפיין שהיישום מנסה לדכא.

5.13.2. מסנן הממוצע

mean() מחליף כל פיקסל בממוצע החשבוני של שכנותו. התוצאה מחליקה שינויים מפיקסל לפיקסל על פני גודל החלון, מה שהופך אותה לדרך הזולה ביותר לדכא נקודות רעש חיישן: שינויים בתדר גבוה ממוצעים החוצה, ותוכן בתדר נמוך שורד.

התמורה היא שגם קצוות ומאפיינים חדים אחרים ממוצעים. קצה בהיר שהיה ברוחב פיקסל אחד לפני המסנן הופך לרוחב שניים או שלושה פיקסלים אחרי מסנן ממוצע size=1, כשהבהירות יורדת בהדרגה בשוליים. עבור הפחתת רעש טהורה בתמונה דלת-מרקם (קיר נקי, פנים של סמן צבעוני) התמורה משתלמת. עבור סצנה עמוסה שבה הקצוות חשובים, אחד המסננים הבאים הוא בדרך כלל התאמה טובה יותר.

img.mean(1)        # 3x3 box average -- fast, gentle smoothing
img.mean(2)        # 5x5 box average -- stronger, slower

5.13.3. חציון, שכיח, נקודת אמצע

שלושת מסנני השכנות הסטטיסטיים האחרים ממירים את הממוצע החשבוני הפשוט במשהו עמיד יותר בפני ערכים חריגים.

median() מחזיר את החציון של השכנות – הערך שמגיע לאמצע הרשימה הממוינת של פיקסלי החלון. פיקסל בודד בהיר מאוד או כהה מאוד בחלון אינו מושך את החציון; הוא פשוט הופך לאחד מהקצוות הנדחים. ההשפעה המעשית היא שסינון חציון מסיר נקודות רעש בודדות ורעש מלח-ופלפל מבלי לרכך קצוות כפי ש-mean עושה. העלות היא יותר חישוב לכל פיקסל – מיון חלון איטי יותר ממיצועו – והתוצאה אינה בדיוק ממוצע, מה שלעיתים חשוב לחישובים בהמשך.

פרמטר percentile (ברירת מחדל 0.5) מזיז את הערך הנבחר מחוץ לחציון המדויק. percentile=0.0 מחזיר את המינימום של השכנות, percentile=1.0 את המקסימום; ערכי ביניים בוחרים באופן יחסי ביניהם בחלון הממוין. זה מעניק ל-median את היכולת להדגיש חלקים כהים או בהירים של השכנות מבלי לאבד את העמידות בפני חריגים של סטטיסטיקת הסדר.

mode() מחזיר את הערך השכיח ביותר בשכנות. שימושי כאשר מודל הרעש הוא ”רוב הפיקסלים נכונים, מעטים נפגמו במידות שונות“, שבו התשובה הנכונה היא הערך שמופיע לעיתים קרובות ביותר – מה שהחציון עלול להחמיץ כשהערכים הפגומים מצטברים בצד אחד של החלון הממוין.

midpoint() מחזיר שילוב משוקלל של המינימום והמקסימום של השכנות – bias=0.5 נותן את נקודת האמצע ביניהם, bias=0.0 נותן את המינימום, bias=1.0 נותן את המקסימום. בשימוש פחות נפוץ מהאחרים אך כדאי להכיר אותו כשהמטרה היא במפורש לחלץ מאפיינים כהים או בהירים.

5.13.4. Bilateral, הגרסה משמרת-הקצוות

bilateral() הוא מסנן השכנות שהכי כדאי להבין היטב. הוא מפיק את אפקט ההחלקה של mean(), אך עם אילוץ נוסף: ככל שפיקסל שכן שונה יותר מפיקסל המרכז, כך הוא נספר פחות בממוצע. התוצאה מחליקה את הפנים של כל אזור אחיד מבלי לדלוף לרוחב הקצוות שמפרידים ביניהם, וזה בדיוק מה שרוב היישומים באמת רוצים.

שני פרמטרים שולטים עד כמה המסנן מנכה פיקסלים באגרסיביות:

  • color_sigma קובע כיצד הבדל הצבע משפיע על השקלול. ערכים קטנים יותר משמעם שהמסנן מחמיר יותר בניכוי פיקסלים השונים מהמרכז.

  • space_sigma קובע כיצד המרחק המרחבי משפיע על השקלול. ערכים קטנים יותר נותנים משקל רב יותר לפיקסלים הקרובים למרכז.

ברירות המחדל (color_sigma=0.1, space_sigma=1.0) הן נקודות מוצא סבירות; כיוונון שלהן הוא בדרך כלל עניין של הרצת המסנן על פריים לדוגמה והתאמה עד שהקצוות חדים והפנים נקיים.

Bilateral יקר יותר מ-median() ויקר משמעותית מ-mean(), ולכן כדאי לפנות אליו רק כאשר ההתנהגות משמרת-הקצוות היא מה שהיישום צריך.

5.13.5. סף אדפטיבי

מסנני הממוצע, החציון, השכיח ונקודת האמצע נושאים כולם את אותו זוג ארגומנטים בעלי מילת מפתח שהופכים את הפלט שלהם לסף בינארי:

  • threshold=True מעביר את המסנן למצב סיפֵּף.

  • offset=N מסיט את הסף המקומי ב-N יחידות לפני ההשוואה.

המנגנון בנוי ישירות על ההתנהגות הרגילה של המסנן. ללא threshold=True, המסנן מחשב את התכונה הסטטיסטית שלו על פני השכנות וכותב את אותה תכונה לפיקסל הפלט. עם threshold=True, המסנן מחשב את אותה תכונה, ואז משווה את פיקסל המקור באותו מיקום מול התכונה ועוד ההיסט, וכותב את הערך המקסימלי של הפורמט אם המקור גדול יותר, ואפס אחרת.

התוצאה היא תמונה בינארית שהסף שלה נע עם הבהירות המקומית על פני הפריים. אזורים בהירים מקבלים סף גבוה, אזורים עמומים מקבלים סף נמוך, ופיקסל קדמה שבהיר מקומית יותר משכניו מתאים בין אם הוא יושב באזור בהיר ובין אם בעמום – וזו בדיוק ההתנהגות שסף גלובלי יחיד לא יכול היה להפיק בתמונה מוארת באופן לא אחיד.

img.mean(3, threshold=True, offset=5)

פרמטר offset הוא המקום שבו היישום שולט עד כמה הבדיקה מחמירה. היסט חיובי קטן דורש שפיקסל המקור יהיה בהיר באופן מדיד יותר משכניו לפני שייחשב להתאמה, מה שמדכא תוצאות חיוביות שגויות של רעש חיישן במחיר השמטת קדמה חלשה. היסט שלילי קטן תופס קדמה חלשה במחיר מעבר של מעט רעש. הבחירה תלויה במה ששאר הצנרת תעשה עם הפלט הבינארי.

Three image panels in a row. The first is an input grayscale frame with a brightness gradient and foreground marks scattered across at uniform darkness. The second panel shows a global threshold applied to it: the foreground is correctly classified on the bright side, but the entire dark side reads as foreground because page and foreground both fall below the cutoff. The third panel shows an adaptive threshold applied to the same input: the foreground is correctly classified across the whole frame.

תחת תאורה לא אחידה, סף גלובלי יחיד אינו יכול לתאר את הקדמה בכל מיקום. מסנן שכנות שמורץ עם threshold=True מפיק סף שנע עם הבהירות המקומית ומסווג את הקדמה נכונה על פני כל הפריים.

משפחת המסננים מריצה את הסף האדפטיבי, ולכן בחירת המסנן הנכון חשובה: mean() לסף האדפטיבי הזול ביותר, median() כאשר לקלט יש רעש מלח-ופלפל שהמסנן צריך לדחות לפני חישוב הסף המקומי.