13.3.1.3. بث الإطارات

يمكن للبرنامج النصي الذي يلتقط الإطارات على الكاميرا أن يبث كل إطار إلى المضيف عبر USB. والنمط المتبع هو استدعاءان على نسخة openmv.Camera: streaming() لتشغيل البث أو إيقافه، وread_frame() لسحب الإطار التالي من القناة.

13.3.1.3.1. حلقة بسيطة للبث والعرض

إن البرنامج النصي على جانب الكاميرا هو حلقة اللقطة المعتادة؛ والجديد هنا هو أن المضيف يفتح البث ويقرأ النتيجة:

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")

تلتقط الكاميرا الإطارات بشكل متواصل؛ ويسحب المضيف كل إطار من مخزن البث المؤقت بمجرد وصوله. تكتب الكاميرا فوق مخزن البث المؤقت في كل لقطة جديدة، لذا فإن المضيف الذي يستطلع بوتيرة أبطأ من وتيرة التقاط الكاميرا سيُسقط إطارات بصمت -- وهذا هو السلوك الصحيح لحالات الاستخدام من نوع العارض.

13.3.1.3.2. قاموس الإطار

ترجع read_frame() إما None (لا يوجد إطار في الانتظار) أو dict يحتوي على خمسة مدخلات:

المفتاح

المعنى

width

عرض الإطار بالبكسل.

height

ارتفاع الإطار بالبكسل.

format

معرّف تنسيق البكسل الذي أعلنته الكاميرا (عدد صحيح من ثوابت csi الخاصة بالكاميرا).

depth

بالنسبة للتنسيقات المضغوطة (JPEG وPNG)، حجم الصورة المضغوطة بالبايت. غير مُستخدم للتنسيقات غير المضغوطة.

data

الإطار كمخزن مؤقت من نوع 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() يفعل الشيء نفسه ضمنيًا بإيقاف البرنامج النصي.