SSIM e VMAF: medindo qualidade como o olho humano de verdade
Olá leitor, seja muito bem vindo a mais um conteúdo aqui no Portal da Micilini! 😊
Este é um artigo extra da nossa série de CODECs. Ele não tem um número fixo na sequência, mas encaixa perfeitamente logo depois do artigo 10 (Quantização), onde a gente falou sobre o PSNR como métrica de qualidade. 📐
E olha, eu preciso ser honesto com você: lá no artigo 10, eu meio que soltei um spoiler. 😅
Falei que "existem métricas mais modernas como SSIM e VMAF" e deixei no ar. Pois é, chegou a hora de pagar essa dívida. Porque pro nosso CODEC, a gente não quer só o "suficiente", a gente quer o que existe de melhor no mundo pra medir qualidade de imagem. 🏆
Então pega o café ☕ e vem entender por que o PSNR, apesar de útil, não é confiável sozinho, e como o SSIM e o VMAF resolvem isso. 🚀
O problema fundamental do PSNR
Vamos relembrar o que o PSNR faz. Ele mede o erro matemático pixel a pixel entre a imagem original e a comprimida:
MSE = média de (pixel_original - pixel_reconstruído)²
PSNR = 10 × log₁₀( 255² / MSE )O problema? Ele trata todos os erros como iguais. Pra ele, um erro de 10 níveis de brilho num pixel é sempre 10, não importa onde esse erro aconteça nem que tipo de erro seja.
Só que o olho humano não funciona assim. Vou te dar dois exemplos que quebram o PSNR:
Exemplo 1: deslocamento. Imagina que você pega uma imagem e desloca ela 1 pixel pra direita. Visualmente, é idêntica — ninguém percebe. Mas o PSNR despenca, porque agora quase todo pixel está "no lugar errado" quando comparado casa a casa. 😵
Exemplo 2: onde o erro acontece. Um errinho numa área de textura caótica (grama, cabelo) é invisível pro olho. O mesmo errinho numa área lisa (céu, parede) salta aos olhos na hora. O PSNR dá o mesmo peso pros dois. ⚖️❌
Ou seja: duas imagens com o mesmo PSNR podem parecer completamente diferentes pro olho humano. E é aí que entram as métricas perceptuais. 🧠
SSIM: comparando estrutura, não pixels soltos
O SSIM (Structural Similarity Index — Índice de Similaridade Estrutural) nasceu em 2004 de uma sacada genial: o olho humano não percebe imagem pixel por pixel, ele percebe estruturas (bordas, texturas, padrões e contornos). 🖼️
Então, em vez de perguntar "quão diferente é cada pixel?", o SSIM pergunta "quão parecida é a estrutura das duas imagens?". Pra isso, ele compara três coisas separadamente:
- ☀️ Luminância (brilho médio): as duas regiões têm o mesmo nível de claro/escuro?
- 🌗 Contraste (variação): as duas regiões têm a mesma "amplitude" de variação?
- 🧩 Estrutura (correlação): os padrões variam juntos, na mesma direção?
E o mais importante: essa comparação não é feita na imagem inteira de uma vez. Ela é feita numa janelinha deslizante (tipicamente 11×11 pixels com peso gaussiano) que percorre a imagem toda. No final, você tira a média de todas as janelas, o que chamamos de MSSIM (Mean SSIM).
Isso faz a métrica ser local, do jeito que o olho realmente é.
A fórmula do SSIM
Não fuja, ela é mais amigável do que parece:
SSIM(x,y) = [ (2·μx·μy + C1) · (2·σxy + C2) ]
─────────────────────────────────────
[ (μx² + μy² + C1) · (σx² + σy² + C2) ]
onde:
μx, μy = média (brilho) de cada janela
σx², σy² = variância (contraste) de cada janela
σxy = covariância entre as duas janelas (a estrutura!)
C1, C2 = constantes pequenas pra evitar divisão por zeroAs constantes são padronizadas assim:
C1 = (0.01 × 255)² = 6.5025
C2 = (0.03 × 255)² = 58.5225O que você precisa levar deste bloco de matemática:
- O termo
2·μx·μycompara brilho. ☀️ - O termo com
eσx²compara contraste. 🌗σy² - O termo
(a covariância) é a alma da coisa: mede se as duas imagens variam juntas. Se onde uma sobe a outra sobe, e onde uma desce a outra desce, a estrutura é preservada. 🧩σxy
E a melhor parte: o resultado é um número de -1 a 1 que é super fácil de se ler.
Como ler o SSIM
Sua leitura se dá de forma super fácil, vejamos:
- SSIM = 1.0 → imagens idênticas. Perfeição. 🤩
- SSIM > 0.95 → diferença praticamente imperceptível. 😍
- SSIM entre 0.90 e 0.95 → boa qualidade, perdas leves. 🙂
- SSIM entre 0.80 e 0.90 → perdas visíveis, começa a incomodar. 😕
- SSIM < 0.80 → degradação clara. 😬
Diferente do PSNR (que vai de 0 a infinito em dB e é meio abstrato), o SSIM tem uma escala limitada e intuitiva. "0.96" já te diz na hora que a imagem está muito boa.
SSIM na prática: o código em C
Vamos implementar o SSIM do zero e rodar ele nos mesmos blocos do artigo 10, pra você ver como ele se compara ao PSNR com números reais.
Pra manter o exemplo direto ao ponto, vou calcular o SSIM global (uma janela só, cobrindo o bloco 8×8 inteiro). Numa imagem real, você deslizaria a janela 11×11 e tiraria a média, mas o cálculo interno de cada janela é exatamente este:
#include <stdio.h>
#include <math.h>
#define BLOCK_SIZE 8
// ============================================================
// SSIM entre dois blocos (janela única, global)
// ============================================================
//
// Compara luminância (média), contraste (variância) e
// estrutura (covariância) entre os blocos x e y.
// Retorna um valor de -1 a 1 (1 = idênticos).
double calcular_ssim(double x[BLOCK_SIZE][BLOCK_SIZE],
double y[BLOCK_SIZE][BLOCK_SIZE]) {
// Constantes padrão do SSIM (baseadas em L=255)
double C1 = 6.5025; // (0.01 * 255)^2
double C2 = 58.5225; // (0.03 * 255)^2
int n = BLOCK_SIZE * BLOCK_SIZE;
// Passo 1: médias (μx, μy) — o "brilho" de cada bloco
double mx = 0.0, my = 0.0;
for (int r = 0; r < BLOCK_SIZE; r++)
for (int c = 0; c < BLOCK_SIZE; c++) {
mx += x[r][c];
my += y[r][c];
}
mx /= n;
my /= n;
// Passo 2: variâncias (σx², σy²) e covariância (σxy)
double vx = 0.0, vy = 0.0, cxy = 0.0;
for (int r = 0; r < BLOCK_SIZE; r++)
for (int c = 0; c < BLOCK_SIZE; c++) {
double dx = x[r][c] - mx;
double dy = y[r][c] - my;
vx += dx * dx; // variância de x (contraste)
vy += dy * dy; // variância de y (contraste)
cxy += dx * dy; // covariância (estrutura!)
}
vx /= (n - 1);
vy /= (n - 1);
cxy /= (n - 1);
// Passo 3: a fórmula do SSIM
double numerador = (2*mx*my + C1) * (2*cxy + C2);
double denominador = (mx*mx + my*my + C1) * (vx + vy + C2);
return numerador / denominador;
}
int main() {
// Bloco original (o clássico do artigo 9/10)
double original[BLOCK_SIZE][BLOCK_SIZE] = {
{52, 55, 61, 66, 70, 61, 64, 73},
{63, 59, 55, 90,109, 85, 69, 72},
{62, 59, 68,113,144,104, 66, 73},
{63, 58, 71,122,154,106, 70, 69},
{67, 61, 68,104,126, 88, 68, 70},
{79, 65, 60, 70, 77, 68, 58, 75},
{85, 71, 64, 59, 55, 61, 65, 83},
{87, 79, 69, 68, 65, 76, 78, 94}
};
// SSIM de um bloco com ele mesmo = 1.0 (prova que a fórmula está certa)
printf("SSIM (bloco vs ele mesmo) = %.4f\n", calcular_ssim(original, original));
return 0;
}Pra compilar:
gcc ssim.c -o ssim -lm
./ssimSSIM vs PSNR: os números lado a lado
No meu caso, vamos validar o PSNR nos três níveis de qualidade do artigo 10 (o bloco passa por DCT → quantização → dequantização → IDCT, e comparamos com o original).
Estes são os resultados reais (validados rodando 😉):
SSIM (bloco vs ele mesmo) = 1.0000
Qualidade 90 -> PSNR: 40.10 dB | SSIM: 0.9933
Qualidade 50 -> PSNR: 32.60 dB | SSIM: 0.9629
Qualidade 10 -> PSNR: 25.69 dB | SSIM: 0.8113Olha como o SSIM conta a história de um jeito mais intuitivo:
- Qualidade 90: SSIM
— quase 1, imagem excelente. 💎0.9933 - Qualidade 50: SSIM
— ainda muito bom, perdas discretas. 🙂0.9629 - Qualidade 10: SSIM
— caiu bem, degradação visível. 😬0.8113
Repara que enquanto o PSNR fala em "40 dB, 32 dB, 25 dB" (uma escala que você precisa ter experiência pra interpretar), o SSIM fala em "0.99, 0.96, 0.81" — e qualquer um entende na hora que 0.81 é bem pior que 0.99.
VMAF: quando a Netflix entra em campo
Se o SSIM é bom, o VMAF é o estado da arte. E ele tem uma história bacana. 📺
VMAF significa Video Multi-Method Assessment Fusion (Fusão de Avaliação Multi-Método de Vídeo).
Foi criado pela Netflix (em parceria com a Universidade de Austin) por uma necessidade bem prática: a Netflix precisava decidir, pra cada filme e cada velocidade de internet, qual nível de compressão usar, sem estragar a experiência de milhões de assinantes. 🌍
O PSNR e o SSIM não eram bons o suficiente pra essa decisão de bilhões de dólares. Então eles construíram algo novo. 💡
Mas dai você pode estar se perguntando: "O que torna o VMAF diferente?".
A sacada do VMAF é que ele não é uma fórmula única. Ele é um modelo de machine learning que combina (faz a "fusão" de) várias métricas diferentes, cada uma boa em capturar um tipo de degradação:
- 🔬 VIF (Visual Information Fidelity): mede quanta informação visual foi preservada, em várias escalas.
- 📐 DLM (Detail Loss Metric): mede especificamente a perda de detalhe e o quanto isso incomoda.
- 🎞️ Motion: mede o movimento entre quadros (isso é específico de vídeo — imagem parada não tem).
Essas métricas viram "ingredientes" que alimentam um modelo de ML (originalmente um SVM — Support Vector Machine).
E aqui está o pulo do gato:
O modelo do VMAF foi treinado com notas dadas por humanos reais assistindo a vídeos degradados. Ou seja, ele aprendeu a prever o que uma pessoa de verdade acharia da qualidade.Por isso o VMAF é considerado a métrica que mais se aproxima da percepção humana real. Ele não está medindo matemática, está prevendo a nota que você daria.
O resultado do VMAF é um número de 0 a 100, onde 100 é qualidade perfeita e valores acima de ~93 são geralmente indistinguíveis do original pro olho humano. 👁️
Como usar o VMAF de verdade
Aqui eu preciso ser 100% honesto com você...
Diferente do PSNR e do SSIM, que a gente implementou do zero em pouquíssimas linhas, o VMAF não é algo que você reimplementa numa tarde.
Ele depende de um modelo de ML pré-treinado (arquivos de milhares de parâmetros) e de várias métricas complexas por baixo. Tentar recriar isso do zero seria reinventar a roda de um jeito pior.
A boa notícia: a Netflix abriu o código do VMAF (a ), e ela já vem integrada no FFmpeg. Então, na prática, você usa assim: libvmaf
ffmpeg -i original.mp4 -i comprimido.mp4 -lavfi libvmaf -f null -
// ou pra imagens/frames:
ffmpeg -i original.png -i comprimido.png -lavfi libvmaf -f null -E ele te devolve o score VMAF direto. Pro nosso CODEC, é exatamente assim que vamos avaliar: geramos a imagem comprimida com o nosso CODEC, e rodamos o VMAF (via libvmaf) contra o original pra ter uma nota que reflete a percepção humana.
O quadro comparativo: PSNR vs SSIM vs VMAF
Deixa eu resumir os três num comparativo direto:
┌──────────┬─────────────┬──────────────┬─────────────────┬──────────────┐
│ Métrica │ Escala │ O que mede │ Perceptual? │ Complexidade │
├──────────┼─────────────┼──────────────┼─────────────────┼──────────────┤
│ PSNR │ 0 a ∞ (dB) │ Erro pixel │ ❌ Não │ Trivial │
│ │ │ a pixel │ │ (fórmula) │
├──────────┼─────────────┼──────────────┼─────────────────┼──────────────┤
│ SSIM │ -1 a 1 │ Estrutura │ ⚠️ Parcialmente │ Simples │
│ │ │ local │ │ (fórmula) │
├──────────┼─────────────┼──────────────┼─────────────────┼──────────────┤
│ VMAF │ 0 a 100 │ Percepção │ ✅ Sim (treinado│ Alta │
│ │ │ humana │ com humanos) │ (modelo ML) │
└──────────┴─────────────┴──────────────┴─────────────────┴──────────────┘Nenhum deles é "o único certo". Cada um tem seu papel.
Porque o nosso CODEC vai usar os 3?
Aqui está a decisão de arquitetura pro nosso CODEC, e o motivo de cada escolha:
- 📐 PSNR — é a nossa bússola rápida. Trivial de calcular, ótimo pra um sanity check durante o desenvolvimento ("será que quebrei alguma coisa?"). Se o PSNR despencou de repente, tem bug.
- 🏗️ SSIM — é a nossa régua estrutural. Fácil de implementar, roda rápido, e já captura muito melhor o que o olho percebe. Vai ser o nosso indicador do dia a dia de "a qualidade está boa?".
- 🎬 VMAF — é o nosso juiz final. Quando a gente for comparar o nosso CODEC contra JPEG, PNG e WebP (lá no artigo 17, o benchmark 💪), o VMAF vai dar a palavra final sobre qualidade percebida, porque é a métrica que mais se aproxima de um humano de verdade avaliando.
Por que os três em vez de só um? Porque medir qualidade é difícil, e cada métrica tem um ponto cego.
Usando os três juntos, a gente cobre os pontos cegos uns dos outros. É a diferença entre um CODEC "amador" (que confia cegamente num único número) e um CODEC feito com rigor de verdade. 🎯
E como eu tinha dito anteriormente... a gente quer tudo do bom e do melhor pro nosso CODEC!
Resumo
- 🔴 O PSNR mede erro matemático pixel a pixel e trata todos os erros como iguais — o que não reflete como o olho funciona.
- 🏗️ O SSIM compara estrutura (brilho, contraste e correlação) em janelas locais, resultando num valor de -1 a 1 muito mais intuitivo e perceptual.
- 🤖 O VMAF é um modelo de machine learning da Netflix que funde várias métricas (VIF, DLM, motion) e foi treinado com notas de humanos reais, e é o mais próximo da percepção humana, numa escala de 0 a 100.
- ⚙️ PSNR e SSIM a gente implementa do zero; VMAF a gente usa via libvmaf/FFmpeg (não vale a pena reimplementar um modelo de ML pré-treinado).
- 🏆 O nosso CODEC vai usar os três: PSNR como sanity check rápido, SSIM como régua estrutural do dia a dia, e VMAF como juiz final no benchmark. Cada um cobre o ponto cego do outro.
Agora sim estamos armados com as ferramentas certas pra avaliar o nosso CODEC com o rigor que ele merece. 🛠️
Voltando pra série principal: no próximo artigo da sequência, retomamos de onde o artigo 10 parou — a varredura em zig-zag e o RLE, transformando aquele mar de zeros da quantização em bytes de verdade num arquivo.
Até lá!

