Trabalho para a A1 da disciplina Introdução à Computação Gráfica

Técnicas de processamento gráfico para a redução de ruído em imagens

Trabalho para a A1 da disciplina Introdução à Computação Gráfica

Lucas Emanuel Resck Domingues

Justificativa

A Computação Gráfica é um ramo da ciência que se desenvolveu muito nas últimas décadas, devido à demanda por conteúdos audiovisuais e pelo próprio avanço da Computação. Sabendo disso, o curso de Matemática Aplicada da Fundação Getulio Vargas ofereceu, no segundo semestre de 2019, a disciplina Introdução à Computação Gráfica, que, por sua vez, tem como parte da sua ementa técnicas de processamento de imagens. Além disso, o método de avaliação se dá por entrega de trabalhos. Sendo assim, técnicas de processamento de imagens se configuram um ótimo tema de projeto.

Introdução

Uma imagem digital é um conjunto de vários pixels, cada um carregando informação. Ruído é uma variação aleatória na informação desses pixels. Geralmente é indesejável, pois omite a informação que a imagem pretende apresentar e, esteticamente, é desagradável. Ruídos podem surgir em uma imagem por diversos motivos: baixa iluminação, temperatura do sensor de imagem, circuito eletrônico, conversão analógico-digital, erros de transmissão, quantização etc. Nesse sentido, foram desenvolvidos muitos algoritmos para a redução de ruído. O objetivo deste trabalho é, portanto, implementar alguns deles, a fim de compará-los e verificar seu funcionamento.

Metodologia

Realizarei a implementação de três filtros: filtro pela mediana, filtro pela média e filtro gaussiano (Gaussian blur). Todo o processamento de imagem será realizado em Jupyter Notebook (Google Colaboratory) na linguagem Python, devido às vantagens didáticas e de implementação. Foram utilizadas as bibliotecas OpenCV para Python, para a leitura e redimensionamento das imagens, e a biblioteca NumPy, para o cálculo e o manuseamento das informações das imagens. O código da implementação pode ser encontrado no repositório do GitHub.

A ideia é, a partir de uma imagem, criar uma nova imagem baseada na anterior, a partir das características de cada pixel. Foram testadas várias imagens nos formatos JPEG e PNG com os filtros implementados.

Neste trabalho, os filtros trabalham com a intensidade de cor na escala de cinza (para imagens em preto e branco) e a intensidade de cor em cada canal de cor da escala RGB (para imagens coloridas). Além disso, os filtros lidam com a ideia de vizinhança: a vizinhança de um pixel pode ser definida como o conjunto de pixels que contém ele mesmo e que contém os pixels que estão em volta dele. Aqui, considerarei uma vizinhança quadrada.

Alguns tipos de ruído serão principalmente explorados neste trabalho. No ruído salt-and-pepper, vários pixels da imagem se tornam aleatoriamente brancos ou pretos. Esse ruído é interessante pois, além de ser facilmente reproduzido, também é relativamente fácil de reobter a informação escondida. Outro ruído interessante de ser explorado é aquele causado pela quantização, ou seja, pela redução da resolução do espaço de cor.

Filtro pela mediana

Dada uma imagem, o filtro pela mediana cria uma nova imagem em que o valor de cada pixel é a mediana dos valores da vizinhança do pixel correspondente na imagem original, sendo este valor alguma característica do pixel. Se a imagem está em preto e branco, basta atribuir a cada pixel da nova imagem a mediana das intensidades de cinza dos pixels da vizinhança do pixel correspondente. Já se a imagem for colorida, esse mesmo processo é realizado em cada canal de cor separadamente.

Um problema muito comum nesse tipo de implementação está em como lidar com as bordas e os cantos da imagem, pois as vizinhanças desses pixels são menores. Minha solução foi realizar a mediana desses conjuntos limitados, nesses casos. Este é um exemplo, em Python, da implementação, para uma imagem preto e branco, em que height e width são as medidas da imagem, r indica quantos pixels para cada direção farão parte da vizinhança (r=1 indica 1 pixel acima, 1 abaixo etc., totalizando um quadrado de 9 pixels) e neighbours representa a matriz da vizinhança:

img2 = np.zeros((height, width))
for h in range(0, height):
    for w in range(0, width):
        neighbours = img[np.maximum(0, h-r):np.minimum(h+r+1, height-1), np.maximum(0, w-r):np.minimum(w+r+1, width-1)]
        img2[h][w] = np.median(neighbours)
                

Ou seja, inicio uma imagem com zeros, e, para cada pixel da imagem img, calculo a matriz da vizinhança e atribuo a mediana desses valores ao pixel correspondente na nova imagem. Observe que no cálculo da matriz de vizinhança já lido com o problema das bordas, selecionando apenas uma matriz dentro da imagem.

Filtro pela média

De maneira semelhante ao método anterior, cada pixel da nova imagem é uma média aritmética dos pixels da sua vizinhança. Os processos para a construção das imagens em preto e branco e coloridas são análogos. Assim também é a solução para o problema das bordas e cantos.

Segue um exemplo de implementação do filtro pela média, porém agora para imagens coloridas:

        red = img[:,:,0]
        green = img[:,:,1]
        blue = img[:,:,2]
        img2 = np.zeros((height, width, 3))
        for h in range(0, height):
            for w in range(0, width):
                red_neighbours = red[np.maximum(0, h-r):1+np.minimum(h+r, height-1), np.maximum(0, w-r):1+np.minimum(w+r, width-1)]
                img2[h][w][0] = np.mean(red_neighbours)
                green_neighbours = green[np.maximum(0, h-r):1+np.minimum(h+r, height-1), np.maximum(0, w-r):1+np.minimum(w+r, width-1)]
                img2[h][w][1] = np.mean(green_neighbours)
                blue_neighbours = blue[np.maximum(0, h-r):1+np.minimum(h+r, height-1), np.maximum(0, w-r):1+np.minimum(w+r, width-1)]
                img2[h][w][2] = np.mean(blue_neighbours)
                

Separo a imagem img em RGB, inicializo uma imagem com três canais de cor preenchidos de zeros e realizo um processo análogo ao do filtro pela mediana em cada canal de cor. Os resultados são atribuídos a uma nova imagem img2.

Filtro gaussiano

O filtro gaussiano, também conhecido como Gaussian blur, trabalha de maneira parecida com o filtro pela média, porém, ao invés de realizar uma média aritmética, esta é ponderada com pesos resultantes da função gaussiana.

\[f(x) = \dfrac{1}{\sigma\sqrt{2\pi}} \exp{-\left[\dfrac{(x-\mu)^2}{2\sigma^2}\right]}\]

Quando é realizado o cálculo do valor de um pixel na nova imagem, \(x\) representa a "distância" de um pixel pertencente à vizinhança em relação a esse pixel. \(\sigma\) é o desvio padrão, que controla a intensidade do filtro. \(\mu\) é a média da função gaussiana; queremos \(\mu=0\). A unidade de medida considerada é o pixel. O cálculo é feito unidimensionalmente, uma vez em cada uma das duas dimensões da imagem. Ou seja, gera-se uma imagem a partir de um cálculo unidimensional e aplica-se o processo novamente.

Teoricamente, ao realizar o cálculo do novo valor de um pixel, deveríamos considerar todos os pixels da imagem, pois a função gaussiana nunca tem valor zero. Porém, a partir de três desvios padrão, o valor da função já é praticamente irrelevante. Assim, a vizinhança considerada é apenas aquela até três desvios padrão.

Segue um exemplo de como calcular os pesos para o cálculo do valor do novo pixel:

    r = int(3*np.ceil(sig))
    kernel = np.zeros(2*r + 1)
    N = len(kernel)
    for n in range(N):
        x = (n + 0.5) - N/2 
        kernel[n] = gaussian(x,0,sig)
    kernel = kernel/np.sum(kernel)
                

Inicializamos um array com zeros, de tamanho \(1 + 2*3\sigma\) pixels, ou seja, três desvios para cada lado. Para cada elemento desse array, calculamos a gaussiana da distância desse elemento ao "centro" do array. Ao final, o array é normalizado, para não aumentar a soma das intensidades.

Neste filtro, a vizinhança é unidimensional e o processo é realizado duas vezes, uma em cada direção, sendo isso realizado em cada canal de cor, se a imagem é colorida. Para lidar com o problema da borda, minha solução foi trabalhar com a vizinhança limitada, como nos outros filtros, porém todos os pesos normalizados novamente.

Resultados

Ruído salt-and-pepper em imagem em preto e branco

Para os filtros pela mediana e pela média, foi utilizado o parâmetro r, apresentado anteriormente. Se \(r=1\), então temos um pixel para cada lado na vizinhança, totalizando um quadrado 3 por 3. \(r=2\), portanto, significa um quadrado 5 por 5. Já para o filtro gaussiano, o parâmetro é o próprio desvio padrão \(\sigma\). Clique na imagem abaixo!

Fonte: Wikimedia

Ruído salt-and-pepper em imagem colorida

Granulação em imagem colorida

Fonte: Wikimedia

Ruído de quantização em imagem colorida

Esse tipo de ruído aparece quando se deseja quantizar, ou seja, reduzir a resolução de cor, de uma imagem. A imagem abaixo foi quantizada uniformemente para 256 cores.

Fonte: Impa

Conclusão

Os resultados demonstram a funcionalidade dos filtros, tanto para a redução de ruído, quanto para um efeito borrão (blur) na imagem.

Nas figuras com ruído tipo salt-and-pepper, o filtro pela mediana se sai muito bem, na verdade a melhor das técnicas apresentadas. Em seguida, o filtro gaussiano se mostra promissor. Uma hipótese razoável para isso é que, nesse tipo de ruído, são inseridos pixels com intensidades extremas (branco e preto), de modo que a intensidade original do pixel, que é próxima às dos seus vizinhos, é aproximadamente capturada pela mediana. O filtro pela média, por sua vez, não captura o valor real de algum dos vizinhos, mas uma média de todos eles, média esta afetada pela inserção do ruído. Pixels que são claros (e seus vizinhos também o são), tendem a escurecer, e vice-versa. O mesmo parece ocorrer com o filtro gaussiano, afinal ele é uma média ponderada dos pixels vizinhos.

Na imagem granulada, temos um resultado razoável para todos os tipos de filtros. Já na imagem quantizada, conseguimos um efeito desejável, que é diminuir a "discretização" da imagem, muito visível originalmente. Porém, com isso, perdemos bastante nitidez e informação na figura.

Referências bibliográficas

COMPUTER HOPE. How to center text in HTML. [S. l.], [201-?]. Disponível em: https://www.computerhope.com/issues/ch001474.htm. Acesso em: 19 set. 2019.

DA COSTA, André Luiz Nunes Targino. REDUÇÃO DE RUÍDO EM IMAGENS. Rio de Janeiro, julho 2009. Disponível em: http://www02.smt.ufrj.br/~eduardo/teses/andre-targino-mestrado.pdf. Acesso em: 18 set. 2019.

HUNEYCUTT, Jake. Convert LaTeX into HTML with MathJax. [S. l.], 26 abr. 2018. Disponível em: https://medium.com/@hjhuney/how-to-convert-latex-into-html-a4334ffda3f4. Acesso em: 18 set. 2019.

KORPELA, Jukka. How do I justify text on both sides on Web pages?. [S. l.], 10 ago. 2000. Disponível em: http://jkorpela.fi/www/justify.html. Acesso em: 18 set. 2019.

PYTHON. PEP 8 -- Style Guide for Python Code. [S. l.], 5 jul. 2001. Disponível em: https://www.python.org/dev/peps/pep-0008/. Acesso em: 19 set. 2019.

SCHULZE, Mark A. What are the mean and median filters?. [S. l.], 24 abr. 2001. Disponível em: https://www.markschulze.net/java/meanmed.html. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML canvas drawImage() Method. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/tags/canvas_drawimage.asp. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML Canvas Text. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/graphics/canvas_text.asp. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML DOM addEventListener() Method. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/jsref/met_document_addeventlistener.asp. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML Entities. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/html/html_entities.asp. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML Links. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/html/html_links.asp. Acesso em: 18 set. 2019.

W3SCHOOLS.COM. HTML <pre> Tag. [S. l.], [201-?]. Disponível em: https://www.w3schools.com/tags/tag_pre.asp. Acesso em: 18 set. 2019.

WOOD, Adam. Using HTML Comment Tags - Easy How-To With Code. [S. l.], [201-?]. Disponível em: https://html.com/tags/comment-tag/. Acesso em: 18 set. 2019.