5.32. Guardar e compressão¶
Todas as páginas até agora trabalharam com imagens na câmara: capturadas para o buffer de fotograma ou alocadas na heap do MicroPython, manipuladas através dos métodos do módulo de imagem, e exibidas na pré-visualização do IDE ou passadas para uma etapa seguinte no mesmo script. A maioria das aplicações precisa, em algum momento, de fazer o oposto: pegar numa imagem que está atualmente em RAM e colocá-la algures de forma persistente – no cartão SD, num anfitrião USB, por rede – onde algo que não seja a câmara possa lê-la.
O módulo de imagem expõe dois caminhos para esse trabalho. O caminho save escreve a imagem num ficheiro no sistema de ficheiros, com o formato escolhido pela extensão e os detalhes de codificação tratados pelo método. O caminho to-format devolve um objeto Image contendo o fluxo de bytes codificado, adequado para passar a uma chamada de transmissão ou de rede sem nunca tocar no sistema de ficheiros. Cada um adapta-se a uma aplicação diferente; ambos assentam no mesmo motor de compressão por baixo.
5.32.1. Guardar num ficheiro¶
save() escreve a imagem no sistema de ficheiros num caminho:
img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)
O formato é escolhido a partir da extensão do ficheiro. São reconhecidas cinco extensões: .bmp escreve um bitmap Windows (sem perdas, sem compressão, byte a byte os pixeis capturados); .pgm escreve um graymap portátil (sem perdas, apenas escala de cinzentos); .ppm escreve um pixmap portátil (sem perdas, RGB); .jpg e .jpeg escrevem ambos um JPEG (com perdas, comprimido). A imagem receptora deve já estar no formato de cor correto para o contentor escolhido – uma imagem a cores guardada como .pgm constitui um erro.
roi restringe o guardado a um sub-rectângulo da imagem, tal como o parâmetro roi de qualquer outro método do módulo de imagem. O padrão é a imagem completa. A palavra-chave é ignorada ao guardar uma imagem com compressão JPEG porque a forma no disco já cobre o fotograma completo e recodificar através de um recorte contrariaria o objetivo de guardar os bytes comprimidos existentes.
quality é a qualidade de compressão JPEG de 0 a 100 e só é relevante quando a saída é JPEG (a palavra-chave é ignorada nos formatos sem perdas). O padrão de 50 é o equilíbrio certo para a maioria das aplicações; 70 a 85 é a gama para maior qualidade visual, 30 a 50 é o intervalo certo para miniaturas pequenas e transmissão com largura de banda limitada, e 90 e acima está reservado para casos em que a imagem será inspecionada manualmente ou processada por um algoritmo que seja sensível a artefactos de compressão.
A imagem receptora é devolvida para que a chamada seja encadeável: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). O objeto devolvido é a mesma imagem em memória; o guardado é um efeito secundário.
Uma utilização típica é o padrão captura-e-registo. Um gatilho dispara (é detetada uma mancha, um botão é premido, um temporizador expira); o script captura um fotograma; acrescenta uma marca temporal ao nome do ficheiro; e chama save() para enviar a imagem para o cartão SD. A pré-visualização do IDE continua a funcionar, o próximo gatilho dispara, e os ficheiros guardados acumulam-se.
5.32.2. Codificar para memória¶
Quando o destino não é o sistema de ficheiros mas uma ligação de rede, uma porta série, ou a entrada de outro módulo, a aplicação precisa do fluxo de bytes codificado em memória em vez de em disco. to_jpeg() e to_png() produzem exatamente isso:
encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)
O comportamento predefinido é a conversão no lugar: o receptor é convertido numa imagem JPEG (ou PNG) e o mesmo objeto é devolvido. Com copy=True a conversão escreve para um novo objeto alocado na heap; com copy_to_fb=True a saída fica no buffer de fotograma. A escolha é a mesma que qualquer outro método de conversão oferece – no lugar por padrão, em cópia quando o original é necessário depois.
quality e subsampling são os mesmos ajustes JPEG que o caminho de guardado expõe. subsampling escolhe o esquema de subamostragem de croma: image.JPEG_SUBSAMPLING_AUTO escolhe o melhor para a qualidade selecionada, image.JPEG_SUBSAMPLING_444 mantém o croma em resolução total (ficheiro maior, melhor precisão de cor), image.JPEG_SUBSAMPLING_422 e image.JPEG_SUBSAMPLING_420 reduzem a resolução do croma a metade ao longo de um ou ambos os eixos (ficheiros menores, ligeiro suavizamento de cor que é invisível a distâncias de visualização típicas). O padrão de AUTO é a escolha certa a menos que a aplicação tenha uma necessidade específica.
PNG via to_png() é sem perdas mas mais lento a codificar e produz ficheiros maiores do que JPEG para conteúdo fotográfico (o conteúdo fotográfico comprime mal com o esquema de previsão do PNG). Use PNG quando a imagem é arte vetorial, uma captura de ecrã, ou contém gráficos de arestas nítidas desenhados sobre um fotograma capturado – a codificação sem perdas preserva as arestas nítidas que o JPEG suavizaria. Caso contrário, JPEG é o padrão correto.
Tanto to_jpeg() como to_png() aceitam as mesmas palavras-chave posicionais e de escala de estilo de desenho que outros métodos de conversão aceitam – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – pelo que a mesma chamada pode codificar uma versão redimensionada, recortada ou mapeada por paleta da fonte num único passo. compress() é a grafia legada de to_jpeg(); os dois aceitam os mesmos argumentos e produzem o mesmo resultado.
5.32.3. O que a compressão proporciona¶
Vale a pena calcular uma vez os números por trás da troca entre JPEG e dados brutos.
Um fotograma RGB565 de 320 por 240 tem 153.600 bytes (um fotograma capturado em QVGA). Um fotograma de 640 por 480 tem 614.400 bytes; um fotograma de 1280 por 960 tem 2.457.600 bytes. Nenhum desses é grande comparado a um ecrã de computador ou telemóvel, mas são substanciais no contexto de uma câmara com alguns MB de RAM no total, um cartão SD com largura de banda de escrita finita, e uma ligação ao anfitrião que tipicamente funciona por USB CDC, UART, ou um módulo sem fios a velocidades modestas.
JPEG com quality=50 tipicamente comprime um fotograma fotográfico capturado 10x a 20x: esse fotograma de 614 KB de 640 por 480 torna-se um fluxo de bytes codificado de 30 a 60 KB. Com quality=85 a compressão desce para 5x a 10x (60 a 120 KB para o mesmo fotograma). Com quality=10 – com artefactos mas ainda reconhecível – a compressão atinge 30x a 50x (12 a 20 KB).
Estes números determinam o que é prático fazer com os fotogramas guardados. Um caminho para cartão SD que sustente 10 MB/s trata 30 fotogramas por segundo de conteúdo VGA codificado em JPEG com quality=50 com folga (cerca de 1 a 2 MB/s); guardar o mesmo conteúdo não comprimido requer mais de 18 MB/s, além do que o caminho do sistema de ficheiros da câmara sustenta para o cartão. Um anfitrião USB a puxar fotogramas codificados em JPEG por CDC a 1 MB/s recebe fotogramas de 30 a 60 KB a aproximadamente 15 a 30 fotogramas por segundo; puxando fotogramas brutos à mesma taxa obtém um ou dois fotogramas por segundo.
Em resumo: os métodos de compressão não são apenas uma conveniência para guardar. São o que torna o fotograma capturado utilizável fora da câmara a taxas de fotogramas relevantes para a aplicação. Escolher a compressão certa – qualidade JPEG 50 para registo geral, 80 para trabalho de qualidade, PNG para captura de arte vetorial – faz parte do trabalho de rotina de qualquer aplicação de câmara não trivial.