Transformação geométrica

Enquanto as Transformações de intensidade alteram apenas o valor do pixel, sem depender de suas coordenadas, as transformações geométricas fazem um mapeamento de coordenadas, sem modificar o valor de cada pixel.

O exercício principal nesta atividade é desenvolvermos o programa que modificará a implementação da transformação afim da toolbox ia898:affine. Para isto, precisamos primeiro rever a teoria de transformação geométrica 2D utilizando coordenadas homogêneas e matriz de transformação.

Estudo preparatório

Estude com cuidado o texto preparado para esta experiência:

Veja também este outro texto que exercita a transformação ia636:iaffine:

  • master:tutorial_trans_geom_2 Transformação geométrica com iaffine

Preparação para o problema a ser modificado

Leia agora o problema que deverá ser entregue: activity_mariecp_3_gg Modificar iaffine. A modificação solicitada diz respeito aos pixels na imagem transformada que não têm correspondência com a imagem de entrada. Na implementação atual da iaffine, estes valores são buscados na imagem original, com o uso da função "clip" do NumPy. Altere este comportamento de modo que estes valores fiquem zerados.

Apesar de parecer fácil, a solução exige um perfeito entendimento da implementação da ia636:iaffine. Utilizando o NumPy, busque uma solução que seja simples e eficiente.

A seguir será feita uma demonstração do funcionamento ia636:iaffine com uma pequena imagem numérica exemplificando seu passo a passo. A ideia é que você utilize este página como rascunho da sua solução. Depois de ver o passo a passo, edite-o para obter o resultado desejado. Uma vez conseguido, coloque sua função modificada no local apropriado para submissão no sistema automático de entrega de programas do Adessowiki.

Entendimento da iaffine da toolbox ia898 - execução passo a passo

Parâmetros de entrada:

Criamos a imagem de entrada e a matriz de transformação geométrica T:


In [1]:
import numpy as np

t = np.array([2.1, 0.8])

T = np.array([[1,0,t[1]], 
              [0,1,t[0]], 
              [0,0,1]])

f = np.array([[ 1, 2, 3, 4, 5],
              [ 6, 7, 8, 9,10],
              [11,12,13,14,15]])

Domínio da imagem de saída

O domínio da imagem de saída é feito igual ao domínio da imagem f de entrada. domain diz respeito ao shape da imagem de saída. H e W dizem respeito às dimensões da imagem de entrada; neste caso, são iguais.


In [2]:
domain = f.shape
n = f.size
H,W = f.shape
print('domain:', domain)


domain: (3, 5)

Cálculo dos índices da imagem de saída

Como fazemos mapeamento indireto, a varredura será nos pixels da imagem de saída. Assim, utilizamos domain para gerar as coordenadas de todos os pixels de g. Note aqui que usamos r1,c1 no lugar de r',c' das equações matemáticas.


In [3]:
r1,c1 = np.indices(domain)
print('r1=\n', r1)
print('c1=\n', c1)


r1=
 [[0 0 0 0 0]
 [1 1 1 1 1]
 [2 2 2 2 2]]
c1=
 [[0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]]

Empacotamento das coordenadas homogêneas

As coordenadas r1 e c1 são vetorizadas e colocadas em 3 linhas: r1, c1 e de 1's, para poder multiplicar por T. A vetorização dos arrays é feita pelo ravel() do NumPy:


In [4]:
rc1 = np.array([ r1.ravel(), 
                 c1.ravel(), 
                 np.ones(n)])
print('rc1=\n', rc1)


rc1=
 [[ 0.  0.  0.  0.  0.  1.  1.  1.  1.  1.  2.  2.  2.  2.  2.]
 [ 0.  1.  2.  3.  4.  0.  1.  2.  3.  4.  0.  1.  2.  3.  4.]
 [ 1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]]

Transformação da coordenadas

Aqui é feito o processamento para o cálculo das novas coordenadas correspondentes. As coordenadas de g são multiplicadas pela inversa da matriz T (mapeamento inverso). Observe que os valores estão em ponto flutuante:


In [5]:
rc_float = np.linalg.inv(T).dot(rc1)
print('rc_float=\n',rc_float)


rc_float=
 [[-0.8 -0.8 -0.8 -0.8 -0.8  0.2  0.2  0.2  0.2  0.2  1.2  1.2  1.2  1.2
   1.2]
 [-2.1 -1.1 -0.1  0.9  1.9 -2.1 -1.1 -0.1  0.9  1.9 -2.1 -1.1 -0.1  0.9
   1.9]
 [ 1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1. ]]

Interpolação do vizinho mais próximo

A coordenada ponto flutuante de f é arredondada pelo vizinho mais próximo. O arredondamento é feito pela operação rint do NumPy:


In [6]:
rr = np.rint(rc_float[0]).astype(int)
cc = np.rint(rc_float[1]).astype(int)
print('rr=\n', rr)
print('cc=\n', cc)


rr=
 [-1 -1 -1 -1 -1  0  0  0  0  0  1  1  1  1  1]
cc=
 [-2 -1  0  1  2 -2 -1  0  1  2 -2 -1  0  1  2]

Ajuste das coordenadas que caíram fora do domínio de f

Aqui é onde é preenchido os valores em que (rr,cc) caíram foram do domínio de f. Neste caso, é feito um "clipping" forçando estes valores cairem dentro de [0,H-1] e [0,W-1] que formam o domínio de f:


In [7]:
r = np.clip(rr,0,H-1).reshape(domain)
c = np.clip(cc,0,W-1).reshape(domain)
print('r=\n', r)
print('c=\n', c)


r=
 [[0 0 0 0 0]
 [0 0 0 0 0]
 [1 1 1 1 1]]
c=
 [[0 0 0 1 2]
 [0 0 0 1 2]
 [0 0 0 1 2]]

Cópia dos valores dos pixels

Uma vez que as coordenadas estão todas calculadas, é feita agora a cópia dos pixels da imagem f nas coordenadas calculadas para os pixels da imagem g. Veja que a indexação de f está sendo feita por dois arrays r e c que possuem as mesmas dimensões da imagem de entrada f. Esta é uma técnica do Numpy de indexação por arrays que é bastante poderosa e eficiente.


In [9]:
g = f[r,c]
print('g=\n', g)


g=
 [[1 1 1 2 3]
 [1 1 1 2 3]
 [6 6 6 7 8]]

Teste de autoavaliação

Faça o teste múltipla escolha a seguir para verificar os conhecimentos adquiridos com a atividade de transformação geométrica. O teste é para autoestudo e pode ser repetido várias vezes:

  • Ainda não está pronto

In [ ]: