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 + otherpor pixel, limitado ao máximo do formato.sub()–self - otherpor pixel, limitado a0no fundo.rsub()–other - selfpor pixel, limitado a0(a mesma aritmética desubcom 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 por255 - pixel(ou o máximo equivalente para o formato).
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
Imagedas 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 0 – 255: 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.