10.2. 回傳一張快照

狀態端點固然不錯,但相機存在的理由是它的鏡頭。我們來新增一個端點,回傳 sensor 此刻所看畫面的 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 物件

回傳 dict 的處理常式讓 microdot 完成其餘工作。但 JPEG 位元組需要使用完整寫法:明確建構一個 microdot.Responsebody 參數可接受任何類位元組的值——相機的 image.Image 緩衝區透過 bytearray() 公開,因此 sensor 寫入的同一個緩衝區會直接送往 socket。

Content-Type: image/jpeg 是用來告訴瀏覽器把主體呈現為影像的依據。少了它,瀏覽器會試圖把 JPEG 位元組當成文字顯示,於是你會看到滿螢幕的亂碼。

image.Image.compress() 會就地對現有的影像緩衝區執行 JPEG 編碼,並回傳同一張影像(現在已是 JPEG 格式),這樣它的位元組就能原樣送出。quality=85 是常見的預設值——高到足以讓畫面清晰,又低到足以讓檔案能透過慢速連線傳輸。

10.2.2. 擷取會阻塞迴圈

csi.CSI.snapshot() 會等待 sensor 完成曝光並以 DMA 傳輸一個影格之後才回傳。在 async 處理常式內部,這意味著事件迴圈會在曝光期間停滯——依照光線狀況而定,可能是十、二十或五十毫秒。當只有一個客戶端一次請求一個路由時,這是看不出來的;但若有多個客戶端,或同時運行一個擷取協程,它就會阻塞其他所有事情。

針對多協程的情況,snapshot() 有一個非阻塞的變體(blocking=False 會回傳下一個就緒的影格,或回傳 None)。對於每個請求只取一張快照的情況,預設的阻塞式呼叫就夠了。

現在擁有者可以戳一個 URL,就取得一張新的影格。