4.16. 이미지 미리보기

프레임 버퍼 풀은 애플리케이션이 프레임을 읽는 곳입니다. 애플리케이션이 그 프레임을 작업하는 동안, 프레임을 미리 보기 위해 카메라에 연결된 무엇이든 각 프레임의 사본도 필요합니다. 카메라에는 이 목적을 위한 두 번째 전용 버퍼가 있으며, 채워지는 시점에 대한 단일 규칙이 있습니다. 애플리케이션이 snapshot() 을 호출할 때마다, 새 프레임이 반환되기 전에 이전에 캡처된 프레임이 미리보기 버퍼로 복사됩니다.

애플리케이션과 미리보기 도구는 동일한 메모리를 두고 절대 경합하지 않습니다. 애플리케이션은 풀에서 자신의 프레임을 읽고, 미리보기 도구는 미리보기 버퍼에서 자신의 프레임을 읽습니다. 둘 다 병렬로 일어납니다.

4.16.1. 스트림 프레임 버퍼

미리보기 버퍼, 즉 스트림 프레임 버퍼(stream framebuffer) 는 프레임 버퍼 풀과 분리된 고정 크기의 단일 RAM 영역입니다. 그 크기는 펌웨어 빌드 시점에 설정되며 framesize()pixformat() 에 따라 변하지 않습니다. 최근 OpenMV Cam에서는 약 1메가바이트가 일반적이며, 이는 적당한 해상도의 미리보기를 담기에 충분하지만 가장 큰 센서 크기의 전체 해상도 프레임보다는 훨씬 작습니다.

애플리케이션 코드는 이 버퍼를 직접 읽거나 쓰지 않습니다. 카메라 드라이버가 snapshot() 의 부수 효과로 이를 채웁니다.

4.16.2. 스냅샷이 미리보기를 위해 하는 일

snapshot() 을 호출할 때마다, 드라이버는 애플리케이션의 이전 프레임 버퍼를 풀로 되돌리고 새 버퍼를 넘겨주기 전에, 이전 프레임을 미리보기 버퍼로 복사합니다. 이때 처리 과정에서 애플리케이션이 그 위에 그린 내용도 이미지에 그대로 남아 있습니다. 두 가지 분기가 가능합니다. 어느 쪽이 실행될지는 카메라가 아니라 미리보기 도구가 선택합니다. 미리보기를 연 소비자가 드라이버에게 원시 이미지를 원하는지 JPEG을 원하는지, 그리고 어떤 원시 창 크기를 수용할 수 있는지 알려줍니다.

  • 원시 다운스케일 사본. 미리보기 도구가 원시 프레임을 요청한 경우, 드라이버는 이전 프레임을 네이티브 픽셀 형식(RGB565, 그레이스케일 등)으로 복사합니다. 프레임이 미리보기 도구가 요청한 원시 창보다 크면 드라이버는 맞을 때까지 양선형(bilinear) 필터링으로 축소합니다. 그렇지 않으면 픽셀이 변경 없이 전달됩니다. 압축 아티팩트가 없으며, 미리보기 도구는 애플리케이션이 작업하던 것과 동일한 픽셀을 봅니다.

  • JPEG 압축. 미리보기 도구가 JPEG을 요청했거나 원시 사본이 스트림 버퍼에 전혀 맞지 않는 경우, 드라이버는 이전 프레임을 전체 해상도로 JPEG 압축하여 스트림 버퍼에 넣습니다. 품질은 압축된 출력이 스트림 버퍼 용량 내에 머무르도록 프레임마다 적응적으로 조정됩니다. 프레임이 맞으면 드라이버는 캡처된 프레임의 픽셀 크기에 따라 달라지는 상한을 향해 품질을 한 단계씩 서서히 올립니다(작은 프레임은 더 높은 품질이 허용되고, 큰 프레임은 작은 내용 변화에도 오버플로되지 않도록 상한이 더 낮게 제한됩니다). 프레임이 맞지 않으면 드라이버는 현재 품질을 절반으로 낮추고, 새 설정이 안정될 시간을 주기 위해 다음 수십 프레임 동안 그 낮춰진 수준을 유지하며, 오버플로된 프레임을 미리보기에서 버립니다. 애플리케이션 루프는 영향받지 않고 계속 실행되며, 미리보기 도구만 버려진 프레임을 놓칩니다.

카메라가 이미 압축된 형식(JPEG을 직접 내보내는 센서의 JPEG 픽셀 형식)으로 생성하는 프레임은 두 분기를 모두 건너뜁니다. 인코딩된 비트스트림이 그대로 미리보기 버퍼로 곧장 복사됩니다.

미리보기 도구는 자체 일정에 따라 폴링하며, 일반적으로 카메라 캡처보다 훨씬 느립니다. 따라서 원시 캡처 속도를 서브샘플링(sub-sample) 합니다. 즉, 제때 읽어낸 스냅샷만 표시됩니다. 미리보기 도구가 이전 프레임을 읽어내기 전에 새 snapshot() 이 미리보기 버퍼에 도착하면, 버퍼가 여전히 미리보기 도구에 의해 잠겨 있으므로 새 미리보기 업데이트는 건너뜁니다. 그 캡처는 미리보기 스트림에서 손실됩니다. 애플리케이션 자체의 프레임 버퍼 풀은 영향받지 않으며, 캡처된 프레임은 여전히 정상적으로 애플리케이션으로 전달됩니다.

4.16.3. 마지막 프레임을 수동으로 푸시하기

미리보기는 snapshot() 의 부수 효과로 갱신되기 때문에, 다시 스냅샷을 호출하지 않고 끝나는 스크립트는 마지막으로 미리보기에 보낸 것이 무엇이든 미리보기 도구에 무기한 남겨둡니다. 첫 스냅샷 이전에 작업을 마치고 종료하는 스크립트의 경우, 이는 빈 미리보기가 됩니다. image.Image.flush() (또는 CSI 객체의 동등한 flush())는 새 프레임을 캡처하지 않고 요청 시 애플리케이션 프레임 버퍼의 현재 내용을 스트림 버퍼로 복사합니다:

img = csi0.snapshot()
# process the image and draw on it
img.flush()                               # previewer sees the annotated frame

같은 호출은 스냅샷 사이에 오래 실행되는 작업이 있어서 그렇지 않으면 미리보기 도구가 내내 오래된 미리보기를 표시하게 될 때도 유용합니다.

참고

미리보기 애플리케이션은 스크립트가 종료되기 전에 스트림 버퍼에서 프레임을 읽어내야 합니다. 짧은 스크립트의 끝에서 호출하는 flush는 프레임을 준비시킬 뿐입니다. 미리보기 도구가 폴링하기 전에 스크립트가 제어를 카메라로 돌려주면, 다음 실행에서 버퍼가 재사용되어 그 마지막 프레임은 손실됩니다. 스크립트 종료 시점의 미리보기를 위해서는, 스크립트가 끝나기 전에 미리보기 도구가 프레임을 가져갈 잠깐의 시간을 주십시오(flush 후 짧은 sleep을 두거나, 단순히 즉시 종료하지 않기).