5.11. השוואת פריימים

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

5.11.1. צינור העיבוד הבסיסי

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

reference = csi0.snapshot().copy()

ה-.copy() חשובה. csi0.snapshot() כשלעצמה מחזירה Image שהחוצץ שלה חי בחוצץ הפריימים (frame buffer), שם הקריאה הבאה ל-snapshot תדרוס אותה. .copy() מקצה חוצץ נפרד עבור הייחוס ומאפשרת לפיקסלים של פריים זה לשרוד מעבר ללכידה הבאה.

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

current = csi0.snapshot()
current.difference(reference)

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

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

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

A horizontal pipeline diagram. The leftmost two panels are a reference frame and a current frame side by side, with a plus mark between them. An arrow leads from the pair to a third panel labelled difference, in which a few patches are bright against a dark background. An arrow leads from there to a fourth panel showing a binary thresholded version of the difference, with the same patches now solid white. A final arrow leads to a fifth panel showing the binary mask annotated with rectangular bounding boxes drawn around each patch.

צינור השוואת הפריימים: פריים ייחוס בתוספת פריים נוכחי הופכים לתמונת הפרש; מעבר סף הופך את ההפרש למסכה בינארית של מיקומים שהשתנו; שלב אזור-מחובר הופך את המסכה לרשימה של אזורי תנועה.

5.11.2. ייחוסים בזיכרון ועל הדיסק

צינור העיבוד הבסיסי שומר את פריים הייחוס ב-RAM. זו התשובה הנכונה כאשר הייחוס נלכד בהרצה הנוכחית של הסקריפט ועליו לשרוד רק כל עוד הסקריפט ממשיך לרוץ.

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

csi0.snapshot().save("/sdcard/reference.bmp")

ולטעון אותו בחזרה בתחילת כל הרצה:

reference = image.Image("/sdcard/reference.bmp")

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

5.11.3. בידוד מקור אור

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

5.11.4. בחירה בין difference ל-sub

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

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