5.9. Operações aritméticas

A família de desenho na secção anterior pinta dentro de uma imagem. A família aritmética combina duas imagens numa terceira – somando os seus valores de pixel, subtraindo uma da outra, calculando o mínimo ou máximo em cada posição. Esse pequeno conjunto de operações aritméticas pixel a pixel é a base sobre a qual são construídos a diferenciação de fotogramas, a subtração de fundo, o empilhamento de exposições e vários outros padrões clássicos.

A família aritmética na classe Image é suficientemente pequena para ser enumerada de uma só vez:

  • add()self + other por pixel, limitado ao máximo do formato.

  • sub()self - other por pixel, limitado a 0 no fundo.

  • rsub()other - self por pixel, limitado a 0 (a mesma aritmética de sub com os operandos invertidos).

  • min() – mínimo dos dois valores por pixel.

  • max() – máximo por pixel.

  • difference()|self - other| por pixel, a diferença absoluta.

Mais duas operações de imagem única relacionadas:

  • invert() – substitui cada pixel por 255 - pixel (ou o máximo equivalente para o formato).

  • negate() – um alias para invert().

Two horizontal gradient bars at the top representing source images A and B -- A going dark to bright left-to-right, B going bright to dark left-to-right. Below them, five gradient bars representing the result of each pairwise operation applied to A and B: A.add(B) appears uniform white because every position sums past 255 and clips; A.sub(B) is zero on the left half and brightens toward the right; A.difference(B) shows a V shape, bright on each end and dark in the middle; A.min(B) is dark on the ends and brighter in the middle; A.max(B) is bright on the ends and grey in the middle.

Dois gradientes de origem A e B, e o resultado de cada operação par a par aplicada a eles. Cada operação é executada posição a posição – o que aparece no resultado em qualquer posição depende apenas dos dois pixels de origem nessa posição.

5.9.1. Duas formas de operando

Cada um dos métodos de duas imagens aceita qualquer uma das formas para o seu segundo operando:

  • Outra Image das mesmas dimensões. A aritmética é executada posição a posição – o resultado em (x, y) é a operação aplicada aos pixels de origem em (x, y) de ambas as imagens.

  • Um valor escalar – um inteiro para escala de cinzentos, um tuplo (r, g, b) para RGB565. O mesmo escalar aplica-se em todas as posições.

A forma escalar é útil quando a aplicação quer deslocar cada pixel por uma quantidade constante. img.add(40) aumenta o brilho de toda a imagem em 40; img.sub((20, 20, 20)) escurece cada pixel em 20 por canal; img.max(50) eleva qualquer pixel abaixo de 50 para 50 e deixa os restantes intactos – o tipo de operação que transforma um nível de base quase preto do sensor num cinzento escuro plano para as fases seguintes.

5.9.2. Limitação

Os valores de pixel mantêm-se dentro do intervalo do formato em cada operação. Para um canal de 8 bits isso significa 0255: qualquer coisa que ultrapassaria 255 é limitada a 255, e qualquer coisa que ficaria abaixo de 0 é limitada a 0. Não existe wrapping.

Essa escolha tem importância prática. O aumento de brilho com add nunca produz um artefacto de escurecimento súbito no extremo brilhante onde a matemática causaria overflow; o escurecimento com sub nunca produz um artefacto de claridade súbita no extremo escuro onde causaria underflow. Os resultados mantêm-se visualmente significativos ao custo de alguma perda de informação nos extremos saturados.

A limitação também explica por que sub e rsub devolvem resultados diferentes entre si. img_a.sub(img_b) fornece a parte de a que é mais brilhante que b e zero em todo o resto; img_a.rsub(img_b) fornece a parte de b que é mais brilhante que a. Qualquer uma é útil para deteção de mudanças unilateral – se a aplicação só se preocupa com pixels que ficaram mais brilhantes, ou apenas com pixels que ficaram mais escuros – mas nenhuma captura toda a mudança entre dois fotogramas.

5.9.3. A operação de diferença

Para deteção de mudanças bilateral, a operação a usar é difference(), que calcula |self - other| em cada posição – a diferença absoluta, sem sinal. Cada pixel que mudou em qualquer direção aparece como um valor não nulo no resultado, com a magnitude proporcional a quanto mudou nessa posição.

Essa propriedade – não nulo exatamente onde as duas imagens discordam – é o que torna difference o motor da deteção de mudanças fotograma a fotograma. Um fotograma de referência guardado na inicialização e uma nova captura, processados com difference, produzem uma imagem cujos pixels não nulos marcam cada posição onde algo na cena se moveu ou alterou o brilho.

5.9.4. Âmbito com máscara

Todos os métodos aritméticos aceitam o argumento de palavra-chave mask introduzido na página de regiões e máscaras. Quando uma máscara é passada, a operação só é executada nas posições onde a máscara é não nula; em todo o resto a imagem de destino é deixada intacta.

Essa composição aparece em dois padrões. O primeiro é restringir uma operação a uma área conhecida: somar dois fotogramas apenas dentro da caixa delimitadora de um marcador detetado, por exemplo. O segundo é construir um fotograma composto peça por peça – min sobre uma sequência de fotogramas dentro de uma máscara de primeiro plano, max sobre a mesma sequência dentro da máscara complementar – esse tipo de padrão.

5.9.5. No local e preservando as entradas

Todos os métodos aritméticos seguem a convenção de operação estabelecida anteriormente: cada um modifica a imagem de origem no local e devolve a mesma imagem para encadeamento. Os pixels da origem desaparecem após a chamada – substituídos pelo resultado da operação com o que foi passado como segundo operando.

Quando a aplicação precisa de preservar ambas as entradas, o padrão seguro é copiar uma delas primeiro:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

Esse padrão – copiar e depois operar – é a base de qualquer pipeline de diferenciação de fotogramas, onde o fotograma de referência tem de sobreviver à comparação para poder ser reutilizado no próximo fotograma capturado.

Com seis operações de combinação, duas operações de imagem única, um motor de diferença absoluta, e a palavra-chave mask para definir o âmbito, o conjunto de ferramentas de aritmética de pixel cobre as combinações de brilho e canal que a visão por computador clássica necessita. As ferramentas restantes com aparência aritmética na superfície operam bit a bit em vez de valor a valor.