10.2. スナップショットを返す¶
ステータスエンドポイントも結構ですが、カメラが存在する理由はレンズにあります。センサーが今まさに見ているものの JPEG を返すエンドポイントを追加しましょう。
import csi
from microdot import Response
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
@app.get('/snapshot.jpg')
async def snapshot(request):
img = csi0.snapshot().compress(quality=85)
return Response(
body=img.bytearray(),
headers={'Content-Type': 'image/jpeg'},
)
ブラウザから http://<cam-ip>/snapshot.jpg にアクセスすると、現在の映像の JPEG がタブいっぱいに表示されます。更新すれば新しいものが得られます。
10.2.1. Response オブジェクト¶
辞書を返すハンドラなら、残りは microdot が処理してくれます。一方、JPEG バイト列にはより詳細な形式が必要です。すなわち microdot.Response を明示的に構築します。body 引数は任意の bytes 系の値を取ります。カメラの image.Image バッファは bytearray() を介して公開されるため、センサーが書き込んだのと同じバッファがそのままソケットへ送られます。
Content-Type: image/jpeg は、ボディを画像としてレンダリングするようブラウザに伝えるものです。これがないと、ブラウザは JPEG バイト列をテキストとして表示しようとし、画面いっぱいの文字化けが見えてしまいます。
image.Image.compress() は既存の画像バッファに対して JPEG エンコードをその場で実行し、同じ画像(JPEG 形式になったもの)を返すので、そのバイト列をそのまま送信できます。quality=85 は一般的なデフォルト値で、画像が鮮明になる程度には高く、低速な回線を通る程度にはファイルが小さくなる値です。
10.2.2. キャプチャはループをブロックする¶
csi.CSI.snapshot() は、センサーがフレームの露出と DMA 転送を終えるのを待ってから返ります。非同期ハンドラ内では、これは露出時間のあいだイベントループが停止することを意味します。照明条件に応じて 10、20、50 ミリ秒です。1 つのクライアントが一度に 1 つのルートを要求するだけなら、これは見えません。しかし複数のクライアントがいる場合や、キャプチャ用のコルーチンが並行して動いている場合には、他のすべてをブロックしてしまいます。
複数コルーチンのケースのために、snapshot() のノンブロッキング版が存在します(blocking=False は次の準備済みフレームか None を返します)。リクエストごとに 1 枚のスナップショットなら、デフォルトのブロッキング呼び出しで問題ありません。
これで所有者は URL を叩いて新しいフレームを取得できるようになりました。