EP9
EP9 - Processamento de Imagens Digitais: detectando e realçando bordas
``Uma imagem vale mais que mil palavras.'',
Provérbio chinês.
Objetivos
Neste exercício programa você vai treinar um pouco o uso de arrays
em Python, como definido no módulo Numpy. Para isso, você vai desenvolver um programa que manipula imagens digitais representadas na forma de Numpy arrays
.
- uso de objetos: usar
arrays
em programas em Python; - criação e manipulação de
arrays
; - uso de objetos que contém
arrays
; - representar imagens usando
arrays
; e - processar imagens digitais usando de filtros de convolução.
O que é uma imagem digital?
Uma imagem digital ou simplesmente imagem é basicamente uma matriz, com, digamos, altura height (número de linhas) e largura width (número de colunas). Cada elemento da matriz é chamada de pixel (picture element), que possui uma "cor".
Na sua forma mais básica um pixel pode ser representado por 1 bit (= dígito binário) para armazenar quando o pixel está aceso ("branco") ou apagado ("preto"). As imagens em que cada pixel possui apenas um dentre dois "níveis" possíveis são chamadas de imagens binárias. Em geral, no entanto, dois nívels são insuficientes para representar o que costumamos chamar de imagens em preto e branco, pois estas, em geral, possuem vários níveis ou tons de cinza. Uma forma comum de representar uma imagem com vários tons de cinza é reservando um byte (8 bits) para cada pixel. Desta forma podemos representar imagens com até 256 níveis de cinza por pixel. Chamamos as imagens em que cada pixel pode ter vários tons de cinza de imagens com níveis de cinza.
Uma imagem com níveis de cinza nos permite ver as variações de luminosidade da cena. Já uma imagem colorida requer ainda mais informação para cada pixel. Baseado no sentido da visão humana, que é tricromática, a representação de imagens mais comum é obtida decompondo uma cor nas componentes básicas red (vermelho), green (verde) e blue (azul) ou RGB. Assim, usando 1 byte para representar cada cor primária (vermelho, verde e azul), podemos representar todo o espéctro visivel de cores. Nesse EP vamos adotar o método True Color (24 bits) para representar imagens coloridas.
Transformando uma imagem colorida para níveis de cinza
Nesse EP, uma imagem colorida será uma matriz de pixeis, onde cada pixel é um outro array com 3 valores representado as intensidades das componentes R
(vermelho), G
(verde), e B
(azul), respectivamente. No formato True Color Vamos usar um inteiro de 8 bits para representar cada intensidade, ou seja, um número de 0 a 255. Assim, a cor vermelha será presentada pela lista [255,0,0], a cor verde por [0,255,0] e a cor branca, que é formada pela combinação de todas as cores, será [255,255,255]. Observe que podemos considerar cada canal separadamente (R
, G
, ou B
) como uma imagem em níveis de cinza.
Para converter uma imagem colorida em níveis de cinza você deve utilizar a regra de Luminância Relativa definida pela seguinte fórmula:
L = 0.2126 R + 0.7152 G + 0.0722 B
Onde L
é a imagem com níveis de cinza resultante da imagem colorida formada por R
, G
e B
.
Convolução
A operação de convolução é muito utilizada para processar imagens e pode ser matematicamente representada por
C = I * F
Onde C
é uma imagem resultado da convolução (operador *
) da imagem de entrada I
por um filtro F
. Vamos assumir que as imagens C
e I
são imagens com níveis de cinza e o filtro é uma matriz 2D de números reais. O filtro representa uma transformação que é aplicada na imagem I
e tem forma quadrada de ordem ímpar, ou seja, é uma matriz 3 × 3, 5 × 5, 7 × 7 etc. Você deve assumir que um filtro tem tamanho ímpar pois assim sempre há um elemento central. O filtro só deve ser aplicado em posições [lin,col]
válidas da imagem.
Uma posição é válida se, quando o ponto central do filtro F
for colocado em [lin,col]
, todos os elementos do filtro possuem um elemento correspondente na imagem, ou seja, o filtro não pode ser aplicado fora da imagem. As posições inválidas ocorrem nas primeiras e últimas linhas e colunas da imagem. A imagem resultado da convolução C
deve receber 0 (zero) nas posições inválidas.
Para cada posição válida, o resultado da convolução C[lin,col]
é calculado como a soma da multiplicação elemento a elemento do filtro F
na posição [lin,col]
pelo conteúdo correspondente da imagem.
As imagens a seguir ilustram a operação de convolução. Na imagem da esquerda, kernel representa o filtro, input faz as vezes da imagem em níveis de cinza, o pixel azul escuro é o da posição do ponto central [lin,col]
e o pixel em vermelho representa o valor resultado da convolução. A imagem da direita apresenta um exemplo numérico de convolução. Ambas as imagens foram copiadas de River Trail documentation.
Detecção de Bordas
Para treinar o uso de objetos com arrays nesse EP vamos detectar e realçar bordas em imagens. De uma maneira geral, os algoritmos para detecção de bordas (edge detection) procuram identificar os pixels da imagem em que há uma mudança brusca ou descontinua na luminosidade (luminous intensity) (ou seja, em uma imagem com níveis de cinza).
Para determinar se um pixel [lin,col]
é de borda, primeiramente utilizaremos o filtro de Sobel (Sobel operator). No método será necessário o cálculo de duas grandezas, os chamados gradientes horizotal gH
e o gradiente vertical gV
para cada pixel [lin,col]
. A seguir denotaremos por L[lin,col]
a luminosidade relativa de um pixel [lin,col]
. Os valores de gH
e gV
em cada pixel (válido) [lin,col]
serão calculados da seguinte maneira:
gH[lin,col] = L[lin-1,col+1] + 2*L[lin,col+1] + L[lin+1,col+1]
- L[lin-1,col-1] - 2*L[lin,col-1] - L[lin+1,col-1]
gV[lin,col] = L[lin+1,col-1] + 2*L[lin+1,col] + L[lin+1,col+1]
- L[lin-1,col-1] - 2*L[lin-1,col] - L[lin-1,col+1]
Observe que gH
e gV
são imagens resultantes da convolução da imagem L
com os filtros de Sobel Sh
(horizontal) e Sv
(vertical), mostrados abaixo:
0 1 2 0 1 2
+----+----+----+ +----+----+----+
0 | -1 | 0 | +1 | 0 | -1 | -2 | -1 |
+----+----+----+ +----+----+----+
1 | -2 | 0 | +2 | 1 | 0 | 0 | 0 |
+----+----+----+ +----+----+----+
2 | -1 | 0 | +1 | 2 | +1 | +2 | +1 |
+----+----+----+ +----+----+----+
Sh para cálculo de gH Sv para cálculo de gV
Após calculadas as componentes gH
e gV
, cada pixel deve ser classificado como borda caso o módulo do seu gradiente seja maior que um limiar, ou seja, pixels de borda são aqueles que satisfazem a seguinte condição:
sqrt(gH * gH + gV * gV) > limiar,
em caso contrário, o pixel não é de borda.
O que você deve fazer?
Você deve implementar os seguintes métodos dentro da classe Imagem
no arquivo esqueleto_ep9.py
.
para_cinza
binarize
filtre
pinte
segmente_bordas
As especificações desses métodos estão no arquivo esqueleto_ep9.py
.
Observe que a classe Imagem já tem vários métodos implementados que você pode utilizar, mas você não deve modificá-los.
Exemplos de execução do programa principal
Nesse EP, o programa principal pede ao usuário o nome de uma imagem no formato PNG (ou seja, arquivo com extensão .png
), o nome do arquivo imagem de saída (também com extensão .png
) e o valor de um limiar utilizado para segmentar as bordas. Quanto mais alto o limiar, menos bordas o programa deve segmentar. As imagens intermediárias são exibidas em janelas, que o usuário deve fechar para que o programa prossiga. Cada borda segmentada é realçada (pintada) e, ao final, o resultado é salvo na imagem de saída. Você pode alterar a função main
para testar o seu EP, alterando parâmetros, cor de realce, filtros etc.
Como exemplo, você pode utilizar as imagens calouro-ime.png e ime-usp. Ao executar o programa para essas imagens teríamos:
Digite o nome do arquivo de entrada [.PNG] >>> ime-usp.png Digite o nome do arquivo de saída [.PNG] >>> saida2.png Digite o limiar desejado (int) >>> 20 Imagem de entrada: ime-usp.png Feche a janela da imagem para continuar....
e o programa deve exibir a imagem de entrada em uma janela como abaixo:
Após fechar a janela, o programa continua com:
Imagem das bordas segmentadas: Feche a janela da imagem para continuar....
exibindo a imagem binária com as bordas:
e após fechar essa janela temos:
Imagem com bordas realçadas Feche a janela da imagem para continuar....
Fim. A imagem realçada foi salva em saida2.png
Rodando o programa para a imagem calouro-ime.png e o limiar 25 temos:
Roteiro
-
Faça o download do arquivo
esqueleto_ep9.py
. -
Mude o nome do arquivo
esqueleto_ep9.py
paraNUSP_ep9.py
, ondeNUSP
é o seu número USP. -
Abra o arquivo
NUSP_ep9.py
no Spyder ou em qualquer outro editor ou ambiente apropriado para desenvolver programas em Python. Esse é o único arquivo que você deve editar. -
Leia e preencha o cabeçalho do arquivo com o seu nome, nusp, etc. Não modifique o resto do cabeçalho.
-
Implemente cada um dos métodos da classe
Imagem
e teste-os, usando imagens bem pequenas antes de usar imagens grandes. A operação de convolução é cara computacionalmente, e pode demorar bastante caso a imagem for grande. -
Teste o seu programa com várias imagens diferentes.
-
Entregue o arquivo
NUSP_ep9.py
na página da disciplina. -
Não deixe de seguir as Instruções para entrega de EPs.
Entrega
A entrega deve ser feita até o dia 20/11 (até 23h55m).
O EP receberá comentários até o dia 22/11.
Uma nova versão poderá ser entregue até o dia 23/11 (até 23h55m).