5.21. Escalar, inverter e recortar

As subsecções anteriores trabalharam todas sobre pixels nas mesmas posições em que começaram. A família de transformações muda isso. O escalonamento envia cada pixel de entrada para uma posição de saída diferente, possivelmente para várias posições de saída ao mesmo tempo (ao ampliar) ou para uma posição partilhada com vários outros pixels de entrada (ao reduzir). A inversão e a rotação fazem o mesmo através de um mapeamento diferente. O recorte mantém um subconjunto retangular dos pixels de entrada e descarta o resto.

O módulo image expõe essa família através de três métodos que partilham a maioria dos seus argumentos e do seu comportamento:

  • copy() – produzir uma cópia da imagem, possivelmente escalada, recortada ou reorientada.

  • crop() – a mesma operação que copy, mas com a expectativa de que a aplicação vai escolher um sub-retângulo da fonte.

  • scale() – o mesmo novamente, com a expectativa de que a aplicação vai redimensionar o resultado.

Os três partilham os mesmos argumentos e o mesmo mecanismo de transformação; a diferença está em onde o resultado fica por padrão. copy() produz uma nova imagem, enquanto crop() e scale() modificam a fonte no lugar.

5.21.1. Os argumentos partilhados

Uma única chamada combina qualquer combinação de escalonamento, recorte, orientação e extração de canal que a aplicação solicitar:

x_scale e y_scale escalam a entrada ao longo dos eixos horizontal e vertical de forma independente. Ambos têm valor padrão 1.0 (sem escalonamento). Valores diferentes para cada um produzem um escalonamento não uniforme – um fotograma esticado o dobro da largura da altura, por exemplo.

roi restringe a entrada a um retângulo da imagem fonte, passando apenas esses pixels pelo resto da transformação. Esta é a parte de «recorte» da operação: passar um roi para extrair uma sub-região.

hint é um campo de bits de sinalizadores que seleciona o método de interpolação e quaisquer inversões de orientação. Vários sinalizadores combinam-se através de OR bit a bit (hint=image.BILINEAR | image.HMIRROR). Os sinalizadores dividem-se em dois grupos – a família de interpolação e a família de orientação – que não têm nada a ver um com o outro mas partilham o mesmo campo de bits.

rgb_channel seleciona um único canal de uma fonte RGB565. 0 significa vermelho, 1 significa verde, 2 significa azul; o resultado sai como uma imagem em escala de cinzentos contendo apenas esse canal. Útil para aplicar limiares no canal vermelho apenas, por exemplo.

color_palette e alpha_palette remapeiam os valores dos pixels através de uma tabela de consulta na saída, da mesma forma que os métodos de conversão to_rainbow() e to_ironbow() o fazem.

copy=True e copy_to_fb=True seguem a mesma convenção que todos os outros métodos que produzem resultados usam – no lugar por padrão, copy=True aloca um resultado separado, copy_to_fb=True coloca o resultado no buffer de fotograma para a pré-visualização do IDE.

5.21.2. Interpolação: AREA, BILINEAR, BICUBIC

Quando o escalonamento envia cada pixel de saída para uma posição que não está alinhada com nenhum pixel de entrada único, o método tem de escolher que valor escrever. Três sinalizadores controlam como:

image.BILINEAR interpola entre os quatro pixels de entrada mais próximos, ponderados pela distância à posição de saída. O resultado é mais suave do que o vizinho mais próximo, sem serrilhado visível em linhas diagonais, mas a aritmética extra custa cerca de quatro vezes a passagem do vizinho mais próximo. A escolha certa para a maioria do trabalho de ampliação e para qualquer fator de escala não inteiro.

image.BICUBIC interpola entre os dezasseis pixels de entrada mais próximos utilizando uma curva cúbica, o que produz resultados ainda mais suaves ao custo de mais aritmética. Melhor qualidade para aplicações sensíveis ao custo que a necessitam; raramente vale a computação extra para fotogramas ao vivo que o IDE apenas vai mostrar.

image.AREA faz a média de todos os pixels de entrada que cabem dentro da área do pixel de saída – o algoritmo certo para reduzir. Bilinear e bicúbico são interpoladores: estimam um valor entre pixels da fonte, o que é o que a ampliação necessita, mas ao reduzir cada pixel de saída cobre muitos pixels da fonte e um interpolador lê apenas os poucos mais próximos – os detalhes que ignora reaparecem como aliasing. image.AREA incorpora cada pixel coberto na média.

O algoritmo de escalonamento padrão sem qualquer indicação é o do vizinho mais próximo, que é o mais económico e a resposta certa quando a fonte já está na resolução de pixel do destino.

5.21.3. Orientação: inversões e rotações

Os sinalizadores de orientação são um pequeno conjunto de transformações booleanas que se compõem livremente entre si e com os sinalizadores de interpolação:

  • image.VFLIP inverte a imagem verticalmente (o topo passa a ser a base).

  • image.HMIRROR espelha-a horizontalmente (a esquerda passa a ser a direita).

  • image.TRANSPOSE troca os eixos x e y (as linhas tornam-se colunas).

A maioria das rotações resulta da composição desses três. O módulo também expõe atalhos nomeados:

  • image.ROTATE_90 (= VFLIP | TRANSPOSE)

  • image.ROTATE_180 (= HMIRROR | VFLIP)

  • image.ROTATE_270 (= HMIRROR | TRANSPOSE)

Em código:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. Tratamento da proporção

Quando a proporção da fonte não corresponde ao retângulo no qual está a ser desenhada, três sinalizadores decidem o que fazer com a discrepância:

image.SCALE_ASPECT_KEEP preserva a proporção da fonte e aplica letterbox ao resultado – a fonte é escalada até caber dentro do destino, com pixels vazios (zero) a preencher o resto do destino. A escolha certa quando manter a fonte sem distorção é mais importante do que preencher toda a saída.

image.SCALE_ASPECT_EXPAND preserva a proporção da fonte e recorta-a – a fonte é escalada até preencher o destino, com as partes que ultrapassam o destino cortadas. A escolha certa quando preencher toda a saída é mais importante do que ver cada parte da fonte.

image.SCALE_ASPECT_IGNORE ignora a proporção e estica a fonte para preencher o destino, aceitando qualquer distorção que isso introduza. A escolha certa quando a aplicação já contabilizou a distorção – quando as dimensões do destino não são na verdade um retângulo da mesma cena, por exemplo.

O padrão (sem sinalizador de proporção definido) é o mesmo que SCALE_ASPECT_IGNORE: esticar para preencher. As aplicações que se preocupam com a proporção especificam um dos três explicitamente.

5.21.5. Quando usar qual

A maioria dos redimensionamentos usa scale() com um par x_scale / y_scale e uma indicação de interpolação:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

A maioria das rotações usa a mesma chamada com hint=image.ROTATE_90 ou similar.

O recorte usa crop() com um roi não padrão:

img.crop(roi=(40, 30, 200, 150))

Quando a fonte tem de sobreviver à operação – capturar um fotograma de referência, tirar uma miniatura de um fotograma que está prestes a ser processado de forma destrutiva – copy() produz o resultado como uma nova imagem e deixa a fonte intacta:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

Esse padrão é a diferença real por trás dos três nomes: scale e crop transformam no lugar, copy aloca. As palavras-chave de colocação do resultado fazem a ponte: copy=True em scale ou crop aloca o resultado como um buffer de heap separado em vez de sobrescrever a fonte, e copy_to_fb=True em qualquer um dos três coloca-o no buffer de fotograma para a pré-visualização do IDE.