“A Fourier transform does not change the information content of a signal; it changes the representation.” — R. C. Gonzalez & R. E. Woods
Nos capítulos anteriores, todas as operações foram realizadas no domínio espacial: filtros de convolução, morfologia e segmentação atuam diretamente sobre os valores de intensidade dos pixels. Este capítulo apresenta uma perspectiva complementar e poderosa — o domínio da frequência — que revela como a imagem é construída a partir de padrões oscilatórios de diferentes escalas.
A ideia central é simples: qualquer imagem pode ser decomposta em uma soma de senoides bidimensionais, cada uma com frequência, amplitude e fase próprias. Baixas frequências correspondem a variações suaves (fundo, iluminação global); altas frequências correspondem a bordas, texturas e ruído.
Esta decomposição abre três caminhos práticos:
Filtragem espectral — suprimir ou realçar seletivamente faixas de frequência;
Análise multi-escala com wavelets — capturar estruturas em diferentes resoluções e posições;
Compressão — eliminar coeficientes imperceptíveis ao olho humano, reduzindo o volume de dados.
5.1 Objetivos
Ao concluir este capítulo, você será capaz de:
Interpretar o espectro de Fourier de uma imagem: distinguir magnitude de fase, localizar baixas e altas frequências, e ler o espectro de forma intuitiva;
Aplicar o Teorema da Convolução para implementar filtragem eficiente via FFT, compreendendo o ganho computacional em relação à convolução direta;
Projetar e comparar filtros espectrais — passa-baixa (Ideal, Gaussiano, Butterworth), passa-alta e notch — com foco nos seus efeitos visuais e nos artefatos que produzem;
Compreender wavelets e multirresolução: interpretar as subbandas LL/LH/HL/HH e relacionar a análise wavelet às camadas de CNNs;
Entender o pipeline JPEG: desde a DCT em blocos 8×8 até a quantização perceptual e os artefatos de compressão;
Escolher o formato de imagem adequado (JPEG, PNG, WebP) com base no conteúdo e na aplicação.
5.2 Configuração do Ambiente
import os, importlib, urllib.requestimport numpy as npimport matplotlib.pyplot as pltimport cv2from scipy import ndimageBASE_URL ="https://raw.githubusercontent.com/fzampirolli/pdi-vc/master/morph"for f in ["morph.py"]:ifnot os.path.exists(f): urllib.request.urlretrieve(f"{BASE_URL}/{f}", f)import morphimportlib.reload(morph)from morph import mmprint("✅ Ambiente pronto")
✅ Ambiente pronto
5.3 Transformada de Fourier Discreta 2D
A análise de Fourier fundamenta-se em um princípio poderoso: qualquer sinal periódico admite representação como soma de senoides com diferentes frequências, amplitudes e fases. Ver exemplo em uma dimensão na Figura 5.1.
Figura 5.1: Decomposição de Fourier 1D: uma onda quadrada (linha tracejada) é aproximada pela soma das primeiras senoides (linhas coloridas). Quanto mais termos, melhor a aproximação.
Aplicada a imagens digitais, essa ideia transforma uma matriz de valores de intensidade \(f(x,y)\) em uma representação no domínio da frequência, revelando quais padrões oscilatórios compõem a cena visual.
5.3.1 Fenômeno: o que uma imagem “parece” no domínio da frequência?
Observe o que acontece quando se transforma uma fotografia: ela se transforma em um padrão radial brilhante ao centro, com energia decaindo progressivamente em direção às bordas. Esse padrão é o espectro de magnitude da imagem — uma radiografia da distribuição de frequências.
Região do espectro
O que representa
Exemplos visuais
Centro (baixas freq.)
Variações lentas — iluminação, cor de fundo, forma global
Gradientes suaves, blocos uniformes
Meio (médias freq.)
Texturas, padrões repetitivos
Tecidos, tijolos, grama
Bordas (altas freq.)
Variações abruptas — contornos, ruído
Bordas de objetos, grão de imagem
Intuição-chave: suprimir o centro do espectro remove a estrutura global e enfatiza bordas; suprimir as bordas suaviza a imagem e remove ruído. É precisamente isso que os filtros espectrais fazem — mas com precisão cirúrgica impossível no domínio espacial.
5.3.2 O Experimento da Grade: Construindo a Imagem do Zero
Antes de mergulhar nas fórmulas complexas, vamos fazer o caminho inverso (IDFT). O que acontece se tivermos um espectro completamente vazio (preto) e acendermos apenas um único ponto brilhante (um coeficiente de frequência)?
O resultado no espaço real é uma grade perfeitamente senoidal. A posição desse ponto no espectro define a direção e a frequência (espessura) dessa grade.
N_grid =100espectro_vazio = np.zeros((N_grid, N_grid), dtype=complex)# Acendendo um único ponto (frequência) fora do centrou0, v0 =10, 5espectro_vazio[N_grid//2- v0, N_grid//2- u0] =1000# Retornando para o domínio espacial (IDFT)onda_2d = np.real(np.fft.ifft2(np.fft.ifftshift(espectro_vazio)))onda_vis = cv2.normalize(onda_2d, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)espectro_vis = cv2.normalize(np.abs(espectro_vazio), None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)# Destaque visual do pontoespectro_color = cv2.cvtColor(espectro_vis, cv2.COLOR_GRAY2BGR)cv2.circle(espectro_color, (N_grid//2- u0, N_grid//2- v0), 2, (0, 0, 255), -1)mm.show([espectro_color, onda_vis], titles=["Espectro (1 ponto ativo)", "Onda 2D Resultante (IDFT)"], cols=2, figsize=(10, 4))
Figura 5.2: Toda frequência no espectro (ponto isolado) corresponde a uma onda senoidal 2D rotacionada no domínio espacial.
5.3.3 Definição Matemática
Dada uma imagem \(f(x,y)\) de dimensões \(M \times N\), sua Transformada de Fourier Discreta 2D (DFT) é definida como:
onde \(u = 0, 1, \ldots, M-1\) e \(v = 0, 1, \ldots, N-1\) são as frequências discretas nas direções horizontal e vertical, respectivamente. O exponencial complexo \(e^{-j2\pi(ux/M + vy/N)}\) representa uma oscilação bidimensional de frequência \((u/M, v/N)\) ciclos por pixel.
A Transformada Inversa de Fourier Discreta 2D (IDFT) recupera a imagem original a partir de seu espectro:
A frequência espacial\((u/M,v/N)\) representa a taxa de variação espacial da intensidade ao longo da imagem.
Valores baixos correspondem a variações suaves e estruturas globais; valores altos correspondem a mudanças rápidas de intensidade, como bordas, texturas e detalhes finos.
Em termos discretos, os índices \((u,v)\) indicam quantos ciclos da oscilação ocorrem ao longo da largura e da altura da imagem.
Cada bloco \(8 \times 8\) da imagem é decomposto em uma combinação ponderada de 64 padrões de base da DCT — funções cossenoidais de frequência crescente em \(x\) e em \(y\). O coeficiente \(C(u,v)\) representa o “peso” do padrão de frequência \((u,v)\) naquele bloco específico.
A figura abaixo exibe as 64 bases: o canto superior esquerdo corresponde ao componente DC (padrão uniforme); ao avançar para a direita e para baixo, a frequência espacial aumenta progressivamente.
NotaO que é o componente DC?
O coeficiente \(F(0,0)\) — chamado DC (Direct Current) — corresponde à média global de todos os pixels. Na grande maioria das imagens naturais, corresponde ao valor de maior magnitude no espectro — imagens com variações abruptas e periódicas podem concentrar energia em outras frequências. Os demais coeficientes (\(u,v \neq 0,0\)) representam variações em torno da média.
Após fftshift — deslocamento circular que move o DC para o centro —, o espectro fica intuitivo: centro = baixas frequências, bordas = altas frequências.
5.3.5 Implementação e Visualização
Anatomia do Espectro de Fourier 2D (após fftshift)
Figura 5.3: Diagrama conceitual do espectro de Fourier 2D centrado.
5.3.6 O que a Magnitude e a Fase carregam?
Uma das demonstrações mais reveladoras em PDI é trocar os espectros de magnitude e fase entre duas imagens distintas e reconstruir cada uma. O resultado mostra que:
A fase carrega a estrutura semântica — contornos, posição dos objetos, geometria da cena;
A magnitude carrega o conteúdo energético e textural — contraste, iluminação geral.
Quando reconstruímos uma imagem com a magnitude de A mas a fase de B, o resultado se parece com B — mesmo usando a “energia” de A. A fase possui papel predominante na preservação da estrutura geométrica e organização espacial da imagem, enquanto a magnitude controla a distribuição energética, contraste e textura. Ver um exemplo na Figura 5.4.
Analogia auditiva: no áudio, a fase determina a percepção de posição (esquerda/direita). Na imagem, ela determina a posição e forma dos objetos. Inverter a fase de uma música torna-a irreconhecível mesmo que a magnitude (timbre) permaneça intacta.
# ── Experimento: A Importância da Fase ───────────────────────────────────────# ── Carregamento da imagem ────────────────────────────────────────────────────url ="https://upload.wikimedia.org/wikipedia/commons/2/25/GAZI.MD.AHAD_11.jpg"caminho ="imagens/coins.jpg"ifnot os.path.exists(caminho): os.makedirs("imagens", exist_ok=True) img_obj = mm.read(url, pil=True) mm.write(img_obj, caminho)else: img_obj = mm.read(caminho, pil=True)img_color = np.array(img_obj)img_gray = mm.gray(img_color)img_a = cv2.resize(img_gray, (400, 400))# Criar uma imagem B sintética (padrão geométrico)img_b = np.zeros((400, 400), dtype=np.uint8)cv2.rectangle(img_b, (100, 100), (300, 300), 255, -1)cv2.circle(img_b, (200, 200), 150, 128, 10)FA = np.fft.fft2(img_a)FB = np.fft.fft2(img_b)# Troca de Faserec_A_mag_B_fase = np.real(np.fft.ifft2(np.abs(FA) * np.exp(1j* np.angle(FB))))rec_B_mag_A_fase = np.real(np.fft.ifft2(np.abs(FB) * np.exp(1j* np.angle(FA))))mm.show( [img_a, img_b, rec_A_mag_B_fase, rec_B_mag_A_fase], titles=["Imagem A", "Imagem B", "Mag(A) + Fase(B)", "Mag(B) + Fase(A)"], cols=4, figsize=(16, 4))print("💡 Interpretação: Observe que a estrutura (formas) segue a FASE, não a magnitude.")
Figura 5.4: Experimento de troca de fase: Imagem A (moedas) e Imagem B (padrão geométrico) reconstruídas com magnitudes e fases trocadas. O resultado confirma que a estrutura visual segue a fase — a imagem reconstruída com a fase de B se parece com B, independentemente de qual magnitude foi usada.
💡 Interpretação: Observe que a estrutura (formas) segue a FASE, não a magnitude.
5.3.7 Simulador: Reconstruindo Sinais com Senoides
Antes de prosseguir para imagens 2D, o simulador interativo da Figura 5.5 permite explorar a essência da análise de Fourier 1D: qualquer forma de onda pode ser reconstruída somando-se senoides simples.
Observe como, ao adicionar mais termos, a soma (curva preta) converge progressivamente para a forma alvo (tracejada). O espectro abaixo mostra as amplitudes de cada frequência — é a “receita” da forma de onda.
Explore ativamente:(i) Com quantos termos a onda quadrada fica visualmente aceitável? (ii) Qual das três formas converge mais rapidamente — por quê? (iii) O que acontece com o espectro ao trocar de onda quadrada para triangular?
∿ Simulador: Decomposição de Fourier 1D
soma de senoides
Termos
1
Erro RMS
–
Forma Alvo
quadrada
Forma Alvo
Nº de Termos
1
Exibição
Figura 5.5: Simulador interativo da decomposição de Fourier 1D: visualização da soma de senoides com diferentes frequências, amplitudes e fases. Adicione termos e observe a convergência para formas de onda arbitrárias.
5.4 Teorema da Convolução
O Teorema da Convolução estabelece uma das relações mais importantes entre os domínios espacial e de frequência:
Figura 5.6: Pipeline completo: Butterworth + Notch para remoção de ruído misto.
ImportanteO Perigo da Convolução Circular (Wrap-around Error)
A DFT pressupõe que a imagem se repete infinitamente como um azulejo. Se aplicarmos um filtro de frequência sem esticar a imagem com bordas de zeros (zero-padding), o topo da imagem se mistura com a base, e a esquerda com a direita. O padding evita esse vazamento.
# Simulação de um filtro de deslocamento brutalH_shift = np.zeros_like(img_gray, dtype=complex)for u inrange(M):for v inrange(N): H_shift[u, v] = np.exp(-1j*2* np.pi * (u*80/M + v*80/N)) # Desloca 80 pixels# Filtragem SEM padding (causa o wrap-around)F_img = np.fft.fft2(img_gray)img_vazada = np.real(np.fft.ifft2(F_img * H_shift))img_vazada_vis = cv2.normalize(img_vazada, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)mm.show([img_gray, img_vazada_vis], titles=["Original", "Filtragem s/ Padding (Vazamento)"], cols=2, figsize=(10, 4))
Figura 5.7: Sem padding, um deslocamento severo faz a imagem vazar para o lado oposto (convolução circular).
# ── Kernel Gaussiano 11×11 ────────────────────────────────────────────────────sigma =3.0K =11ks = np.arange(K) - K //2gauss1d = np.exp(-ks**2/ (2* sigma**2))gauss1d /= gauss1d.sum()kernel = np.outer(gauss1d, gauss1d) # kernel 2D separável# ── Método 1: Convolução espacial direta ─────────────────────────────────────f_float = img_gray.astype(np.float64)conv_esp = cv2.filter2D(f_float, -1, kernel, borderType=cv2.BORDER_CONSTANT)# ── Método 2: Multiplicação em frequência (via FFT) ──────────────────────────M, N = f_float.shape# Padding para convolução linear (evita aliasing circular)Mpad =2**int(np.ceil(np.log2(M + K -1)))Npad =2**int(np.ceil(np.log2(N + K -1)))# Posiciona o kernel com a origem no (0,0) e padding com zeroskernel_pad = np.zeros((Mpad, Npad))kh, kw = kernel.shapekernel_pad[:kh, :kw] = kernelF_img = np.fft.fft2(f_float, (Mpad, Npad))F_kern = np.fft.fft2(kernel_pad)conv_freq = np.real(np.fft.ifft2(F_img * F_kern))# Recorte para compensar o deslocamento introduído pelo posicionamento do kerneloffset = K //2conv_freq_crop = conv_freq[offset:offset+M, offset:offset+N]# ── Verificação numérica ──────────────────────────────────────────────────────diff = np.abs(conv_esp - conv_freq_crop)print(f"Diferença máxima (|conv_esp - conv_freq|): {diff.max():.2e}")print(f"Diferença média (|conv_esp - conv_freq|): {diff.mean():.2e}")print(f"→ Teorema da Convolução verificado numericamente.")conv_esp_vis = cv2.normalize(conv_esp, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)conv_freq_vis = cv2.normalize(conv_freq_crop, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)diff_vis = cv2.normalize(diff, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)mm.show( [img_gray, conv_esp_vis, conv_freq_vis, diff_vis], titles=["Original","Convolução espacial","Multiplicação em frequência",f"Diferença (máx={diff.max():.1e})" ], cols=4, figsize=(16, 5))
Diferença máxima (|conv_esp - conv_freq|): 2.56e-13
Diferença média (|conv_esp - conv_freq|): 2.79e-14
→ Teorema da Convolução verificado numericamente.
Figura 5.8: Verificação do Teorema da Convolução: a diferença pixel a pixel entre a convolução espacial (cv2.filter2D) e a multiplicação em frequência (FFT) é numericamente nula — confirmando a equivalência teórica.
5.5 Filtros no Domínio da Frequência
Um filtro no domínio da frequência é, essencialmente, uma máscara aplicada ao espectro: valores próximos de 1 deixam a frequência passar; valores próximos de 0 a atenuam. A forma dessa máscara determina o comportamento visual do filtro.
O dilema do corte abrupto: um filtro ideal (corte perfeito em \(D_0\)) parece ótimo na teoria, mas causa o fenômeno de Ringing — oscilações próximas às bordas dos objetos. Isso ocorre porque um corte abrupto no espectro corresponde, no domínio espacial, a uma convolução com uma função sinc de suporte infinito — que produz os anéis visíveis, ver Figura 5.9.
A solução: filtros com transição suave — Gaussiano ou Butterworth — eliminam o ringing ao custo de uma fronteira de corte menos precisa.
# Simulando o Filtro Ideal na Frequência (Cilindro) e sua representação Espacial (Sinc)N_grid =128u = np.arange(-N_grid//2, N_grid//2)U, V = np.meshgrid(u, u)D = np.sqrt(U**2+ V**2)# Frequência: Cilindro Ideal (1 no centro, 0 fora do raio 20)H_freq = np.zeros((N_grid, N_grid))H_freq[D <=20] =1# Espaço: A inversa resulta na famigerada Sinc 2Dh_space = np.fft.fftshift(np.real(np.fft.ifft2(np.fft.ifftshift(H_freq))))fig, ax = plt.subplots(1, 2, subplot_kw={'projection': '3d'}, figsize=(12, 4))ax[0].plot_surface(U, V, H_freq, cmap='viridis', edgecolor='none')ax[0].set_title("Frequência: Filtro Ideal (Cilindro)")ax[0].set_zlim(0, 1.2)ax[1].plot_surface(U, V, h_space, cmap='plasma', edgecolor='none')ax[1].set_title("Espaço Real: Ondulações da Sinc (Causa do Ringing)")plt.tight_layout(); plt.show()
Figura 5.9: A Dualidade Perigosa: O corte abrupto na Frequência (Cilindro) transforma-se obrigatoriamente numa Sinc espacial. Suas ondulações causam o ringing fantasma nas bordas da imagem.
5.5.1 Filtros Passa-Baixa
Filtros passa-baixa atenuam altas frequências, suavizando a imagem e reduzindo ruído. Definem-se em termos da distância ao centro do espectro:
O corte abrupto em \(D_0\) causa o fenômeno de Ringing: oscilações artificiais próximas às bordas dos objetos, análogas ao comportamento da série de Fourier truncada.
O parâmetro \(n\) controla a inclinação da transição: valores baixos (\(n=1,2\)) produzem transições suaves (sem ringing); valores altos (\(n \geq 5\)) aproximam o comportamento do filtro ideal. Ver exemplos na Figura 5.10.
Perfis dos Filtros Passa-Baixa — comparação visual (D₀ = 30)
À medida que a ordem do Butterworth aumenta, o perfil se aproxima do filtro Ideal — e o ringing aumenta.
Figura 5.10: Filtros passa-baixa.
5.5.2 Filtros Passa-Alta e Passa-Banda
Filtros passa-alta são obtidos pelo complemento de qualquer filtro passa-baixa: \(H_{\text{HP}} = 1 - H_{\text{LP}}\). O efeito visual é o inverso — preserva bordas e detalhes, atenua regiões uniformes.
Filtros passa-banda combinam um corte inferior \(D_L\) e um corte superior \(D_H\), preservando apenas as frequências entre eles: \(H_{\text{BP}} = H_{\text{HP}}^{(D_L)} \cdot H_{\text{LP}}^{(D_H)}\).
São especialmente úteis na remoção de ruído periódico: padrões regulares (interferência elétrica, grade de scanner) aparecem como picos no espectro de magnitude e podem ser suprimidos com um filtro rejeita-banda (notch filter) posicionado sobre esses picos. Ver exemplos de aplicações de filtros passa baixa na Figura 5.12 e passa alta na Figura 5.13.
Frequência de corte D₀30Tipo de filtro
Resposta H(D)
Espectro filtrado |F · H|
Sinal 1D — original vs filtrado
Energia retida por banda (%)
Figura 5.11: Simulador interativo de filtros no domínio da frequência.
import iodef distancia_centro(M, N):"""Matriz de distâncias ao centro do espectro.""" u = np.arange(M) - M //2 v = np.arange(N) - N //2 V, U = np.meshgrid(v, u)return np.sqrt(U**2+ V**2)def aplicar_filtro_freq(img, H):"""Aplica filtro H (centrado) a imagem via FFT.""" F = np.fft.fftshift(np.fft.fft2(img.astype(np.float64))) Fg = F * H g = np.real(np.fft.ifft2(np.fft.ifftshift(Fg)))return cv2.normalize(g, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)M, N = img_gray.shapeD = distancia_centro(M, N)D0 =30# frequência de corten_bw =2# ordem do Butterworth# ── Funções de transferência ──────────────────────────────────────────────────H_ideal = (D <= D0).astype(np.float64)H_gauss = np.exp(-D**2/ (2* D0**2))H_bw =1.0/ (1.0+ (D / D0)**(2* n_bw))# ── Imagens filtradas ─────────────────────────────────────────────────────────img_ideal = aplicar_filtro_freq(img_gray, H_ideal)img_gauss = aplicar_filtro_freq(img_gray, H_gauss)img_bw = aplicar_filtro_freq(img_gray, H_bw)# ── Perfis de H(u,v) ─────────────────────────────────────────────────────────def fig2img(fig): b = io.BytesIO(); fig.savefig(b, format='png', dpi=100); plt.close(fig); b.seek(0)return (plt.imread(b)[:,:,:3]*255).astype(np.uint8)fig, ax = plt.subplots(figsize=(6, 3))linha = M //2ax.plot(H_ideal[linha, :], label="Ideal", color="#D85A30", lw=1.5, ls="--")ax.plot(H_gauss[linha, :], label="Gaussiano", color="#1D9E75", lw=1.5)ax.plot(H_bw[linha, :], label="Butterworth", color="#534AB7", lw=1.5)ax.axvline(N//2-D0, color="#aaa", lw=0.8, ls=":")ax.axvline(N//2+D0, color="#aaa", lw=0.8, ls=":")ax.set(title="Perfis H(u,v) — linha central", xlabel="v", ylabel="H(u,v)")ax.legend(fontsize=8); plt.tight_layout()perfil_img = fig2img(fig)# ── Filtros H visualizados ────────────────────────────────────────────────────def H_vis(H):return cv2.normalize((H*255).astype(np.uint8), None, 0, 255, cv2.NORM_MINMAX)mm.show( [img_gray, img_ideal, img_gauss, img_bw, H_vis(H_ideal), H_vis(H_gauss), H_vis(H_bw), perfil_img], titles=["Original", "LPF Ideal", "LPF Gaussiano", "LPF Butterworth (n=2)","H Ideal", "H Gaussiano","H Butterworth", "Perfis H(u,v)" ], cols=4, figsize=(16, 9))
Figura 5.12: Comparação entre filtros passa-baixa: Ideal (D₀=30), Gaussiano (D₀=30) e Butterworth (D₀=30, n=2). Perfis de H(u,v) ao longo de uma linha central e imagens filtradas correspondentes.
Figura 5.13: Filtro passa-alta Gaussiano. (a) Original; (b) Filtro passa-alta (D₀=30) - as bordas das moedas e fundo texturizado são realçados.
5.5.3 Remoção de Ruído Periódico
Ruído periódico — proveniente de interferência elétrica, sensores com padrão regular ou artefatos de digitalização — manifesta-se no espectro de Fourier como picos pontuais simétricos em torno do centro. O filtro rejeita-banda notch suprime seletivamente essas frequências, preservando o restante da imagem.
# ── Imagem com ruído periódico sintético ──────────────────────────────────────h_img, w_img = img_gray.shapex = np.arange(w_img); y = np.arange(h_img)X, Y = np.meshgrid(x, y)# Ruído senoidal com frequências (u0=20, v0=20) e simétricasruido =40* np.sin(2* np.pi * (20* X / w_img +20* Y / h_img))img_ruidosa = np.clip(img_gray.astype(np.float64) + ruido, 0, 255).astype(np.uint8)# ── Espectro da imagem ruidosa ───────────────────────────────────────────────F_r = np.fft.fftshift(np.fft.fft2(img_ruidosa.astype(np.float64)))mag_r = np.log1p(np.abs(F_r))mag_vis = cv2.normalize(mag_r, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)# ── Máscara notch (suprime os picos do ruído) ────────────────────────────────mascara = np.ones((h_img, w_img), dtype=np.float64)r_notch =10# raio da região suprimidadef suprimir_pico(mask, cy, cx, r):"""Zera um disco de raio r centrado em (cy, cx) na máscara.""" yy, xx = np.ogrid[:mask.shape[0], :mask.shape[1]] dist = np.sqrt((yy - cy)**2+ (xx - cx)**2) mask[dist <= r] =0return mask# Coordenadas dos picos no espectro centralizadocy0, cx0 = h_img //2, w_img //2dy0 =int(round(20* h_img / h_img)) # = 20dx0 =int(round(20* w_img / w_img)) # = 20for dy, dx in [(+dy0,+dx0),(-dy0,-dx0),(+dy0,-dx0),(-dy0,+dx0)]: mascara = suprimir_pico(mascara, cy0+dy, cx0+dx, r_notch)mascara_vis = (mascara *255).astype(np.uint8)# ── Filtragem e reconstrução ─────────────────────────────────────────────────F_filtrada = F_r * mascaraimg_rest = np.real(np.fft.ifft2(np.fft.ifftshift(F_filtrada)))img_rest_vis = cv2.normalize(img_rest, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)psnr = cv2.PSNR(img_gray, img_rest_vis)print(f"PSNR (original vs restaurada): {psnr:.2f} dB")mm.show( [img_ruidosa, mag_vis, mascara_vis, img_rest_vis], titles=["Com ruído periódico","Espectro (log)","Máscara notch",f"Restaurada (PSNR={psnr:.1f} dB)" ], cols=4, figsize=(16, 4))
PSNR (original vs restaurada): 30.62 dB
Figura 5.14: Remoção de ruído periódico via filtro notch no domínio da frequência: (a) imagem com ruído senoidal, (b) espectro mostrando os picos do ruído, (c) máscara notch centrada nos picos, (d) imagem restaurada.
📌 Síntese — Filtros Espectrais
Filtro
Efeito visual
Artefato
Uso
Passa-baixa Ideal
Suavização forte
Ringing (anéis)
Ilustrativo apenas
Passa-baixa Gaussiano
Suavização suave
Nenhum
Suavização geral
Passa-baixa Butterworth
Suavização controlada
Ringing leve (ordens altas)
Compromisso suavidade/precisão
Passa-alta
Realce de bordas
Pode amplificar ruído
Detecção de contornos
Notch
Remove frequências específicas
Pode criar artefatos locais
Remoção de ruído periódico
O design de filtros no domínio da frequência é direto e intuitivo — mas os artefatos espaciais (ringing, borramento) emergem de escolhas no espectro.
5.6Wavelets e Multirresolução
A Transformada de Fourier decompõe o sinal em frequências globais: cada coeficiente \(F(u,v)\) recebe contribuições de toda a imagem, sem informação sobre onde uma frequência ocorre. Uma borda localizada no canto da imagem se mistura ao espectro inteiro — o que limita sua capacidade de representar explicitamente onde determinadas frequências ocorrem espacialmente.
As wavelets (ondaletas) resolvem essa limitação com funções de base compactas — energia concentrada em uma região finita — que podem ser deslocadas e escaladas, oferecendo representação simultânea em frequência e localização espacial.
5.6.1 O Limite de Fourier: Onde ocorreu o evento?
Fourier nos diz perfeitamente quais frequências existem, mas perde totalmente a noção do tempo/espaço de onde elas ocorreram. Veja o experimento abaixo: uma imagem com duas linhas nítidas, e outra com as mesmas linhas deslocadas. O espectro de magnitude é virtualmente idêntico.
Figura 5.15: Fourier global é cego para a posição. Os espectros não dizem onde as bordas estão.
5.6.2 Transformada Wavelet Discreta 2D
A DWT aplica, separadamente em linhas e colunas, dois filtros complementares — passa-baixa\(h\) (aproximações) e passa-alta\(g\) (detalhes) — seguidos de subamostragem por 2, produzindo quatro subbandas:
A decomposição é recursiva: aplicando a DWT novamente sobre LL obtém-se o próximo nível. Após \(J\) níveis, a representação hierárquica contém \(3J+1\) subbandas — cada nível com metade da resolução do anterior.
NotaConexão com CNNs
A análise multirresolução das Wavelets possui forte relação conceitual com representações hierárquicas utilizadas em métodos modernos de visão computacional, como as CNNs.
5.6.3 Famílias de Wavelets
Diferentes wavelets resultam em diferentes compromissos entre localização, suavidade e compactação:
Wavelet
Suporte
Momentos nulos
Simetria
Uso típico
Haar
2
1
Assimétrica
Análise básica, educação
Daubechies db4
8
4
Assimétrica
Compressão, análise geral
Symlet sym4
8
4
Quase simétrica
Reconstrução de sinais
Biortogonal 5/3
5/3
2/2
Simétrica
JPEG 2000 (sem perda)
Biortogonal 9/7
9/7
4/4
Simétrica
JPEG 2000 (com perda)
O número de momentos nulos controla a compactação: com \(p\) momentos nulos, a wavelet produz coeficientes exatamente nulos para polinômios de grau até \(p-1\) — quanto maior \(p\), mais coeficientes próximos de zero em regiões suaves e maior a eficiência de compressão.
Figura 5.18: Decomposição wavelet 2D de 2 níveis com wavelet Haar: subbandas LL, LH, HL, HH em cada nível. As subbandas de detalhe revelam estruturas orientadas em diferentes escalas.
Figura 5.19: Comparação entre famílias de wavelets: Haar, db4, sym4 e bior2.2. Subbanda LL₁ (aproximação) e HH₁ (diagonal) para cada escolha, ilustrando o compromisso entre compactação e suavidade.
def dwt_threshold_reconstruct(img, wavelet='db4', nivel=2, threshold=0.0):"""Decompõe, aplica limiar e reconstrói via IDWT.""" coefs = pywt.wavedec2(img.astype(np.float64), wavelet, level=nivel)# Copia e aplica hard thresholding em todos os detalhe coefs_t = [coefs[0]] # LL final não é limiarizadofor detalhe in coefs[1:]: coefs_t.append(tuple(pywt.threshold(sb, threshold, mode='hard') for sb in detalhe)) rec = pywt.waverec2(coefs_t, wavelet)# Recorte para dimensão original rec = rec[:img.shape[0], :img.shape[1]]return np.clip(rec, 0, 255).astype(np.uint8)thresholds = [0, 10, 30, 60, 100]imgs_thr = [img_gray]titles_thr = ["Original"]for t in thresholds: rec = dwt_threshold_reconstruct(img_gray, threshold=t) psnr = cv2.PSNR(img_gray, rec) imgs_thr .append(rec) titles_thr.append(f"T={t} PSNR={psnr:.1f} dB")mm.show(imgs_thr, titles=titles_thr, cols=3, figsize=(14, 10))
Figura 5.20: Reconstrução wavelet com limiamento de coeficientes (hard thresholding): à medida que o limiar aumenta, mais detalhes são zerificados, produzindo imagens progressivamente mais suaves. Métrica PSNR quantifica a perda de qualidade.
5.6.4 📌 Síntese - Fourier vs Wavelets: quando usar cada uma?
Critério
Fourier (DFT)
Wavelet (DWT)
Base
Senoides de suporte infinito
Funções compactas, localizadas
Localização espacial
✗ — informação global
✓ — frequência e posição
Filtragem espectral
✓ — controle preciso de banda
Limitado
Compressão
DCT (JPEG) — excelente em blocos
DWT (JPEG 2000) — melhor para imagens inteiras
Análise multi-escala
✗
✓ — hierarquia natural
Ruído periódico
✓ — localização exata no espectro
✗
Texturas não-estacionárias
✗
✓
Regra prática: use Fourier para filtragem espectral e remoção de ruído periódico; use wavelets para compressão, análise multi-escala e processamento de sinais não-estacionários.
5.7 Compressão de Imagens
As wavelets estabelecem a fundação teórica do padrão JPEG 2000, mas o padrão JPEG original — dominante em fotografia digital — utiliza uma transformada mais simples e igualmente eficaz: a Transformada de Cossenos Discreta (DCT). Ambas exploram o mesmo princípio: concentrar a energia da imagem em poucos coeficientes e descartar os demais com impacto visual mínimo.
A compressão visa reduzir a quantidade de dados necessária para armazenar ou transmitir uma imagem, explorando redundâncias presentes na representação original.
5.7.1 Taxonomia das Redundâncias
Três categorias principais de redundância são exploradas por algoritmos de compressão:
Tabela 5.2: Tipos de redundância em imagens e como são exploradas.
Tipo
Definição
Explorada por
Espacial (interpixel)
Pixels vizinhos são altamente correlacionados
DCT, codificação preditiva
Espectral (interchannel)
Canais de cor são correlacionados (R ≈ G ≈ B em imagens naturais)
Conversão YCbCr
Psicovisual
O sistema visual humano é insensível a certas variações de alta freq.
Quantização JPEG
A compressão é classificada em duas categorias fundamentais:
Sem perda (lossless): reconstrução bit-a-bit idêntica ao original. Usada quando integridade é crítica (imagens médicas, documentos).
Com perda (lossy): permite distorção controlada para ganhos maiores de compressão. Adequada para fotografia e vídeo, onde o SVH tolera imperfeições.
5.7.2 Transformada de Cossenos Discreta (DCT)
A Transformada de Cossenos Discreta (DCT) é a operação central do padrão JPEG. Diferentemente da DFT (que usa base complexa), a DCT é puramente real, o que a torna computacionalmente mais eficiente em implementações de propósito geral e a DCT é puramente real, o que simplifica implementações computacionais e favorece aplicações de compressão de imagens.
Para um bloco \(f(x,y)\) de dimensões \(N \times N\), a DCT-II 2D é definida como:
onde \(\alpha(0) = \sqrt{1/N}\) e \(\alpha(k) = \sqrt{2/N}\) para \(k > 0\) (fator de normalização ortogonal). O coeficiente \(C(0,0)\) corresponde ao componente DC.
NotaDCT vs DFT: vantagem da compactação de energia
Tanto a DCT quanto a DFT transformam um bloco \(N \times N\) em \(N \times N\) coeficientes. A diferença crítica para compressão é a compactação de energia: para imagens naturais. Para imagens naturais, a DCT frequentemente apresenta melhor compactação de energia nos coeficientes de baixa frequência do que a DFT, favorecendo aplicações de compressão perceptual. Isso ocorre porque a DCT assume simetria par do sinal — equivalente a uma extensão periódica sem descontinuidade — reduzindo o ringing nas bordas do bloco. A consequência prática é que mais coeficientes DCT podem ser zerados sem degradação visível, resultando em maior compressão.
fig, axes = plt.subplots(8, 8, figsize=(6, 6))fig.subplots_adjust(hspace=0.05, wspace=0.05)for i inrange(8):for j inrange(8):# Criamos um "impulso" isolado na frequência coef = np.zeros((8, 8)); coef[i, j] =1# A inversa revela o padrão geométrico espacial equivalente b = idct(idct(coef.T, norm='ortho').T, norm='ortho') axes[i, j].imshow(b, cmap='gray') axes[i, j].axis('off')plt.suptitle("As 64 Bases da DCT 8x8", y=0.92, fontsize=12, fontweight='bold')plt.show()
Figura 5.21: O Alfabeto Visual do JPEG: As 64 funções de base da DCT-II. O coeficiente DC fica no topo esquerdo (suave). Ao descer e avançar à direita, a oscilação espacial aumenta drasticamente.
5.7.3 Quantização: a principal fonte de compressão
Após a DCT, cada coeficiente \(C(u,v)\) é dividido por um valor da tabela de quantização\(Q(u,v)\) e arredondado para o inteiro mais próximo. Coeficientes de alta frequência — menos perceptíveis ao SVH — recebem valores altos em \(Q\), forçando o resultado ao arredondamento para zero. Longas sequências de zeros são então comprimidas eficientemente pela codificação entrópica.
Quando o fator de qualidade é baixo, o descarte de altas frequências é agressivo: o bloco \(8 \times 8\) é reconstruído com poucos coeficientes, o que produz os característicos artefatos de bloco visíveis nas fronteiras entre blocos adjacentes.
from scipy.fft import dct, idctdef dct2(bloco):"""DCT-II 2D ortogonal (separável)."""return dct(dct(bloco.T, norm='ortho').T, norm='ortho')def idct2(coefs):"""IDCT-II 2D ortogonal."""return idct(idct(coefs.T, norm='ortho').T, norm='ortho')# ── Bloco 8×8 centralizado da imagem ─────────────────────────────────────────cy, cx = img_gray.shape[0]//2, img_gray.shape[1]//2bloco = img_gray[cy:cy+8, cx:cx+8].astype(np.float64) -128.0C = dct2(bloco)print("Coeficientes DCT do bloco 8×8:")print(np.round(C).astype(int))print(f"\nEnergia DC : {C[0,0]**2:.1f}")print(f"Energia total : {(C**2).sum():.1f}")print(f"Fração no DC : {C[0,0]**2/ (C**2).sum():.1%} ← concentração de energia")# ── Reconstrução progressiva ──────────────────────────────────────────────────imgs_rec = [cv2.normalize((bloco+128).astype(np.uint8), None, 0, 255, cv2.NORM_MINMAX)]titles_rec = ["Bloco original\n(8×8 pixels)"]for keep in [1, 4, 10, 20, 40, 64]: C_trunc = np.zeros_like(C) indices =sorted([(u,v) for u inrange(8) for v inrange(8)], key=lambda p: p[0]+p[1])for u, v in indices[:keep]: C_trunc[u, v] = C[u, v] rec = np.clip(idct2(C_trunc) +128, 0, 255).astype(np.uint8) imgs_rec.append(rec) titles_rec.append(f"{keep} coef.\n({keep/64:.0%} do total)")mm.show(imgs_rec, titles=titles_rec, cols=4, figsize=(12, 7))
RGB → YCbCr: separa luminância (Y) de crominância (Cb, Cr)
O SVH é ~4× mais sensível a variações de luminância do que de cor
2
Sub-amostragem 4:2:0: Cb e Cr reduzidos à metade da resolução
Elimina ~50% dos dados de cor com impacto visual mínimo
3–4
Blocos 8×8 centrados em zero e transformados pela DCT
Concentra energia nos primeiros coeficientes
5
Divisão por \(Q(u,v)\) e arredondamento: \(\tilde{C}(u,v) = \text{round}[C(u,v)/Q(u,v)]\)
Zeros em altas frequências → principal fonte de compressão
6
Varredura zigzag + RLE + Huffman
Comprime longas sequências de zeros resultantes da quantização
A tabela de quantização\(Q\) é o parâmetro central do compromisso qualidade-compressão: o fator de qualidade JPEG (1–100) escala \(Q\) globalmente — valores altos preservam mais coeficientes; valores baixos geram mais zeros e maiores artefatos.
Por que o Ziguezague? A DCT acumula a energia vital no canto superior esquerdo (baixas frequências) e empurra os zeros para a direita e para baixo. Ler a matriz na diagonal (ziguezague) cria uma “corrida de zeros” contínua no final do vetor, permitindo que o algoritmo simplesmente anote “e os próximos 45 elementos são zero”, comprimindo o dado violentamente (Run-Length Encoding).
# ── Tabela de quantização luminância (padrão JPEG) ────────────────────────────Q_luma = np.array([ [16,11,10,16,24,40,51,61], [12,12,14,19,26,58,60,55], [14,13,16,24,40,57,69,56], [14,17,22,29,51,87,80,62], [18,22,37,56,68,109,103,77], [24,35,55,64,81,104,113,92], [49,64,78,87,103,121,120,101], [72,92,95,98,112,100,103,99]], dtype=np.float64)def jpeg_compress_block(bloco, Q_table):"""DCT → quantização → dequantização → IDCT em bloco 8×8.""" C = dct2(bloco.astype(np.float64) -128) Cq = np.round(C / Q_table) * Q_table # quantiza e dequantizareturn np.clip(idct2(Cq) +128, 0, 255)def jpeg_quality_compress(img, qualidade=50):"""JPEG simplificado: comprime imagem inteira por blocos 8×8."""# Fator de escala: qualidade 50 = sem escala; >50 = maior qualidadeif qualidade <50: escala =5000/ qualidadeelse: escala =200-2* qualidade Q = np.clip(np.round(Q_luma * escala /100), 1, 255) h, w = img.shape result = np.zeros_like(img, dtype=np.float64)for r inrange(0, h-7, 8):for c inrange(0, w-7, 8): result[r:r+8, c:c+8] = jpeg_compress_block(img[r:r+8, c:c+8], Q)return result.astype(np.uint8)# ── Comparação de fatores de qualidade ───────────────────────────────────────qualidades = [10, 25, 50, 75, 90]imgs_jpeg = [img_gray]titles_jpeg = ["Original"]for q in qualidades: rec = jpeg_quality_compress(img_gray, qualidade=q) psnr = cv2.PSNR(img_gray, rec) imgs_jpeg .append(rec) titles_jpeg.append(f"Q={q} PSNR={psnr:.1f}dB")mm.show(imgs_jpeg, titles=titles_jpeg, cols=3, figsize=(14, 10))
Figura 5.23: Pipeline JPEG simplificado: DCT em blocos 8×8, quantização com diferentes fatores de qualidade e reconstrução via IDCT. O artefato de blocos torna-se visível para qualidades baixas (Q=10–20).
5.7.5 Simulador Interativo: Quantização DCT
O simulador abaixo permite explorar o efeito da quantização sobre um bloco \(8 \times 8\) real, visualizando em tempo real:
os coeficientes DCT (mapa de calor — quanto maior, mais quente);
os coeficientes quantizados (observe os zeros aparecerem com qualidade baixa);
Figura 5.24: Simulador interativo de compressão DCT-JPEG: ajuste o fator de qualidade e visualize em tempo real os coeficientes zerados, o bloco reconstruído e o erro de quantização.
5.8 Comparação de Formatos de Imagem
A escolha do formato de arquivo impacta diretamente o compromisso entre qualidade, tamanho e velocidade de decodificação. Os três formatos mais relevantes para aplicações web e computação visual são JPEG, PNG e WebP.
5.8.1 Características dos Formatos
Tabela 5.3: Comparação entre os principais formatos de imagem rasterizados.
Característica
JPEG
PNG
WebP
Compressão
Com perda
Sem perda
Com e sem perda
Transparência (alfa)
✗
✓
✓
Suporte a animação
✗
Limitado
✓
Algoritmo base
DCT + Huffman
DEFLATE (LZ77 + Huffman)
VP8 / VP8L
Melhor para
Fotografia
Gráficos, texto, ícones
Web (substituto universal)
Pior para
Texto, bordas nítidas
Fotos de alta resolução
Compatibilidade legada
5.8.2 Métricas de Qualidade
Duas métricas objetivas são amplamente usadas para avaliar a qualidade de imagens comprimidas:
onde \(L = 255\) para imagens de 8 bits e MSE é o erro quadrático médio. Valores típicos: PSNR > 40 dB indica qualidade excelente; 30–40 dB, qualidade boa; < 30 dB, degradação visível.
O SSIM combina três fatores medidos em janelas locais: luminância (\(\mu_f, \mu_g\)), contraste (\(\sigma_f, \sigma_g\)) e estrutura (\(\sigma_{fg}\)), ponderados por constantes de estabilidade \(c_1, c_2\). O resultado varia em \([-1, 1]\), com 1 indicando identidade perfeita. Diferentemente do PSNR, o SSIM é sensível à organização espacial dos erros, sendo mais alinhado com a percepção humana — consulte Gonzalez; Woods (2018) para a formulação completa.
NotaPSNR vs SSIM: qual usar?
O PSNR é simples e rápido, mas pode superestimar a qualidade em imagens com artefatos localizados (blocos JPEG) ou subestimá-la quando o ruído é perceptualmente irrelevante (diferença de brilho global). O SSIM captura melhor a percepção humana, mas é mais caro computacionalmente.
Para avaliação rigorosa de algoritmos de compressão, recomenda-se reportar ambas as métricas, além de avaliação subjetiva em estudos perceptuais.
5.8.3 Inspeção Visual: Qualidade não é só PSNR
A filosofia de compressão dita o tipo de degradação. O JPEG corta a imagem em quadrados (DCT), gerando “Mosaicos”. Formatos baseados em wavelets ou filtros preditivos modernos (como WebP/JPEG2000) evitam blocos, mas sofrem de perda de textura e “derretimento” (borramento).
# Recorte de área de alto contraste das imagens já comprimidas no loop anterior (Dando zoom de 4x)zoom_original = cv2.resize(img_gray[120:200, 150:230], (320, 320), interpolation=cv2.INTER_NEAREST)rec_jpeg = cv2.imread("imagens/comp_test/coins_q10.jpg", 0)zoom_jpeg = cv2.resize(rec_jpeg[120:200, 150:230], (320, 320), interpolation=cv2.INTER_NEAREST)# WebP Q=10cv2.imwrite("imagens/comp_test/coins_q10.webp", img_gray, [cv2.IMWRITE_WEBP_QUALITY, 10])rec_webp = cv2.imread("imagens/comp_test/coins_q10.webp", 0)zoom_webp = cv2.resize(rec_webp[120:200, 150:230], (320, 320), interpolation=cv2.INTER_NEAREST)mm.show([zoom_original, zoom_jpeg, zoom_webp], titles=["Zoom Original", "JPEG Q=10 (DCT Blocos)", "WebP Q=10 (Suavização)"], cols=3, figsize=(14, 5))
Figura 5.25: Forte compressão (Q=10). À esquerda o artefato de bloco clássico da DCT. À direita, a suavização de bordas característica do WebP.
os.makedirs("imagens/comp_test", exist_ok=True)resultados = []# ── JPEG ──────────────────────────────────────────────────────────────────────for q in [10, 20, 30, 40, 50, 60, 70, 80, 90, 95]: path =f"imagens/comp_test/coins_q{q}.jpg" cv2.imwrite(path, img_gray, [cv2.IMWRITE_JPEG_QUALITY, q]) rec = cv2.imread(path, cv2.IMREAD_GRAYSCALE) resultados.append({"formato": "JPEG", "qualidade": q,"PSNR": cv2.PSNR(img_gray, rec),"KB": os.path.getsize(path)/1024})# ── PNG ───────────────────────────────────────────────────────────────────────path_png ="imagens/comp_test/coins.png"cv2.imwrite(path_png, img_gray, [cv2.IMWRITE_PNG_COMPRESSION, 9])resultados.append({"formato": "PNG", "qualidade": "lossless","PSNR": float('inf'), "KB": os.path.getsize(path_png)/1024})# ── WebP ──────────────────────────────────────────────────────────────────────for q in [50, 75, 90]: path_w =f"imagens/comp_test/coins_q{q}.webp" cv2.imwrite(path_w, img_gray, [cv2.IMWRITE_WEBP_QUALITY, q]) rec_w = cv2.imread(path_w, cv2.IMREAD_GRAYSCALE) resultados.append({"formato": "WebP", "qualidade": q,"PSNR": cv2.PSNR(img_gray, rec_w),"KB": os.path.getsize(path_w)/1024})# ── Curva taxa-distorção ──────────────────────────────────────────────────────jpeg_r = [r for r in resultados if r["formato"]=="JPEG"]webp_r = [r for r in resultados if r["formato"]=="WebP"]png_r = [r for r in resultados if r["formato"]=="PNG"]fig, ax = plt.subplots(figsize=(8, 4.5))ax.plot([r["KB"] for r in jpeg_r], [r["PSNR"] for r in jpeg_r],"o-", label="JPEG", color="#D85A30", lw=2, ms=5)ax.plot([r["KB"] for r in webp_r], [r["PSNR"] for r in webp_r],"s-", label="WebP", color="#534AB7", lw=2, ms=5)ax.axhline(50, color="#1D9E75", lw=2, ls="--", label=f"PNG sem perda ({png_r[0]['KB']:.0f} KB)")ax.axhspan(40, 60, alpha=0.05, color="#1D9E75", label="Qualidade excelente (PSNR>40)")ax.set(xlabel="Tamanho do arquivo (KB)", ylabel="PSNR (dB)", title="Curva Taxa-Distorção: JPEG × WebP × PNG")ax.legend(fontsize=9); ax.grid(True, alpha=0.3); plt.tight_layout()plt.show()print(f"\nTamanho bruto (sem compressão): {img_gray.nbytes/1024:.0f} KB")print(f"\n{'Formato':>8}{'Qual.':>6}{'KB':>7}{'PSNR (dB)':>11}")print("-"*38)for r in resultados: psnr_s =f"{r['PSNR']:>11.2f}"if r['PSNR']!=float('inf') elsef"{'∞ (lossless)':>11}"print(f"{r['formato']:>8}{str(r['qualidade']):>6}{r['KB']:>7.1f}{psnr_s}")
Figura 5.26: Curva taxa-distorção: PSNR vs tamanho de arquivo para JPEG, WebP e PNG.
Figura 5.27: Mapas de erro JPEG em diferentes qualidades: artefatos de bloco nas bordas.
📌 Síntese — Compressão JPEG
Etapa
O que faz
Ganho
YCbCr
Separa luminância de crominância
Modela percepção humana
Subamostragem 4:2:0
Reduz Cb/Cr à metade
~50% de dados a menos
DCT 8×8
Concentra energia nos primeiros coeficientes
Alta compactação
Quantização
Zera coeficientes imperceptíveis
Principal fonte de compressão
Huffman
Codifica estatisticamente os zeros
~30–50% adicional
Artefatos típicos de JPEG: - Artefato de bloco (qualidades baixas): fronteiras 8×8 visíveis; - Ringing (qualidades muito baixas): oscilações ao redor de bordas nítidas; - Perda de textura (qualidades baixas): regiões com textura fina ficam “plásticas”.
5.9 Aplicação Prática: Remoção de Ruído por Filtragem Espectral
Reunindo as técnicas do capítulo, apresenta-se um pipeline completo de remoção de ruído que combina análise no domínio da frequência com filtragem adaptativa. O objetivo é remover um ruído misto (Gaussiano + periódico) preservando ao máximo a estrutura original da imagem.
O par de métricas PSNR / SSIM fornece uma avaliação complementar: - PSNR penaliza uniformemente todos os erros pixel a pixel. - SSIM avalia a preservação de estruturas perceptualmente relevantes.
Um bom filtro maximiza ambos, mas na prática existe um compromisso: filtros muito agressivos reduzem o ruído de alta frequência mas destroem bordas e texturas, o que pode melhorar o PSNR em relação à imagem ruidosa mas reduz o PSNR em relação à imagem original e degrada o SSIM.
5.10 Resumo do Capítulo
A transição do domínio espacial para o domínio da frequência revela a distribuição de energia da imagem, estabelecendo a base matemática para filtragem avançada e compressão de dados. Ver um mapa conceitual do domínio da frequência na Figura 5.28.
%%{init: {
"theme": "default",
"flowchart": {
"useMaxWidth": false
},
"themeVariables": {
"fontSize": "9px"
}
}}%%
flowchart LR
A["f(x,y)<br/>Domínio Espacial"] <--> B["DFT / IDFT"]
B --> C["F(u,v)"]
D["|F(u,v)|<br/>Magnitude<br/>Energia por frequência"]
E["φ(u,v)<br/>Fase<br/>Estrutura e posição"]
C --> D
C --> E
A -- "Convolução * h" --> F["Multiplicação · H(u,v)"]
style D fill:#dbeafe,stroke:#2563eb
style E fill:#fef3c7,stroke:#d97706
Figura 5.28: Mapa conceitual do domínio da frequência.
🔑 Fundamentos Essenciais
DFT e Percepção Visual: O espectro decompõe a imagem em ondas. A fase carrega a inteligibilidade da forma e os contornos; a magnitude dita apenas a distribuição de contraste.
Eficiência Algorítmica: O Teorema da Convolução permite que filtros de grande escala sejam aplicados via FFT, despencando a complexidade de \(O(N^2 K^2)\) para \(O(N^2 \log N)\).
O Artefato do Ringing: Cortes abruptos de frequência (Filtro Ideal) geram ondulações indesejadas no espaço. Filtros Gaussianos e Butterworth garantem transições suaves.
A Era das Wavelets: Superando a análise global de Fourier, as Wavelets capturam frequência e localização espacial simultaneamente (multirresolução), fundamentando o JPEG 2000 e a arquitetura das CNNs.
Compressão DCT (JPEG): O algoritmo explora a cegueira humana a altas frequências espaciais. A DCT compacta a energia visual em blocos de \(8 \times 8\), quantizando e descartando detalhes finos (o que gera artefatos de bloco em qualidades baixas).
Próximos Passos: O Capítulo 6 inicia a Parte II da obra, estendendo estas ferramentas aos Espaços de Cor e inaugurando os conceitos de Visão Computacional.
5.11 🤖 Uso do NotebookLM como Tutor Complementar
Nesta edição, incentiva-se o uso do NotebookLM como ferramenta complementar de aprendizagem. Baseado em inteligência artificial, o sistema utiliza exclusivamente os documentos fornecidos pelo autor como fonte de conhecimento, produzindo respostas alinhadas ao conteúdo e à abordagem adotada ao longo do livro.
(10%) Implemente a DFT 2D manualmente (sem np.fft.fft2) usando a definição da Equação 5.1 para uma imagem \(16 \times 16\). Compare com np.fft.fft2 e verifique que as diferenças absolutas são menores que \(10^{-8}\). Meça o tempo de execução de ambas as implementações e explique a diferença.
(15%) Adicione ruído senoidal com frequências \((u_0, v_0) \in \{(5,10), (20,5), (30,30)\}\) à imagem de moedas. Para cada frequência, projete um filtro notch e remova o ruído. Avalie quantitativamente a restauração com PSNR e SSIM. Discuta o compromisso entre remoção de ruído e preservação de detalhes.
(15%) Compare os filtros passa-baixa Ideal, Gaussiano e Butterworth (ordens \(n = 1, 2, 4\)) com \(D_0 = 20, 40, 60\) pixels. Para cada combinação, calcule o PSNR e o SSIM da imagem filtrada em relação à original. Apresente os resultados em uma tabela e plote as curvas de resposta em frequência \(H(u, 0)\).
(15%) Implemente a decomposição wavelet 2D manualmente usando a wavelet Haar: calcule os filtros \(h\) (passa-baixa) e \(g\) (passa-alta) de Haar, aplique-os por separabilidade em linhas e colunas, e subamostreie por 2. Compare o resultado com pywt.dwt2(img, 'haar') e verifique a equivalência numérica.
(15%) Aplique o limiamento de coeficientes wavelet (wavelet thresholding) com limiares \(T \in \{5, 10, 20, 40, 80\}\) e wavelets Haar, db4 e sym4. Para cada combinação, calcule PSNR e SSIM após reconstrução com pywt.waverec2. Identifique a combinação wavelet × limiar que maximiza o SSIM.
(15%) Implemente o pipeline JPEG completo incluindo: conversão RGB → YCbCr, subamostragem 4:2:0 de Cb/Cr, DCT em blocos \(8 \times 8\), quantização com tabela padrão escalada por fator de qualidade, e reconstrução com IDCT. Compare os resultados com cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, q]) para qualidades 20, 50 e 80.
(15%) Crie uma imagem sintética composta por três regiões: fotografia de textura natural (canto superior esquerdo), região de texto nítido (centro) e região de gradiente suave (canto inferior direito). Compare JPEG, PNG e WebP para essa imagem mista em termos de PSNR, SSIM e tamanho de arquivo. Justifique qual formato é mais adequado considerando o conteúdo heterogêneo.
Referências do Capítulo
A fundamentação teórica deste capítulo baseia-se nas seguintes obras:
Gonzalez; Woods (2018) para os conceitos de DFT 2D, filtros no domínio da frequência, DCT e fundamentos de compressão de imagens.
Oppenheim (1999) para a teoria de sinais e sistemas no domínio discreto, incluindo o Teorema da Convolução e propriedades da DFT.
Mallat (1999) para a teoria de wavelets, análise multirresolução e bancos de filtros.
Wallace (1991) para o padrão de compressão JPEG original e o fundamento da quantização DCT.
Szeliski (2022) para métricas de qualidade (PSNR, SSIM) e comparação de formatos de imagem modernos.
GONZALEZ, R. C.; WOODS, R. E. Digital Image Processing. 4th. ed. New York: Pearson, 2018.
MALLAT, Stéphane. A wavelet tour of signal processing. [S.l.]: Elsevier, 1999.
OPPENHEIM, Alan V. Discrete-time signal processing. [S.l.]: Pearson Education India, 1999.