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.