4.16. Pré-visualização de imagem¶
O pool de framebuffers é onde a aplicação lê seus quadros. Enquanto a aplicação está trabalhando nesses quadros, o que quer que esteja conectado à câmera para pré-visualizá-los também precisa de uma cópia de cada quadro. A câmera tem um segundo buffer, dedicado a esse propósito, e uma única regra para quando ele é preenchido: cada vez que a aplicação chama snapshot(), o quadro capturado anterior é copiado para o buffer de pré-visualização antes que o novo quadro seja devolvido.
A aplicação e o pré-visualizador nunca disputam a mesma memória. A aplicação lê seu quadro do pool; o pré-visualizador lê seu quadro do buffer de pré-visualização. Ambos acontecem em paralelo.
4.16.1. O framebuffer de stream¶
O buffer de pré-visualização – o framebuffer de stream – é uma única região de RAM de tamanho fixo, separada do pool de framebuffers. Seu tamanho é definido no momento da compilação do firmware e não muda com framesize() ou pixformat(). Cerca de um megabyte é típico nas OpenMV Cams recentes – grande o suficiente para conter uma pré-visualização de resolução moderada, muito menor que um quadro em resolução completa nos maiores tamanhos de sensor.
O código da aplicação não lê nem escreve neste buffer diretamente; o driver da câmera o preenche como um efeito colateral de snapshot().
4.16.2. O que o snapshot faz pela pré-visualização¶
A cada chamada a snapshot(), antes de o driver liberar o framebuffer anterior da aplicação de volta para o pool e entregar o novo, ele copia o quadro anterior para o buffer de pré-visualização – com o que quer que a aplicação tenha desenhado sobre ele durante o processamento ainda na imagem. Dois ramos são possíveis. Qual deles é executado é escolhido pelo pré-visualizador, não pela câmera: o consumidor que abriu a pré-visualização informa ao driver se deseja a imagem bruta (raw) ou um JPEG, e qual tamanho de janela raw ele pode aceitar.
Cópia raw reduzida. Quando o pré-visualizador solicitou quadros raw, o driver copia o quadro anterior em seu formato de pixel nativo (RGB565, escala de cinza, etc.). Se o quadro for maior que a janela raw que o pré-visualizador solicitou, o driver o reduz com filtragem bilinear até que caiba; caso contrário, os pixels passam inalterados. Sem artefatos de compressão; o pré-visualizador vê os mesmos pixels com os quais a aplicação estava trabalhando.
Compressão JPEG. Quando o pré-visualizador solicitou JPEG – ou quando a cópia raw simplesmente não caberia no buffer de stream – o driver comprime o quadro anterior em JPEG na resolução completa dele para dentro do buffer de stream. A qualidade é ajustada adaptativamente por quadro, de modo que a saída comprimida permaneça dentro da capacidade do buffer de stream. Quando um quadro cabe, o driver aumenta a qualidade de volta em um passo em direção a um teto que depende do tamanho de pixel do quadro capturado (quadros menores têm permissão para qualidade mais alta; quadros maiores são limitados a um nível mais baixo para que não possam transbordar em uma pequena mudança de conteúdo). Quando um quadro não cabe, o driver reduz a qualidade atual pela metade, mantém-na no nível reduzido pelas próximas várias dezenas de quadros para que a nova configuração tenha tempo de se estabilizar, e descarta da pré-visualização o quadro que transbordou. O loop da aplicação continua funcionando sem ser afetado; apenas o pré-visualizador perde o quadro descartado.
Quadros que a câmera produz em um formato que já é comprimido (o formato de pixel JPEG em sensores que emitem JPEG diretamente) pulam ambos os ramos: o bitstream codificado é copiado diretamente para o buffer de pré-visualização como está.
O pré-visualizador faz polling em seu próprio ritmo, geralmente muito mais lento do que a câmera captura, então ele subamostra a taxa de captura raw: apenas os snapshots que ele consegue ler a tempo são exibidos. Se um novo snapshot() chega ao buffer de pré-visualização antes que o pré-visualizador tenha lido o quadro anterior, o buffer ainda está bloqueado pelo pré-visualizador e a nova atualização de pré-visualização é ignorada – essa captura é perdida do fluxo de pré-visualização. O próprio pool de framebuffers da aplicação não é afetado; o quadro capturado ainda vai para a aplicação normalmente.
4.16.3. Enviando o último quadro manualmente¶
Como a pré-visualização é atualizada como um efeito colateral de snapshot(), um script que termina sem nunca chamar o snapshot novamente deixa o que quer que tenha enviado por último para a pré-visualização parado no pré-visualizador indefinidamente – o que, para um script que faz seu trabalho antes do primeiro snapshot e então encerra, é uma pré-visualização vazia. image.Image.flush() (ou o equivalente flush() no objeto CSI) copia o conteúdo atual do framebuffer da aplicação para o buffer de stream sob demanda, sem capturar um novo quadro:
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 ocorre entre snapshots e o pré-visualizador, de outra forma, mostraria uma pré-visualização obsoleta o tempo todo.
Nota
A aplicação de pré-visualização precisa ler o quadro do buffer de stream antes que o script encerre. Um flush no fim de um script curto apenas prepara o quadro; se o script então devolver o controle para a câmera antes que o pré-visualizador tenha feito o polling, o buffer é reutilizado na próxima execução e esse quadro final é perdido. Para pré-visualizações de fim de script, dê ao pré-visualizador um momento para captar o quadro (um breve sleep após o flush, ou simplesmente não encerrar imediatamente) antes que o script termine.