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 객체

dict를 반환하는 핸들러는 나머지를 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 전송을 마칠 때까지 기다린 뒤에 반환합니다. async 핸들러 안에서는 이것이 노출이 진행되는 동안 – 조명에 따라 10, 20, 50밀리초 동안 – 이벤트 루프를 멈추게 한다는 뜻입니다. 한 번에 하나의 클라이언트가 하나의 라우트를 요청할 때는 이것이 눈에 띄지 않습니다. 하지만 여러 클라이언트가 있거나 캡처 코루틴이 함께 돌고 있다면, 그 외 모든 것을 막아 버릴 것입니다.

여러 코루틴이 동작하는 경우를 위해 snapshot() 의 논블로킹 변형이 존재합니다(blocking=False 는 다음으로 준비된 프레임 또는 None 을 반환합니다). 요청당 스냅샷 하나라면 기본 블로킹 호출로 충분합니다.

이제 소유자는 URL을 찔러 새 프레임을 받을 수 있습니다.