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.