Artigos

Carregando e Manipulando Imagens em Python

Introdução

Manipulação de imagem refere-se ao processo de trazer alterações a uma imagem digitalizada, de forma a transformá-la em uma imagem desejada.

A linguagem Python conta com diversas bibliotecas que nos permitem trabalhar com imagens de forma intuitiva e eficiente.

Conhecendo e Instalando as Bibliotecas

Nesta etapa vamos conhecer e instalar as bibliotecas que utilizaremos em nosso tutorial.

A Biblioteca Pillow

A biblioteca padrão mais popular em Python para carregar e trabalhar com dados de imagem é o Pillow.

Pillow é uma versão atualizada da Python Image Library, também conhecida como PIL, e suporta uma variedade de funcionalidades simples e sofisticadas de manipulação de imagens.

Instalação

Utilizaremos pip para instalar a biblioteca Pillow, para isso podemos executar o seguinte comando:

pip install Pillow

O seguinte código nos confirma se a instalação ocorreu com sucesso e nos informa a versão atual da biblioteca Pillow

import PIL
print(f'Versão Pillow: {PIL.__version__}')

Ao executá-lo ele irá me reportar: Versão Pillow: 6.2.0.

A Biblioteca OpenCV-Python

O OpenCV foi iniciado na Intel em 1999 por Gary Bradsky e o primeiro lançamento ocorreu em 2000. Vadim Pisarevsky se uniu a Gary Bradsky para gerenciar a equipe OpenCV de software russa da Intel.

Em 2005, o OpenCV foi usado no Stanley, o veículo que venceu o 2005 DARPA Grand Challenge. Mais tarde, seu desenvolvimento ativo continuou com o apoio da Willow Garage, com Gary Bradsky e Vadim Pisarevsky liderando o projeto. No momento, o OpenCV suporta muitos algoritmos relacionados a Computer Vision e Machine Learning e está se expandindo dia a dia.

Atualmente, o OpenCV suporta uma ampla variedade de linguagens de programação como C++, Python, Java, entre outras.

OpenCV-Python é a API Python do OpenCV. Ela combina as melhores qualidades da API OpenCV C++ e da linguagem Python.

A Biblioteca OpenCV-Python conta com operações que vão nos auxiliar na manipulação e processamento de imagens.

Instalação

Novamente vamos instalar a biblioteca Opencv-Python utilizando o pip:

pip install opencv-python

Executamos o seguinte comando em nosso terminal para checar se a instalação ocorreu com sucesso:

python -c 'import cv2; print(cv2.__version__);'

Ele nos irá reportar a versão 4.2.0. Caso você esteja utilizando uma máquina Windows e ocorra algum problema, você pode visitar este guia.

A Biblioteca Matplotlib

Matplotlib é uma biblioteca Python de plotagem 2D capaz de produzir figuras de qualidade para publicação em uma variedade de formatos de cópia impressa e ambientes interativos entre plataformas.

Matplotlib nos permite carregar, manipular e plotar imagens.

Instalação

O Matplotlib e suas dependências estão disponíveis como pacotes para distribuições do macOS, Windows e Linux, vamos instalá-los com o seguinte comando:

pip install matplotlib

Para testarmos se a instalação ocorreu com sucesso, podemos executar:

python -c 'import matplotlib; print(help(matplotlib));'

Como output ele nos trará o manual de instruções de matplotlib.

A Biblioteca NumPy

NumPy é o pacote fundamental para a computação científica em Python e conta com um poderoso objeto array de N-dimensões.

Frequentemente, em Machine Learning, desejamos trabalhar com imagens como arrays NumPy de dados em pixels.

Instalação

Também é possível instalar NumPy através do pip:

pip install numpy

Verificando se a instalação ocorreu com sucesso:

python -c 'import numpy as np; print(np.__version__);'

O código acima nos trará como output a seguinte versão: 1.17.2.

Tutorial

Uma vez que todas as bibliotecas necessárias foram instaladas e já conhecemos um pouco sobre elas, é o momento de selecionarmos imagens para manipularmos:

img


img


img


img


img

Salvarei todas as imagens em um diretório chamado images que ficará dentro do mesmo diretório de nossos scripts experimentais. Sinta-se livre para escolher suas imagens favoritas.

Carregando e Apresentando Imagens com Pillow

As imagens geralmente estão no formato PNG ou JPEG e podem ser carregadas diretamente usando a função open() da classe Image.

Essa operação nos retorna um objeto de imagem que contém os dados de pixel da imagem, além de detalhes sobre a imagem.

from PIL import Image

# Carregando a imagem
imagem = Image.open('images/creation.jpg')

# Sumarizando detalhes sobre a imagem
# Modo da imagem
print(imagem.mode)
# Formato da imagem
print(imagem.format)
# Tamanho da imagem (largura, altura)
print(imagem.size)

# Apresentando a imagem
imagem.show()

O código irá exibir a seguinte imagem:

img

E nos trará como output em nosso console:

RGB
JPEG
(932, 310)

De forma que:

  • RGB é o modo, em outras palavras, o formato de canal de pixel.
  • JPEG é o formato da imagem que carregamos.
  • (932, 310) são as dimensões da imagem.

Carregando, Apresentando e Manipulando Imagens com Matplotlib

A função mpimg do módulo matplotlib.image nos permite carregar uma imagem como um numpy.ndarray 3D, sendo cada dimensão um canal de cor, RED, GREEN, BLUE e cada lista interna representa um pixel.

from matplotlib import pyplot as plt
import matplotlib.image as mpimg

# Carrega a imagem como um array de pixels
data = mpimg.imread('images/japanese.png')
# Imprime os arrays de dados
print(data)
# Imprime o tipo 
print(type(data))

# Dados sobre o array de pixels
# Tipo de dados
print(data.dtype)
# Dimensão de dados
print(data.shape)

# Apresenta o array de pixels como uma imagem
lum_img = data[:, :, 0]
imgplot = plt.imshow(lum_img, cmap='twilight')
plt.axis('off')
plt.show()

O código retornará em nosso console o seguinte output:

[[[0.9254902  0.9490196  0.89411765]
  [0.9254902  0.9529412  0.8901961 ]
  [0.93333334 0.9490196  0.8901961 ]
  ...
  [0.9372549  0.9490196  0.9137255 ]
  [0.9372549  0.9490196  0.9137255 ]
  [0.93333334 0.94509804 0.91764706]]
  ...
 [[0.9254902  0.9490196  0.9019608 ]
  [0.9254902  0.9490196  0.9019608 ]
  [0.9254902  0.9490196  0.9019608 ]
  ...
  [0.9372549  0.9607843  0.9137255 ]
  [0.92156863 0.94509804 0.90588236]
  [0.91764706 0.9411765  0.9019608 ]]]
<class 'numpy.ndarray'>
float32
(691, 470, 3)

E nos apresentará a imagem modificada, uma vez que alteramos o cmap='twilight'.

img

Para que possamos fazer essa alteração, foi necessário a seleção de apenas um canal de nossos dados lum_img = data[:, :, 0].

Carregando, Apresentando e Manipulando Imagens com Matplotlib, NumPy e Pillow

Neste exemplo vamos carregar uma imagem com a função open() e imediatamente utilizaremos a função convert('L') para convertermos a imagem em Preto e Branco, em seguida usamos a função np.array() para carregarmos a imagem como um numpy.ndarray.

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

imagem = np.array(Image.open('images/hal9000.jpg').convert("L"))
imagem[100:120] = 188
imagem[500:520] = 188

lx, ly = imagem.shape
print(lx,ly)

X, Y = np.ogrid[0:lx, 0:ly]
mask = (X - lx / 2) ** 2 + (Y - ly / 2) ** 2 > lx * ly / 4
imagem[mask] = 188

imagem[range(lx), range(ly)] = 255

plt.imshow(imagem, cmap='gray')
plt.show()

Observe que estamos capturando as dimensões da imagem através das variáveis lx e ly, que nos trará os valores 630 630, dimensão de nossa imagem carregada.

Nas linhas a seguir estamos aplicando um valor de cinza arbitrário 188 nos valores selecionados do array imagem, criando assim duas linhas horizontais em nossa imagem, uma superior e outra inferior.

imagem[100:120] = 188
imagem[500:520] = 188

Em seguida calculamos uma mascará circular e à aplicamos em nosso array imagem, também estamos aplicando uma linha diagonal branca(255) em nossa imagem.

X, Y = np.ogrid[0:lx, 0:ly]
mask = (X - lx / 2) ** 2 + (Y - ly / 2) ** 2 > lx * ly / 4
imagem[mask] = 188

imagem[range(lx), range(ly)] = 255

Nos será então apresentada a seguinte imagem:

img

Uma vez que não desabilitamos os eixos X e Y, é possível vermos a imagem como um gráfico de pixels.

Dividindo os Canais da Imagem (RED, GREEN, BLUE)

Neste script vamos carregar nossa imagem com a função imread() da biblioteca OpenCV.

Em seguida imprimimos o array de dados, seu tipo e número de dimensões.

Para dividir a imagem em seus três canais, basta chamar a função split() do módulo cv2, passando como entrada nossa imagem original, esta função retornará uma lista com os três canais. Cada canal é representado como um ndarray de 2 Dimensões

Em seguida criamos um array único para cada cor, com a ajuda de um “canal vazio” com a chamada da função zeros() do módulo numpy. Isso retornará um novo ndarray com a forma especificada e preenchida com zeros.

Para criar uma imagem RGB a partir de três canais separados, basta chamarmos a função de merge(), passando como entrada uma tupla com os canais.

Portanto, para criar a imagem RGB a partir do canal Blue, por exemplo, o primeiro elemento da tupla deve ser o canal Blue e os demais devem ser o “canal vazio”. Consideramos então o script:

import numpy as np
import cv2

img = cv2.imread('images/alchemy.jpg')
print(img)
print(img.dtype)
print(img.ndim)

blue, green, red = cv2.split(img)
zeros = np.zeros(blue.shape, np.uint8)
blueBGR = cv2.merge((blue,zeros,zeros))
greenBGR = cv2.merge((zeros,green,zeros))
redBGR = cv2.merge((zeros,zeros,red))

cv2.imshow('imagem',img)
cv2.imshow('blue BGR',blueBGR)
cv2.imshow('green BGR',greenBGR)
cv2.imshow('red BGR',redBGR)

cv2.waitKey(0)
cv2.destroyAllWindows()

Além da imagem original, nos será apresentado uma versão de cada cor

RED

img

GREEN

img

BLUE

img

Histograma de uma Imagem

Você pode considerar o histograma como um gráfico que fornece uma idéia geral sobre a distribuição de intensidade de uma imagem.

É um gráfico com valores de pixel (variando de 0 a 255, nem sempre) no eixo X e número correspondente de pixels na imagem no eixo Y.

É apenas outra maneira de entender a imagem. Ao olhar para o histograma de uma imagem, obtemos intuição sobre contraste, brilho, distribuição de intensidade dessa imagem.

Neste exemplo, usamos a função cv.calcHist() para encontrar o histograma, esta que recebe os seguintes parâmatros:

  1. images: é a imagem de origem do tipo uint8 ou float32. deve ser fornecido entre colchetes, ou seja, ”[img]“.
  2. channels: também é fornecido entre colchetes. É o índice do canal para o qual calculamos o histograma. Por exemplo, se a entrada for uma imagem em escala de cinza, seu valor será [0]. Para imagens coloridas, você pode passar [0], [1] ou [2] para calcular o histograma dos canais azul, verde ou vermelho, respectivamente.
  3. mask: mascarar imagem. Para encontrar o histograma da imagem completa, é fornecido como “None”. Mas se você deseja encontrar o histograma de determinada região da imagem, é necessário criar uma imagem de máscara para isso e fornecê-la como máscara.
  4. histSize: representa nossa contagem de BIN. Precisa ser fornecido entre colchetes. Para escala completa, passamos [256].
  5. ranges: essa é o RANGE. Normalmente é [0,256].

A seguir temos o código que vamos executar

from matplotlib import pyplot as plt
import cv2

plt.style.use('classic')

img = cv2.imread('images/buddhism.jpg')
color = ('b','g','r')

for i,col in enumerate(color):
    histr = cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color=col,lw=2)
    plt.xlim([0,256])
plt.grid()
plt.show()

O script nos apresentará o seguinte gráfico para a imagem selecionada:

img

Com o gráfico dessa imagem específica podemos observar que o AZUL tem grande quantidade de valores próximos de 50 em diversas regiões da imagem.

Transformando a Perspectiva com OpenCV

Para transformação de perspectiva, vamos precisar de uma matriz de transformação 3x3. As linhas retas permanecerão retas, mesmo após a transformação.

Para encontrar essa matriz de transformação, precisamos de 4 pontos na imagem de entrada e pontos correspondentes na imagem de saída. Entre esses 4 pontos, 3 deles não devem ser colineares.

Em seguida, a matriz de transformação pode ser encontrada pela função cv2.getPerspectiveTransform(). Por fim, aplicamos cv2.warpPerspective() com essa matriz de transformação 3x3.

Vejamos o script de exemplo

import matplotlib.pyplot as plt
import numpy as np
import cv2

img = cv2.imread('images/creation.jpg')
rows,cols,ch = img.shape

pts1 = np.float32([[390,5],[750,5],[505,265],[885,265]])
pts2 = np.float32([[0,0],[310,0],[0,310],[350,310]])

M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(img,M,(350,310))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

O resultado será

img

Redimensionando Imagens

Redimensionamento é uma tarefa muito comum ao trabalharmos com imagens.

Às vezes, é desejável que todas as imagens em miniatura tenham a mesma largura ou altura. Isso pode ser alcançado com Pillow usando a função thumbnail().

A função usa uma tupla com a largura e a altura, e a imagem será redimensionada para que a largura e a altura da imagem sejam iguais ou menores que a forma especificada.

Por exemplo, a imagem de teste com a qual vamos trabalhar no script a seguir, tem largura e altura de (932, 310). Podemos redimensioná-lo para (400, 200), a maior dimensão, neste caso, a largura, será reduzida para 400 e a altura será redimensionada para manter a proporção da imagem, por este motivo nossa altura resultante será 133.

from PIL import Image

# Carrega a imagem
image = Image.open('images/creation.jpg')
# cria um thumbnail e preserva o aspect ratio
image.thumbnail((400,200))
# reporta o tamanho do thumbnail
print(image.size)

image.save('images/creation.png',format='PNG')

Nos será apresentado o seguinte resultado.

img

Observe também que optamos por salvar a imagem no formato PNG.

É possível que não desejemos preservar a proporção e, em vez disso, forçamos os pixels a uma nova forma.

Isso pode ser obtido usando a função resize() que permite especificar a largura e a altura em pixels e a imagem será reduzida ou esticada para se ajustar à nova forma.

O exemplo abaixo demonstra como redimensionar uma nova imagem e ignorar a proporção original

from PIL import Image

# Carrega a imagem
image = Image.open('images/creation.jpg')
print(image.size)
# altera o tamanho da imagem e ignora o aspect ratio original
img_resized = image.resize((400,200))
# reporta o tamanho do thumbnail
print(img_resized.size)

img_resized.save('images/creation_resized.jpg')

Nos será trazido o seguinte resultado

img

Observe que agora a altura de nossa imagem será exatamente 200 pixels.

Girando uma Imagem

Uma imagem pode ser girada chamando a função transpose() e passando um método como FLIP_LEFT_RIGHT para um giro horizontal ou FLIP_TOP_BOTTOM para um giro vertical.

O exemplo a seguir cria versões de giros horizontais e verticais da imagem carregada

from matplotlib import pyplot
from PIL import Image

# Carregando a imagem
image = Image.open('images/creation.png')
# Giro horizontal
hoz_flip = image.transpose(Image.FLIP_LEFT_RIGHT)
# Giro vertical
ver_flip = image.transpose(Image.FLIP_TOP_BOTTOM)

# Projetando imagens
pyplot.subplot(311)
pyplot.imshow(image)
pyplot.subplot(312)
pyplot.imshow(hoz_flip)
pyplot.subplot(313)
pyplot.imshow(ver_flip)
pyplot.show()

Nos será apresentado o seguinte gráfico

img

Rotacionando uma Imagem

Podemos utilizar a função rotate() para rotacionar imagens, passando o ângulo de rotação desejada como argumento.

Vejamos um simples exemplo

from PIL import Image

# Carregando a imagem
imagem = Image.open('images/creation.jpg')

# Rotacionando a imagem em 13 graus
img_rotacao = imagem.rotate(13)

# Apresentando a imagem
img_rotacao.show()

Será aplicada uma rotação de 13 graus, nos trazendo o seguinte resultado

img

Cortando uma Imagem e Aplicando Gaussian Blur

Uma imagem pode ser cortada: em outras palavras, um pedaço pode ser cortado para criar uma nova imagem, usando a função crop(). A função crop() recebe como argumento uma tupla que define as duas coordenadas x/y da caixa para cortar a imagem.

Por exemplo, se nossa imagem é de 1000 por 1000 pixels, podemos recortar uma caixa de 100 por 100 no meio da imagem, definindo uma tupla com os pontos superior esquerdo e inferior direito de (450, 450, 550, 550)

Vejamos um exemplo para ilustrar a ideia

from PIL import Image, ImageFilter

# Carregando a imagem
image = Image.open('images/japanese.png')
# Cria uma imagem cortada
cropped = image.crop((55, 60, 320, 400))
# Aplica o GaussianBlur
img_filter = cropped.filter(ImageFilter.GaussianBlur(1.3))
# Mostra a imagem
img_filter.show()

Obteremos o seguinte output

img

Observe também que estamos aplicando um filtro Gaussian blur que é o resultado de desfocar uma imagem por uma função Gaussiana(em homenagem ao matemático e cientista Carl Friedrich Gauss). Este é um efeito amplamente usado em software gráfico, geralmente usado para reduzir o ruído da imagem e os seus detalhes.

Detectação de Edge

A detecção de edges é uma técnica de processamento de imagem para encontrar limites de objetos na imagem. Os pontos nos quais o brilho da imagem muda muito são tipicamente organizados em um conjunto de segmentos de linhas curvas denominados edges.

O Canny Edge Detection é um algoritmo popular de detecção de edges. Foi desenvolvido por John F. Canny em 1986. Ele é um algoritmo de vários estágios, você poder ler mais detalhes sobre ele nesse link

A biblioteca OpenCV nos fornece uma função Canny(), utilizada para detecção de edges.

Iremos aplicá-la na imagem que carregaremos e em seguida vamos redimensioná-la, vejamos o script de exemplo

import cv2
 
# Carregamos a imagem
img = cv2.imread('images/hal9000.jpg')

# Utilizamos a função Canny() para detectar edges
edges = cv2.Canny(img,100,200)

COLOR = [102,102,153]

scale_percent = 40 # percentagem do tamanho original
width = int(edges.shape[1] * scale_percent / 100)
height = int(edges.shape[0] * scale_percent / 100)
dim = (width, height)

# redimensiona a imagem
resized = cv2.resize(edges, dim, interpolation=cv2.INTER_AREA)

# adiciona borda a imagem
image = cv2.copyMakeBorder(resized, 10, 10, 10, 10,cv2.BORDER_CONSTANT,value=COLOR) 
 
# salva a imagem modificada
cv2.imwrite('images/haledge.jpg', image) 

Teremos como output a seguinte imagem modificada

img

Observe que também aplicamos uma borda na imagem através da função copyMakeBorder().

Conclusão

Através desse tutorial fomos capazes de experimentar diversas funcionalidades das bibliotecas Pillow, OpenCV-Python, Matplotlib e NumPy, que tornam a manipulação de imagens com Python muito mais simples e intuitiva.

Especificamente, aprendemos:

  • Instalação das bibliotecas necessárias
  • Carregamento de imagens
  • Manipulações como: redimensionamento, rotações, giros, corte, gaussian blur, conversões
  • Salvar as imagens em formatos diferentes

Para maiores detalhes e guias você pode consultar as referências do tutorial.

Tenha um excelente dia, ou noite!

Referências