Capítulo 2: Processamento de Imagens#

Técnicas para o Pré-processamento de Imagens#
O pré-processamento visa preparar a imagem para análise, removendo ruídos e padronizando características. Segundo Gonzalez e Woods (2018), essas técnicas são cruciais para a eficiência de algoritmos de visão computacional e aprendizado de máquina.
Por que Pré-processar?#
Qualidade: Melhora o contraste e remove ruídos indesejados.
Padronização: Mantém a consistência entre diferentes conjuntos de dados.
Performance: Facilita a convergência de modelos de IA e reduz o custo computacional.
Principais Técnicas#
Técnica |
Objetivo |
|---|---|
Normalização |
Ajustar intensidades (ex: [0, 1]) para estabilidade numérica. |
Filtros Espaciais |
Suavizar (Blur) ou realçar bordas (Sharpen). |
Equalização |
Maximizar o contraste global ou local da imagem. |
Redimensionamento |
Padronizar as dimensões para entrada em Redes Neurais (CNNs). |
Normalização de Intensidade#
Ajustar os valores dos pixels é um passo fundamental antes do treinamento de modelos de IA. Vamos explorar os dois métodos mais comuns:
Normalização Min-Max#
Redimensiona os dados para um intervalo fixo, geralmente \([0, 1]\).
Padronização (Z-score)#
Transforma os dados para terem média 0 e desvio padrão 1, sendo mais robusta a valores discrepantes (outliers).
Exemplo Prático: Comparando Técnicas
Baixar sun.jpeg
import matplotlib.pyplot as plt
import numpy as np
import cv2
# Carregar e preparar a imagem
img = cv2.imread('sun.jpeg', cv2.IMREAD_GRAYSCALE)
# 1. Normalização Min-Max [0, 1]
img_min_max = (img - img.min()) / (img.max() - img.min())
# 2. Padronização (Z-score)
img_z_score = (img - img.mean()) / img.std()
# Exibir resultados
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.imshow(img, cmap='gray')
plt.title(f'Original\nRange: [{img.min()}, {img.max()}]')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(img_min_max, cmap='gray')
plt.title(f'Min-Max [0, 1]\nμ={img_min_max.mean():.2f}')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(img_z_score, cmap='gray')
plt.title(f'Z-score\nμ={img_z_score.mean():.2f}, σ={img_z_score.std():.2f}')
plt.axis('off')
plt.tight_layout()
plt.show()

Histograma de Imagens#
O histograma é um gráfico que representa a distribuição das intensidades dos pixels em uma imagem. Ele nos diz quantos pixels existem para cada tom (ex: de 0 a 255).
Definição Matemática:
Onde \(n_k\) é o número de pixels com intensidade \(k\) e \(N\) é o total de pixels.
import numpy as np
import matplotlib.pyplot as plt
import cv2
# Carregar em escala de cinza
img = cv2.imread('sun.jpeg', cv2.IMREAD_GRAYSCALE)
# Calcular histograma
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# Visualizar
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('Imagem Original')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.plot(hist, color='black')
plt.title('Histograma de Intensidades')
plt.xlabel('Tom (0-255)')
plt.ylabel('Frequência')
plt.show()
A função cv2.calcHist possui os seguintes parâmetros principais:
[img]: Imagem de entrada (deve estar entre colchetes).[0]: Índice do canal (0 para tons de cinza).None: Máscara para processar apenas parte da imagem.[256]: Número de bins (quantidade de tons representados).[0, 256]: Intervalo das intensidades (0 a 255).

Interpretação de Histogramas#
Padrão |
Significado |
Ação Sugerida |
|---|---|---|
Picos Agudos |
Grandes áreas uniformes (ex: céu ou paredes). |
- |
Faixa Estreita |
Baixo contraste (imagem “lavada”). |
Equalização ou CLAHE. |
Deslocado à Esquerda |
Imagem escura (subexposta). |
Aumentar brilho / Ganho. |
Deslocado à Direita |
Imagem muito clara (superexposta). |
Reduzir brilho / Exposição. |
Acumulado nos Cantos |
Perda de detalhes nos pretos ou brancos. |
Ajuste de Curvas ou HDR. |
Bimodal |
Objeto claro sobre fundo escuro (ou vice-versa). |
Segmentação por Limiar. |
Equalização de Histograma#
A equalização é uma técnica de processamento de imagem que visa aumentar o contraste global ao redistribuir as intensidades dos pixels. O objetivo é que o histograma da imagem resultante seja o mais uniforme possível, aproveitando melhor toda a faixa dinâmica disponível (ex: de 0 a 255).
Como funciona?#
O processo se baseia na Função de Distribuição Cumulativa (CDF). Imagine que a CDF nos diz a probabilidade de um pixel ter intensidade menor ou igual a um valor \(r_k\). Na equalização, usamos essa probabilidade para mapear os tons originais para novos valores:
Mapeamento: \(s_k = T(r_k) = (L-1) \cdot \text{CDF}(r_k)\)
Intuição: Áreas da imagem que concentram muitos pixels em poucos tons são “esticadas”, enquanto áreas com poucos pixels são comprimidas. Isso faz com que detalhes escondidos em sombras ou luzes muito fortes se tornem visíveis.
import cv2
import matplotlib.pyplot as plt
# Carregar imagem original
img = cv2.imread('sun.jpeg', cv2.IMREAD_GRAYSCALE)
# Aplicar equalização global
img_eq = cv2.equalizeHist(img)
# Configurar visualização 2x2
fig, axs = plt.subplots(2, 2, figsize=(12, 8))
# Linha 1: Imagens
axs[0, 0].imshow(img, cmap='gray')
axs[0, 0].set_title('Original')
axs[0, 0].axis('off')
axs[0, 1].imshow(img_eq, cmap='gray')
axs[0, 1].set_title('Após Equalização')
axs[0, 1].axis('off')
# Linha 2: Histogramas
axs[1, 0].hist(img.ravel(), 256, [0, 256], color='black')
axs[1, 0].set_title('Histograma Original')
axs[1, 1].hist(img_eq.ravel(), 256, [0, 256], color='black')
axs[1, 1].set_title('Histograma Equalizado')
plt.tight_layout()
plt.show()

CLAHE: Equalização Adaptativa#
Enquanto a equalização global utiliza a mesma transformação para a imagem inteira, o CLAHE (Contrast Limited Adaptive Histogram Equalization) adota uma abordagem local. Ele divide a imagem em pequenas regiões chamadas tiles (geralmente 8x8 pixels) e aplica a equalização em cada uma delas separadamente.
Por que usar o CLAHE?
Detalhes Locais: Melhora o contraste em áreas específicas sem distorcer o brilho global da imagem.
Limite de Ruído: O mecanismo de “Limite de Contraste” corta picos muito altos no histograma local, evitando a amplificação excessiva de ruídos.
Naturalidade: Produz resultados mais equilibrados e visualmente agradáveis do que a equalização comum.
Experimento Interativo: Equalização e Histograma#
Utilize o playground abaixo para carregar uma imagem e ajustar o contraste usando o CLAHE. O slider controla o limite de contraste (Clip Limit), permitindo observar como o histograma se redistribui em tempo real.
import gradio as gr
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
def interactive_equalization(input_img, clip_limit):
# Converter para escala de cinza
img = np.array(input_img)
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# Aplicar CLAHE
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(8, 8))
img_clahe = clahe.apply(img)
# Gerar Comparação de Histogramas
fig, axs = plt.subplots(1, 2, figsize=(10, 4))
axs[0].hist(img.flatten(), bins=256, range=[0, 256], color='gray', alpha=0.7)
axs[0].set_title("Histograma Original")
axs[1].hist(img_clahe.flatten(), bins=256, range=[0, 256], color='blue', alpha=0.7)
axs[1].set_title(f"Histograma CLAHE (Clip={clip_limit})")
plt.tight_layout()
plt.close() # Evita exibição dupla no notebook
return img_clahe, fig
demo = gr.Interface(
fn=interactive_equalization,
inputs=[
gr.Image(type="pil", label="Upload Imagem"),
gr.Slider(1, 10, value=2, label="Limite de Contraste (Clip Limit)")
],
outputs=[
gr.Image(label="Resultado CLAHE"),
gr.Plot(label="Comparação de Histogramas")
],
title="Playground de Equalização Adaptativa"
)
# demo.launch()
Desafio de Aprimoramento: Tente modificar o código acima para adicionar uma nova funcionalidade simples, como:
Um slider para ajustar o brilho da imagem antes da equalização.
Um botão para alternar entre a Equalização Global (
cv2.equalizeHist) e o CLAHE.
Convolução de Imagens#
A convolução (vid) é uma operação matemática central no processamento digital de imagens. Seu principal objetivo é extrair e realçar características relevantes — como bordas, texturas, contornos ou regiões homogêneas — ao combinar uma imagem com um kernel (ou filtro). O resultado é uma nova imagem, cujas propriedades visuais são modificadas de acordo com a estrutura do kernel utilizado.
Matemática da Convolução#
Para imagens digitais, representadas como matrizes bidimensionais de pixels, a convolução discreta é definida por:
Onde:
\(*\): Operador de convolução
\(f\): Matriz da imagem original (dimensão M×N)
\(g\): Kernel ou filtro (dimensão K×K, com K ímpar, como 3×3 ou 5×5)
\((x,y)\): Coordenadas do pixel na imagem de saída
\(k\): Metade do tamanho do kernel, ou seja, \((K-1)/2\), necessário para centralizar o kernel sobre o pixel atual
Esse processo é repetido para cada pixel da imagem, levando em conta a vizinhança local e os coeficientes definidos pelo kernel.
Kernels: O Coração da Convolução#
Os kernels são pequenas matrizes de pesos que determinam como os valores dos pixels vizinhos devem ser combinados para gerar um novo valor. Suas propriedades determinam o tipo de transformação que será aplicada à imagem.
Propriedade |
Descrição |
|---|---|
Dimensões Ímpares |
Garantem que o kernel tenha um centro bem definido para alinhar ao pixel sendo processado. |
Normalização e Balanceamento |
Preserva o brilho da imagem (soma dos pesos = 1) ou enfatiza transições com pesos positivos e negativos. |
Direcionalidade e Gradientes |
Detectam variações horizontais, verticais ou diagonais — úteis para encontrar bordas. |
Objetivo Específico |
Cada kernel serve a uma tarefa: suavizar, realçar, detectar ruído, etc. |
Localidade e Paralelismo |
Operação local e independente — ideal para paralelismo em GPUs e redes neurais convolucionais. |
Exemplo de Kernel de Detecção de Bordas (Sobel Vertical):#
Esse kernel detecta bordas verticais ao responder a variações horizontais na imagem. Parece um pouco abstrato mas entenderemos em seguida.
Catálogo de Kernels Úteis#
Cada kernel foi projetado para uma tarefa específica. A tabela abaixo reúne os principais filtros usados em visão computacional, descrevendo o efeito produzido e a intuição por trás de seus coeficientes.
Kernel |
Aplicação |
Intuição |
|---|---|---|
Identidade |
Saída idêntica à entrada |
Centro = 1, todo o restante = 0: passa apenas o pixel atual sem misturar vizinhos. |
Média (Box) |
Suavização uniforme |
Todos os pesos iguais a 1/9: tira a média da janela — borra detalhes e reduz ruído. |
Gaussiano |
Suavização natural |
Pesos maiores no centro (1/16, 2/16, 4/16): suavização gradual que preserva melhor as bordas que o Box. |
Sobel Horizontal |
Detecção de bordas horizontais |
Pesos opostos no topo (−) e na base (+): responde a variações verticais de intensidade. |
Sobel Vertical |
Detecção de bordas verticais |
Pesos opostos na esquerda (−) e na direita (+): responde a variações horizontais de intensidade. |
Laplaciano |
Bordas em todas as direções |
Centro negativo (−4) rodeado de positivos: mede a curvatura local — zero em regiões planas, alto nas bordas. |
Nitidez (Sharpen) |
Realce de detalhes |
Centro amplificado (5) com vizinhos negativos (−1): acentua diferenças entre o pixel e seus vizinhos. |
Realce de Bordas |
Bordas com alto contraste |
Centro = 8, todos os vizinhos = −1: versão mais agressiva do Laplaciano — bordas ficam muito marcadas. |
Convolução Passo a Passo: Suavização com Filtro de Média#
Suponha uma imagem 5×5 e um kernel de média 3×3. A janela do kernel desliza pela imagem, posicionando-se em cada pixel válido e realizando multiplicações ponto a ponto seguidas de uma soma. O resultado de cada etapa preenche uma posição na matriz de saída (3×3 no modo valid).
Imagem A (5×5) e Kernel B (3×3):
Etapa 1 — Posição de saída \(C[0,0]\)#
O kernel é posicionado no canto superior esquerdo, cobrindo \(A[0{:}3,\, 0{:}3]\):
Matriz de saída parcial:
Etapa 2 — Posição de saída \(C[0,1]\)#
O kernel desliza uma coluna à direita, cobrindo \(A[0{:}3,\, 1{:}4]\):
Matriz de saída parcial:
Etapa 3 — Posição de saída \(C[1,1]\) (centro)#
Após percorrer toda a primeira linha e avançar uma linha, o kernel cobre \(A[1{:}4,\, 1{:}4]\):
Matriz de saída parcial:
Resultado Final — Todas as 9 posições preenchidas#
Repetindo o processo para todas as posições válidas, a matriz de saída completa é:
A imagem resultante é 3×3 (modo valid), pois a janela 3×3 cabe em apenas 3×3 posições dentro de uma imagem 5×5.
Efeitos de Borda e Estratégias de Padding#
Durante a convolução, nas bordas da imagem faltam vizinhos para o kernel. Para corrigir isso, se for de interesse, usamos técnicas de padding.
Modos Comuns de Padding#
Modo |
Descrição |
Efeito no Tamanho |
Frequência de Uso |
Onde Você Mais Verá |
|---|---|---|---|---|
|
Preenchimento com zeros |
Mantém o tamanho original |
Altíssima |
CNNs modernas (YOLO, Transformers) |
|
Sem preenchimento |
Reduz a imagem |
Alta |
Redução de dimensionalidade controlada |
|
Espelha os valores da borda |
Mantém o tamanho |
Moderada |
Fotografia digital, imagens de satélite e médicas |
|
Repete o pixel da borda |
Mantém o tamanho |
Baixa |
Filtros simples de borda e otimizações de hardware |
Cálculo da Dimensão de Saída#
A fórmula responde à pergunta: quantas vezes a janela do kernel cabe na imagem? Após subtrair o tamanho do kernel (\(I - K\)) e adicionar o padding de ambos os lados (\(+2P\)), dividimos pelo passo (\(S\)) e somamos 1 pela posição inicial.
Símbolo |
Significado |
Valor típico |
|---|---|---|
\(I\) |
Tamanho da imagem original (largura ou altura) |
ex: 224 |
\(K\) |
Tamanho do kernel |
3 ou 5 |
\(P\) |
Padding adicionado em cada lado |
0 ou 1 |
\(S\) |
Stride — passo da janela entre posições |
1 ou 2 |
\(O\) |
Tamanho da imagem de saída |
calculado |
Sobre o stride: quando \(S = 1\) (padrão), o kernel avança pixel a pixel. Com \(S = 2\), ele pula uma posição a cada passo — reduzindo a saída pela metade. CNNs usam stride > 1 para fazer downsampling sem pooling.
Resumo por modo (com \(I=5\), \(K=3\), \(S=1\)):
Modo |
\(P\) |
Cálculo |
Saída \(O\) |
|---|---|---|---|
|
\(0\) |
\(\lfloor(5-3+0)/1\rfloor+1\) |
3 |
|
\(1\) |
\(\lfloor(5-3+2)/1\rfloor+1\) |
5 |
|
\(1\) |
\(\lfloor(5-3+2)/1\rfloor+1\) |
5 |
Exemplo passo a passo — Modo valid
Para \(I=5\) e \(K=3\), a janela do kernel desliza pela imagem com passo \(S=1\). A ilustração abaixo mostra as 3 posições horizontais válidas na primeira linha — o kernel para quando ultrapassaria o limite da imagem. O mesmo raciocínio se aplica às linhas: 3 posições × 3 posições = saída 3×3.
Confirmando com a fórmula:
Exemplo passo a passo — Modo same
Para manter \(O = I\), calcula-se o padding necessário: \(P = \lfloor K/2 \rfloor = \lfloor 3/2 \rfloor = 1\).
Com 1 pixel de borda adicionado em cada lado, a saída mantém o tamanho original: 5×5.
Exemplo Visual: Comparação de Modos
Mesma imagem e kernel para os três modos, mostrando o padding aplicado e a saída completa.
valid — sem padding → saída 1×1
Com kernel 3×3 sobre uma imagem 3×3, há apenas uma posição onde o kernel cabe completamente: sobre a imagem inteira.
same — zero padding → saída 3×3
Uma borda de zeros é adicionada em todos os lados:
Resultado completo:
Os valores das bordas ficam atenuados pois os zeros do padding reduzem a média local.
reflect — espelhamento → saída 3×3
A borda é preenchida com o reflexo dos pixels vizinhos (sem repetir o pixel da borda):
Resultado completo:
Os valores das bordas ficam próximos ao conteúdo real — o espelhamento preserva a continuidade local.
Comparação dos resultados:
Posição |
|
|
|
|---|---|---|---|
Canto superior esquerdo |
— |
13.3 |
36.7 |
Centro |
50.0 |
50.0 |
50.0 |
Canto inferior direito |
— |
31.1 |
63.3 |
O centro é sempre 50.0 independente do modo — é o único pixel que nunca toca a borda de padding.
Aplicações Práticas com Python#
Com os conceitos estabelecidos, é hora de implementar. Esta seção está organizada em duas abordagens complementares:
Abordagem |
Quando usar |
|---|---|
OpenCV |
Imagens reais, produção, performance |
NumPy + scipy |
Kernels personalizados, fins didáticos, experimentação |
Verificação com as matrizes da teoria
Antes de partir para imagens reais, confirmamos o que calculamos manualmente: a convolução da matriz \(A\) (5×5) com o kernel de média \(B\) (3×3) no modo valid deve produzir a saída \(C\) (3×3) com os valores que já conhecemos.
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve2d
# Define a matriz da imagem
A = np.array([[10, 20, 30, 40, 50],
[20, 30, 40, 50, 60],
[30, 40, 50, 60, 70],
[40, 50, 60, 70, 80],
[50, 60, 70, 80, 90]])
# Define o kernel de média
B = np.array([[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9]])
# Realiza a convolução usando a função convolve2d do scipy
C = convolve2d(A, B, mode='valid')
# Subplots com aspect ratio
fig, axes = plt.subplots(1, 3, figsize=(16, 6),
gridspec_kw={'width_ratios': [5, 3, 3]}) # Proporção dos tamanhos
# Imagem original
im1 = axes[0].imshow(A, cmap='gray', aspect='equal')
axes[0].set_title(f'Imagem Original ({A.shape[0]}x{A.shape[1]})')
#fig.colorbar(im1, ax=axes[0], fraction=0.046, pad=0.04)
# Kernel - respeitando a proporção
im2 = axes[1].imshow(B, cmap='gray', aspect='equal')
axes[1].set_title(f'Kernel ({B.shape[0]}x{B.shape[1]})')
axes[1].set_xticks([]) # Remove os valores do eixo x
axes[1].set_yticks([]) # Remove os valores do eixo y
#fig.colorbar(im2, ax=axes[1], fraction=0.046, pad=0.04)
# Imagem convolucionada
im3 = axes[2].imshow(C, cmap='gray', aspect='equal')
axes[2].set_title(f'Imagem Convolucionada ({C.shape[0]}x{C.shape[1]})')
axes[2].set_xticks([]) # Remove os valores do eixo x
axes[2].set_yticks([]) # Remove os valores do eixo y
#fig.colorbar(im3, ax=axes[2], fraction=0.046, pad=0.04)
plt.tight_layout()
plt.show()
A saída \(C\) bate exatamente com os valores calculados no passo a passo anterior — confirmando que a teoria e o código estão alinhados.
🎬 Exercício — Animação do Processo de Convolução
Você viu o processo de convolução acontecer passo a passo. Agora, que tal torná-lo visível de forma animada?
Seu desafio: usando
matplotlib.animation, crie um vídeo que mostre o kernel de média 3×3 deslizando sobre a matriz \(A\) (5×5) no modovalid. A cada quadro da animação, destaque:
A região da imagem coberta pelo kernel (retângulo colorido)
Os valores sendo multiplicados
O valor resultante sendo preenchido na matriz de saída \(C\)
Dicas:
Use
FuncAnimationdo módulomatplotlib.animationPara salvar como vídeo
.mp4, instale offmpeg:!apt-get install ffmpeg -yPara exibir no notebook sem salvar, use
HTML(anim.to_jshtml())from matplotlib.animation import FuncAnimation from IPython.display import HTML
🔹 Com OpenCV (Funções prontas e otimizadas)#
O OpenCV oferece funções de convolução altamente otimizadas, com suporte a aceleração por hardware e integração direta com formatos de imagem reais. Em vez de implementar manualmente o deslizamento do kernel, utilizamos funções dedicadas para cada tipo de filtro — o que torna o código mais conciso, robusto e adequado para uso em produção.
A seguir, exploramos três filtros clássicos com aplicações distintas:
Filtro |
Função OpenCV |
Efeito principal |
|---|---|---|
Gaussiano |
|
Suavização — reduz ruído e detalhes finos |
Sobel |
|
Detecção de bordas — realça gradientes de intensidade |
Mediana |
|
Remoção de ruído impulsivo (sal e pimenta) |
Para os exemplos, usaremos a imagem a seguir — baixe aqui antes de executar o código.
Suavização com Filtro Gaussiano
import cv2
import matplotlib.pyplot as plt
# Carrega a imagem
img = cv2.imread('bridge.jpeg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Número de iterações do filtro Gaussiano
num_iterations = 5 # Altere conforme desejar
# Cria uma cópia da imagem para aplicar o filtro iterativamente
blurred_img = img.copy()
for _ in range(num_iterations):
blurred_img = cv2.GaussianBlur(blurred_img, (5, 5), 0) #img, kernel, std
# Plota apenas a original e a última versão borrada
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("Imagem Original")
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(blurred_img)
plt.title(f"Filtro Gaussiano ({num_iterations}x)")
plt.axis('off')
plt.tight_layout()
plt.show()
Explicação:
cv2.GaussianBlur(src, ksize, sigmaX)aplica uma suavização com base em uma distribuição gaussiana.src: imagem original.ksize=(5,5): tamanho da máscara (kernel). Quanto maior, mais borrado o resultado.sigmaX=0: o desvio padrão da Gaussiana. Zero permite que o OpenCV calcule automaticamente com base no tamanho do kernel.
Aqui, aplicamos o filtro várias vezes para reforçar o efeito.
Útil para remover ruídos leves e suavizar gradientes antes de aplicar detecção de bordas.
Detecção de Bordas com Sobel
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Carrega a imagem original
img = cv2.imread('bridge.jpeg', cv2.IMREAD_GRAYSCALE) # Sobel funciona melhor em tons de cinza
# Aplica Sobel nos dois eixos
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel_combined = cv2.magnitude(sobel_x, sobel_y)
# Aplica Gaussiano DEPOIS (opcional para suavizar ruído nas bordas)
blurred_edges = cv2.GaussianBlur(sobel_combined, (5,5), 0)
# Plota os resultados
fig, axs = plt.subplots(1, 3, figsize=(15,5))
axs[0].imshow(img, cmap='gray')
axs[0].set_title("Original")
axs[0].axis('off')
axs[1].imshow(sobel_combined, cmap='gray')
axs[1].set_title("Sobel Puro")
axs[1].axis('off')
axs[2].imshow(blurred_edges, cmap='gray')
axs[2].set_title("Sobel + Gaussiano")
axs[2].axis('off')
plt.tight_layout()
plt.show()
Explicação:
cv2.Sobel(src, ddepth, dx, dy, ksize)calcula derivadas para encontrar bordas.ddepth=cv2.CV_64F: profundidade dos dados de saída (64 bits flutuante para evitar perda de sinal negativo).dx=1, dy=0: para detectar bordas horizontais (vice-versa para verticais).ksize=3: tamanho do kernel de derivada.
cv2.magnitude(x, y)combina as bordas em X e Y para formar uma borda geral.Aplicar o filtro Gaussiano depois ajuda a suavizar ruídos nas bordas detectadas.
Redução de Ruído com Filtro Mediano
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Carregar imagem original
img = cv2.imread('bridge.jpeg') # Substitua pelo seu arquivo
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Adicionar ruído artificial (sal e pimenta)
def add_salt_pepper_noise(image, prob=0.05):
output = np.copy(image)
# Ruído sal (branco)
salt = np.random.rand(*image.shape[:2]) < prob/2
output[salt] = 255
# Ruído pimenta (preto)
pepper = np.random.rand(*image.shape[:2]) < prob/2
output[pepper] = 0
return output
noisy_img = add_salt_pepper_noise(img, prob=0.3) # 30% de ruído
# Aplicar filtro mediano
median = cv2.medianBlur(noisy_img, 5) # Kernel size 5 (deve ser ímpar)
# Visualização comparativa
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
axs[0].imshow(noisy_img)
axs[0].set_title(f"Imagem com Ruído (30% sal-pimenta)")
axs[0].axis('off')
axs[1].imshow(median)
axs[1].set_title("Filtro Mediano (kernel=5)")
axs[1].axis('off')
plt.tight_layout()
plt.show()
Explicação:
cv2.medianBlur(src, ksize)aplica um filtro que substitui cada pixel pela mediana da vizinhança.Muito eficaz contra ruídos do tipo sal e pimenta (pontinhos pretos e brancos aleatórios).
ksizedeve ser ímpar (3, 5, 7…).
Aqui, um ruído artificial foi adicionado para mostrar a eficácia do filtro.
Dica: para preservar bordas, o filtro mediano é geralmente melhor que o Gaussiano.
🔹 Com NumPy (Definindo o Kernel Manualmente)#
Utilizando
scipy.signal.convolve2dpara aplicar filtros 2D com controle total, ideal para fins didáticos e experimentação com kernels personalizados.
Filtro de Média (Suavização Simples)
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve2d
# Carregar imagem em escala de cinza
img = plt.imread('bridge.jpeg')
img = img.mean(axis=2) # Converter para escala de cinza
# Define o kernel de média (suavização simples)
kernel = np.ones((3, 3)) / 9
# Aplica a convolução
output = img.copy()
# Aplica o filtro n vezes: cada passagem suaviza mais
# (equivalente a aumentar o kernel, útil para visualizar o efeito progressivo)
n = 10
for i in range(n):
output = convolve2d(output, kernel, mode='same', boundary='fill', fillvalue=0)
# Visualização
fig, axs = plt.subplots(1, 2, figsize=(10, 4))
axs[0].imshow(img, cmap='gray')
axs[0].set_title("Imagem Original (Gray)")
axs[0].axis('off')
axs[1].imshow(output, cmap='gray')
axs[1].set_title("Filtro de Média (3x3)")
axs[1].axis('off')
plt.tight_layout()
plt.show()
Explicação:
np.ones((3,3))/9cria um kernel onde todos os elementos têm peso igual — média aritmética dos vizinhos.convolve2d(img, kernel, mode='same', boundary='fill', fillvalue=0):mode='same': saída com o mesmo tamanho da imagem original.boundary='fill': bordas externas tratadas como zero.
Suaviza transições na imagem, útil para remover ruídos leves.
Detecção de Bordas com Filtro Laplaciano
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve2d
# Carregar imagem em escala de cinza
img = plt.imread('bridge.jpeg')
if img.ndim == 3: # Se for colorida (RGB)
img = img.mean(axis=2) # Converter para escala de cinza
# Define o kernel Laplaciano
kernel = np.array([[0, 1, 0],
[1, -4, 1],
[0, 1, 0]])
# Aplica convolução
output = convolve2d(img, kernel, mode='same', boundary='fill', fillvalue=0)
# Pré-processamento para visualização
output_visual = np.abs(output) # Valor absoluto para ver todas as bordas
output_visual = (output_visual - output_visual.min()) / (output_visual.max() - output_visual.min()) # Normaliza para [0,1]
# Visualização melhorada
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# Imagem original
axs[0].imshow(img, cmap='gray')
axs[0].set_title("Imagem Original")
axs[0].axis('off')
# Resultado Laplaciano (valores brutos)
axs[1].imshow(output, cmap='seismic', vmin=-100, vmax=100) # Cores: vermelho=negativo, azul=positivo
axs[1].set_title("Laplaciano (valores reais)")
axs[1].axis('off')
# Resultado Laplaciano (absoluto normalizado)
axs[2].imshow(output_visual, cmap='gray')
axs[2].set_title("Laplaciano (absoluto normalizado)")
axs[2].axis('off')
plt.tight_layout()
plt.show()
Explicação:
O kernel realça regiões com mudança brusca de intensidade (bordas).
O centro negativo e vizinhos positivos atuam como uma segunda derivada discreta.
Ideal para detectar bordas finas e simétricas.
CNNs#
Daqui a pouco estudaremos as CNNs (Convolução em Redes Neurais), é importante saber que os kernels são aprendidos automaticamente durante o treinamento. Cada camada convolucional extrai características específicas e cada vez mais complexas da imagem:
Camada |
Tamanho do Kernel |
Nº de Filtros |
Função da Camada |
|---|---|---|---|
1 |
3×3 |
32 |
Bordas e cores básicas |
2 |
5×5 |
64 |
Padrões e texturas |
3 |
3×3 |
128 |
Formas complexas e objetos |
Esses filtros tornam possíveis aplicações como reconhecimento facial, análise médica, e detecção de objetos em tempo real.
(Exercício Prático) Processamento de Vídeo com OpenCV#
Objetivo: Aprender a carregar, modificar e salvar vídeos usando a biblioteca OpenCV.
Tarefas:
Carregar um vídeo (de arquivo ou webcam).
Converter cada frame para escala de cinza.
Salvar o vídeo processado em um novo arquivo.
Código Base (Passo a Passo):
import cv2
# 1. Carregar vídeo (substitua 'video.mp4' pelo seu arquivo ou use 0 para webcam)
cap = cv2.VideoCapture('video.mp4')
# Verifica se o vídeo foi carregado corretamente
if not cap.isOpened():
print("Erro ao abrir o vídeo!")
exit()
# 2. Obter propriedades do vídeo (largura, altura e FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
# 3. Criar o objeto de escrita do novo vídeo (grayscale)
output = cv2.VideoWriter('video_out.mp4',
cv2.VideoWriter_fourcc(*'mp4v'), # Codec MP4
fps,
(width, height),
isColor=False) # Vídeo em tons de cinza
# 4. Processar frame a frame, aplicar filtros, etc
# ...
# 5. Liberar recursos
cap.release()
output.release()
cv2.destroyAllWindows()
print("Vídeo processado e salvo como 'video_out.mp4'!")
Próximos Desafios (opcional):
Aplique equalização de histograma em cada frame antes de salvar.
Escolha e aplique um filtro (mediana, gaussiano, bordas etc).
Crie um vídeo que combine frames originais e processados lado a lado.
Desafio:
Criar um gerador de filtros adaptativo e especializado para CNNs. Falar com o professor!
Exercícios Conceituais#
1. Importância do Pré-processamento de Imagens Explique por que o pré-processamento é uma etapa essencial em projetos de visão computacional.
Quais problemas podem surgir ao utilizar imagens brutas sem pré-processamento?
Cite algumas técnicas comuns de pré-processamento e suas finalidades.
2. Normalização de Intensidade A normalização de intensidade ajusta os valores dos pixels para uma faixa específica.
Descreva como a normalização min-max transforma os valores de uma imagem.
Em quais situações essa técnica é particularmente útil?
3. Equalização de Histograma A equalização de histograma redistribui os níveis de intensidade para melhorar o contraste da imagem.
Como essa técnica pode realçar detalhes em imagens com baixa variação tonal?
Quais são as possíveis limitações da equalização de histograma?
4. Filtros Espaciais: Média, Mediana e Gaussiano Compare os filtros de média, mediana e Gaussiano em termos de suas aplicações e efeitos nas imagens.
Qual filtro seria mais adequado para remover ruídos do tipo “sal e pimenta”?
Em que casos o filtro Gaussiano é preferido?
5. Realce de Bordas em Imagens O realce de bordas é fundamental para destacar transições abruptas de intensidade.
Quais operadores são comumente utilizados para essa finalidade?
Como o realce de bordas contribui para a segmentação de objetos em uma imagem?
6. Redimensionamento de Imagens Redimensionar imagens para dimensões fixas é uma prática comum em redes neurais convolucionais (CNNs).
Quais são os desafios ao redimensionar imagens sem distorcer informações importantes?
Como o redimensionamento afeta o desempenho de modelos de aprendizado de máquina?
7. Convolução e Kernels A convolução é uma operação fundamental em processamento de imagens.
Explique como os kernels (ou filtros) são utilizados na convolução para detectar características específicas na imagem.
Dê exemplos de kernels comuns e suas aplicações.
8. Padding em Convoluções O padding é uma técnica utilizada para lidar com as bordas durante a convolução.
Quais são os tipos comuns de padding e como eles influenciam o resultado da convolução?
Por que o uso adequado de padding é importante em redes neurais convolucionais?
Referências e Conteúdo Extra#
GONZALEZ, R. C.; WOODS, R. E. Digital Image Processing. 4th ed. Pearson, 2018.
Vídeo(s)
Site(s):