5.32. שמירה ודחיסה

כל עמוד עד כה עבד עם תמונות במצלמה: שנלכדו לתוך חוצץ הפריימים (frame buffer) או הוקצו על ה-heap של MicroPython, עברו עיבוד באמצעות המתודות של מודול image, והוצגו בתצוגה המקדימה של ה-IDE או הוזנו לשלב המשך באותו סקריפט. רוב היישומים זקוקים בשלב כלשהו לעשות את ההפך: לקחת תמונה שנמצאת כעת ב-RAM ולהציב אותה במקום מתמיד כלשהו – על כרטיס ה-SD, על מארח USB, או דרך רשת – שבו משהו אחר מלבד המצלמה יכול לקרוא אותה.

מודול image חושף שני מסלולים לעבודה הזו. מסלול ה-save כותב את התמונה לקובץ במערכת הקבצים, כאשר פורמט הקובץ נבחר לפי הסיומת ופרטי הקידוד מטופלים על ידי המתודה. מסלול ה-to-format מחזיר אובייקט Image המכיל את זרם הבייטים המקודד, המתאים להעברה לקריאת הזרמה (streaming) או רשת מבלי לגעת כלל במערכת הקבצים. כל אחד מתאים ליישום שונה; שניהם נבנים על אותו מנוע דחיסה שמתחת.

5.32.1. שמירה לקובץ

save() כותב את התמונה למערכת הקבצים בנתיב נתון:

img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)

הפורמט נבחר לפי סיומת הקובץ. חמש סיומות מזוהות: .bmp כותב Windows bitmap (ללא אובדן, ללא דחיסה, בייט-אחר-בייט של הפיקסלים שנלכדו); .pgm כותב portable graymap (ללא אובדן, גווני אפור בלבד); .ppm כותב portable pixmap (ללא אובדן, RGB); גם .jpg וגם .jpeg כותבים JPEG (עם אובדן, דחוס). תמונת היעד חייבת להיות כבר בפורמט הצבע הנכון עבור המכל הנבחר – תמונה צבעונית שנשמרת כ-.pgm היא שגיאה.

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

quality הוא איכות דחיסת ה-JPEG מ-0 עד 100 ובעל משמעות רק כאשר הפלט הוא JPEG (מילת המפתח מתעלמת עבור הפורמטים ללא אובדן). ברירת המחדל של 50 היא האיזון הנכון עבור רוב היישומים; 70 עד 85 הוא הטווח לאיכות חזותית גבוהה יותר, 30 עד 50 הוא הטווח הנכון לתמונות ממוזערות קטנות ולשידור מוגבל-רוחב-פס, ו-90 ומעלה שמור למקרים שבהם התמונה תיבדק ידנית או תועבר דרך אלגוריתם המשך הרגיש לעיוותי דחיסה.

תמונת היעד מוחזרת כך שהקריאה משתרשרת: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). האובייקט המוחזר הוא אותה תמונה בזיכרון; השמירה היא תופעת לוואי.

שימוש טיפוסי הוא דפוס ה-capture-and-log. טריגר נורה (רכיב/כתם (blob) מזוהה, לחצן נלחץ, טיימר חולף); הסקריפט לוכד פריים; הוא מוסיף חותמת זמן לשם הקובץ; והוא קורא ל-save() כדי לדחוף את התמונה לכרטיס ה-SD. התצוגה המקדימה של ה-IDE ממשיכה לרוץ, הטריגר הבא נורה, והקבצים השמורים מצטברים.

5.32.2. קידוד לזיכרון

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

encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)

התנהגות ברירת המחדל היא המרה במקום: תמונת היעד מומרת לתמונת JPEG (או PNG) ואותו אובייקט מוחזר. עם copy=True ההמרה כותבת לאובייקט heap שהוקצה מחדש; עם copy_to_fb=True הפלט נוחת בחוצץ הפריימים (frame buffer). הבחירה זהה לזו שכל מתודת המרה אחרת מציעה – במקום כברירת מחדל, העתקה כאשר המקור נדרש לאחר מכן.

quality ו-subsampling הם אותם כפתורי כוונון JPEG שמסלול השמירה חושף. subsampling בוחר את סכמת תת-הדגימה של הכרומה: image.JPEG_SUBSAMPLING_AUTO בוחר את הטוב ביותר עבור האיכות הנבחרת, image.JPEG_SUBSAMPLING_444 שומר את הכרומה ברזולוציה מלאה (הקובץ הגדול ביותר, דיוק הצבע הטוב ביותר), image.JPEG_SUBSAMPLING_422 ו-image.JPEG_SUBSAMPLING_420 מקטינים בחצי את רזולוציית הכרומה לאורך ציר אחד או שניהם (קבצים קטנים יותר, ריכוך צבע קל שאינו נראה במרחקי צפייה טיפוסיים). ברירת המחדל של AUTO היא הבחירה הנכונה אלא אם ליישום יש צורך ספציפי.

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

גם to_jpeg() וגם to_png() מקבלים את אותן מילות מפתח מיקומיות וסגנון-ציור וקנה מידה שמתודות המרה אחרות מקבלות – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – כך שאותה קריאה יכולה לקדד גרסה מוקטנת, חתוכה, או ממופת-פלטה של המקור בצעד אחד. compress() הוא הכתיב הישן של to_jpeg(); השניים מקבלים את אותם ארגומנטים ומייצרים את אותה תוצאה.

5.32.3. מה הדחיסה קונה

המספרים שמאחורי הפשרה של JPEG-מול-גולמי שווים עבודה דרכם פעם אחת.

פריים RGB565 בגודל 320 על 240 הוא 153,600 בייטים (פריים אחד שנלכד ב-QVGA). פריים 640 על 480 הוא 614,400 בייטים; פריים 1280 על 960 הוא 2,457,600 בייטים. אף אחד מאלה אינו גדול בהשוואה לתצוגת שולחן עבודה או טלפון, אך הם משמעותיים בהקשר של מצלמה שיש לה בסך הכול כמה MB של RAM, כרטיס SD עם רוחב פס כתיבה סופי, וקישור מארח שבדרך כלל פועל מעל USB CDC, UART, או מודול אלחוטי במהירויות צנועות.

JPEG ב-quality=50 דוחס בדרך כלל פריים צילומי שנלכד פי 10 עד פי 20: אותו פריים 640 על 480 של 614 KB הופך לזרם בייטים מקודד של 30 עד 60 KB. ב-quality=85 הדחיסה יורדת לפי 5 עד פי 10 (60 עד 120 KB עבור אותו פריים). ב-quality=10 – מלא עיוותים אך עדיין ניתן לזיהוי – הדחיסה מגיעה לפי 30 עד פי 50 (12 עד 20 KB).

המספרים האלה קובעים מה מעשי לעשות עם הפריימים השמורים. מסלול כרטיס SD המקיים 10 MB/s מטפל ב-30 פריימים לשנייה של תוכן VGA מקודד-JPEG ב-quality=50 עם מקום בשפע (כ-1 עד 2 MB/s); שמירת אותו תוכן ללא דחיסה דורשת מעל 18 MB/s, מעבר למה שמסלול מערכת הקבצים של המצלמה מקיים אל הכרטיס. מארח USB המושך פריימים מקודדי-JPEG מעל CDC ב-1 MB/s מקבל פריימים של 30 עד 60 KB בקצב של בערך 15 עד 30 פריימים לשנייה; משיכת פריימים גולמיים באותו קצב מקבלת פריים אחד או שניים בשנייה.

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