Tarefa 7 - Processando imagens

Prazo de entrega recomendado:

Nesta tarefa iremos trabalhar com algumas operações presentes em processamento de imagens. Você deve manipular matrizes para aplicar filtros em imagens e deverá manipular arquivos para armazená-las.


O processamento de imagens está presente em várias áreas da computação e é responsável por tarefas como classificação, extração de características, reconhecimento de padrões, entre outros. É caracterizado como um algoritmo que recebe uma imagem como entrada, produzindo outra imagem como saída.

Nesta tarefa, trabalharemos com dois tipos de formatos de imagem, PBM e PPM. Ambos trazem uma representação simplificada (sem compressão de dados), o que facilitará nossa utilização. O primeiro formato trata de imagens que podem ser representadas apenas por branco e preto, enquanto o segundo é um formato para imagens coloridas. Mais informações sobre os formatos podem ser vistas aqui.

1. Imagens monocromáticas

Imagens preto e branco podem ser facilmente descritas por uma matriz de bits, onde 0 e 1 representam, respectivamente, pixels branco e preto. Este é o objetivo do formato PBM: a primeira linha indica o tipo do arquivo, P1; a segunda linha indica as dimensões da imagem, largura e altura; cada linha seguinte representa uma linha da imagem, conforme descrito acima.

Em seguida, temos um arquivo PBM e sua imagem correspondente (ampliada, para facilitar a visualização).

P1
7 11
0 0 0 0 0 0 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 1 0 0 0 1 0
0 0 1 1 1 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0

Detectando bordas

Em processamento de imagens, uma tarefa recorrente é detectar as fronteiras de objetos em uma imagem. A borda de um objeto é formada pelos pixels do objeto que têm ao menos um vizinho que não está no objeto, ou seja, um pixel 0. A vizinhança considera os 8 pixels ao redor do pixel central.

A seguir, podemos ver um exemplo de imagem binária e a sua imagem de bordas.

Você deve implementar um programa bordas.py que, dada uma imagem descrita no formato PBM, produza uma imagem de suas bordas, descrita no mesmo formato. Você deverá utilizar manipulação de arquivos em Python para ler/escrever essas imagens. O seu programa receberá dois parâmetros de linha comando representando, respectivamente, o nome da imagem original e a imagem com as bordas detectadas (que será criada pelo seu programa).

user@host:../tarefa07$ ls
aviao.pbm   bordas.py   testar.py   testes/
user@host:../tarefa07$ python3 bordas.py aviao.pbm aviao_bordas.pbm
user@host:../tarefa07$ ls
aviao.pbm   bordas.py   aviao_bordas.pbm   testar.py   testes/

2. Imagens coloridas

Podemos representar imagens coloridas (RGB) com o formato PPM, que se assemelha ao formato anterior ao descrever os pixels da imagem, linha a linha. Neste caso, porém, damos três valores para cada pixel, um para cada canal (red, green, blue), que formam a cor do pixel.

Veja abaixo o formato do arquivo PPM (note que o 255 na terceira linha indica o valor máximo de cada cor, e sempre estará presente).

P3
3 2
255
255 0 0 0 255 0 0 0 255
255 255 0 255 255 255 0 0 0

Aplicando filtros

Filtros de convolução são usados em imagens para aplicar diversos efeitos, como blur, sharpening, detecção de bordas, entre outros. Estes filtros são descritos através de um kernel, que é essencialmente uma pequena matriz, geralmente 3x3. A imagem com o efeito é o resultado da convolução da imagem original com o kernel.

A convolução é um processo operado em cada pixel da imagem original: somam-se os valores dos pixels em sua volta, multiplicados pelo valor daquela posição no kernel.

Suponha que o kernel seja a matriz à esquerda, enquanto a matriz à direita seja uma parte da imagem, representando os valores dos pixels. Neste momento, estamos calculando o pixel da posição cujo valor na imagem original é 25. O valor deste pixel na imagem com filtro, após a convolução, será

$$ a \times 10 + b \times 10 + c \times 10 + d \times 10 + e \times 25 + f \times 10 + g \times 10 + h \times 10 + i \times 10. $$

Quando realizamos esse procedimento em todos os pixels da imagem, temos a imagem filtrada.

Temos que tomar alguns cuidados ao escrever um algoritmo para aplicar um filtro de convolução. Se ao aplicarmos o kernel em um pixel, algum vizinho desse pixel estiver fora da imagem (ou seja, se o pixel central estiver na borda), então a posição desse pixel na imagem filtrada receberá valor 0. Além disso, o valor calculado pode sair dos níveis mínimo e máximo (0 e 255). Portanto, é a sua responsabilidade impor estes limites na imagem com filtro

Como nós aplicaremos a convolução em uma imagem PPM, que tem três canais de cor, iremos realizar a operação de convolução separadamente em cada cor, somando apenas valores do mesmo canal. Note que a soma opera somente sobre os valores dos pixels na imagem original, nunca sobre valores recém calculados.

Escreva um programa filtros.py que receba três parâmetros de linha comando que representam, respectivamente, o nome do filtro utilizado, o nome da imagem original e o nome da imagem com filtro. Assim como no primeiro exercício, você deve ler e escrever arquivos para manipular as imagens.

Seu programa deve implementar um filtro bordas, cujo kernel $K$ está descrito abaixo. Seu programa será avaliado somente com esse filtro nas imagens fornecidas.

$$ K = \begin{pmatrix} -1 & -1 & -1 \\ -1 & 8 & -1 \\ -1 & -1 & -1 \end{pmatrix} $$

Uma chamada usual de seu programa terá o formato abaixo.

user@host$ python3 filtros.py bordas bike.ppm bike_bordas.ppm

Esse comando recebe a imagem original e cria uma nova imagem modificada com o filtro de detecção de bordas aplicado. Veja o exemplo abaixo.

Mais filtros (opcional)

Existem muitos outros filtros baseados em kernel para processamento de imagens, veja aqui e aqui. O seu programa é genérico para aplicar qualquer kernel $n \times n$?

filtro_sharpen = [[0, -1, 0], [-1, 5, -1], [0, -1, 0]]
filtro_left_sobel = [[1, 0, -1], [2, 0, -2], [1, 0, -1]]
filtro_emboss = [[-2, -1, 0], [-1, 1, 1], [0, 1, 2]]
filtro_blur = [[0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04] ]
filtro_unsharp = [[-0.00390625, -0.015625, -0.0234375, -0.015625, -0.00390625], [-0.015625, -0.0625, -0.09375, -0.0625, -0.015625], [-0.0234375, -0.09375, 1.859375, -0.09375, -0.0234375], [-0.015625, -0.0625, -0.09375, -0.0625, -0.015625], [-0.00390625, -0.015625, -0.0234375, -0.015625, -0.00390625]]

Dicas

Testando seu programa

Correção

Depois de submetida e terminada a tarefa, você deverá apresentá-la a um monitor PED. Para isso, você deve procurar atendimento em algum horário com monitor PED e digitar apresentar 7 no canal fila-apresentar.

Temos cinco casos de testes, dois para o primeiro problema e três para o segundo, onde o último é opcional (não altera sua nota). Ambos os problemas possuem imagens pequenas para ajudar no processo de resolução da tarefa, compare sua solução com essas imagens.

Para obter o conceito C, você precisa resolver o primeiro problema com todas as imagens PBM; o conceito B é obtido resolvendo o segundo problema com as imagens pequeno.ppm e colored_star.ppm, e o conceito A é obtido considerando todas as imagens PPM no segundo problema.

A pasta da tarefa no seu repositório vem inicialmente com um arquivo nao_corrigir.txt que serve para indicar que você ainda está trabalhando nesta tarefa. Você pode realizar quantos pushes quanto forem necessários. O monitor só irá corrigir sua tarefa quando esse arquivo for removido do repositório.