Exercício 6 de IA639-2009

Autor: Matias
Data: 24/04/2009

Discussão teorica

A definição de convolução, para o caso não discreto e apenas em uma dimensão, é a seguinte:

Também se pode usar a seguinte notação:

Mas nesse texto vamos utilizar a primeira apresentada.

É importante notar que nessa definição se assume que ambas as funções estão definidas para o conjunto inteiro de Reais, mesmo que essa definição seja ter valor igual a 0.

No caso de imagens com coordenadas inteiras estaremos lidando com convoluções discretas. A definição anterior fica da seguinte forma no domínio discreto:

Entretanto, para imagens, ainda precisaremos que a convolução seja aplicada em duas dimensões. Extendo a definição geral, temos:

Falta apenas ver a definição para convolução discreta. Entretanto, antes é necessário voltar na exigência que as funções sejam definidas em todo o domínio. Em funções periódicas, se tem a repetição do comportamento da mesma, mas para as não periódicas, como é o caso de uma imagem definida na memória, é preciso ver o que vai se assumir como valor fora das coordenadas preenchidas.

Uma maneira de se lidar com esse problema é simplesmente limitar os limites da convolução a coordenadas para as quais temos valores na imagem, mas isso implica em se descartar uma borda da imagem, de acordo com o tamanho da mascara sendo usada (para o caso de uma mascara 3x3, uma borda de 1 pixel é "perdida").

Para evitar esta perda, a forma adotada nessa solução foi, nos casos nos limites, assumir um valor para as coordenadas fora da imagem. O comportamento padrão é assumir o valor 0. Entretanto, pode-se tratar a imagem com uma função periódica, que se repete.

Com base nesse comportamento, de imagens extendidas, a discretização fica da seguinte forma:

Se f(x,y) e g(x,y) são imagens com dimenões (Fx,Fy) e (Gx,Gy), a imagem final I(x,y) terá dimensões:

E a discretização fica da seguinte forma:

Uma limitação dessa forma de implementação é que assumi que uma a matriz de kernel vai ter dimensão impar (3x3, 5x5, etc).

Implementação

 1 def convolution(imagem,kernel,periodic='false'):
 2 
 3     kernel = array(kernel)
 4     imagem_padded = iapad(imagem,[kernel.shape[0]/2,kernel.shape[1]/2])
 5 
 6     if periodic is None: periodic = 'false'
 7 
 8     if periodic == 'true':
 9         imagem_padded[0:kernel.shape[0]/2,::]=imagem_padded[imagem.shape[0]:imagem.shape[0]+kernel.shape[0]/2,::]
10         imagem_padded[imagem.shape[0]+kernel.shape[0]/2:imagem_padded.shape[0],::]=imagem_padded[kernel.shape[0]/2:(kernel.shape[0]/2)*2,::]
11         imagem_padded[::,0:kernel.shape[1]/2,]=imagem_padded[::,imagem.shape[1]:imagem.shape[1]+kernel.shape[1]/2,]
12         imagem_padded[::,imagem.shape[1]+kernel.shape[1]/2:imagem_padded.shape[1]]=imagem_padded[::,kernel.shape[1]/2:(kernel.shape[1]/2)*2,]
13 
14     imagem_resposta = zeros([imagem_padded.shape[0],imagem_padded.shape[1]])
15 
16     for i in range(kernel.shape[0]/2,imagem_padded.shape[0]-kernel.shape[0]/2):
17         for j in range(kernel.shape[1]/2,imagem_padded.shape[1]-kernel.shape[1]/2):
18             imagem_resposta[i,j]=(imagem_padded[i-kernel.shape[0]/2:(i+kernel.shape[0]/2)+1,j-kernel.shape[1]/2:j+(kernel.shape[1]/2)+1]*kernel).sum()
19 
20     return imagem_resposta[kernel.shape[0]/2:kernel.shape[0]/2+imagem.shape[0],kernel.shape[1]/2:kernel.shape[1]/2+imagem.shape[1]]

Como primeiro exemplo, aplicamos o operador laplaciano para duas imagens, não utilizando o recurso de periodicidade. As matrizes resultantes terão valores negativos, então antes de mostra-las como imagens foi aplicada uma função de normalização.

 1 imagem = mmreadgray('boat.ppm')
 2 kernel = array([[0,1,0],[1,-4,1],[0,1,0]])
 3 print kernel
 4 
 5 imagem_nova = convolution(imagem,kernel,periodic='false');
 6 adshow(ianormalize(imagem_nova,[0,255]),title='Operador Laplaciano')
 7 
 8 imagem = mmreadgray('cameraman.pgm')
 9 imagem_nova = convolution(imagem,kernel,periodic='false');
10 adshow(ianormalize(imagem_nova,[0,255]),title='Operador Laplaciano')
[[ 0  1  0]
 [ 1 -4  1]
 [ 0  1  0]]

Operador Laplaciano

Operador Laplaciano

Para testar a periodicidade, vamos utilizar um kernel que provoca somente o deslocamento da imagem:

1 kernel = zeros([45,45])
2 kernel[0,0]=1
3 imagem = mmreadgray('cameraman.pgm')
4 imagem_nova = convolution(imagem,kernel,periodic='true');
5 adshow(imagem_nova,[0,255],title='Deslocamento com periodicidade')

Deslocamento com periodicidade

Para ilustrar o uso de periodicidade, uma mascara de suavização por média de (15x15):

1 kernel = ones([15,15]) / 225
2 imagem = mmreadgray('cameraman.pgm')
3 imagem_nova = convolution(imagem,kernel,periodic='false');
4 adshow(imagem_nova,[0,255],title='Suavização por média, 15x15, sem periodicidade')
5 
6 imagem_nova = convolution(imagem,kernel,periodic='true');
7 adshow(imagem_nova,[0,255],title='Suavização por média, 15x15, com periodicade')

Suavização por média, 15x15, sem periodicidade

Suavização por média, 15x15, com periodicade

Como último exemplo, vamos pegar uma imagem, aplicar o operador laplaciano, e combinar as duas imagens

 1 imagem = mmreadgray('boat.ppm')
 2 kernel = array([[0,1,0],[1,-4,1],[0,1,0]])
 3 
 4 imagem_nova = convolution(imagem,kernel,periodic='true');
 5 
 6 #define o impacto que imagem laplaciana vai ter na original.
 7 imagem_nova = ianormalize(imagem_nova,[-80,80])
 8 
 9 #Soma as imagens, e remove valores que tenham ultrapassado o limite [0,255]
10 imagem2 = imagem - imagem_nova
11 imagem2 = maximum(imagem2,0)
12 imagem2 = minimum(imagem2,255)
13 adshow(imagem,title='Imagem original')
14 adshow(imagem2,title='Imagem combinada')

Imagem original

Imagem combinada

Exemplos interativos

Escolha do tamanho da mascara de suavização

 1 parser = OptionParser()
 2 parser.add_option("--Tamanho", type='int', default=3, help='Tamanho da mascara de suavização, *IMPAR*')
 3 parser.add_option("--Periodico", type='string', default='false', help='Usar periodicidade, true ou false')
 4 
 5 opt, arg = parser.parse_args()
 6 
 7 imagem = mmreadgray('cameraman.pgm')
 8 kernel = ones([opt.Tamanho,opt.Tamanho])/(opt.Tamanho*opt.Tamanho)
 9 
10 imagem_nova = convolution(imagem,kernel,periodic=opt.Periodico);
11 
12 adshow(imagem,title='Imagem original')
13 adshow(imagem_nova,title='Imagem com suavização de tamanho %d aplicada' % (opt.Tamanho))

Imagem original

Imagem com suavização de tamanho 9 aplicada

Definição de um kernel 3x3 qualquer

 1 parser = OptionParser()
 2 parser.add_option("--kernel00", type='float', default=0, help='Posicao [0,0]')
 3 parser.add_option("--kernel01", type='float', default=0, help='Posicao [0,1]')
 4 parser.add_option("--kernel02", type='float', default=0, help='Posicao [0,2]')
 5 parser.add_option("--kernel10", type='float', default=0, help='Posicao [1,0]')
 6 parser.add_option("--kernel11", type='float', default=1, help='Posicao [1,1]')
 7 parser.add_option("--kernel12", type='float', default=0, help='Posicao [1,2]')
 8 parser.add_option("--kernel20", type='float', default=0, help='Posicao [2,0]')
 9 parser.add_option("--kernel21", type='float', default=0, help='Posicao [2,1]')
10 parser.add_option("--kernel22", type='float', default=0, help='Posicao [2,2]')
11 parser.add_option("--Periodico", type='string', default='false', help='Usar periodicidade, true ou false')
12 
13 
14 opt, arg = parser.parse_args()
15 
16 imagem = mmreadgray('cameraman.pgm')
17 kernel = [ [opt.kernel00, opt.kernel01, opt.kernel02], [opt.kernel10, opt.kernel11, opt.kernel12], [opt.kernel20, opt.kernel21, opt.kernel22] ]
18 print kernel
19 
20 imagem_nova = convolution(imagem,kernel,periodic=opt.Periodico);
21 adshow(imagem,title='Imagem original')
22 adshow(ianormalize(imagem_nova,[0,255]),title='Imagem com kernel aplicado')
[[0, 0, 0], [0, 1, 0], [0, 0, 0]]

Imagem original

Imagem com kernel aplicado