Os filtros são encarregados de tarefas muito importantes em processamento de imagens. Seja para remover ruído aumentar contraste ou ressaltar característica da imagem como cantos bordas e agrupamentos.
Processamento de sinais é uma área bastante ampla e você pode projetar seus próprios filtros. Entretanto, existem alguns filtros com uso mais frequentes em processamento de imagem. E claro, eles estão implementados no OpenCV.
Nesse poste vamos estudar às quatro opções de filtros para desfoque em imagem que mais utilizo em meus projetos de visão computacional.
A documentação de OpenCV classifica esse filtros como filtros de alisamento ou desfoque (Smoothing). Com certeza você sabe o que é uma imagem fora de foco (embaçada). É exatamente isso que esses filtros fazem.
Mais porque alguém pode querer desfocar uma imagem?
O resultado visual, é uma imagem desfocada, mais, na verdade, a operação remove componente de baixa frequência. Isso pode ser útil para remover detalhes e manter apenas a estrutura dos objetos.
Os algoritmos de extração de contorno produz resultados melhores em imagens desfocadas.
Configuração do ambiente
Para executar os script desse póster você precisa:
- python 3.x
- pip3 install opencv-contrib-python
- uma image no local do escript com nome img.jpg
obs: o pacote opencv-python também serve (no windows o comando é pip e não pip3)
Como aplicar um filtro.
A implementação de filtro no OpenCV usa uma estrutura bem simples. Ele consiste em convolver uma matriz (kernel) pela imagem, as operações são executadas entre os valores do kernel e os pixels embaixo.
\begin{bmatrix}
1 & 1 & 1 \\
1 & 1 & 1 \\
1 & 1 & 1 \\
\end{bmatrix}
Normalmente os kernel são normalizados, isto é, cada valor é dividido pelo tamanho do kernel. Nesse caso 3×3=9, essa divisão és implemente para não distorcer as cores da imagem, assim a operação depende apenas de sua vizinhança.
Na Figura 1a, o kernel é posicionado na imagem, com apenas um pixel maior que 0 (255) embaixo do kernel. Logo o resultado é 225/9=25, esse valor é colocado no centro do kernel na nova imagem.
Na segunda etapa (Figura 1b), temos 2 pixels maior que zero, logo 225/9+225/9 =50
A operação é equivalente somar os pixels e dividir pelo total, ou seja, estamos calculando a média.
As implementações de filtro no OpenCV segue essa lógica, quando mudamos a operação temos outro filtro. Por exemplo, se calcularmos a mediana temos um filtro com característica diferente.
Além dos filtros padrões no OpenCV existe uma implementação genérica onde podemos construir nosso próprio kernel. Veja uma exemplo.
import cv2 import numpy as np img=cv2.imread("img.jpg") kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) filter2d = cv2.filter2D(src=img, ddepth=-1, kernel=kernel) cv2.imshow("janela",filter2d) cv2.waitKey()
Esse kernel é conhecido, ele melhora a nitidez da imagem. Observe que o kernel é normalizado, a soma dos valores é igual e 1.
O resultado pode conter número decimal com vírgulas, ou seja, profundidade da imagem de saída pode diferir da imagem de entrada. O parâmetro ddepth=-1 converte o resultado para a mesma profundidade da imagem de entrada (inteiro de 8 bits).
Filtro Blur
O blur é o filtro de média, ele executa a operação que vimos no exemplo. Para aplicar esse filtro em uma imagem basta definir o tamanho do kernel e passar como parâmetro para a função cv2.blur.
import cv2 img=cv2.imread("img.jpg") filter_blur=cv2.blur(img,ksize=(3,3)) cv2.imshow("janela",filter_blur) cv2.waitKey()
Os kenel de tamanho 3 e 5 são mais rápidos, mais o efeito visível começa ser significativo a partir de 9.
Filtro GaussianBlur
O GaussianBlur usa um kernel em forma de Gaussiana. Isso é como um cone no centro do kernel, os valores vão decaindo a medida que afasta.
import cv2 img=cv2.imread("img.jpg") gaugaussian=cv2.GaussianBlur(src=img,ksize=(13,13),sigmaX=0) cv2.imshow("janela",gaugaussian) cv2.waitKey()
O kernel precisa ter dimensões ímpares para manter a simetria da Gaussiana. O parâmetro sigmaX permite definir o desvio padrão na direção x, (existem sigmaY, se não especificado sigmaY=sigmaX), quando o valor é 0 o sigmaX vai ser calculado em função do tamanho do kernel.
O GaussinBlur devolve um resultado muito parecido com blur, porém sua execução é mais lenta.
Filtro medianBlur
A principal característica do filtro medianBlur é que o pixel da nova imagem é sempre um valor de pixel da imagem anterior. Isso não causa o efeito de suavização de borda como o filtro blur e GaussianBlur.
O tamanho do kernel para medianBlur deve ser um valor ímpar
import cv2 img=cv2.imread("img.jpg") median=cv2.medianBlur(img,13) cv2.imshow("janela",median) cv2.waitKey()
O medianBlur é o filtro mais eficaz para remoção de ruído, porém pode custar computacionalmente 8 vezes em relação do blur.
Filtro Bilateral
O filtro bilateral também utiliza uma Gaussiana, porem tem uma pequena diferença. No filtro gaussianoBlur é espacial, os seja ele calcula uma média ponderada dos pixels vizinhos. A Gaussiana do filtro bilateral é uma função da intensidade, somente os pixels com intensidade parecida é considerado para o desfoque.
import cv2 img=cv2.imread("img.jpg") bilateral=cv2.bilateralFilter(img,d=13,sigmaColor=75,sigmaSpace=75) cv2.imshow("janela",bilateral) cv2.waitKey()
d=13 é o tamanho do kernel. sigmaColor e sigmaspace é o desvio padrão para os kernel no espaço de cor e no espaço 2D da imagem.
Sua principal característica é a preservação das bordas dos objetos na imagem.
Resultados
O filtro blur causa desfoque na imagem, o GaussianBlur tem resultado similar com exceto por ser mais eficaz na remoção de ruído Gaussiano. Felizmente nossos sensores melhoraram muito e esses ruídos é quase inexistente..
Os filtros medianBlur e bilateral preserva a borda dos objetos. Isso pode ser interessante para algumas aplicações de extração de contornos..
Os kernel construído com gaussinas obrigatoriamente precisão que o tamanho seja um número ímpar, pois a gaussiana deve ser simétrica, medianBlur também. Por outro lado, não é aconselhável usar dimensão par, porque o kernel não terá uma posição de centro, isso resultará no deslocamento da imagem.
Para algumas aplicações você pode ficar com a dúvida, qual filtro usar? Pois, alguns deles produz resultado muito parecidos.
O filtro blur é 26/7=3,7 vezes mais rápido que o GaussianBlur, 26/3=8,6 vezes mais rápido que o medianBlur e 26/1=26 mais rápido que o bilateral.
O tamanho do kernel afeta a desempenho de diferente forma. O bilateral segue uma curva exponencial e medianBlur tem um amento considerável quando usando o kernel 7×7
O filtro Blur tem a melhor resposta em função do tamanho do kernel. O custo computacional quase não é afetado pelo tamanho do kernel.
Como vimos, o blur é 3,7 vezes mais rápido que o GaussianBlur para kernel 5×5. Porém, isso agrava quando aumentamos o kernel.
Conclusão
Para realizar desfoque em imagem tente primeiro usar o filtro blur. Comece com kernel 5×5
A implementação do filtro genérico (cv2.filter2D) é tão eficiente quanto o filtro blur.
Quando utilizar o filtro bilateral teste distâncias menores, por exemplo, 3 e 5. Talvez o medianBlur pode resolver.
Se você assim, como eu sente falta dessas comparações de desempenho nos artigos de visão computacional. Me deixe saber aqui nos comentários.