Operações com imagens ====================== Nessa aula vamos complementar a classe NPImagem, que começamos a construir na aula anterior, para fortalecer sua habilidade de abstração de dados e sua experiência com arrays. Objetivos de aprendizagem -------------------------- Realize seus estudos para que, ao final dessa aula, você seja capaz de: * utilizar fatias de arrays para evitar o uso de laços; * manipular imagens do tipo NPImagem, sem pensar nelas como arrays. Introdução ---------- Vamos primeiro pensar em algumas operações simples para manipular imagens como por exemplo: * recortar um pedaço (região retangular) de uma imagem; * pintar uma região com uma "cor" (nível de cinza); e * sobrepor uma imagem (uma região) sobre outra imagem. Por exemplo, considere as duas imagens abaixo, que vamos chamar de Elvis e Einstein (obtidas da Wikipedia): .. image:: ./figuras/a13/elvis.png :width: 30% .. image:: ./figuras/a13/einstein.png :width: 30% Com essas duas imagens podemos compor outra imagem realizando as seguintes operações: * pintar de cinza uma área ao redor da cabeça de Elvis; * recortar uma região contendo a cabeça de Einstein; * colar a cabeça de Einstein sobre a cabeça de Elvis. .. image:: ./figuras/a13/composicao.png :width: 60% Você mesmo pode compor essa imagem, talvez apenas tomando um certo cuidado com a escala, que não vamos tratar nessa aula. Para isso, você pode complementar a classe NPImagem da aula anterior com os métodos `pinte_retangulo()` e `paste()` como definidos a seguir: .. code:: python def pinte_retangulo(self, sup, esq, inf, dir, v=0): ''' (NPImagem, int, int, int, int, int) -> None Recebe 4 inteiros que definem o canto superior-esquerdo (sup, esq) e o canto inferior-direito (inf,dir) de uma região retangular com relação a posição (0,0) de self, ou seja, os cantos são "deslocamentos" em pixeis com relação à origem. Esse método pinta, com o valor v, os pixeis de self que tenham sobreposição com o retângulo (sup,esq)x(inf,dir). ''' def paste(self, other, sup, esq): '''(NPImagem, NPImagem, int, int) -> None Recebe um objeto NPImagem other e um par de inteiros (sup, esq) que indica um deslocamento em relação à origem de self (posição (0,0)) onde a NPImagem other deve ser sobreposta sobre self. Observe que esse deslocamento pode ser negativo. Caso não existir sobreposição, a imagem self fica inalterada. ''' Observe que o método paste deve permitir a sobreposição de uma imagem sobre outra. Imaginando a imagem `self` como referência e os índices de cada pixel como "coordenadas", podemos considerar esse sistema de coordenadas para posicionar o canto superior-esquerdo da imagem `other`. Assumindo que essas coordenadas podem conter posições fora da imagem `self` (tanto posições negativas quanto maiores que as dimensões de `self`), o método `paste()` precisa calcular a área de ``sobreposição`` entre as imagens e trocar os valores dessa região apenas, como ilustra a figura abaixo, onde `self` é o retângulo azul e `other` é o retângulo vermelho. Observer que as dimensões de cada imagem podem ser diferentes. .. raw:: html

Sobreposição.
Para tornar a atividade mais interessante, podemos carregar e exibir imagens podemos usar o módulo `Matplotlib` do Python. Esse módulo também é instalado pelo Anaconda, junto com o NumPy. Esse é um outro módulo bastante utilizado em computação científica para a construção e exibição de gráficos em geral. Para carregar e exibir imagens usando `Matplotlib` use o seguinte trecho de código usando o Spyder em seu computador: .. code:: python import numpy as np import matplotlib.pyplot as plt import matplotlib.image as mpimg from npimagem import NPImagem def main(): fname = 'elvis.png' image = mpimg.imread(fname) gray = np.array(image)[:,:,2] ## pega canal G da imagem RGB elvis = NPImagem( (), gray ) # transforma o array em uma NPImagem print("elvis.shape: ", elvis.shape) plt.gray() plt.imshow(elvis.data) plt.show() fname = 'einstein.png' image = mpimg.imread(fname) gray = np.array(image)[:,:,2] ## pega canal G da imagem RGB einstein = NPImagem( (), gray) print("einstein.shape", einstein.shape) plt.imshow(einstein.data) plt.show() main() Esse trecho ilustra também como podemos converter imagens do módulo `matplotlib.image` em imagens do tipo `NPImagem`. Para executar esse trecho, baixe as imagens na mesma pasta do seu computador contendo esse trecho de código e nomeie as imagens `elvis.png` e `einstein.png`. Ao executar esse exemplo, você deve "fechar" a janela que exibe uma imagem para abrir a próxima janela. Modifique (estenda) esse programa para produzir as visualizações que você desejar. Mais operações com imagens --------------------------- Uma outra operação interessante e divertida é a mistura de duas imagens (*blend*). Duas imagens, :math:`I0` e :math:`I1` podem ser misturadas por meio da seguinte fórmula para gerar a imagem :math:`I2`. .. math:: I2 = \alpha \; I0 + (1 - \alpha) \; I1 Para entender essa fórmula, considere um valor de :math:`\alpha` no intervalo [0.0, 1.0]. Para :math:`\alpha = 0.3`, por exemplo, a imagem :math:`I2` resultante seria composta por 30% de :math:`I0` e :math:`1 - 0.3 = 0.7` (ou 70%) de :math:`I1`. Exercícios ---------- Estenda a classe NPImagem com os métodos definidos a seguir que permitam misturar imagens. .. code:: python def __add__(self, other): ''' (NPImagem, NPImagem ou int ou float) -> NPImagem Quando recebe dois objetos NPImagem, retorna a soma, elemento-a-elemento, dos pixels de self e other. Quando other for int ou float, todos os elementos de self são adicionados de other. ''' def __mul__(self, other): ''' (NPImagem, NPImagem ou int ou float) -> NPImagem Quando recebe dois objetos NPImagem, retorna a multiplicação, elemento-a-elemento, dos pixels de self e other. Quando other for int ou float, todos os elementos de self são multiplicados por other. ''' Escreva também os métodos `__radd__()` e `__rmul__()`. Procure usar as propriedades de arrays do NumPy, evitando o uso de laços. Essas operações, realizadas diretamente com arrays, são conhecidas como **vetoriais**, ao invés de por-elemento. Por fim, escreva também o seguinte método ``blend()`` na classe NPImagem: .. code:: python def blend(self, other, alfa=0.5): ''' (NPImagem, NPImagem, float) -> NPImagem Recebe duas NPImagens e retorna uma nova NPImagem que mistura essas imagens com peso alfa e (1-alfa) tal que o resultado seja: self * alfa + other * (1-alfa) ''' O trecho de código a seguir ilustra o comportamento esperado desses métodos. .. code:: python img1 = NPImagem( (3,5), 10.0 ) img2 = NPImagem( (3,5), 20.0 ) img3 = img1.blend(img2, 0.10) print(img3) deve resultar na saída (do print): .. code:: python [[19. 19. 19. 19. 19.] [19. 19. 19. 19. 19.] [19. 19. 19. 19. 19.]] Onde estamos e para onde vamos? ------------------------------- Nessa primeira parte da disciplina focamos no desenvolvimento da capacidade de abstrair diversos tipos de dados e utilizar essas abstrações em nossos programas. Vimos que a criação desses tipos é simplificada pela programação orientada a objetos. Os tipos podem ser armazenados em módulos, como os módulos NumPy e Matplotlib, que podem ser reutilizados por outros programas. Dessa forma, podemos construir tipos cada vez mais complexos, como imagens, tornando nossos programas cada vez mais poderosos. Nas próximas aulas vamos começar a introduzir outro conceito fundamental do pensamento computacional, conhecido como **recursão**. Para saber mais --------------- * `Matplotlib: Visualization with Python `__. * `NumPy: the absolute basics for beginners `__. * `NumPy quickstart `__. * `NumPy fundamentals `__. O Python é conhecido por vir com suas "baterias incluídas", ou seja, há módulos disponíveis para simplificar a programação em muitas áreas diferentes. Para saber ainda mais dos módulos disponíveis para a área científica, além de NumPy e Matplotlib, ainda podemos citar SciPy, Pandas, Statsmodel, Keras e TensorFlow. Basta fazer uma busca na Internet para conhecer mais sobre esses e outros módulos do Python.