Visão Computacional: Como o computador vê uma imagem

Computação visual é a habilidade do computador de tratar, analisar e processar imagens. Ela está presente na computação desde 1920, quando uma imagem foi enviada de Londres para Nova York através de um cabo submarino. Hoje, essa prática faz parte do cotidiano de diversas pessoas via aplicativos de visualização e manipulação de fotos e imagens, como Instagram e Photoshop. O avanço da inteligência artificial, com o reconhecimento de objetos e aliado à baixa de preços de computadores forte processamento, possibilitou novas utilidades à visão computacional. Como exemplo, podemos citar o reconhecimento de veículos e obstáculos para a criação de carros autônomos; o reconhecimento de rostos utilizado para identificar foragidos (até mesmo em festas); a identificação de padrões em imagens médicas para reconhecimento de tumores; e imagens de satélite que calculam o desmatamento em florestas.

Faremos uma série de artigos para explicar o funcionamento das técnicas de visão computacional aliado aos processos de machine learning. Serão 3 artigos: o primeiro (no post de hoje) vamos tratar sobre como o computador vê uma imagem; já o segundo sobre as técnicas de redes neurais; e no último sobre redes neurais convolucionais. Vale lembrar que todos os capítulos terão códigos em Python para que você possa fazer seu próprio sistema!

Como o computador enxerga uma imagem?

Na computação, imagem é uma matriz de pixels de várias dimensões.

Se ampliarmos bem uma imagem vamos ver diversos “quadrinhos coloridos” (como o exemplo abaixo). Estes quadrados são chamados de pixel, ou seja, a menor divisão de uma imagem. Uma imagem é formada com diversos destes pontos (uma matriz de pixels)!

Computadores trabalham com números e não cores, então cada pixel é salvo no computador como um valor que corresponde a intensidade de cor presente nele. Normalmente a cor é salva com intervalos de 0 a 255, no qual 0 é totalmente preto e 255 totalmente branco. Concluímos que para um computador uma imagem é uma matriz de números. O gif abaixo demonstra essa troca:

Cores

Você deve ter percebido que nos exemplos acima demonstramos somente imagens em tons de cinzas. Então, como funciona com imagens  coloridas?

Apesar de existir diversas maneiras de trabalharmos com cores, nas imagens elas são representadas apenas pela combinação de 3 cores base: vermelho, verde e azul. Este sistema é chamado de RGB (do inglês red, green e blue) e podemos colocar uma porcentagem de cada tonalidade para formar uma cor. Por exemplo: a cor vermelha é definida com um tom de 100% vermelho, 0% verde e 0% azul; já o amarelo combina igualmente verde e vermelho (100% vermelho, 100% verde e 0% azul). Para termos mais precisão, e consequentemente mais cores, dividimos a tonalidade entre valores de 0 a 255, assim o vermelho seria (255,0,0) e o amarelo (255,255,0). Abaixo temos alguns exemplos de como o sistema funciona:

 

Em uma imagem colorida, cada pixel pode ter uma combinação destas três cores, ou seja, um pixel é formado por 3 valores. Assim, uma imagem colorida não é formada por uma matriz, mas 3 matrizes que representam a porcentagem de cada cor básica em um pixel.

Extrair Informações

Para processarmos e analisarmos imagens, precisamos extrair informações para a análise dos pixels. Existem diferentes maneiras para conduzir essa análise. Seguem algumas opções de análise abaixo:

Histogramas: uma das maneiras mais famosas. São gráficos que descrevem a distribuição de frequências de valores discretos. No caso de imagens, ele pode mostrar como está distribuído os valores dos pixels, também entre 0 a 255. No caso do histograma demonstrar mais valores baixos significa que a imagem está escura, enquanto valores altos são imagens claras.

Os histogramas também podem ser separados pelas camadas RGB:

Filtros Convolucionais: outra maneira importantíssima de extrair informações para os modelos mais recentes de redes neurais. Esse filtro preserva a relação espacial entre os pixels, ou seja, preservamos dados sobre quais pixels estão próximos. Não vamos entrar em detalhes matemáticos dos processos de convolução, mas resumindo, ele se dá ao aplicar uma matriz chamada sobre a imagem.

Dado a matriz 5×5, abaixo:

Podemos aplicar a segunda matriz 3 x 3 sobre ela como um filtro:

Ao aplicá-los teríamos uma nova matriz resultante, como o gif abaixo:

Ao aplicar diferentes filtros podemos ter diferentes imagens. Cada filtro ressalta características específicas da imagem:

 

Demonstração do Código em Python

Após uma breve explicação sobre imagem em computação, vamos a prática!

Com o Python podemos fazer todas as operações descritas. Podemos utilizar algumas bibliotecas de processamento de imagens para facilitar a codificação.  As duas libs mais famosas são o PIL e o OpenCV. Nos exemplos abaixo vamos utilizar o OpenCV.

Primeiramente precisamos instalar a biblioteca em sua máquina:

pip install opencv-python

Nos exemplos vamos utilizar a imagem a abaixo:

 

Para lermos a foto acima, precisamos utilizar o seguinte código:

import cv2
img = cv2.imread('flor.jpg')

E para visualizarmos a imagem usaremos o Matplotlib com os seguintes comandos:

import matplotlib.pyplot as plt
plt.imshow(img)
plt.axis('off');

Você vai ver o seguinte resultado:

Ops! A imagem ficou com uma cor estranha, não? Isso acontece pois o Opencv não usa por padrão RGB, e sim BGR, logo devemos fazer fazer uma troca no formato:

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

Para extrairmos as camadas RGB da imagem precisamos separar cada camada da matriz:

r = img.copy()
r = r[:, :, 0]
g = img.copy()
g = g[:, :, 1]
b = img.copy()
b = b[:, :, 2]

Com os comandos abaixo conseguiremos ver as 3 imagens com um filtro para cada camada:

plt.imshow(b,cmap='Blues_r');
plt.imshow(r,cmap='Reds_r');
plt.imshow(g,cmap='Greens_r');

   

Para extrairmos o histograma usaremos a biblioteca Seaborn com o seguinte comando:

sns.distplot(img.flatten())
plt.xlim([0,256])

Vale lembrar que cada camada tem intensidades diferentes: o azul tem mais cores claras e o vermelho mais cores escuras. Utilizaremos o código abaixo para extrair os histogramas por camadas:

sns.kdeplot(r.flatten(),color='red',bw=0.005)
sns.kdeplot(g.flatten(),color='green',bw=0.005)
sns.kdeplot(b.flatten(),color='blue',bw=0.005)
plt.xlim([0,256])

Nesta imagem temos o seguinte resultado:

Por fim, podemos aplicar os filtros convolucionais ao criar um matriz (kernel) e aplicar o comando cv2.filter2D. Segue alguns exemplos:

Filtro Sharpen

kernel = np.array([[0 , -1,  0],
                   [-1,  5, -1],
                   [0 , -1,  0]])

opencvSaida= cv2.filter2D(img, -1, kernel)

 

Filtro De bordas 1

kernel = np.array([[-1, -1, -1],
                   [-1,  8, -1],
                   [-1, -1, -1]])

opencvSaida = cv2.filter2D(img, -1, kernel)

Filtro De bordas 2

kernel = np.array([[ 1,  1,  1],
                   [ 0,  0,  0],
                   [-1, -1, -1]])
opencvSaida = cv2.filter2D(img, -1, kernel)

Filtro Gaussiano

Podemos também usar funções prontas como o filtro gaussiano:

opencvSaida = cv2.GaussianBlur(img,(21,21),0)

Extra (Filtro Sépia)

Alguns filtros aplicam operações aritméticas com o valor de cada pixel da imagem. Um exemplo é o filtro Sépia (tom de envelhecido na foto), que alterna valores do RGB em função dos valores anteriores.

img = cv2.imread('flores.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#Função para os valores não ultrapassarem 255 nem serem menor que 0
def max_min(valor):
   if valor > 255:
      return 255
   elif valor < 0:
      return 0
   return valor

for l in range(larg):
   for a in range(alt):
      R = img[l,a,0]
      G = img[l,a,1]
      B = img[l,a,2]newR =max_min(R * 0.393 + G * 0.769 + B * 0.189)
      newG = max_min(R * 0.349 + G * 0.686 + B * 0.168)
      newB = max_min(R * 0.272 + G * 0.534 + B * 0.131)
      img[l,a] = [newR,newG,newB]

Temos a seguinte saída:

Próximos Passos

Agora que sabemos um pouco mais sobre leitura de imagens e como manipulá-las em Python, já podemos utilizar esse conhecimento para identificar objetos com técnicas de aprendizado de máquina. Nas próximas semanas postaremos mais detalhes!