5.17. קטלוג של גרעינים סטנדרטיים¶
עיבוד תמונה קלאסי צבר קטלוג גדול למדי של תבניות משקלי גרעין שחוזרות על עצמן שוב ושוב – מזהי קצה, מחדדים, חריטות (emboss), מחליקים, טשטושי תנועה – וכל אחת מהן מורצת דרך morph(). כל אחת קצרה, כל אחת עושה דבר אחד, ורובן פשוטות לקריאה ברגע שההיגיון הבסיסי של המשקלים מובן.
הגרעינים שלהלן הם כולם 3 על 3 אלא אם צוין אחרת, ולכן כולם משתמשים ב-size=1 בקריאה. מבנה המשקלים של כל גרעין מתואר לצדו, מכיוון שקריאת המשקלים היא מה שבונה את האינטואיציה לגבי הסיבה שגרעין אחד חורט ואחר מחדד.
5.17.1. גרעין הזהות¶
הגרעין הפשוט ביותר האפשרי הוא גרעין הזהות – אחד במרכז, אפס בכל מקום אחר:
identity = [0, 0, 0,
0, 1, 0,
0, 0, 0]
img.morph(1, identity)
כל פיקסל פלט לוקח את ערכו ממרכז הסביבה, שהוא פיקסל הקלט באותה מיקום. התמונה עוברת ללא שינוי. לזהות אין שימוש מעשי כמסנן, אך היא קו הבסיס השימושי להבנת כל גרעין אחר: כל גרעין שאינו זהות הוא הזהות בתוספת שינוי כלשהו.
גרעין שמשקל המרכז שלו גדול עם משקלים שליליים קטנים סביבו מחסר את הסביבה מן המרכז. גרעין עם משקל מרכז אפס מתעלם מן הפיקסל עצמו ומגיב רק להבדלים בין שכניו. קריאת גרעין בדרך זו – מה משקל המרכז עושה לפיקסל, מה המשקלים שמסביב מוסיפים או גורעים – היא הדרך המהירה ביותר לחזות את השפעתו.
5.17.2. זיהוי קצוות¶
גרעיני זיהוי קצוות מגיבים בעוצמה למיקומים שבהם הבהירות משתנה במהירות בכיוון מסוים, ומפיקים פלט קרוב לאפס היכן שהבהירות אחידה. הם המשפחה שמשקליה מסתכמים לאפס: רצף שטוח (כל פיקסל באותו ערך) מפיק פלט אפס, מכיוון שכל משקל חיובי מבוטל בדיוק על ידי משקל שלילי בעל גודל זהה.
Sobel-x הוא הדוגמה הקנונית. הוא מזהה קצוות אנכיים (מעברי בהירות שמאל/ימין):
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
img.morph(1, sobel_x, mul=0.25, add=128)
ה-Sobel-y המתאים הוא אותה תבנית מסובבת ב-90 מעלות; הוא מזהה קצוות אופקיים (מעברי בהירות מעלה/מטה):
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
השורה האמצעית של Sobel-x נושאת משקלים -2 ו-2 במקום -1 ו-1. המשקל הנוסף בשורת המרכז מעניק לגרעין החלקה מובנית קטנה בכיוון לאורך הקצה, מה שהופך אותו לעמיד יותר בפני רעש מאשר אופרטור Prewitt הפשוט יותר שמוותר על הגדלים הנוספים האלה:
prewitt_x = [-1, 0, 1,
-1, 0, 1,
-1, 0, 1]
prewitt_y = [-1, -1, -1,
0, 0, 0,
1, 1, 1]
Prewitt משקלל כל שורה באופן שווה, ולכן תגובתו חדה במעט מזו של Sobel, במחיר רגישות גבוהה יותר לרעש בפיקסל בודד (עלות הרצת הגרעין זהה – הקונבולוציה מבצעת את אותה עבודה תהיינה המשקלים אשר תהיינה). על תמונה נקייה עם קצוות חזקים, הוא תחליף תקין לחלוטין ל-Sobel.
Scharr הולך בכיוון ההפוך. משקליו גדולים יותר ומכווננים לזיהוי מדויק של כיוון הקצה בזוויות עדינות יותר:
scharr_x = [-3, 0, 3,
-10, 0, 10,
-3, 0, 3]
img.morph(1, scharr_x, mul=0.0625, add=128)
המחלק mul=0.0625 (1/16) מחזיר את הפלט אל תוך טווח 0 – 255 לאחר סכום-המכפלות הגדול יותר. Scharr הוא התשובה הנכונה כאשר היישום זקוק לתגובת מפל (gradient) הנאמנה ביותר גאומטרית ומוכן לשלם מעט יותר חישוב עבורה.
5.17.3. הלפלסיאן¶
גרעין לפלסיאן מגיב לקצוות בכל כיוון שהוא בבת אחת. בעוד שכל אחד מגרעיני ה-Sobel מזהה שינויי בהירות לאורך ציר אחד, תבנית המשקלים הסימטרית של הלפלסיאן מגיבה באותו אופן ללא תלות בכיוון אליו פונה הקצה:
laplacian_4 = [ 0, -1, 0,
-1, 4, -1,
0, -1, 0]
img.morph(1, laplacian_4, add=128)
המבנה: משקל מרכז 4, ארבעה שכנים אופקיים/אנכיים במשקל -1, וארבעת האלכסונים במשקל אפס. הגרעין מסתכם לאפס, ולכן רצפים שטוחים מפיקים פלט אפס. היכן שהבהירות משתנה, ערך המרכז שונה מממוצע ארבעת שכניו הראשיים, והפלט הוא גודל ההפרש הזה.
הווריאנט בעל 8 קישוריות כולל גם את השכנים האלכסוניים:
laplacian_8 = [-1, -1, -1,
-1, 8, -1,
-1, -1, -1]
כל גרעין מזהה דברים שונים במקצת. הגרסה בעלת 4 הקישוריות מפיקה פלט נקי יותר על קצוות אופקיים ואנכיים; זו בעלת 8 הקישוריות איזוטרופית יותר – היא מגיבה באותה מידה בכל כיוון – אך מפיקה פלט רועש מעט יותר. הגרעין בעל 8 הקישוריות מתהלך גם תחת השם outline, על שם שימושו להמחשת קצוות.
5.17.5. חריטה (Emboss)¶
גרעין חריטה (emboss) מפיק את אפקט התאורה-מן-הצד הנמצא בעורכי תמונה קלאסיים. הפלט נראה כאילו התמונה הובלטה לתבליט ואז הוארה מאחת הפינות:
emboss = [-2, -1, 0,
-1, 1, 1,
0, 1, 2]
img.morph(1, emboss, add=128)
הטריק הוא האי-סימטריה לרוחב האלכסון. בפינה השמאלית-עליונה יש את המשקל השלילי ביותר, בפינה הימנית-תחתונה יש את המשקל החיובי ביותר, והאלכסון מפינה לפינה נע משלילי דרך אחד אל חיובי. בכל פיקסל הגרעין מחשב למעשה ”בהירות בפינה הימנית-תחתונה שלי פחות בהירות בפינה השמאלית-עליונה שלי“, שהיא חיובית היכן שהתמונה מתבהרת בכיוון זה ושלילית היכן שהיא מתכהה. הוספת 128 ממרכזת מחדש את הפלט בעל הסימן לאפור-ביניים כך שהאפקט נראה לעין.
סיבוב האי-סימטריה לרוחב האלכסון האחר חורט מן הכיוון ההפוך:
emboss_alt = [ 0, 1, 2,
-1, 1, 1,
-2, -1, 0]
img.morph(1, emboss_alt, add=128)
שני כיווני החריטה שימושיים בשילוב – חיסור אחד מן השני, או הרצת כל אחד על אותה תמונה והשוואת התגובות – כאשר יישום זקוק לזהות התמצאות.
5.17.6. החלקה¶
גרעיני החלקה הם המשפחה שמשקליה מסתכמים לאחד (וכולם אי-שליליים). רצף שטוח שעובר דרך גרעין כזה מפיק את אותה בהירות שטוחה, מכיוון שהגרעין ממצע את ערכי הפיקסלים יחד במקום להגביר את ההבדלים ביניהם.
הפשוט ביותר הוא box blur (טשטוש תיבה), שהוא בדיוק מה ש-mean() מחשבת:
box_blur = [1, 1, 1,
1, 1, 1,
1, 1, 1]
img.morph(1, box_blur)
הגרעין מסתכם ל-9, ולכן החלוקה האוטומטית בסכום הגרעין הופכת את סכום-המכפלות לממוצע אמיתי על פני תשעת פיקסלי הסביבה. בפועל mean() היא הדרך הטובה יותר להריץ גרעין זה – היא מפיקה את אותו פלט מהר יותר, דרך מסלול מותאם לחישוב הממוצע ותו לא, בעוד ש-morph מריצה את מנגנון הקונבולוציה הכללי. ה-box blur נמצא בקטלוג מכיוון שהוא קו הבסיס הנכון להבנת כל גרעין החלקה אחר.
קירוב 3 על 3 של גרעין Gaussian משקלל את המרכז ואת השכנים הראשיים יותר מאשר את הפינות:
gaussian = [1, 2, 1,
2, 4, 2,
1, 2, 1]
img.morph(1, gaussian)
המשקלים הם שורת משולש פסקל 1, 2, 1 בכפל-חיצוני עם עצמה. משקל המרכז 4 הוא הגדול ביותר מכיוון שפיקסל המרכז תורם הכי הרבה לפלט של עצמו; הפינות הן 1 מכיוון שהן הרחוקות ביותר מן המרכז. הגרעין מסתכם ל-16, והחלוקה האוטומטית בסכום הגרעין מטפלת בנירמול – ללא צורך בארגומנט mul. צורת ה-3 על 3 היא קירוב גס של גאוסיאן אמיתי ובלתי ניתנת להבחנה מ-gaussian() ב-size=1; צורת ה-morph שימושית בעיקר כאשר יישום מעוניין להרכיב את ההחלקה עם פעולה אחרת באותו מעבר.
5.17.7. טשטוש תנועה¶
גרעין motion-blur (טשטוש תנועה) ממצע פיקסלים לאורך כיוון אחד, ומשאיר את הכיוון הניצב ללא טשטוש. המקרה הפשוט ביותר הוא אופקי:
motion_h = [0, 0, 0,
1, 1, 1,
0, 0, 0]
img.morph(1, motion_h)
השורה האמצעית ממצעת שלושה פיקסלים לאורך הציר האופקי; השורות העליונה והתחתונה הן אפס. הגרעין מסתכם ל-3, ולכן החלוקה האוטומטית בסכום הגרעין מפיקה ממוצע אמיתי של שלושה פיקסלים ללא צורך ב-mul כלשהו. הפלט הוא עותק מרוח-אופקית של הקלט – האפקט שמצלמה לוכדת כשהנושא נע הצדה במהלך החשיפה. טשטוש התנועה האנכי הוא אותה תבנית מסובבת:
motion_v = [0, 1, 0,
0, 1, 0,
0, 1, 0]
טשטוש תנועה אלכסוני משתמש באלכסון הראשי:
motion_diag = [1, 0, 0,
0, 1, 0,
0, 0, 1]
img.morph(1, motion_diag)
גרעיני טשטוש תנועה שימושיים הן כאפקט (טשטוש מכוון של פריים למטרות חזותיות) והן כתבנית-בדיקה עבור אלגוריתמים שצריכים להיות עמידים בפני ארטיפקטים של תנועה (הרצת האלגוריתם על קלט מטושטש-תנועה ובדיקה שהוא עדיין מפיק את התשובה הנכונה).
5.17.8. קריאת גרעינים במבט חטוף¶
כמה כללי אצבע הופכים גרעינים חדשים לקלים יותר לקריאה במבט:
סכום-לאחד עם משקלים אי-שליליים ⇒ החלקה (משמרת את הבהירות הממוצעת).
סכום-לאפס עם משקלים חיוביים ושליליים כאחד ⇒ תגובת קצה (אפס על רצפים שטוחים).
סכום-לאחד עם מרכז חיובי גדול וסביבה שלילית קטנה ⇒ חידוד (זהות בתוספת תגובת קצה).
אי-סימטרי לרוחב אלכסון עם סכום לאחד ⇒ חריטה (מבליט צד אחד של כל מעבר בהירות).
מרוכז לאורך ציר אחד עם סכום לאחד ⇒ טשטוש כיווני.
הראשון מבין אלה שהגרעין תואם לו הוא בדרך כלל הניחוש הנכון לגבי מה שהוא עושה. רוב הגרעינים השימושיים ניתנים לזיהוי מעצם פריסת תבנית המשקלים שלהם בלבד.
כאשר אף אחד מן הגרעינים הסטנדרטיים אינו עושה את מה שהיישום מבקש, הצעד הבא הוא לכוונן אחד ידנית. השילוב של הכללים לעיל ושל פקדי ה-mul / add מכסה כמעט כל מעבר ליניארי שצינור ראייה ממוחשבת קלאסי אי פעם רצה; משם זה עניין של ניסוי משקלים, התבוננות בפלט, וחזרה על התהליך.