13.3.1.3. הזרמת פריימים

סקריפט שלוכד פריימים במצלמה יכול להזרים כל פריים בחזרה אל המארח דרך USB. התבנית היא שתי קריאות על מופע ה-openmv.Camera: streaming() להפעלה או כיבוי של ההזרמה, ו-read_frame() למשיכת הפריים הבא מתוך הערוץ.

13.3.1.3.1. לולאת הזרמה-ותצוגה מינימלית

הסקריפט בצד המצלמה הוא לולאת ה-snapshot הרגילה; מה שחדש הוא שהמארח פותח הזרמה וקורא את התוצאה בחזרה:

from openmv import Camera

script = """
import csi
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
while True:
    csi0.snapshot()
"""

with Camera('/dev/ttyACM0') as cam:
    cam.stop()
    cam.exec(script)
    cam.streaming(True)

    while True:
        if frame := cam.read_frame():
            print(f"{frame['width']}x{frame['height']}, "
                  f"{frame['raw_size']} bytes")

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

13.3.1.3.2. מילון הפריים

read_frame() מחזיר אחד משניים: None (אין פריים ממתין) או dict עם חמש רשומות:

מפתח

משמעות

width

רוחב הפריים בפיקסלים.

height

גובה הפריים בפיקסלים.

format

מזהה פורמט הפיקסל שהמצלמה הצהירה עליו (מספר שלם מתוך קבועי ה-csi של המצלמה).

depth

עבור פורמטים דחוסים (JPEG, PNG), גודל התמונה הדחוסה בבתים. אינו בשימוש עבור פורמטים לא דחוסים.

data

הפריים כחוצץ (buffer) מסוג bytes בפורמט RGB888. כל פיקסל הוא שלושה בתים (R, G, B); האורך הכולל הוא width * height * 3.

raw_size

מספר הבתים שהמצלמה שלחה דרך USB לפני הפענוח. שימושי לחישוב התפוקה בפועל.

החבילה ממירה את הפורמט המקורי של המצלמה (GRAYSCALE, RGB565, JPEG) ל-RGB888 לפני ההחזרה, כך שהמארח לעולם אינו צריך להתמודד בעצמו עם מסלול ה-RGB565 הארוז ברמת הביט או עם פענוח ה-JPEG. פריימים בגווני אפור חוזרים כשערך הבהירות (luma) משוכפל לכל שלושת הערוצים.

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

13.3.1.3.3. מצב הזרמה גולמי

כברירת מחדל המצלמה דוחסת ב-JPEG כל פריים שנלכד לפני שהיא מציבה אותו בערוץ ההזרמה, ו-read_frame() מבצע פענוח דחיסה בצד המארח. במצלמות ללא תמיכת חומרה ב-JPEG, הדחיסה התוכנתית היא השלב האיטי ביותר בלולאה. העברת raw=True מדלגת עליה:

cam.streaming(True, raw=True, resolution=(320, 240))

במקרה זה המצלמה שולחת את חוצץ הפיקסלים ללא דחיסה. פריימים לא דחוסים גדולים בהרבה מהמקבילים שלהם ב-JPEG, ולכן המצלמה מקטינה כל פריים שנלכד כדי שיתאים לערוץ ההזרמה לפני שליחתו; הארגומנט resolution=(width, height) קובע את היעד הזה. המארח עדיין מקבל RGB888 בשדה data – החבילה ממירה מכל פורמט פיקסל שהמצלמה דיווחה עליו בשדה format.

13.3.1.3.4. מתן אפשרות לאירועים להניע את הלולאה

לולאת דגימה שקוראת ל-read_frame() מהר יותר מקצב יצירת הפריימים של המצלמה מבזבזת את רוב זמנה בקבלת None בחזרה. כאשר למארח יש גם עבודה אחרת לבצע (ממשק משתמש לעדכן, ערוצים אחרים לדגום), read_status() היא הבדיקה הזולה יותר: היא מחזירה מילון הממפה כל שם ערוץ רשום לערך בוליאני של ”הנתונים מוכנים“:

while True:
    status = cam.read_status()

    if status.get('stream'):
        frame = cam.read_frame()
        # ... process the frame ...

    if status.get('stdout'):
        text = cam.read_stdout()
        print(text, end='')

    if status.get('my_channel'):
        data = cam.channel_read('my_channel')
        # ... process custom-channel data ...

זוהי צורת הלולאה שבה משתמש מציג ה-CLI עצמו.

13.3.1.3.5. עצירת ההזרמה

קראו ל-streaming() עם enable=False כדי לעצור. המצלמה ממשיכה להריץ את הסקריפט שלה אך אינה ממלאת עוד את חוצץ ההזרמה; read_frame() פשוט מחזיר None מאותה נקודה ואילך. קריאה ל-stop() עושה את אותו הדבר באופן מרומז על ידי עצירת הסקריפט.