5.3. Formatos de pixel

Um algoritmo que deteta arestas espera que cada pixel contenha um valor de brilho. Um algoritmo que rastreia um objeto colorido espera que cada pixel transporte cor. Um algoritmo que executa fecho morfológico espera que cada pixel esteja ligado ou desligado. O formato de pixel que uma Image transporta – um dos enumerados no catálogo de Vision Sensors – é o que torna essas expectativas verificáveis antecipadamente: o formato diz, previamente, em que forma se encontram os pixels e quais os algoritmos que podem, por isso, operar neles sem uma etapa de conversão.

Esta página aborda como essa restrição se manifesta na prática. O formato correto depende do que a pipeline vai fazer, e os métodos de conversão entre formatos são a forma como uma pipeline que precisa de mais de um deles encadeia as etapas.

A vertical stack of five labelled byte-layout strips. BINARY shows one byte split into eight single-bit cells, marked "8 pixels per byte". GRAYSCALE shows three labelled single-byte cells each marked "1 pixel". RGB565 shows two adjacent bytes with bit fields RRRRR GGGGGG BBBBB labelled "1 pixel". YUV422 shows four labelled byte cells Y0, U, Y1, V marked "2 pixels". BAYER shows two rows of four labelled byte cells: R G R G on the top row, G B G B on the bottom row.

Os cinco formatos de pixel não comprimidos e como os seus bytes são agrupados. JPEG e PNG não são representados aqui porque são fluxos comprimidos de comprimento variável e não grelhas de pixels de tamanho fixo.

5.3.1. O formato de escala de cinzentos

A maior parte da visão por computador clássica resume-se a trabalhar com valores de brilho. Deteção de arestas, correspondência de modelos, descodificação de AprilTag, estimativa de fluxo ótico, os operadores morfológicos, análise de manchas – todos eles, ao nível em que os algoritmos operam, analisam o brilho de cada pixel e comparam esse brilho com o dos pixels vizinhos. A cor da cena é muitas vezes útil para a aplicação que os invoca, mas os algoritmos em si não precisam dela.

O formato de escala de cinzentos fornece aos algoritmos exatamente isso, sem qualquer sobrecarga. Um byte por pixel contém um valor de brilho de 0 (preto) a 255 (branco). O formato tem metade do tamanho de RGB565 e YUV422 e um terço do tamanho de RGB888, pelo que cada operação processa menos dados – de forma mais rápida e com menor pressão sobre o buffer. Nas câmaras mais pequenas, onde o buffer de fotograma compete com o resto do script pela RAM, essa diferença de dimensão pode ser o que decide se uma pipeline cabe ou não. Se a cor não for a indicação de que o algoritmo precisa, a escala de cinzentos é a resposta certa.

5.3.2. Cor através de RGB565

Quando a cor é a indicação – rastrear um marcador colorido, distinguir maçãs vermelhas das verdes, identificar um elemento de interface pela sua tonalidade – dois bytes por pixel fornecem cor suficiente para os tipos de classificação que os algoritmos realizam. RGB565 é o formato de cor predefinido na câmara e aquele que os métodos sensíveis à cor na superfície esperam.

Renderizar um fotograma anotado – desenhar caixas de deteção, escrever texto de diagnóstico, enviar o fotograma para um ecrã ou para um visualizador remoto – também apela naturalmente ao RGB565. A pré-visualização do IDE, os controladores de visualização integrados e a maioria dos destinos de rede consomem o formato diretamente ou convertem a partir dele de forma económica.

5.3.3. Bayer como formato de armazenamento

Uma imagem Bayer é a saída bruta do sensor, antes de o ISP a ter debayerizado para uma representação de cor completa. Cada pixel é um byte que contém um único canal de cor – o que o filtro de cor nessa posição da mosaico passou. Isso faz com que uma imagem Bayer tenha o mesmo tamanho que uma imagem em escala de cinzentos e um terço do tamanho de RGB888, o que se alinha com o que o Bayer é realmente útil para: armazenar muitos fotogramas de uma vez quando a RAM é o recurso limitante.

O problema é que os algoritmos do módulo de imagem não operam diretamente em imagens Bayer. Sem debayerização, nenhum pixel transporta informação suficiente para fazer um julgamento de cor por si só, e os padrões que os algoritmos procuram – arestas, cantos, manchas – seriam distorcidos pelo mosaico. As únicas formas de ler ou modificar uma imagem Bayer são get_pixel() e set_pixel(); todo o resto espera uma representação completa.

O padrão resultante é armazenar fotogramas em Bayer enquanto precisam de estar numa fila e converter cada um para escala de cinzentos ou RGB565 no momento em que o seu processamento efetivamente começa. A conversão custa ciclos de CPU, mas poupa a RAM que de outra forma estaria ocupada a manter fotogramas completos durante toda a duração da aplicação.

Nota

As únicas operações do módulo de imagem sobre pixels Bayer diretamente são get_pixel(), set_pixel() e o caminho de codificação JPEG que alimenta a pré-visualização do IDE ou um visualizador remoto. Desenho, análise e filtragem requerem primeiro a conversão para escala de cinzentos, RGB565 ou binário.

5.3.4. YUV422 para pipelines que precisam de ambos

YUV422 separa a informação de cada pixel num canal de luminância (Y) e dois canais de crominância (U e V), e faz subamostragem da crominância para que pares de pixels adjacentes partilhem um único U e um único V. A média de bytes por pixel é dois – o mesmo que RGB565 – mas estão dispostos de forma que o canal Y já é uma imagem em escala de cinzentos de 8 bits contínua em deslocamentos conhecidos no buffer.

Essa disposição é exatamente o que uma pipeline quer quando algumas das suas etapas são trabalho em escala de cinzentos e outras precisam de cor. Ler os valores Y diretamente para as etapas em escala de cinzentos evita o custo de uma conversão explícita; os canais U e V estão disponíveis quando uma etapa posterior realmente precisa de cor. Fora esse padrão específico, RGB565 é geralmente a escolha mais simples para cor e a escala de cinzentos é a escolha mais simples para trabalho apenas com brilho – o valor do YUV422 vem de ser bom em ambos ao mesmo tempo.

Nota

O módulo de imagem opera em YUV422 de forma mais limitada do que em escala de cinzentos, RGB565 ou binário – leituras diretas do canal Y para trabalho em escala de cinzentos e o caminho de codificação JPEG que alimenta a pré-visualização do IDE ou um visualizador remoto. Os métodos sensíveis à cor esperam RGB565; os fotogramas YUV422 necessitam de uma conversão explícita antes da análise de cor ou do desenho.

5.3.5. Binário, máscaras e saída limiarizada

Uma imagem binária é um bit por pixel: cada pixel é 0 ou 1. O formato raramente aparece como uma captura do sensor; em vez disso, surge como a saída natural da limiarização (onde um teste de cor ou brilho classifica cada pixel em «sim, corresponde» ou «não, não corresponde») e como a entrada natural para operações morfológicas e para o argumento mask que muitos métodos aceitam.

A vantagem prática do formato é o seu tamanho. Uma imagem binária tem um oitavo do tamanho de uma imagem em escala de cinzentos, pelo que transportar uma máscara grande – uma escolha por pixel de quais posições uma operação a jusante deve tocar – é económico. O facto de muitas operações aceitarem uma imagem binária como argumento de palavra-chave mask= é o outro lado do mesmo ponto: o formato é pequeno, e encadear a saída binária de uma etapa na entrada de máscara de outra é um padrão comum em pipelines.

5.3.6. JPEG e PNG na fronteira

Os objetos Image de JPEG e PNG são diferentes dos outros no catálogo. Não são grelhas de pixels; são fluxos de bytes comprimidos que codificam dados de pixels de uma forma que as operações ao nível de pixel não conseguem ler. Chamar get_pixel() num JPEG não devolve o pixel numa posição; o pixel não está desempacotado em lugar algum no buffer para o método o obter.

JPEG e PNG aparecem na fronteira do processamento de imagem, onde os dados de pixels estão a sair ou a entrar na câmara em forma comprimida. Guardar um fotograma em disco como JPEG mantém o ficheiro pequeno; enviar um fotograma por uma rede como JPEG mantém a transmissão económica; carregar um fotograma de referência de um ficheiro JPEG permite que fique em disco numa forma muito mais pequena do que os pixels brutos. Para qualquer um desses casos de utilização, a representação comprimida é a resposta certa. Para fazer qualquer processamento real num JPEG, porém, a aplicação converte-o primeiro para um formato operável – e é nessa conversão que os bytes comprimidos são expandidos em pixels e onde o balão de buffer (um JPEG de 30 KB pode tornar-se 600 KB de RGB565) realmente acontece.

5.3.7. Conversão entre formatos

O caminho de conversão é o que liga diferentes formatos numa única pipeline. Cinco métodos na classe Image recebem uma imagem existente e devolvem uma nova num formato diferente:

  • to_grayscale() produz uma imagem de um byte por pixel, o formato que os algoritmos clássicos esperam.

  • to_rgb565() produz o formato de cor de dois bytes por pixel que os métodos sensíveis à cor e a pré-visualização do IDE entendem.

  • to_bitmap() produz uma imagem binária de um bit, o formato que a morfologia e os argumentos mask aceitam.

  • to_jpeg() produz uma imagem comprimida em JPEG adequada para guardar ou transmitir.

  • to_png() produz uma imagem comprimida em PNG quando a codificação sem perdas é preferível aos ficheiros mais pequenos do JPEG.

Cada conversão é executada no local por predefinição: o buffer da imagem de origem é substituído pelo resultado convertido, e os pixels originais da origem desaparecem depois de a chamada retornar. Esta é a opção mais económica tanto para a CPU como para a memória, e é a resposta certa quando o fotograma de origem não será necessário para mais nada.

Quando a origem ainda é necessária – quando uma etapa posterior da pipeline precisa de ver o fotograma original – dois argumentos de palavra-chave substituem o comportamento predefinido no local. copy=True aloca um buffer separado para a imagem convertida no heap Python e deixa a origem intacta. copy_to_fb=True faz a mesma alocação mas coloca-a no buffer de fotograma em vez do heap – que é o que uma aplicação usa quando a imagem convertida precisa de aparecer na pré-visualização do IDE, uma vez que o IDE lê a partir do buffer de fotograma.

Dois métodos adicionais produzem imagens RGB565 coloridas através de uma paleta em vez de por uma conversão direta. to_rainbow() mapeia cada valor de entrada de canal único para uma cor ao longo de um gradiente suave que percorre o espetro visível. to_ironbow() mapeia cada valor de entrada para a paleta não linear de imagiologia térmica que vai do preto através de vermelhos escuros e laranjas até ao branco. Ambos são ferramentas de visualização e não de medição; o objetivo é tornar legível à primeira vista uma imagem de canal único cujos valores brutos seriam de outra forma invisíveis ao olho.

5.3.8. Tamanho do buffer

Um último detalhe sobre formatos que vale a pena explicitar. size() reporta sempre o tamanho do buffer de bytes, não a contagem de pixels. Para formatos não comprimidos, isso decorre diretamente das dimensões e dos bytes por pixel: width * height * bytes_per_pixel. Para JPEG e PNG é o tamanho do fluxo comprimido, que varia de fotograma para fotograma dependendo do que a cena contém. O código que aloca buffers a partir de orçamentos de bytes usa size() para o primeiro caso; o código que transmite fotogramas comprimidos da câmara lê-o após cada compressão para saber quantos bytes o fluxo contém efetivamente.