Filtros de Convolução em Imagens¶
Objetivos da Aula¶
Ao final desta aula você será capaz de:
- Compreender o que é um filtro de convolução e como ele funciona matematicamente
- Aplicar filtros de suavização (blurring) e entender quando usá-los
- Aplicar filtros de realce (sharpening) e detecção de bordas
- Usar limiarização para segmentar objetos em imagens
- Conectar esses conceitos com aplicações reais: filtros de redes sociais e imagens médicas
- Entender como esses filtros são a base das Redes Neurais Convolucionais (CNNs)
## Vou fazer o download das imagens do laboratório diretamente do repositório para ficar mais facil....
import requests
import os
# Define o laboratório
laboratorio = 'lab04' ### altere para o laboratório desejado
diretorio = 'lab_images' ### altere para o diretório que deseja salvar as imagens
# Download de um arquivo
def download_file(url, destination):
response = requests.get(url, stream=True)
if response.status_code == 200:
with open(destination, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
print(f"Baixado: {destination}")
else:
print(f"Erro ao baixar {url}")
# Monta a URL completa
api_url = "https://api.github.com/repos/arnaldojr/cognitivecomputing/contents/material/aulas/PDI/"
url_completa = api_url + laboratorio
print(f"Fazendo o download de: {url_completa}")
# checa se a URL está acessível
response = requests.get(url_completa)
if response.status_code != 200:
raise Exception(f"Erro ao acessar o repositório: {response.status_code}")
files = response.json()
# Faz o download de cada arquivo
os.makedirs(diretorio, exist_ok=True) # Cria a pasta downloads
for file in files:
file_name = file['name']
if file_name.endswith(('.png', '.jpg', '.jpeg', '.mp4')): # Adicione mais extensões se necessário
file_url = file['download_url']
destination = os.path.join(diretorio, file_name)
download_file(file_url, destination)
print(f"Download concluído. Arquivos salvos na pasta {diretorio}.")
Fazendo o download de: https://api.github.com/repos/arnaldojr/cognitivecomputing/contents/material/aulas/PDI/lab04 Baixado: lab_images/convolution.png Baixado: lab_images/lena.png Baixado: lab_images/people-walking.mp4 Baixado: lab_images/saida.png Baixado: lab_images/sudoku.png Baixado: lab_images/tux.png Download concluído. Arquivos salvos na pasta lab_images.
O que é um Filtro de Convolução?¶
Intuição¶
Imagine que você coloca uma janela pequena (3×3, 5×5...) sobre cada pixel da imagem. Para cada posição, você:
- Multiplica cada pixel da janela por um peso correspondente no kernel
- Soma todos os resultados
- O resultado vira o novo valor daquele pixel
Esse processo se chama correlação cruzada (na prática, o que o OpenCV implementa) — e a convolução difere apenas por espelhar o kernel antes de aplicar. Para kernels simétricos (a maioria), o resultado é idêntico.
Fórmula¶
Para uma imagem $I$ e um kernel $K$ de tamanho $m \times n$:
$$ S(i, j) = \sum_{u=0}^{m-1} \sum_{v=0}^{n-1} I(i+u,\; j+v) \cdot K(u, v) $$
Onde:
- $S(i,j)$ é o pixel de saída na posição $(i,j)$
- $I(i+u, j+v)$ é o pixel da imagem na janela
- $K(u,v)$ é o peso do kernel naquela posição
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('convolution.png')
plt.imshow(img); plt.show()
from IPython.display import Image
Image(open('same_padding_no_strides.gif','rb').read())
Experimento Interativo¶
Antes de rodar qualquer código, acesse: https://setosa.io/ev/image-kernels/
Mude os valores do kernel e veja o efeito em tempo real. Tente responder:
- O que acontece quando todos os pesos são iguais?
- O que acontece quando o centro tem peso positivo e os vizinhos negativos?
Filtros de Suavização (Blurring)¶
Filtros de suavização são filtros passa-baixa: eles reduzem ruído e detalhes finos, preservando as estruturas grandes.
Conexão Real: Modo Retrato e Blur de Fundo¶
O desfoque seletivo de fundo no Instagram e no modo retrato do iPhone funciona aplicando um blur apenas na região do fundo, detectado por segmentação. O blur em si é exatamente o que veremos abaixo.
| Filtro | Kernel | Característica | Uso Típico |
|---|---|---|---|
| Box / Média | Todos iguais | Rápido, borramento uniforme | Pré-processamento simples |
| Gaussiano | Pesos decrescentes do centro | Mais natural, sem artefatos | Padrão na maioria dos apps |
| Mediana | Não é convolução: pega a mediana | Excelente contra ruído "sal e pimenta" | Fotos com ruído forte |
- filtro da média (box filter): blur = cv.blur(img,(5,5))
- filtro gaussiano: blur = cv.GaussianBlur(img,(5,5),0)
- filtro da mediana: blur = cv.medianBlur(img,5)
- filtro bilateral: blur = cv.bilateralFilter(img,9,75,75)
import numpy as np
import cv2
#carrega imagem
img = cv2.imread('lena.png')
# adciona ruido 'salt and pepper'
def salt_and_pepper(image, salt_prob, pepper_prob):
noisy_image = np.copy(image)
total_pixels = image.size
num_salt = int(total_pixels * salt_prob)
num_pepper = int(total_pixels * pepper_prob)
# Adiciona 'salt' (pixels brancos)
salt_coords = [np.random.randint(0, i - 1, num_salt) for i in image.shape]
noisy_image[salt_coords[0], salt_coords[1]] = 255
# Adiciona 'pepper' (pixels pretos)
pepper_coords = [np.random.randint(0, i - 1, num_pepper) for i in image.shape]
noisy_image[pepper_coords[0], pepper_coords[1]] = 0
return noisy_image
# Parâmetros de ruído
salt_prob = 0.05 # Probabilidade de 'salt'
pepper_prob = 0.05 # Probabilidade de 'pepper'
# Gera a imagem com ruído
noisy_img = salt_and_pepper(img, salt_prob, pepper_prob)
ksize = 9 # tamanho do kernel (deve ser ímpar)
# Realiza o blur
blur_media = cv2.blur(noisy_img, (ksize, ksize))
blur_gaussiano = cv2.GaussianBlur(noisy_img, (ksize, ksize), sigmaX=0)
blur_mediana = cv2.medianBlur(noisy_img, ksize)
# Exibir as imagens
plt.figure(figsize=(15, 5))
plt.subplot(1, 4, 1)
plt.title('Original')
plt.imshow(noisy_img, cmap='gray')
plt.subplot(1, 4, 2)
plt.title('Blur Média')
plt.imshow(blur_media, cmap='gray')
plt.subplot(1, 4, 3)
plt.title('Blur Gaussiano')
plt.imshow(blur_gaussiano, cmap='gray')
plt.subplot(1, 4, 4)
plt.title('Blur Mediana')
plt.imshow(blur_mediana, cmap='gray')
plt.subplot(1, 4, 4)
plt.title('Blur Mediana')
plt.imshow(blur_mediana, cmap='gray')
plt.show()
# efeito do tamanho do kernel
tamanhos = [3, 9, 21, 51]
resultados = [[cv2.GaussianBlur(img, (k, k), 0) for k in tamanhos],
[cv2.blur(img, (k, k)) for k in tamanhos],
[cv2.medianBlur(img, k) for k in tamanhos]]
# exibir os resultados
plt.figure(figsize=(20, 15))
for i, metodo in enumerate(['GaussianBlur', 'Blur', 'MedianBlur']):
for j, resultado in enumerate(resultados[i]):
plt.subplot(3, len(tamanhos), i*len(tamanhos) + j + 1)
plt.title(f'{metodo} - Kernel {tamanhos[j]}x{tamanhos[j]}')
plt.imshow(resultado, cmap='gray')
plt.show()
Desafio 1 — Simulando o Blur de Fundo do Instagram¶
Objetivo: criar um efeito simples de "modo retrato" — foco no centro, blur na borda.
Instruções:
- Carregue
img_color(a astronauta) - Crie uma máscara elíptica que define a "região de foco" no centro da imagem
- Aplique um
GaussianBlurforte na imagem inteira - Combine a imagem original (região central) com a imagem borrada (bordas) usando a máscara
- Exiba: original | borrada | resultado final
Dica: cv2.ellipse() e cv2.addWeighted() serão úteis. A máscara deve ter valores entre 0 e 1.

# Seu código aqui — Desafio 1
import cv2
import numpy as np
from matplotlib import pyplot as plt
#carrega imagem
img_color = cv2.imread('lena.png')
# Passo 1: Aplique blur forte na imagem colorida
img_borrada = cv2.GaussianBlur(img_color, (51, 51), sigmaX=0)
# Passo 2: Crie a máscara elíptica (zeros = blur, 255 = foco)
mascara = np.zeros(img_color.shape[:2], dtype=np.uint8)
h, w = img_color.shape[:2]
cv2.ellipse(mascara, center=(w//2, h//2), axes=(w//3, h//3), angle=0,
startAngle=0, endAngle=360, color=255, thickness=-1)
# passo 3
resultado = np.copy(img_borrada) # Começa com a imagem borrada
resultado[mascara == 255] = img_color[mascara == 255] # Aplica os pixels da imagem original onde a máscara é 255 (foco)
# Passo 3: Forma mais pythonica usando np.where
# resultado = np.where(mascara[..., None] == 255, img_color, img_borrada) # A máscara precisa ser expandida para 3 canais usando [..., None]
# Passo 4: Exiba os resultados
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.title('Imagem Original')
plt.imshow(img_color)
plt.subplot(1, 3, 2)
plt.title('Imagem Borrada')
plt.imshow(img_borrada)
plt.subplot(1, 3, 3)
plt.title('Resultado Final')
plt.imshow(resultado)
plt.show()
Filtros de Realce (Sharpening) e Detecção de Bordas¶
Filtros de realce são filtros passa-alta: eles amplificam diferenças entre pixels vizinhos, destacando bordas e texturas.
Os Kernels Mais Importantes¶
Laplaciano — detecta bordas em todas as direções (derivada segunda): $$ K_{lap} = \begin{bmatrix} 0 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 0 \end{bmatrix} $$
Sobel X e Y — detectam bordas em direções específicas (derivada primeira): $$ K_{Sobel_x} = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} \qquad K_{Sobel_y} = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} $$
Sharpening (realce) — soma a imagem original com o Laplaciano: $$ K_{sharp} = \begin{bmatrix} 0 & -1 & 0 \\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{bmatrix} $$
Conexão Real: Filtro "Nitidez" do Instagram¶
O slider de Nitidez de app de foto aplica exatamente o kernel de sharpening acima. Valores altos criam o exagerado efeito "HDR" popular em fotos de paisagem.
import numpy as np
import cv2
#carrega imagem
img_gray = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
# ─── Comparação dos filtros de realce e bordas ────────────────────────────────
# Kernels
kernel_sharpen = np.array([[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]], dtype=np.float32)
kernel_laplaciano = np.array([[ 0, -1, 0],
[-1, 4, -1],
[ 0, -1, 0]], dtype=np.float32)
# Aplicando os filtros
img_sharp = cv2.filter2D(img_gray, ddepth=-1, kernel=kernel_sharpen)
img_lap = cv2.Laplacian(img_gray, cv2.CV_64F)
img_lap_abs = cv2.convertScaleAbs(img_lap) # converte para uint8 para exibir
# Sobel nos dois eixos
sobel_x = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3)
# Magnitude do gradiente: combina Sobel X e Y
magnitude = cv2.magnitude(sobel_x, sobel_y)
magnitude = cv2.convertScaleAbs(magnitude)
sobel_x_abs = cv2.convertScaleAbs(sobel_x)
sobel_y_abs = cv2.convertScaleAbs(sobel_y)
# Visualização
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
pares = [
(img_gray, 'Original'),
(img_sharp, 'Sharpening\n(realce)'),
(img_lap_abs, 'Laplaciano\n(todas as bordas)'),
(sobel_x_abs, 'Sobel X\n(bordas verticais)'),
(sobel_y_abs, 'Sobel Y\n(bordas horizontais)'),
(magnitude, 'Magnitude do Gradiente\n(Sobel X² + Sobel Y²)^0.5'),
]
for ax, (img, titulo) in zip(axes.flat, pares):
ax.imshow(img, cmap='gray')
ax.set_title(titulo, fontsize=12, fontweight='bold')
ax.axis('off')
plt.suptitle('Filtros de Realce e Detecção de Bordas', fontsize=15, fontweight='bold', y=1.01)
plt.show()
Desafio 2 — Unsharp Masking (técnica profissional de nitidez)¶
O Unsharp Masking é a técnica usada em editores profissionais (Photoshop, Lightroom). A ideia:
- Crie uma versão borrada da imagem
- Subtraia o borrado do original → obtém os detalhes (a "máscara")
- Adicione os detalhes de volta, com intensidade controlada por um parâmetro
alpha
$$\text{saída} = \text{original} + \alpha \cdot (\text{original} - \text{borrado})$$
Instruções:
- Implemente a fórmula acima para
alpha= 0.5, 1.0 e 2.0 - Use
np.clip(resultado, 0, 255).astype(np.uint8)para evitar overflow - Exiba os 4 resultados lado a lado
Dica: cv2.GaussianBlur(img_gray, (5, 5), 1.0) para o blur.

# Seu código aqui — Desafio 2
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_gray = cv2.imread('lena.png',0)
# passo 1
borrado = cv2.GaussianBlur(img_gray, (5, 5), sigmaX=1)
# passo 2 os detalhes são obtidos subtraindo a imagem borrada da original
mascara = cv2.subtract(img_gray, borrado) # Máscara de detalhes (high-pass)
# passo 3: Para cada alpha, crie a imagem de saída usando a fórmula:
# Para cada alpha:
alphas = [0.5, 1.0, 2.0]
# saída = original+ alpha * (original-borrado)
saida1 = img_gray + alphas[0] * (mascara) # Multiplicando por 1.0 para garantir tipo float
saida2 = img_gray + alphas[1] * (mascara) # Multiplicando por 1.0 para garantir tipo float
saida3 = img_gray + alphas[2] * (mascara) # Multiplicando por 1.0 para garantir tipo float
saidas = [saida1, saida2, saida3] # Armazena as saídas para cada alpha
# faz o clip para garantir que os valores fiquem entre 0 e 255
# vou mostrar as duas formas de fazer o clip, voce pode escolher a que preferir,
# a primeira é a forma pythonica usando list comprehension e
# a segunda é a forma tradicional usando um loop for
saidas = [np.clip(saida, 0, 255).astype(np.uint8) for saida in saidas] ## de forma pythonica em uma unica linha
# ou fazendo de forma tradicional
for i in range(len(saidas)):
saidas[i] = np.clip(saidas[i], 0, 255).astype(np.uint8)
# passo 3 alternativo, rodando em um loop para cada alpha
resultados = []
for alpha in alphas:
# saída = original + alpha * detalhes
saida = img_gray + alpha * mascara
# Clip para garantir valores entre 0-255 e converter para uint8
res = np.clip(saida, 0, 255).astype(np.uint8)
resultados.append(res)
# exibir os resultados
plt.figure(figsize=(20, 5))
plt.subplot(1, 4, 1)
plt.title('Original')
plt.imshow(img_gray, cmap='gray')
for i, alpha in enumerate(alphas):
plt.subplot(1, 4, i + 2)
plt.title(f'Alpha = {alpha}')
plt.imshow(saidas[i], cmap='gray')
plt.show()
## um video que mostra o efeito do unsharp masking https://youtu.be/u_4d51bOsVs?si=xdaeDjFyd4US5zYA
Detector de Bordas de Canny¶
O Canny é o detector de bordas mais usado na prática. Ele não é um único filtro — é um pipeline de 4 etapas:
Imagem → [1] Suavização Gaussiana → [2] Gradiente Sobel
→ [3] Supressão de não-máximos → [4] Histerese (dois limiares)
| Etapa | O que faz | Por quê? |
|---|---|---|
| Gaussiano | Remove ruído | Evita detectar bordas falsas |
| Sobel | Encontra gradientes | Localiza onde a intensidade muda |
| Supressão | Afina as bordas para 1 pixel | Bordas limpas, sem espessura |
| Histerese | Dois limiares: forte e fraco | Conecta bordas reais, descarta ruído |
Conexão Real: Segmentação em Imagens Médicas¶
A detecção de bordas é o primeiro passo para segmentar estruturas anatômicas em raios-X e tomografias — por exemplo, contornar os pulmões para medir área ou detectar nódulos.
import cv2
import matplotlib.pyplot as plt
# Carregar a imagem
image = cv2.imread('lab_images/lena.png', cv2.IMREAD_GRAYSCALE)
threshold_min = 100
threshold_max = 200
# Aplicar o detector de bordas de Canny
edges = cv2.Canny(image, threshold1=threshold_min, threshold2=threshold_max)
# Exibir a imagem original e a imagem com bordas detectadas
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title('Imagem Original')
plt.imshow(image, cmap='gray')
plt.subplot(1, 2, 2)
plt.title('Bordas Detectadas (Canny)')
plt.imshow(edges, cmap='gray')
plt.show()
Desafio 3¶
O Filtro de Canny é um dos mais utilizados até hoje, por ser robusto e apresentar bons resultados.
Ajuste os valores de threshold1 e threshold2 no detector de Canny e observe como os limiares afetam a detecção de bordas.
# carrega imagem de moeda
img = cv2.imread('moeda1.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Pipeline Canny completo
img_suavizada = cv2.GaussianBlur(img_gray, (5, 5), sigmaX=0)
bordas = cv2.Canny(img_suavizada, threshold1=50, threshold2=150)
# Sobrepõe bordas na imagem original (em vermelho)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_rgb[bordas > 0] = [255, 0, 0] # aplica a cor vermelha nas bordas detectadas
# Exibir resultados
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.title('Imagem Original')
plt.imshow(img_gray, cmap='gray')
plt.subplot(1, 3, 2)
plt.title('Bordas Detectadas (Canny)')
plt.imshow(bordas, cmap='gray')
plt.subplot(1, 3, 3)
plt.title('Bordas Sobrepostas')
plt.imshow(img_rgb)
plt.show()
FILTRO DE LIMIARIZAÇÃO¶
O filtro de limiarização é uma técnica que converte uma imagem em tons de cinza em uma imagem binária, onde os pixels são classificados como preto ou branco com base em um valor de limiar. Essa técnica é utilizada em tarefas como segmentação de objetos, detecção de bordas e pré-processamento de imagens.
O OpenCV oferece várias técnicas de limiarização, cada uma com suas particularidades. Abaixo estão as principais:
- cv2.THRESH_BINARY: Pixels acima do limiar são definidos como branco (255), e os abaixo, como preto (0).
- cv2.THRESH_BINARY_INV: Inverso do THRESH_BINARY. Pixels acima do limiar são definidos como preto, e os abaixo, como branco.
- cv2.THRESH_TRUNC: Pixels acima do limiar são truncados ao valor do limiar, enquanto os abaixo permanecem inalterados.
- cv2.THRESH_TOZERO: Pixels abaixo do limiar são definidos como preto, e os acima permanecem inalterados.
- cv2.THRESH_TOZERO_INV: Inverso do THRESH_TOZERO. Pixels acima do limiar são definidos como preto, e os abaixo permanecem inalterados.
- cv2.THRESH_OTSU: Método automático para determinar o limiar ideal, especialmente útil para imagens com histogramas bimodais
DICA
- cv2.threshold(): Função usada para aplicar a limiarização. Recebe a imagem, o valor do limiar, o valor máximo (geralmente 255) e o tipo de limiarização.
- cv2.THRESH_OTSU: Método automático que calcula o limiar ideal com base no histograma da imagem. Não é necessário fornecer um valor de limiar manualmente.
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Carregar imagem em escala de cinza
img_original = cv2.imread('lena.png')
img = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)
# Definir um valor de limiar
threshold_value = 127
# Aplicando Filtro de Limiarização
_, thresh_binary = cv2.threshold(img, threshold_value, 255, cv2.THRESH_BINARY)
_, thresh_binary_inv = cv2.threshold(img, threshold_value, 255, cv2.THRESH_BINARY_INV)
_, thresh_trunc = cv2.threshold(img, threshold_value, 255, cv2.THRESH_TRUNC)
_, thresh_tozero = cv2.threshold(img, threshold_value, 255, cv2.THRESH_TOZERO)
_, thresh_tozero_inv = cv2.threshold(img, threshold_value, 255, cv2.THRESH_TOZERO_INV)
_, thresh_otsu = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
titles = ['Original','Gray', 'THRESH_BINARY', 'THRESH_BINARY_INV', 'THRESH_TRUNC', 'THRESH_TOZERO', 'THRESH_TOZERO_INV', 'THRESH_OTSU']
images = [img_original, img, thresh_binary, thresh_binary_inv, thresh_trunc, thresh_tozero, thresh_tozero_inv, thresh_otsu]
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
for ax, title, image in zip(axes.flat, titles, images):
ax.imshow(image, cmap='gray')
ax.set_title(title)
ax.axis('off')
plt.tight_layout()
plt.show()
Desafio Final — Pipeline Completo¶
Contexto: Você foi contratado para desenvolver a feature de "filtros criativos" de um app de fotos. O requisito é implementar 3 filtros inspirados em apps populares.
Requisitos obrigatórios:
Filtro "Esboço" (sketch effect): simula um desenho a lápis
- Dica:
esboço = 1 - borda_normalizadasobre fundo branco - Mais sofisticado: divide a imagem em cinza pelo blur da mesma
- Dica:
Filtro "Vintage / Envelhecido": aplica suavização leve + ajuste de contraste
- Dica:
cv2.addWeightedcom a imagem original e uma versão borrada, mais equalização de histograma
- Dica:
Filtro "HDR Falso" (hyper-sharpening): exagero de nitidez
- Dica: Unsharp Masking com
alphaalto, depois ajuste de contraste
- Dica: Unsharp Masking com
Estrutura de código exigida:
- Cada filtro deve ser uma função Python com docstring
- Uma função
aplicar_filtro(img, nome_filtro)que recebe o nome e chama a função correta - Exibição final em grade 1×4 (original + 3 filtros)
Entrega:
- O notebook com os 3 filtros implementados
- Uma célula Markdown com análise: qual filtro se aproxima mais de um filtro real do Instagram? Por quê?
# Seu código aqui — Desafio Final
def filtro_esboco(img_color):
"""
Aplica efeito de esboço a lápis na imagem.
1. **Filtro "Esboço"** (sketch effect): simula um desenho a lápis
- Dica: `esboço = 1 - borda_normalizada` sobre fundo branco
- Mais sofisticado: divide a imagem em cinza pelo blur da mesma
Parâmetros:
img_color: ndarray RGB
Retorna:
ndarray com efeito esboço (escala de cinza)
"""
# Seu código aqui
img_gray = cv2.cvtColor(img_color, cv2.COLOR_RGB2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (21, 21), sigmaX=0)
esboco = cv2.divide(img_gray, img_blur, scale=256)
return esboco
def filtro_vintage(img_color):
"""
Aplica efeito vintage/envelhecido na imagem.
2. **Filtro "Vintage / Envelhecido"**: aplica suavização leve + ajuste de contraste
- Dica: `cv2.addWeighted` com a imagem original e uma versão borrada, mais equalização de histograma
"""
# Seu código aqui
img_blur = cv2.GaussianBlur(img_color, (3, 3), sigmaX=1.4)
img_vintage = cv2.addWeighted(img_color, 1.2, img_blur, -0.2, 0)
img_vintage[:,:,0] = cv2.equalizeHist(img_vintage[:,:,0])
img_vintage[:,:,1] = cv2.equalizeHist(img_vintage[:,:,1])
img_vintage[:,:,2] = cv2.equalizeHist(img_vintage[:,:,2])
out = cv2.addWeighted(img_color, 0.7, img_vintage, 0.3, 0)
return out
def filtro_hdr_falso(img_color):
"""
Aplica hiper-nitidez (falso HDR) na imagem.
3. **Filtro "HDR Falso"** (hyper-sharpening): exagero de nitidez
- Dica: Unsharp Masking com `alpha` alto, depois ajuste de contraste
"""
img_blur = cv2.GaussianBlur(img_color, (5, 5), sigmaX=1)
mascara = cv2.subtract(img_color, img_blur)
alphas = 3.0
saida = img_color + alphas * (mascara)
saida = np.clip(saida, 0, 255).astype(np.uint8)
saida[:,:,0]= cv2.equalizeHist(saida[:,:,0])
saida[:,:,1]= cv2.equalizeHist(saida[:,:,1])
saida[:,:,2]= cv2.equalizeHist(saida[:,:,2])
return saida
# Exemplo de uso
img_color = cv2.imread('image.png')
img_color = cv2.cvtColor(img_color, cv2.COLOR_BGR2RGB)
img_esboco = filtro_esboco(img_color)
img_vintage = filtro_vintage(img_color)
img_hdr = filtro_hdr_falso(img_color)
# Exibir resultados
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.title('Efeito Esboço')
plt.imshow(img_esboco, cmap='gray')
plt.subplot(1, 3, 2)
plt.title('Efeito Vintage')
plt.imshow(img_vintage)
plt.subplot(1, 3, 3)
plt.title('Efeito HDR Falso')
plt.imshow(img_hdr)
plt.show()