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. 프레임 dict

read_frame()None (대기 중인 프레임 없음) 또는 다섯 개의 항목을 가진 dict 중 하나를 반환합니다:

의미

width

프레임 너비(픽셀 단위).

height

프레임 높이(픽셀 단위).

format

캠이 선언한 픽셀 포맷 식별자(캠의 csi 상수에서 가져온 정수).

depth

압축 포맷(JPEG, PNG)의 경우 압축된 이미지의 크기(바이트 단위). 비압축 포맷에서는 사용되지 않습니다.

data

RGB888 형식의 bytes 버퍼로 된 프레임. 각 픽셀은 세 바이트 (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) 인수가 그 목표 크기를 설정합니다. 호스트는 여전히 data 필드에서 RGB888을 받습니다. 패키지가 캠이 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. 스트림 중지하기

중지하려면 enable=False 와 함께 streaming() 을 호출하십시오. 캠은 스크립트를 계속 실행하지만 더 이상 스트림 버퍼를 채우지 않으며, read_frame() 은 그 시점부터 그냥 None 을 반환합니다. stop() 을 호출하면 스크립트를 중단함으로써 암묵적으로 동일한 작업을 수행합니다.