4.15. 프레임 버퍼

카메라 센서가 초기화되면 애플리케이션의 준비 여부와 관계없이 프레임 속도에 맞춰 프레임 주기마다 새 프레임을 하나씩 연속적으로 내보냅니다. 각 프레임은 RAM 어딘가에 저장될 곳이 필요하며, 그렇지 않으면 손실됩니다. 프레임 버퍼 풀은 이러한 프레임이 DMA를 떠난 뒤 사용자 코드에 의해 처리되기 전까지 머무는 곳이며, 카메라가 그 풀에 유지하는 프레임 버퍼의 개수가 DMA와 애플리케이션이 이를 공유하는 방식을 결정합니다. 이 선택은 framebuffers() 를 통해 노출되며, 버퍼 개수로 선택되는 네 가지 모드가 제공됩니다.

4.15.1. 단일 버퍼 (count = 1)

RAM에 하나의 프레임 버퍼가 있습니다. DMA가 이를 채우고, 애플리케이션이 이를 읽습니다. 동일한 버퍼가 양쪽 모두에 필요하기 때문에, 다음 snapshot() 호출은 애플리케이션이 버퍼를 해제할 때까지 시작될 수 없습니다.

카메라와 애플리케이션이 보조를 맞춰(lock-step) 동작합니다. DMA는 애플리케이션이 끝나기를 기다려야 하고, 애플리케이션은 DMA가 끝나기를 기다려야 하므로, 달성 가능한 프레임 속도는 잘해야 센서 프레임 속도의 절반 입니다. 센서가 내보내는 프레임은 한 번 걸러 한 번씩 버퍼가 사용 중일 때 도착하여 손실됩니다.

이 모드는 RAM을 가장 적게 사용하지만 처리량은 가장 느립니다. 두 번째 버퍼를 할당하기에 RAM이 너무 부족할 때만 사용하십시오.

4.15.2. 이중 버퍼 (count = 2)

RAM에 두 개의 프레임 버퍼가 있습니다. DMA가 채우는 백(back) 버퍼 하나와 애플리케이션이 읽는 프론트(front) 버퍼 하나입니다. 애플리케이션이 프론트 버퍼 처리를 끝내면 두 역할이 서로 교체되어, DMA는 방금 해제된 버퍼를 채우기 시작하고 애플리케이션은 방금 채워진 버퍼에서 읽습니다.

애플리케이션이 각 프레임을 카메라 프레임 주기 하나보다 짧은 시간에 처리하는 한, 애플리케이션은 센서의 전체 프레임 속도를 볼 수 있습니다. 애플리케이션이 다시 snapshot() 을 호출할 때 DMA의 다음 프레임이 이미 백 버퍼에서 대기하고 있기 때문입니다. 그러나 처리 시간이 프레임 주기 하나를 초과하는 순간 속도는 절반으로 떨어집니다. 애플리케이션이 한 프레임을 처리하는 시간 동안 카메라는 두 프레임을 생성하고, 그 둘 중 두 번째 프레임만 전달됩니다.

그 지점을 넘어서면 속도는 처리 시간에 따라 매끄럽게 저하됩니다. 애플리케이션이 아직 프론트 버퍼를 작업하는 동안 DMA가 새 백 버퍼 프레임을 완성할 때마다, 새 프레임은 버려지는 대신 이전 캡처를 그 자리에서 덮어씁니다. 애플리케이션은 다음 snapshot() 시 항상 카메라가 생성한 가장 최근 프레임을 받으며, 달성 가능한 애플리케이션 속도는 처리 시간의 역수가 됩니다.

4.15.3. 삼중 버퍼 (count = 3)

RAM에 세 개의 프레임 버퍼가 있습니다. DMA가 번갈아 사용하는 백(back) 버퍼 두 개와 애플리케이션이 현재 작업 중인 프론트(front) 버퍼 하나입니다. 이는 RAM에 여유가 있을 때 OpenMV Cam이 선택하는 기본 모드이며, 여유가 없을 때는 이중 또는 단일 버퍼로 자동 폴백됩니다.

세 번째 버퍼는 카메라 프레임 속도를 애플리케이션 프레임 속도로부터 완전히 분리합니다. DMA는 항상 쓸 버퍼를 가지고 있고, 애플리케이션은 항상 읽을 버퍼를 가지고 있습니다. 각 snapshot() 호출 시 준비된 가장 최근의 백 버퍼가 새 프론트 버퍼가 되고 이전 프론트 버퍼는 DMA를 위해 해제됩니다. 애플리케이션의 프레임 속도는 각 프레임을 처리하는 데 실제로 걸리는 시간과 일치하며, 처리 시간이 프레임 주기 하나를 살짝 넘길 때 이중 버퍼가 빠지는 1/2 단계 현상이 없습니다.

4.15.4. 비디오 FIFO (count = 4 이상)

RAM에 네 개 이상의 프레임 버퍼가 있으며, 연속적으로 캡처된 프레임의 링(ring) 형태로 배열됩니다. 카메라가 전달하는 모든 프레임이 FIFO에 큐잉되며, snapshot() 은 가장 최근 프레임이 아니라 가장 오래된 큐잉된 프레임을 반환합니다. 애플리케이션은 캡처된 프레임을 캡처 순서대로, 각 프레임에 실제로 할애할 수 있는 시간 내에 차례로 처리합니다.

이 모드는 모든 프레임이 중요하고 짧은 처리 지연이 예상될 때 올바른 선택입니다. 지우기(erase) 작업 중 저장 스택이 수십 밀리초 동안 블로킹될 수 있는 SD 카드에 비디오를 기록하거나, 읽기를 잠시 멈추는 호스트로 USB를 통해 스트리밍하거나, 빠른 이벤트의 짧은 버스트를 코드에서 검사하기 위해 버퍼링하는 경우입니다.

FIFO가 애플리케이션이 비우기 전에 가득 차는 경우를 두 가지 정책으로 처리합니다.

  • 오래된 프레임 버리기 (기본값). FIFO가 가득 차면 활성 프레임을 제외한 모든 큐잉된 프레임이 버려지므로 다음 snapshot() 은 오래된 프레임이 아니라 최근 프레임을 반환합니다. DMA는 그동안 계속 캡처하므로 애플리케이션은 지연 후에도 항상 새로운 데이터를 봅니다. 이는 캡처된 스트림을 최신 상태로 유지하는 것이 목표일 때(비디오 녹화, 라이브 스트리밍) 올바른 정책입니다.

  • 오버플로 시 캡처 중지. CSI 생성자에 fflush=False 를 전달하면 FIFO가 가득 찼을 때 DMA가 채우기를 멈추고 큐잉된 프레임을 그대로 유지합니다. snapshot() 은 애플리케이션이 이를 모두 비울 때까지 캡처 순서대로 프레임을 계속 반환하며, 그 후 DMA가 재개됩니다. 이는 짧은 버스트의 모든 프레임을 보존하는 것이 목표일 때(빠른 동작을 캡처하여 나중에 코드에서 프레임 단위로 검사) 올바른 정책입니다.

전체 API는 csi.CSI.framebuffers() 를 참조하십시오.

4.15.5. 트리거 모드

위의 항상 실행되는 모드에 대한 대안은 트리거(triggered) 캡처로, 센서는 snapshot() 이 요청할 때만 프레임을 내보냅니다. 카메라는 스냅샷 사이에 유휴 상태로 있다가 애플리케이션이 호출할 때마다 새로운 노출을 시작합니다.

그 대가는 처리량입니다. 트리거 캡처는 이전 캡처와 겹칠 수 없으므로 달성 가능한 최대 프레임 속도는 센서 정상 속도의 절반입니다. 이점은 노출 타이밍입니다. 스냅샷은 노출이 시작되는 시점을 정확히 제어하는데, 이는 노출이 외부 이벤트(스트로브 플래시, 컨베이어 위치 센서, GPIO 라인의 펄스 등)와 정렬되어야 할 때 애플리케이션이 원하는 것입니다. 그렇지 않으면 애플리케이션이 읽을 준비가 되었을 때 자유 동작(free-running) 센서의 롤링 프레임이 우연히 위치한 곳에서 노출이 이루어집니다.

트리거 모드는 센서별로 다릅니다. 지원되는 센서에서는 csi0.ioctl(csi.IOCTL_SET_TRIGGERED_MODE, True) 를 호출하여 활성화하고 False 를 전달하여 비활성화합니다.