4.16. Pré-visualização de imagem

O conjunto de framebuffers é onde a aplicação lê os seus fotogramas. Enquanto a aplicação trabalha nesses fotogramas, o que estiver ligado à câmara para os pré-visualizar também precisa de uma cópia de cada fotograma. A câmara tem um segundo buffer dedicado para esse fim, e uma única regra para quando é preenchido: de cada vez que a aplicação chama snapshot(), o fotograma capturado anteriormente é copiado para o buffer de pré-visualização antes de o novo fotograma ser entregue.

A aplicação e o visualizador nunca disputam a mesma memória. A aplicação lê o seu fotograma do conjunto; o visualizador lê o seu fotograma do buffer de pré-visualização. Ambos acontecem em paralelo.

4.16.1. O framebuffer de transmissão

O buffer de pré-visualização – o framebuffer de transmissão – é uma região de RAM de tamanho fixo separada do conjunto de framebuffers. O seu tamanho é definido no momento da compilação do firmware e não muda com framesize() ou pixformat(). Cerca de um megabyte é o valor típico nas OpenMV Cams recentes – suficientemente grande para conter uma pré-visualização de resolução moderada, muito menor do que um fotograma a resolução máxima nos maiores tamanhos de sensor.

O código da aplicação não lê nem escreve diretamente neste buffer; o controlador da câmara preenche-o como efeito secundário de snapshot().

4.16.2. O que o snapshot faz para a pré-visualização

Em cada chamada a snapshot(), antes de o controlador libertar o framebuffer anterior da aplicação de volta ao conjunto e entregar o novo, copia o fotograma anterior para o buffer de pré-visualização – com tudo o que a aplicação desenhou sobre ele durante o processamento ainda na imagem. São possíveis dois ramos. O que é executado é escolhido pelo visualizador, não pela câmara: o consumidor que abriu a pré-visualização informa o controlador se quer a imagem em bruto ou um JPEG, e o tamanho da janela bruta que consegue aceitar.

  • Cópia bruta reduzida. Quando o visualizador pediu fotogramas em bruto, o controlador copia o fotograma anterior no seu formato de pixel nativo (RGB565, escala de cinzentos, etc.). Se o fotograma for maior do que a janela bruta solicitada pelo visualizador, o controlador reduz a sua escala com filtragem bilinear até caber; caso contrário, os pixels passam sem alteração. Sem artefactos de compressão; o visualizador vê os mesmos pixels com que a aplicação estava a trabalhar.

  • Compressão JPEG. Quando o visualizador pediu JPEG – ou quando a cópia bruta não caberia de todo no buffer de transmissão – o controlador comprime em JPEG o fotograma anterior com a sua resolução completa para o buffer de transmissão. A qualidade é ajustada adaptativamente por fotograma para que a saída comprimida caiba dentro da capacidade do buffer de transmissão. Quando um fotograma cabe, o controlador aumenta gradualmente a qualidade num passo em direção a um teto que depende do tamanho em pixels do fotograma capturado (fotogramas mais pequenos permitem qualidade mais elevada; fotogramas maiores têm um teto mais baixo para que não possam transbordar com uma pequena alteração de conteúdo). Quando um fotograma não cabe, o controlador reduz a qualidade atual para metade, mantém-na ao nível reduzido durante as próximas dezenas de fotogramas para que a nova configuração tenha tempo de estabilizar, e descarta o fotograma transbordado da pré-visualização. O ciclo da aplicação continua a funcionar sem ser afetado; apenas o visualizador perde o fotograma descartado.

Os fotogramas que a câmara produz num formato já comprimido (o formato de pixel JPEG em sensores que emitem JPEG diretamente) saltam ambos os ramos: o bitstream codificado é copiado diretamente para o buffer de pré-visualização tal como está.

O visualizador faz polling ao seu próprio ritmo, geralmente muito mais lento do que a câmara captura, pelo que subamostra a taxa de captura bruta: apenas as capturas que consegue ler a tempo são mostradas. Se um novo snapshot() chegar ao buffer de pré-visualização antes de o visualizador ter lido o fotograma anterior, o buffer ainda está bloqueado pelo visualizador e a nova atualização de pré-visualização é ignorada – essa captura perde-se da corrente de pré-visualização. O conjunto de framebuffers da própria aplicação não é afetado; o fotograma capturado continua a ir normalmente para a aplicação.

4.16.3. Enviar o último fotograma manualmente

Uma vez que a pré-visualização é atualizada como efeito secundário de snapshot(), um script que termina sem nunca mais chamar snapshot deixa indefinidamente no visualizador o último fotograma que lhe enviou – o que, no caso de um script que faz o seu trabalho antes da primeira captura e depois sai, é uma pré-visualização vazia. image.Image.flush() (ou o equivalente flush() no objeto CSI) copia os conteúdos atuais do framebuffer da aplicação para o buffer de transmissão a pedido, sem capturar um novo fotograma:

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

A mesma chamada também é útil quando uma operação de longa duração está intercalada entre capturas e o visualizador mostraria de outro modo uma pré-visualização desatualizada durante todo esse tempo.

Nota

A aplicação de pré-visualização tem de ler o fotograma do buffer de transmissão antes de o script sair. Um flush no final de um script curto apenas prepara o fotograma; se o script devolver então o controlo à câmara antes de o visualizador ter feito polling, o buffer é reutilizado na próxima execução e esse fotograma final perde-se. Para pré-visualizações no final do script, dê ao visualizador um momento para recolher o fotograma (uma breve suspensão após o flush, ou simplesmente não sair imediatamente) antes de o script terminar.