Uma teoria muito importante e também muito estudada é a de filtros ou transformações lineares invariantes à translação. Quando um filtro tiver as seguintes três propriedades:
A filtragem da soma de duas imagens é igual à soma das imagens filtradas
A filtragem de uma imagem multiplicada por uma constante é igual à multiplicação pela constante da imagem transformada
A filtragem de uma imagem transladada é igual à translação da imagem filtrada
diz-se, então, que o filtro é linear e invariante à translação. Ainda mais importante é o seguinte:
Qualquer filtro linear e invariante à translação pode ser implementado por uma convolução.
Esta afirmação facilita em muito a eficiência de um programador de processamento de imagens, pois a implementação de todos os filtros lineares invariantes à translação é feita com apenas um função, bastando variar o seu parâmetro.
Apesar do nome ser estranho para quem nunca ouviu falar em convolução, a sua versão mais simples já foi certamente vista por todos: um filtro de média móvel é uma convolução.
Assim, o objetivo desta atividade é olharmos com detalhes como está implementada a função
Existem duas dificuldades maiores relativas à implementação da convolução discreta:
É uma operação de vizinhança, o que torna o processamento em NumPy potencialmente mais difícil. Uma das dicas para se implementar a convolução evitando ou diminuindo o número de laços explícitos é operar na imagem transladada e não na translação da janela.
Como toda operação de vizinhança, é preciso definir qual é a vizinhança de um pixel que esteja nos limites da imagem, normalmente chamado borda da imagem. Existem várias soluções possíveis para este tratamento. A que usaremos aqui, no caso da convolução linear é supor que a imagem é infinita e os pixels que estão fora do seu shape tem seus valores zerados.
Na realidade, a convolução é uma operação entre duas imagens quaisquer, f e h, onde f é a imagem propriamente dita e h é
o filtro linear. h tem muitas denominações: núcleo ou máscara da convolução, função de espalhamento, entre outras.
Normalmente a imagem f é grande e núcleo h é pequeno. Por exemplo, se quero um filtro de média móvel onde a vizinhança ou janela a
aplicar-se a média consiste no pixel em questão e os seus dois pixels vizinhos à esquerda, a imagem h será um array de 3 elementos: [1/3, 1/3, 1/3].
No Python/NumPy podemos calcular esta média móvel de duas formas principais:
Varredura em todos os pixels e para cada pixel seleciona-se os pixels vizinhos por fatiamento para o cálculo da média.
Soma da imagem f com suas translações de 1 e 2 pixels para a direita, dividindo o resultado por 3.
Esta mudança de varredura da imagem para varredura nos elementos de h é possível porque a convolução possui uma propriedade importante: ela é comutativa. Isto é, fazer a convolução de f por h é igual a fazer a convolução de h por f. Cuidado, pois nem toda implementação de convolução respeita esta propriedade. As implementações da convolução feitas na toolbox ia898 são todas comutativas.
In [2]:
import numpy as np
f = np.arange(25).reshape(5,5)
h = np.array([1./3, 1./3, 1./3])
print('f:\n', f)
print('h:', h.round(2))
In [3]:
H,W = f.shape
g = np.zeros((H,W+2))
g[:,:-2] = f * h[0]
print('g:\n', g.round(2))
g[:,1:-1] += f * h[1]
print('g:\n', g.round(2))
g[:,2:] += f * h[2]
print('g:\n', g.round(2))
Veja o
ia898:conv
está implementada na toolbox ia898.
In [ ]: