As funções indices
e meshgrid
são extremamente úteis na geração de imagens sintéticas e o seu aprendizado permite também
entender as vantagens de programação matricial, evitando-se a varredura seqüencial da imagem muito usual na programação na linguagem C.
A função indices
recebe como parâmetros uma tupla com as dimensões (H,W) das matrizes a serem criadas. No exemplo a seguir, estamos
gerando matrizes de 5 linhas e 10 colunas. Esta função retorna uma tupla de duas matrizes que podem ser obtidas fazendo suas atribuições
como no exemplo a seguir onde criamos as matrizes r
e c
, ambas de tamanho (5,10), isto é, 5 linhas e 10 colunas:
In [1]:
import numpy as np
r,c = np.indices( (5, 10) )
print('r=\n', r)
print('c=\n', c)
Note que a matriz r
é uma matriz onde cada elemento é a sua coordenada linha e a matriz c
é uma matriz onde cada elemento é
a sua coordenada coluna. Desta forma, qualquer operação matricial feita com r
e c
, na realidade você está processando as
coordenadas da matriz. Assim, é possível gerar diversas imagens sintéticas a partir de uma função de suas coordenadas.
Como o NumPy processa as matrizes diretamente, sem a necessidade de fazer um for
explícito, a notação do programa fica bem simples
e a eficiência também. O único inconveniente é o uso da memória para se calcular as matrizes de índices r
e c
. Iremos
ver mais à frente que isto pode ser minimizado.
Por exemplo seja a função que seja a soma de suas coordenadas $f(r,c) = r + c$:
In [2]:
f = r + c
print('f=\n', f)
Ou ainda a função diferença entre a coordenada linha e coluna $f(r,c) = r - c$:
In [3]:
f = r - c
print('f=\n', f)
Ou ainda a função $f(r,c) = (r + c) \% 2$ onde % é operador módulo. Esta função retorna 1 se a soma das coordenadas for ímpar e 0 caso contrário. É uma imagem no estilo de um tabuleiro de xadrez de valores 0 e 1:
In [4]:
f = (r + c) % 2
print('f=\n', f)
Ou ainda a função de uma reta $ f(r,c) = (r = \frac{1}{2}c)$:
In [5]:
f = (r == c//2)
print('f=\n', f)
print('f=\n',f.astype(np.int))
Ou ainda a função parabólica dada pela soma do quadrado de suas coordenadas $$ f(r,c) = r^2 + c^2 $$:
In [6]:
f = r**2 + c**2
print('f=\n', f)
Ou ainda a função do círculo de raio 4, com centro em (0,0) $f(r,c) = (r^2 + c^2 < 4^2)$:
In [7]:
f = ((r**2 + c**2) < 4**2)
print('f=\n', f * 1)
Vejamos os exemplos acima, porém gerados em imagens. A diferença será no tamanho da matriz, iremos utilizar matriz (200,300), e
a forma de visualizá-la através do adshow
, ao invés de imprimir os valores como fizemos acima.
Como muitas vezes o resultado das funções poderão estar fora
da faixa 0-255 admitida pelo adshow
, iremos sempre normalizar os valores finais da imagem calculada para a faixa 0-255 utilizando
a função ia636:ianormalize ianormalize
da toolbox ia636.
Gerando as coordenadas utilizando indices
:
Observe que o parâmetro de indices
é uma tupla. Verifique o número de parêntesis utilizados:
In [8]:
import numpy as np
import sys,os
ia898path = os.path.abspath('../../')
if ia898path not in sys.path:
sys.path.append(ia898path)
import ia898.src as ia
nb = ia.nbshow(3)
In [9]:
r,c = np.indices( (200, 300) )
rn = ia.normalize(r)
cn = ia.normalize(c)
nb.nbshow(rn,'rn:linhas')
nb.nbshow(cn,'cn:colunas',flush=True)
In [10]:
f = r + c
ia.adshow(ia.normalize(f),'r + c')
In [11]:
f = r - c
ia.adshow(ia.normalize(f),'r - c')
In [12]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(f,cmap='gray')
plt.colorbar()
Out[12]:
In [13]:
f = (r//8 + c//8) % 2
ia.adshow(ia.normalize(f),'(r//8 + c//8) % 2')
In [14]:
f = (r == c//2)
ia.adshow(f,'r == c//2')
In [15]:
f = r**2 + c**2
ia.adshow(ia.normalize(f),'r**2 + c**2')
In [16]:
f = (r**2 + c**2 < 190**2)
ia.adshow(ia.normalize(f),'r**2 + c**2) < 190**2')
A função meshgrid
é semelhante à função indices
visto
anteriormente, porém, enquanto indices
gera as coordenadas inteiras não negativas a partir de um shape(H,W)
,
o meshgrid
gera os valores das matrizes a partir de dois vetores de valores reais quaisquer, um para as linhas e outro para as colunas.
Veja a seguir um pequeno exemplo numérico. Para que o meshgrid
fique compatível com a nossa convenção de (linhas,colunas), deve-se
usar o parâmetro indexing='ij'
.
In [17]:
import numpy as np
r, c = np.meshgrid( np.array([-1.5, -1.0, -0.5, 0.0, 0.5]),
np.array([ -20, -10, 0, 10, 20, 30]), indexing='ij')
print('r=\n',r)
print('c=\n',c)
A função linspace
gera vetor em ponto flutuante recebendo os parâmetro de valor inicial, valor final e número de pontos do vetor.
Desta forma ele é bastante usado para gerar os parâmetro para o meshgrid
.
Repetindo os mesmos valores do exemplo anterior, porém usando linspace
. Observe que o primeiro vetor possui 5 pontos,
começando com valor -1.5 e o valor final é 0.5 (inclusive). O segundo vetor possui 6 pontos, começando de -20 até 30:
In [18]:
rows = np.linspace(-1.5, 0.5, 5)
cols = np.linspace(-20, 30, 6)
print('rows:', rows)
print('cols:', cols)
Usando os dois vetores gerados pelo linspace
no meshgrid
:
In [19]:
r, c = np.meshgrid(rows, cols, indexing='ij')
print('r = \n', r)
print('c = \n', c)
Podemos agora gerar uma matriz ou imagem que seja função destes valores. Por exemplo ser o produto deles:
In [20]:
f = r * c
print('f=\n', f)
Neste exemplo, geramos a imagem da função $ sinc(r,c)$ em duas dimensões, nos intervalos na vertical, de -5 a 5 e na horizontal de -6 a 6. A função sinc é uma função trigonométrica que pode ser utilizada para filtragens.
A equação é dada por:
$$ sinc(r,c) = \frac{\sin(r^2 + c^2)}{r^2 + c^2}, \text{para\ } -5 \leq r \leq 5, -6 \leq c \leq 6 $$Na origem, tanto r como c são zeros, resultando uma divisão por zero. Entretanto pela teoria dos limites, $\frac{sin(x)}{x}$ é
igual a 1 quando $x$ é igual a zero.
Uma forma de se obter isto em ponto flutuante é somar tanto no numerador como no denominador um epsilon, que é a
menor valor em ponto flutuante. Epsilon pode ser obtido pela função np.spacing
.
In [29]:
e = np.spacing(1) # epsilon to avoid 0/0
rows = np.linspace(-5.0, 5.0, 150) # coordenadas das linhas
cols = np.linspace(-6.0, 6.0, 180) # coordenadas das colunas
r, c = np.meshgrid(rows, cols, indexing='ij') # Grid de coordenadas estilo numpy
z = np.sin(r**2 + c**2 + e) / (r**2 + c**2 + e) # epsilon is added to avoid 0/0
ia.adshow(ia.normalize(z),'Função sinc: sen(r² + c²)/(r²+c²) em duas dimensões')
In [30]:
plt.imshow(z)
plt.colorbar()
Out[30]:
In [32]:
n_rows = len(rows)
n_cols = len(cols)
r,c = np.indices((n_rows,n_cols))
r = -5. + 10.*r.astype(float)/(n_rows-1)
c = -6. + 12.*c.astype(float)/(n_cols-1)
zi = np.sin(r**2 + c**2 + e) / (r**2 + c**2 + e) # epsilon is addes to avoid 0/0
ia.adshow(ia.normalize(zi),'Função sinc: sin(r² + c²)/(r²+c²) em duas dimensões')
Verificando que as duas funções são iguais:
In [33]:
print('Máxima diferença entre z e zi?', abs(z - zi).max())
In [34]:
np.exp(-1/2.)
Out[34]:
Na realidade a função indices
retorna um único array n-dimensional com uma dimensão a mais que o indicado pelo
shape usado como parâmetro. Assim, quando é feito r,c = np.indices((rows,cols))
, r é atribuído para o elemento 0 e
c é atribuído para o elemento 1 do ndarray. No caso do meshgrid
, ele retorna tantos arrays quanto forem o número
de vetores passados como parâmetro para meshgrid
.
ia636:iacircle Uso do meshgrid na função iacircle
ia636:iarectangle Uso meshgrid na função iarectangle
ia636:iacos Uso de indices na função iacos - cossenóide
ia636:ialog Uso de indices na função ialog - Laplaciano da gaussiana