13.3.1.3. フレームのストリーミング

カメラ上でフレームを取得するスクリプトは、各フレームをUSB経由でホストにストリーミングして返すことができます。そのパターンは openmv.Camera インスタンスに対する2つの呼び出しです。ストリームのオンとオフを切り替える 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 (フレームが待機していない)か、5つのエントリを持つ dict のいずれかを返します。

キー

意味

width

フレームの幅(ピクセル単位)。

height

フレームの高さ(ピクセル単位)。

format

カメラが宣言したピクセルフォーマット識別子(カメラの csi 定数の整数値)。

depth

圧縮フォーマット(JPEG、PNG)の場合、圧縮された画像のサイズ(バイト単位)。非圧縮フォーマットでは使用されません。

data

RGB888 形式の bytes バッファとしてのフレーム。各ピクセルは3バイト (R, G, B) で、合計の長さは width * height * 3 です。

raw_size

デコード前にカメラがUSB経由で送信したバイト数。実際のスループット計算に役立ちます。

パッケージは返す前にカメラのネイティブフォーマット(GRAYSCALERGB565JPEG)をRGB888に変換するため、ホストはビットパックされたRGB565やJPEGの伸張処理を自分で扱う必要がありません。グレースケールフレームは、輝度(luma)値が3つのチャンネルすべてに複製された状態で返されます。

data バッファは上から下へ行ごとにレイアウトされています。これをそのまま表示ライブラリに渡したり、RAW RGBファイルとして保存したりしても、それ以上の並べ替えなしで動作します。

13.3.1.3.3. RAWストリーミングモード

デフォルトでは、カメラは取得した各フレームをストリームチャンネルに置く前に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() はより安価なチェックです。これは登録された各チャンネル名を「データが準備できている」ことを示すブール値にマッピングした辞書を返します:

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() を呼び出すと、スクリプトを停止することで暗黙的に同じ動作をします。