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 เอง เฟรมระดับสีเทาจะถูกส่งคืนโดยคัดลอกค่าลูมาไปยังทั้งสามช่องสัญญาณ

บัฟเฟอร์ 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 กลับมา เมื่อโฮสต์มีงานอื่นต้องทำด้วย (อัปเดต UI หรือโพลล์ช่องสัญญาณอื่น) read_status() เป็นการตรวจสอบที่ประหยัดกว่า โดยคืนค่า dict ที่แมปชื่อช่องสัญญาณที่ลงทะเบียนทุกช่องไปยังค่าบูลีน "ข้อมูลพร้อม":

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() ทำสิ่งเดียวกันโดยปริยายด้วยการหยุดสคริปต์