5.9. Operações aritméticas¶
A família de desenho da seção anterior pinta dentro de uma imagem. A família aritmética combina duas imagens em uma terceira – somando seus valores de pixel, subtraindo um do outro, tomando o mínimo ou o máximo em cada posição. Esse pequeno conjunto de operações aritméticas pixel a pixel é a base sobre a qual o frame differencing, a subtração de fundo, o empilhamento de exposição e um punhado de outros padrões clássicos são construídos.
A família aritmética na classe Image é pequena o suficiente para enumerar de uma vez:
add()–self + otherpor pixel, limitado ao máximo do formato.sub()–self - otherpor pixel, limitado a0no piso.rsub()–other - selfpor pixel, limitado a0(a mesma aritmética desubcom os operandos invertidos).min()– mínimo por pixel dos dois valores.max()– máximo por pixel.difference()–|self - other|por pixel, a diferença absoluta.
Mais duas operações relacionadas de imagem única:
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 por posição – o que aparece no resultado em qualquer local depende apenas dos dois pixels de origem naquele local.¶
5.9.1. Duas formas de operando¶
Cada um dos métodos de duas imagens aceita qualquer das formas para seu segundo operando:
Outra
Imagedas mesmas dimensões. A aritmética é executada posição por 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 cinza, uma tupla
(r, g, b)para RGB565. O mesmo escalar se aplica a todas as posições.
A forma escalar é útil quando a aplicação quer deslocar todos os pixels por uma quantidade constante. img.add(40) clareia a imagem inteira em 40; img.sub((20, 20, 20)) escurece cada pixel em 20 por canal; img.max(50) eleva qualquer pixel abaixo de 50 até 50 e deixa o resto inalterado – o tipo de operação que transforma um piso de sensor quase preto em um cinza escuro plano para as etapas seguintes trabalharem.
5.9.2. Saturação (clipping)¶
Os valores de pixel permanecem dentro da faixa do formato em todas as operações. Para um canal de 8 bits isso significa 0 – 255: qualquer coisa que teria estourado além de 255 é saturada de volta a 255, e qualquer coisa que teria ido abaixo de 0 é saturada até 0. Não há wrap-around.
Essa escolha importa na prática. add clareando pixels nunca produz um artefato de escurecimento repentino na extremidade clara onde a matemática, de outra forma, estouraria; sub escurecendo pixels nunca produz um artefato de clareamento repentino na extremidade escura onde, de outra forma, ocorreria underflow. Os resultados permanecem visualmente significativos ao custo de alguma perda de informação nos extremos saturados.
A saturação também é o motivo de sub e rsub retornarem resultados diferentes um do outro. img_a.sub(img_b) fornece a parte de a que é mais clara que b e zero em todo o resto; img_a.rsub(img_b) fornece a parte de b que é mais clara que a. Qualquer uma é útil para detecção de mudança unilateral – se a aplicação se importa apenas com pixels que ficaram mais claros, ou apenas com pixels que ficaram mais escuros – mas nenhuma captura toda a mudança entre dois quadros.
5.9.3. A operação de diferença¶
Para detecção de mudança bilateral, a operação a se recorrer é 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 diferente de zero no resultado, com a magnitude proporcional a quanto ele mudou naquela posição.
Essa propriedade – diferente de zero exatamente onde as duas imagens discordam – é o que faz de difference o burro de carga da detecção de mudança quadro a quadro. Um quadro de referência armazenado na inicialização e uma nova captura, passados por difference, produzem uma imagem cujos pixels diferentes de zero marcam cada posição onde algo na cena se moveu ou mudou de brilho.
5.9.4. Delimitando com máscara¶
Todos os métodos aritméticos aceitam o argumento de palavra-chave mask apresentado na página de regiões e máscaras. Quando uma máscara é passada, a operação é executada apenas nas posições onde a máscara é diferente de zero; em todos os outros lugares a imagem de destino é deixada inalterada.
Essa composição aparece em dois padrões. O primeiro é restringir uma operação a uma área conhecida: somar dois quadros apenas dentro da caixa delimitadora de um marcador detectado, por exemplo. O segundo é construir um quadro composto peça por peça – min sobre uma sequência de quadros 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¶
Os métodos aritméticos seguem todos a convenção de operação estabelecida anteriormente: cada um modifica a imagem de origem no local e retorna a mesma imagem para encadeamento. Os pixels da origem desaparecem após a chamada – substituídos pelo resultado da operação contra o que foi passado como segundo operando.
Quando a aplicação precisa 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, depois operar – é a espinha dorsal de qualquer pipeline de frame differencing, onde o quadro de referência tem que sobreviver à comparação para que possa ser reutilizado no próximo quadro capturado.
Com seis operações de combinação, duas operações de imagem única, um burro de carga de diferença absoluta e a palavra-chave mask para delimitação, o conjunto de ferramentas de aritmética de pixels cobre as combinações de brilho e canal de que a visão de máquina clássica precisa. As ferramentas restantes semelhantes à aritmética na superfície trabalham bit a bit em vez de valor a valor.