Espaços de Cor: RGB, YCbCr e por que separar luminância de crominância
Olá leitor, seja muito bem vindo de volta a mais uma etapa da nossa jornada aqui no Portal da Micilini 😊
Se você está chegando agora, saiba que este é o segundo artigo de uma série de mais ou menos 20 artigos, onde estamos construindo todo o conhecimento teórico necessário para, no final, criar o nosso próprio CODEC de imagens do zero, 100% autoral.
No artigo anterior, a gente mergulhou fundo no mundo das cores, dos pixels e do formato RGB24.
Aprendemos que cor é uma percepção criada pelo trio luz + matéria + cérebro, que nossos olhos possuem 3 tipos de cones (R, G, B), e que uma imagem crua nada mais é do que uma sequência de bytes organizados em trios de valores.
Se você ainda não leu o artigo anterior, recomendo fortemente que leia antes de continuar, porque tudo o que vamos ver aqui depende daqueles fundamentos.
Dito isso, hoje vamos dar o próximo passo e responder uma pergunta que talvez você nem saiba que precisa fazer:
Se o RGB já representa todas as cores que enxergamos, por que raios os CODECs não trabalham direto com RGB? 🤔
A resposta curta é: porque existe um jeito muito mais inteligente de representar cores quando o objetivo é comprimir dados. E esse jeito se chama YCbCr.
Mas calma, antes de chegar lá, a gente precisa entender o que é um espaço de cor, por que existem tantos deles, e o que exatamente muda quando a gente troca de um espaço pra outro.
Então pega seu café ☕, sente-se confortávelmente, e vamos nessa!
O que é um Espaço de Cor?
Antes de sairmos falando de YCbCr, luminância, crominância e essas palavras bonitas, precisamos dar um passo atrás e entender um conceito mais fundamental: o que é um espaço de cor?
Pense assim: no artigo anterior, a gente aprendeu que qualquer cor visível pode ser representada por 3 números no modelo RGB.
Por exemplo, o amarelo é (255, 255, 0), o ciano é (0, 255, 255), e por aí vai...
Mas essas representações são apenas uma forma de descrever uma cor, não sendo às únicas.
Imagina a seguinte situação: você está num restaurante com um amigo estrangeiro. Você aponta pro prato e diz:
- "Isso é uma coxinha."
Seu amigo americano olha pro mesmo prato e diz:
- "That's a chicken croquette."
Os dois estão falando da mesma coisa, do mesmo prato, da mesma coxinha gordurosa e deliciosa. Mas cada um está usando um sistema de descrição diferente (português vs inglês) para se referir ao mesmo objeto.
Um espaço de cor funciona exatamente assim. Ele é um sistema de coordenadas que define como descrever uma cor usando números. A cor em si não muda, o que muda é a forma como você escreve ela.
No RGB, você descreve uma cor pela quantidade de Vermelho, Verde e Azul.
No YCbCr, você descreve a mesma cor pela sua luminosidade e por dois valores de diferença de cor.
No HSL, você descreve pela matiz (hue), saturação e luminosidade.
No CMYK, você descreve pela quantidade de Ciano, Magenta, Amarelo e Preto (usado em impressoras).
Todos esses espaços podem representar a mesma cor. A diferença está em como eles organizam a informação.
Mas dai você pode estar se perguntando... se todos representam a mesma coisa, pra que ter mais de um? 🤔
E a resposta é: porque cada espaço de cor tem vantagens diferentes dependendo do que você quer fazer com a imagem.
O RGB é ótimo pro monitor, porque é assim que os pixels físicos funcionam (3 lâmpadas: vermelha, verde e azul).
O CMYK é ótimo pra impressora, porque é assim que as tintas se misturam no papel.
O HSL é ótimo pra designers, porque é mais intuitivo pensar em "quero esse azul mais claro" do que "quero aumentar o G e o B em 30 unidades".
E o YCbCr? Ele é ótimo pra compressão, porque separa a informação de um jeito que permite jogar dados fora sem que o olho humano perceba.
E é exatamente esse último ponto que faz o YCbCr ser o queridinho dos CODECs.
A hierarquia RGB - CMYK - HSL - YCBCR
Mas espera aí, se o monitor só entende RGB, isso significa que todos os outros espaços de cor-luz (como YCbCr e HSL) são convertidos pra RGB no final das contas antes de chegar na tela? E o CMYK, ele só existe pro mundo da impressão? 🤔
Exato, nossa intuição está correta.
O monitor, no final das contas, só entende RGB, e cada pixel físico da sua tela tem aquelas 3 lâmpadinhas (vermelha, verde e azul) que a gente viu no artigo anterior.
Sendo assim, não importa em qual espaço de cor a informação esteja trafegando nos bastidores, na hora de acender os pixels, tudo precisa virar RGB.
Então quando você abre uma imagem JPEG, por exemplo, os dados internos estão em YCbCr, mas antes de chegar na tela, o decoder faz a conversão inversa (YCbCr → RGB) usando fórmulas que vamos aprender mais pra frente.
A grande verdade é que o monitor nunca vê YCbCr, ele só recebe os valores R, G e B finais.
O mesmo vale pro HSL. Quando você está no Photoshop ajustando o "Hue" de uma cor, internamente o software está trabalhando em HSL porque é mais intuitivo pra você. Mas na hora de renderizar na tela, ele converte pra RGB e manda pro monitor.
Então a hierarquia ficaria assim: todos os espaços de cor-luz (YCbCr, HSL, HSV, Lab, ICtCp...) são convertidos pra RGB antes de chegar no pixel físico da tela. O RGB é a "linguagem nativa" do hardware de exibição.
E o CMYK?
O CMYK vive no mundo da cor-pigmento (subtrativa). Ele é a "linguagem nativa" das impressoras. Quando você manda um documento pra uma impressora profissional, ela trabalha com 4 tintas: Ciano, Magenta, Amarelo e Preto (o K vem de "Key", que é o preto).
Mas aqui tem um detalhe importante: dizer que CMYK é "pra cores da vida real" não é exatamente correto.
As cores da vida real não são nem RGB nem CMYK.
Lembra do artigo anterior? As cores da vida real são comprimentos de onda de luz que o trio luz + matéria + cérebro interpreta. RGB e CMYK são ambos aproximações que tentam reproduzir essas cores, cada um no seu meio (tela ou papel).
O que acontece é que o CMYK tem um gamut menor que o RGB.
Isso quer dizer que existem cores que sua tela consegue mostrar (em RGB) mas que a impressora não consegue reproduzir (em CMYK), e vice-versa.
É por isso que às vezes você faz um design lindo e vibrante na tela e quando imprime as cores ficam "apagadas", a impressora simplesmente não consegue atingir certas saturações que o monitor atinge com luz direta.
É importante ressaltar que nenhum deles é "melhor" ou "mais real" que o outro. São todos traduções diferentes da mesma realidade, e a conversão entre eles é só matemática 😄
Dito isso, acho que já estamos prontos para entender dois conceitos fundamentais:
- luminância
- crominância
Luminância: o brilho que seus olhos amam
Lembra que no artigo anterior a gente falou sobre os bastonetes e os cones do olho humano?
Recapitulando rapidamente:
- Bastonetes: são cerca de 125 milhões em cada olho, extremamente sensíveis à luz, mas não distinguem cores. São responsáveis pela visão noturna.
- Cones: são cerca de 7 milhões em cada olho, responsáveis pela visão colorida, mas precisam de bastante luz pra funcionar.
Percebe a proporção? Você tem quase 18 vezes mais bastonetes do que cones!
Isso significa que o seu olho é absurdamente mais sensível ao brilho (luminosidade) do que às cores em si.
E é aqui que entra o conceito de luminância.
Luminância (representada pela letra Y) é, de forma simples, o componente de brilho de uma imagem. É a informação que diz "esse pixel é claro" ou "esse pixel é escuro", sem dizer nada sobre a cor dele.
Sabe quando você pega o controle da TV e reduz a saturação até zero?
A imagem fica em preto e branco, certo? Aquilo que sobra, aquela imagem em tons de cinza, é a luminância. É o "esqueleto" da imagem.

E aqui vem a parte mais importante pra gente: como o olho humano é muito mais sensível ao brilho do que à cor, a luminância carrega quase toda a informação visual que o cérebro considera importante.
Pra você ter uma ideia, faça o seguinte experimento mental: Imagine uma foto de uma paisagem qualquer. Agora imagine duas versões dessa foto.
Versão A: Você remove toda a informação de cor, mas mantém o brilho perfeito (imagem em preto e branco, nítida, com todos os detalhes de sombra e luz).
Versão B: Você mantém as cores perfeitas, mas borra completamente o brilho (como se a imagem estivesse fora de foco).
Qual das duas versões você consegue "entender" melhor? Qual delas te permite reconhecer os objetos na cena?

A Versão A, sem dúvida!
Uma imagem em preto e branco nítida é perfeitamente compreensível. Você reconhece rostos, objetos, texturas, profundidade. Mas uma imagem colorida e borrada vira uma mancha incompreensível.
Isso prova que a luminância é a informação mais preciosa de uma imagem. Sem ela, a imagem não faz sentido.
Sem as cores? Bom... a gente viveu séculos com fotos e filmes em preto e branco e entendíamos tudo perfeitamente 😄
💡 Curiosidade: A TV em preto e branco transmitia apenas luminância. Quando a TV colorida foi inventada nos anos 1950-60, os engenheiros precisaram adicionar a informação de cor sem quebrar a compatibilidade com os aparelhos antigos. A solução? Separar luminância de crominância. As TVs antigas liam só o Y e ignoravam o resto. Já as TVs novas liam Y + cor. Genial, não?
Crominância: a informação de cor "extra"
Se a luminância é o brilho (o "esqueleto" da imagem), a crominância é a informação de cor que vem "por cima" desse esqueleto.
A crominância (representada por Cb e Cr) descreve quão diferente uma cor é em relação ao cinza neutro da mesma luminosidade.
Mas peraí, por que dois componentes de crominância e não um só?
Pensa assim: se eu te disser que um pixel tem luminância Y = 180 (um cinza claro), você sabe que ele é "claro", mas não sabe se ele é um azul claro, um amarelo claro, um rosa claro, certo?
Pra especificar a cor, você precisa de mais informação. E como estamos trabalhando com um sistema baseado em 3 tipos de cones (R, G, B), precisamos de pelo menos 2 coordenadas de cor além do brilho pra descrever qualquer cor completamente.
É como dar as coordenadas de um ponto no espaço: você precisa de 3 valores (x, y, z). Se a luminância é uma das coordenadas, as outras duas são as de crominância.
No sistema YCbCr:
- Cb (Chrominance Blue) → indica o quanto a cor se desvia do cinza na direção do azul (ou do amarelo, no sentido oposto).
- Cr (Chrominance Red) → indica o quanto a cor se desvia do cinza na direção do vermelho (ou do ciano, no sentido oposto).
Mas dai você pode estar se perguntando... e o verde? Cadê o componente verde?
Aí é que esta... o verde não precisa de um componente próprio porque ele já está embutido nos três valores juntos. Se você sabe o brilho total (Y), sabe o desvio pro azul (Cb), e sabe o desvio pro vermelho (Cr), matematicamente já dá pra calcular quanto de verde tem ali.
É como se eu te dissesse: "Tenho 100 reais no total. Gastei 30 em café e 25 em pão de queijo." Você não precisa que eu te diga quanto sobrou, né?
Basta fazer 100 - 30 - 25 = 45 reais. O verde funciona assim no YCbCr, ele é o valor que "sobra" depois de tirar a contribuição do azul e do vermelho.
E lembra do artigo anterior, quando eu falei que 72% dos cones do olho são verdes?
Pois é, o verde é justamente o componente que mais contribui pro cálculo da luminância Y. Por isso ele não precisa de um eixo de crominância separado, ele já está fortemente representado dentro do Y.
🧠 Resumindo: No YCbCr, a informação é reorganizada assim:
- Y = quanto brilho esse pixel tem (a parte que o olho mais liga)
- Cb = quanto de "azulidade" vs "amarelidade" esse pixel tem
- Cr = quanto de "vermelhidade" vs "cianidade" esse pixel tem
Colocar luzes tão pequenas dentro de um único pixel é possível graças à tecnologia MicroLED, onde engenheiros utilizam métodos de fabricação avançados, semelhantes aos de chips de computador, para criar diodos emissores de luz (LEDs) que medem menos de 50 a 100 micrômetros — muitas vezes menores que um fio de cabelo humano.Por que o RGB não é bom pra compressão?
Agora que você já sabe o que é luminância e crominância, vamos entender por que o RGB é um problema quando o assunto é comprimir dados.
No formato RGB, os 3 canais (R, G, B) estão completamente entrelaçados. O brilho de um pixel não está em nenhum canal específico, ele é resultado da mistura dos três canais juntos.
Imagina que você quer fazer uma compressão e precisa decidir: "onde posso economizar dados sem que o olho humano perceba?"
No RGB, essa pergunta é quase impossível de responder de forma inteligente. Se você reduzir a precisão do canal R, por exemplo, não está claro se você está afetando mais o brilho ou mais a cor do pixel.
Provavelmente está afetando os dois ao mesmo tempo, e o resultado pode ser terrível.
É como se os três canais RGB fossem três fios de um cabo de aço entrelaçados. Se você cortar um pedaço de qualquer fio, o cabo inteiro enfraquece de maneira imprevisível, até porque uma cor depende da atuação dos 3 (R, G, B)

Vou te dar um exemplo concreto pra ficar mais claro.
Imagine um pixel com o valor RGB (200, 180, 50). Isso dá um tom de amarelo-mostarda. Agora imagine que um CODEC quisesse "economizar" dados e decidisse arredondar os valores pro múltiplo de 10 mais próximo: (200, 180, 50) vira (200, 180, 50)... beleza, nesse caso não mudou muito.
Mas e se o arredondamento fosse mais agressivo, digamos, pro múltiplo de 50...
Aí ficaria (200, 200, 50). Parece pouca diferença, mas aquele canal G indo de 180 pra 200 mudou tanto o brilho quanto a cor do pixel ao mesmo tempo.
O amarelo-mostarda ficou mais esverdeado e mais claro simultaneamente 🫠
Agora imagine isso acontecendo em milhões de pixels ao mesmo tempo.
O resultado são artefatos de compressão por toda a imagem, aqueles quadradinhos borrados e cores estranhas que a gente vê em JPEGs muito comprimidos, tipo esses da ilustração abaixo:

O problema fundamental é que no RGB, brilho e cor estão misturados nos mesmos números. Você não consegue tocar em um sem afetar o outro.
E é exatamente isso que o YCbCr resolve.
O YCbCr: separando o que importa do que pode ser sacrificado
No espaço YCbCr, a informação é separada de forma limpa, em basicamente duas frentes principais:
- Y (luminância) carrega o brilho, os contornos, as texturas, os detalhes finos. É o canal que o olho humano mais percebe.
- Cb e Cr (crominância) carregam a informação de cor. É o canal que o olho humano percebe com muito menos detalhes.
Essa separação é o que torna a compressão inteligente possível.
Voltando à analogia do cabo de aço: no YCbCr, é como se em vez de ter 3 fios entrelaçados, você tivesse um fio grosso e forte (Y) e dois fios finos e decorativos (Cb, Cr).
Se você cortar um pedaço dos fios decorativos, o cabo continua segurando. Mas se cortar o fio grosso, tudo desmorona.

É por isso que os CODECs fazem o seguinte:
➡️ Mantêm a luminância (Y) com alta qualidade, preservando todos (ou quase todos) os detalhes de brilho.
➡️ Reduzem a resolução da crominância (Cb e Cr), porque o olho não percebe a diferença.
Esse "truque" de reduzir a crominância tem um nome técnico que vamos estudar em detalhes no próximo artigo: Chroma Subsampling (sub-amostragem de crominância).
Mas por enquanto, o importante é entender por que ele funciona.
E ele funciona porque o olho humano é biologicamente construído dessa forma. Não é uma "gambiarra" dos engenheiros. É a exploração inteligente de uma limitação real do sistema visual humano.
Imagine que você está pintando um quadro à distância. Você é extremamente cuidadoso com a iluminação e as sombras do quadro (cada detalhe de brilho está perfeito).
Mas quando pinta as cores, você usa pinceladas grossas e imprecisas. Quando alguém olha o quadro de longe, ele parece perfeito, porque o cérebro se guia muito mais pelas sombras e contornos do que pela precisão das cores.
Observe um exemplo disso que eu acabei de te falar:

Observando a mona lisa de longe, vemos que ela está em alta definição, mas a medida em que chegamos mais perto, vemos os aspectos do pincel.
Os CODECs fazem exatamente isso: mantêm a "iluminação" perfeita (Y) e economizam na "pintura de cores" (Cb, Cr) 😉
A matemática por trás: como converter RGB para YCbCr?
Agora vamos ver como essa conversão é feita na prática. Não se assuste com as fórmulas, elas são mais simples do que parecem!
A conversão de RGB para YCbCr segue estas equações (padrão ITU-R BT.601, que é o mais usado):
Y = 0.299 × R + 0.587 × G + 0.114 × B
Cb = -0.169 × R - 0.331 × G + 0.500 × B + 128
Cr = 0.500 × R - 0.419 × G - 0.081 × B + 128Parece complicado? Relaxa, a partir de agora nós vamos destrinchar cada linha.
Linha 1 — Cálculo do Y (luminância):
Y = 0.299 × R + 0.587 × G + 0.114 × BEssa fórmula calcula o brilho do pixel. Repara nos coeficientes:
- Verde contribui com 58.7% do brilho total
- Vermelho contribui com 29.9%
- Azul contribui com apenas 11.4%
Isso te faz lembrar de alguma coisa? Sim, no artigo anterior, a gente viu que 72% dos cones do olho são verdes, 21% são vermelhos e 7% são azuis.
Os coeficientes da fórmula não são iguais a essas porcentagens (porque a relação entre cones e percepção de brilho é mais complexa), mas a tendência é a mesma: o verde domina, o vermelho vem em segundo, e o azul quase não conta.
Esses coeficientes não foram inventados aleatoriamente. Eles foram derivados de experimentos psicofísicos nos anos 1950, onde pesquisadores mediam a percepção de brilho de voluntários humanos sob diferentes combinações de luz colorida.
O resultado é essa ponderação que reflete como o sistema visual humano realmente percebe luminosidade.
Vamos fazer um cálculo de exemplo. Pegue a cor amarelo-mostarda que usamos antes: RGB(200, 180, 50).

E aplique ela a formula do YCbCr:
Y = 0.299 × 200 + 0.587 × 180 + 0.114 × 50
Y = 59.8 + 105.66 + 5.7
Y = 171.16
Y ≈ 171Então a luminância desse pixel é 171 (num cinza claro, numa escala de 0 a 255). Se você olhasse só o canal Y, esse pixel apareceria como um pontinho cinza claro. Faz sentido?
Amarelo-mostarda é uma cor relativamente clara, então um Y alto bate com a nossa intuição.
Linha 2 — Cálculo do Cb (crominância azul):
Cb = -0.169 × R - 0.331 × G + 0.500 × B + 128O Cb mede o quanto a cor desvia do cinza na direção do azul. Note que:
- O coeficiente do B é positivo (quanto mais azul, maior o Cb)
- Os coeficientes de R e G são negativos (vermelho e verde "puxam" o Cb pra baixo)
- O +128 é um offset pra manter o valor na faixa positiva (0 a 255), já que sem ele o resultado poderia ser negativo
Calculando pro nosso amarelo-mostarda RGB(200, 180, 50):
Cb = -0.169 × 200 - 0.331 × 180 + 0.500 × 50 + 128
Cb = -33.8 - 59.58 + 25.0 + 128
Cb = 59.62
Cb ≈ 60Cb = 60, que é um valor abaixo de 128 (o ponto neutro). Isso faz sentido! Amarelo-mostarda tem muito pouco azul, então o Cb fica baixo. Se fosse um pixel azul puro (0, 0, 255), o Cb seria muito alto (perto de 255).
Linha 3 — Cálculo do Cr (crominância vermelha):
Cr = 0.500 × R - 0.419 × G - 0.081 × B + 128O Cr mede o desvio na direção do vermelho, e conta com a mesma lógica:
- O coeficiente do R é positivo (quanto mais vermelho, maior o Cr)
- G e B são negativos
- O +128 é o offset pro mesmo motivo
Colocando o nosso Amarelo-mostarda na formula, nós temos:
Cr = 0.500 × 200 - 0.419 × 180 - 0.081 × 50 + 128
Cr = 100.0 - 75.42 - 4.05 + 128
Cr = 148.53
Cr ≈ 149Cr = 149, que é acima de 128. Faz sentido! Amarelo-mostarda tem bastante vermelho na composição, então o Cr fica acima do ponto neutro.
Resultado final da conversão:
RGB(200, 180, 50) → YCbCr(171, 60, 149)Percebe que a informação é a mesma cor, só que organizada de um jeito diferente? Em vez de dizer "200 de vermelho, 180 de verde, 50 de azul", estamos dizendo "171 de brilho, pouco azul (60), bastante vermelho (149)".
E o verde? O decoder faz o resto quando se deparar com essa formula e adiciona depois 🤗
E a mágica é que agora podemos mexer nos valores Cb e Cr (arredondando, reduzindo precisão, diminuindo a resolução) sem afetar o brilho (Y), que é o que o olho mais percebe.
Convertendo de volta: YCbCr para RGB
Obviamente que se dá pra converter RGB para YCbCr, também é possível fazer o caminho inverso. Caso contrário, o monitor não conseguiria exibir a imagem (lembra-se de que monitores trabalham com RGB).
A fórmula inversa é a seguinte:
R = Y + 1.402 × (Cr - 128)
G = Y - 0.344 × (Cb - 128) - 0.714 × (Cr - 128)
B = Y + 1.772 × (Cb - 128)Pegando o YCbCr que conseguimos antes, vamos colocar ele de volta na formula: YCbCr(171, 60, 149):
R = 171 + 1.402 × (149 - 128)
R = 171 + 1.402 × 21
R = 171 + 29.442
R ≈ 200 ✅
G = 171 - 0.344 × (60 - 128) - 0.714 × (149 - 128)
G = 171 - 0.344 × (-68) - 0.714 × 21
G = 171 + 23.392 - 14.994
G ≈ 179 ≈ 180 ✅
B = 171 + 1.772 × (60 - 128)
B = 171 + 1.772 × (-68)
B = 171 - 120.496
B ≈ 50 ✅Voltamos ao RGB(200, 180, 50)! A conversão é reversível (com pequenos arredondamentos).
Essa é uma propriedade muito importante: a conversão RGB ↔ YCbCr não perde dados (ou perde quantidades desprezíveis por arredondamento).
A perda de dados que os CODECs fazem acontece depois da conversão, quando eles deliberadamente reduzem a precisão dos canais Cb e Cr.
Visualizando os canais separados
Pra ficar ainda mais claro por que essa separação é tão poderosa pra compressão, vamos imaginar como seria olhar pros canais separados de uma imagem.
Pega a ilustração abaixo, e note que é uma foto de uma praia num dia ensolarado: céu azul, areia amarela, mar esverdeado, pessoas com roupas coloridas:

Agora vamos separar essa foto nos 3 canais YCbCr e olhar cada um individualmente:
Canal Y (luminância):
Se você olhar só o canal Y, vai ver uma imagem em tons de cinza que parece uma versão preto-e-branco da foto original. Mas repare: ela é extremamente detalhada.
Você consegue ver as ondas do mar, os grãos de areia, os contornos dos rostos, as dobras das roupas, as nuvens no céu. Basicamente toda a informação "importante" está aqui.
Canal Cb (crominância azul):
Se você olhar só o canal Cb, vai ver uma imagem cinza meio estranha, quase uniforme, com algumas variações suaves.
As áreas de céu e mar ficam um pouco mais claras (indicando mais azul), e as áreas de areia ficam mais escuras (indicando menos azul, ou seja, mais amarelo).
Mas repare que essa imagem é muito menos detalhada que o canal Y. Parece uma versão borrada e simplificada.
Canal Cr (crominância vermelha):
Mesmo esquema do Cb: uma imagem cinza com variações suaves. As áreas com tons quentes (pele das pessoas, areia) ficam um pouco mais claras, e as áreas frias (céu, mar) ficam mais escuras. De novo, muito menos detalhe que o canal Y.
Percebeu o padrão? O canal Y é rico em detalhes, enquanto os canais Cb e Cr são suaves e com pouca variação.
Mas espere ai, porque na ilustração daquelas 3 pessoas na praia, os canais cb e cr estão em tons de cinza? Não eram pra estarem mais azulados ou avermelhados?
Na verdade, a confusão se deve porque a gente fala "crominância azul" e "crominância vermelha" e o instinto é esperar ver azul e vermelho, não estou certo? 😄
Lembre-se de que a gente acabou de ver que o canal (Y, Cb, Cr) é um valor numérico único por pixel, de 0 a 255.
É só um número, não uma cor. Quando você salva esse canal como imagem separada, a única forma de visualizar "um número de 0 a 255" é como tom de cinza (0 = preto, 255 = branco).
É como se fosse um mapa de calor, só que em cinza. O pixel mais claro no canal Cb não significa "azul", significa "esse pixel tem um valor alto de Cb", ou seja, tem bastante desvio na direção do azul.
Mas a representação visual disso é cinza claro, porque é um número alto.
Aquelas imagens coloridas que você já deve ter visto por aí (canal Cb pintado de azul, canal Cr pintado de vermelho) são visualizações artificiais feitas por softwares que aplicam um color map por cima pra ficar mais didático.
Tipo quando você vê uma imagem térmica pintada de vermelho e azul, na verdade a câmera não "vê" vermelho, ela vê números de temperatura, e o software pinta as cores depois pra facilitar a leitura humana.
Voltando ao assunto deste tópico...
É como se a luminância fosse um mapa topográfico detalhado de uma montanha, com cada curva de nível, e a crominância fosse uma aquarela pastel borrada por cima desse mapa, dando só uma noção geral de cor para cada região.
E é exatamente por isso que dá pra comprimir a crominância muito mais agressivamente do que a própria luminância.
O que os CODECs fazem na prática?
Agora que você entende a teoria toda, vamos ver o que os CODECs realmente fazem com essa informação.
O fluxo básico de compressão de praticamente todo CODEC de imagem moderno (JPEG, WebP, HEIC, AVIF...) segue essa sequência:

Passo 1: Receber a imagem em RGB24.
Passo 2: Converter de RGB para YCbCr (usando as fórmulas que a gente viu).
Passo 3: Reduzir a resolução dos canais Cb e Cr (Chroma Subsampling, que veremos no próximo artigo).
Passo 4: Aplicar transformadas matemáticas (como a DCT, que veremos mais pra frente).
Passo 5: Quantizar os coeficientes (onde a "mágica lossy" acontece).
Passo 6: Codificar com entropia (Huffman, etc.) pra comprimir ainda mais.
A conversão pra YCbCr (Passo 2) é literalmente o primeiro passo real de compressão que a maioria dos CODECs faz.
Antes de qualquer truque sofisticado, antes de qualquer transformada matemática complexa, o CODEC reorganiza os dados de RGB pra YCbCr.
E só o Passo 3 (reduzir a resolução da crominância) já é capaz de reduzir o tamanho dos dados em 33% a 50% sem perda perceptível de qualidade visual!
Isso mesmo, antes mesmo de começar a compressão "de verdade", só por trocar a forma como organizamos os números, já conseguimos cortar quase metade dos dados. Isso é absurdamente eficiente 🤯
Outros espaços de cor que vale a pena conhecer
Agora, eu quero te apresentar brevemente alguns outros espaços de cor que existem por aí, pra você ter uma visão mais completa.
É importante ressaltar que nós não vamos nos aprofundar em todos eles agora, mas é bom saber que existem e pra que servem.
YUV:
O YUV é o "pai" do YCbCr. Ele foi originalmente usado em transmissões de TV analógica e funciona de forma muito parecida: Y é a luminância, U e V são componentes de crominância.
A diferença principal é que o YUV trabalha com sinais analógicos contínuos, enquanto o YCbCr é a versão digital (com valores discretos de 0 a 255). Na prática, muita gente usa "YUV" e "YCbCr" como sinônimos no contexto digital, apesar de tecnicamente não serem exatamente a mesma coisa.
YCgCo:
Uma variação mais simples e computacionalmente barata. Em vez de usar coeficientes "quebrados" como 0.299 e 0.587, ela usa operações mais simples (somas e subtrações com shifts de bit).
É usado em alguns codecs de vídeo modernos como o H.264 em modo lossless. A desvantagem é que a separação luminância/crominância não é tão "limpa" quanto no BT.601.
HSL e HSV:
HSL (Hue, Saturation, Lightness) e HSV (Hue, Saturation, Value) são espaços que descrevem cor de forma mais intuitiva pra humanos. Em vez de pensar em "quanto vermelho, verde e azul", você pensa em "qual a cor (matiz), quão vibrante ela é (saturação), e quão clara ou escura (luminosidade)".
São usados em interfaces de seleção de cor em programas como Photoshop e editores de vídeo, mas não são bons pra compressão.
Lab (CIELAB):
O Lab é um espaço de cor projetado pra ser perceptualmente uniforme.
Isso significa que uma diferença numérica de, digamos, 10 unidades entre duas cores no Lab sempre "parece" a mesma diferença pro olho humano, independente da região do espaço de cor.
No RGB isso não acontece, uma diferença de 10 unidades entre dois tons de azul escuro pode ser imperceptível, mas entre dois tons de amarelo claro pode ser gritante.
O Lab é muito usado em ciência da cor e controle de qualidade, mas não diretamente em CODECs.
ICtCp:
Uma evolução mais moderna do YCbCr, projetada pra funcionar melhor com conteúdo HDR (High Dynamic Range) e profundidades de cor de 10 bits ou mais.
Ela faz uma separação luminância/crominância ainda melhor que o YCbCr tradicional. É usada no Dolby Vision e em alguns padrões de TV de nova geração.
Pra nossa jornada, o foco será no YCbCr com BT.601, que é o padrão usado pelo JPEG e pela grande maioria dos CODECs de imagem. Se você entender esse, entender os outros vira fichinha ✅
E já que entramos no assunto do padrão BT.601, acho que a vela a pena falar dele, não?
O padrão BT.601 vs BT.709: qual usar?
Você pode ter notado que eu mencionei "ITU-R BT.601" lá atrás. Esse é o padrão que define os coeficientes que usamos nas fórmulas de conversão.
Começando pelo BT.601, ele foi criado pela ITU (International Telecommunication Union), que é um órgão da ONU responsável por padronizar telecomunicações no mundo todo.
Não foi um cientista específico que inventou, foi um comitê internacional com engenheiros de várias emissoras e fabricantes de TV (BBC, NHK, empresas europeias e americanas) que se juntaram pra resolver um problema prático: na época, o mundo estava migrando da TV analógica pro vídeo digital, e cada país fazia do seu jeito.
O NTSC (EUA/Japão) rodava a 30fps, o PAL (Europa/Brasil) a 25fps, e ninguém concordava em nada.
O BT.601 nasceu justamente pra unificar isso. Ele definiu como digitalizar o sinal de TV de definição padrão (SDTV, 480i/576i), incluindo os coeficientes de conversão RGB → YCbCr que a gente ta vendo por aqui nessa jornada.
Esses coeficientes (0.299, 0.587, 0.114) na verdade são ainda mais antigos, eles vieram das pesquisas dos anos 1950 feitas pela NTSC (National Television System Committee) nos Estados Unidos, quando estavam inventando a TV colorida e precisaram definir como calcular luminância de forma compatível com os TVs preto-e-branco existentes.
Os experimentos psicofísicos que mediram a sensibilidade do olho humano ao brilho de cada cor (verde > vermelho > azul) foram feitos nessa época, e o BT.601 simplesmente "oficializou" esses números pro mundo digital.
Entretanto, esse padrão não é o único. Hoje em dia temos o BT.709!
Ele surgiu 8 anos depois, em que a TV de alta definição (HDTV) estava chegando ás prateleiras dos comercios.
O problema é que os televisores HD usavam fósforos e tecnologias de tela diferentes dos TVs de definição padrão. As "cores primárias" (os vermelhos, verdes e azuis mais puros que a tela conseguia emitir) eram ligeiramente diferentes.
A ITU reuniu o comitê de novo e recalculou os coeficientes pra refletir as características dos novos displays HD.
O resultado foi o ITU-R BT.709, com coeficientes atualizados (0.2126, 0.7152, 0.0722) onde o verde ganhou ainda mais peso.
Esse padrão se tornou a base do HDTV no mundo todo e é usado até hoje em praticamente todo conteúdo de vídeo HD (1080p, 720p).
Os CODECs mais famosos do mundo, H.264 e o H.265 usam BT.709 por padrão quando a resolução é HD ou superior.
Uma curiosidade: existe também o BT.2020 (de 2012), que é o padrão pra TV 4K/8K e conteúdo HDR, com um gamut de cores ainda maior.
Sendo assim, podemos dizer que:
BT.601 (de 1982): Originalmente criado pra TV de definição padrão (SDTV). Usa os coeficientes que vimos anteriormente, tais como:
Y = 0.299R + 0.587G + 0.114BJá o BT.709 (de 1990): Criado pra TV de alta definição (HDTV). Usa coeficientes ligeiramente diferentes:
Y = 0.2126R + 0.7152G + 0.0722BPercebe que no BT.709 o verde tem peso ainda maior (71.5% vs 58.7%) e o azul tem peso ainda menor (7.2% vs 11.4%)?
Isso aconteceu porque os coeficientes do BT.709 foram recalculados pra funcionar melhor com os fósforos e LEDs usados em telas modernas de alta definição.
Na prática, a diferença visual entre usar BT.601 e BT.709 é sutil na maioria dos casos. Mas é importante saber que os dois existem porque:
- O
JPEGusa BT.601 - O
H.264/H.265(codecs de vídeo) podem usar BT.601 ou BT.709 dependendo da resolução - Se você usar o padrão errado na hora de decodificar, as cores ficam levemente "erradas" (um problema real que muitos players de vídeo enfrentam)
Pro nosso CODEC autoral, vamos usar BT.601 (e posteriormente o BT.709), o mesmo do JPEG. Simples, amplamente suportado e fácil de implementar.
Colocando em prática: convertendo RGB para YCbCr em C
Antes de encerrar, que tal vermos como ficaria essa conversão em código C? Afinal, estamos construindo um CODEC, então eventualmente vamos precisar disso.
Lembra que no artigo anterior a gente criou uma imagem vermelha pura em C? Agora vamos fazer algo mais interessante: pegar uma imagem RGB24 e converter ela pra YCbCr, salvando os 3 canais separados pra poder visualizar.
#include <stdio.h> // Para fopen, fwrite, fclose, printf
#include <stdlib.h> // Para malloc, free
#include <math.h> // Para round()
// Estrutura que representa um pixel RGB
typedef struct {
unsigned char r; // Canal vermelho (0 a 255)
unsigned char g; // Canal verde (0 a 255)
unsigned char b; // Canal azul (0 a 255)
} PixelRGB;
// Estrutura que representa um pixel YCbCr
typedef struct {
unsigned char y; // Luminância (0 a 255)
unsigned char cb; // Crominância azul (0 a 255)
unsigned char cr; // Crominância vermelha (0 a 255)
} PixelYCbCr;
// Função que converte um pixel RGB para YCbCr (BT.601)
PixelYCbCr rgb_para_ycbcr(PixelRGB rgb) {
PixelYCbCr ycbcr;
// Calcula a luminância (Y)
// O verde tem o maior peso porque nosso olho é mais sensível a ele
double y = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
// Calcula a crominância azul (Cb)
// Mede o desvio na direção do azul. O +128 mantém o valor positivo.
double cb = -0.169 * rgb.r - 0.331 * rgb.g + 0.500 * rgb.b + 128.0;
// Calcula a crominância vermelha (Cr)
// Mede o desvio na direção do vermelho.
double cr = 0.500 * rgb.r - 0.419 * rgb.g - 0.081 * rgb.b + 128.0;
// Garante que os valores fiquem no intervalo 0-255
// (arredondamentos podem jogar pra fora do range)
if (y < 0) y = 0; if (y > 255) y = 255;
if (cb < 0) cb = 0; if (cb > 255) cb = 255;
if (cr < 0) cr = 0; if (cr > 255) cr = 255;
ycbcr.y = (unsigned char) round(y);
ycbcr.cb = (unsigned char) round(cb);
ycbcr.cr = (unsigned char) round(cr);
return ycbcr;
}
int main() {
int width = 100; // Largura da imagem
int height = 100; // Altura da imagem
int total = width * height; // Total de pixels
// Aloca memória para a imagem RGB de entrada
PixelRGB *imagem_rgb = (PixelRGB *) malloc(total * sizeof(PixelRGB));
// Aloca memória para os 3 canais Y, Cb, Cr separados
unsigned char *canal_y = (unsigned char *) malloc(total);
unsigned char *canal_cb = (unsigned char *) malloc(total);
unsigned char *canal_cr = (unsigned char *) malloc(total);
if (!imagem_rgb || !canal_y || !canal_cb || !canal_cr) {
printf("Erro ao alocar memória.\n");
return 1;
}
// Vamos criar uma imagem RGB de teste com um degradê colorido
// A ideia é que cada pixel tenha uma cor diferente pra gente
// poder visualizar bem a separação dos canais
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
int idx = j * width + i;
// R varia da esquerda pra direita (0 a 255)
imagem_rgb[idx].r = (unsigned char)((i * 255) / (width - 1));
// G varia de cima pra baixo (0 a 255)
imagem_rgb[idx].g = (unsigned char)((j * 255) / (height - 1));
// B fica fixo em 128 (meio caminho)
imagem_rgb[idx].b = 128;
}
}
// Converte cada pixel de RGB para YCbCr
for (int i = 0; i < total; i++) {
PixelYCbCr ycbcr = rgb_para_ycbcr(imagem_rgb[i]);
canal_y[i] = ycbcr.y;
canal_cb[i] = ycbcr.cb;
canal_cr[i] = ycbcr.cr;
}
// Salva a imagem RGB original como arquivo bruto
FILE *f_rgb = fopen("original.rgb", "wb");
fwrite(imagem_rgb, sizeof(PixelRGB), total, f_rgb);
fclose(f_rgb);
// Salva cada canal como imagem em tons de cinza (1 byte por pixel)
// Pra visualizar, usaremos o formato "gray" do ffplay
FILE *f_y = fopen("canal_y.gray", "wb");
fwrite(canal_y, 1, total, f_y);
fclose(f_y);
FILE *f_cb = fopen("canal_cb.gray", "wb");
fwrite(canal_cb, 1, total, f_cb);
fclose(f_cb);
FILE *f_cr = fopen("canal_cr.gray", "wb");
fwrite(canal_cr, 1, total, f_cr);
fclose(f_cr);
// Libera a memória alocada
free(imagem_rgb);
free(canal_y);
free(canal_cb);
free(canal_cr);
printf("Arquivos gerados com sucesso!\n");
printf("\nPara visualizar, use os comandos:\n");
printf(" ffplay -f rawvideo -pixel_format rgb24 -video_size 100x100 original.rgb\n");
printf(" ffplay -f rawvideo -pixel_format gray -video_size 100x100 canal_y.gray\n");
printf(" ffplay -f rawvideo -pixel_format gray -video_size 100x100 canal_cb.gray\n");
printf(" ffplay -f rawvideo -pixel_format gray -video_size 100x100 canal_cr.gray\n");
return 0;
}Pra compilar e rodar, é o mesmo esquema que realizamos no artigo anterior:
mkdir -p ~/ycbcr-c
cd ~/ycbcr-c
# Crie o arquivo main.c com o código acima
gcc main.c -o converter -lm
./converterO -lm no final é necessário porque estamos usando a função round() da biblioteca matemática.
Depois de rodar, você terá 4 arquivos:
original.rgb→ a imagem RGB original (degradê colorido)canal_y.gray→ só a luminância (brilho)canal_cb.gray→ só a crominância azulcanal_cr.gray→ só a crominância vermelha

Use os comandos ffplay que o programa imprime pra visualizar cada um. Você vai ver com os próprios olhos como o canal Y é o que carrega os "detalhes" (o degradê diagonal) enquanto os canais Cb e Cr são mais "suaves".
Observação: a flag -lm na compilação linkea a biblioteca de matemática. Sem ela, o round() não vai funcionar e o compilador vai reclamar. Esse é um detalhe específico do GCC no Linux.
A conversão inversa em C (YCbCr -> RGB)
E é claro, se a gente consegue converter para YCbCr, obviamente conseguimos converter de volta para RGB, vamos aprender a fazer essa façanha em C:
// Função que converte um pixel YCbCr de volta para RGB
PixelRGB ycbcr_para_rgb(PixelYCbCr ycbcr) {
PixelRGB rgb;
double r = ycbcr.y + 1.402 * (ycbcr.cr - 128.0);
double g = ycbcr.y - 0.344 * (ycbcr.cb - 128.0) - 0.714 * (ycbcr.cr - 128.0);
double b = ycbcr.y + 1.772 * (ycbcr.cb - 128.0);
// Clamp: garante que os valores fiquem entre 0 e 255
if (r < 0) r = 0; if (r > 255) r = 255;
if (g < 0) g = 0; if (g > 255) g = 255;
if (b < 0) b = 0; if (b > 255) b = 255;
rgb.r = (unsigned char) round(r);
rgb.g = (unsigned char) round(g);
rgb.b = (unsigned char) round(b);
return rgb;
} O (aqueles ifs que limitam o valor entre 0 e 255) é essencial na conversão inversa. clamp
Sem ele, erros de arredondamento podem gerar valores como -2 ou 258, que estourariam o e causariam cores completamente erradas na imagem. unsigned char
Abaixo está o código completo para você realizar os testes e as chamadas que deve fazer no Terminal:
#include <stdio.h> // printf, scanf
#include <stdlib.h> // exit
#include <math.h> // round
// Estrutura que representa um pixel RGB
typedef struct {
unsigned char r;
unsigned char g;
unsigned char b;
} PixelRGB;
// Estrutura que representa um pixel YCbCr
typedef struct {
unsigned char y;
unsigned char cb;
unsigned char cr;
} PixelYCbCr;
// Função que converte um pixel YCbCr de volta para RGB
PixelRGB ycbcr_para_rgb(PixelYCbCr ycbcr) {
PixelRGB rgb;
double r = ycbcr.y + 1.402 * (ycbcr.cr - 128.0);
double g = ycbcr.y - 0.344 * (ycbcr.cb - 128.0) - 0.714 * (ycbcr.cr - 128.0);
double b = ycbcr.y + 1.772 * (ycbcr.cb - 128.0);
// Clamp: garante que os valores fiquem entre 0 e 255
if (r < 0) r = 0;
if (r > 255) r = 255;
if (g < 0) g = 0;
if (g > 255) g = 255;
if (b < 0) b = 0;
if (b > 255) b = 255;
rgb.r = (unsigned char) round(r);
rgb.g = (unsigned char) round(g);
rgb.b = (unsigned char) round(b);
return rgb;
}
int main() {
int y, cb, cr;
printf("Conversor YCbCr -> RGB\n");
printf("-----------------------\n");
printf("Digite os valores de Y, Cb e Cr (0 a 255).\n");
printf("Exemplo para vermelho puro aproximado: 76 85 255\n\n");
printf("Y: ");
if (scanf("%d", &y) != 1) {
printf("Erro: valor invalido para Y.\n");
return 1;
}
printf("Cb: ");
if (scanf("%d", &cb) != 1) {
printf("Erro: valor invalido para Cb.\n");
return 1;
}
printf("Cr: ");
if (scanf("%d", &cr) != 1) {
printf("Erro: valor invalido para Cr.\n");
return 1;
}
// Validação do intervalo
if (y < 0 || y > 255 || cb < 0 || cb > 255 || cr < 0 || cr > 255) {
printf("Erro: todos os valores devem estar entre 0 e 255.\n");
return 1;
}
PixelYCbCr entrada;
entrada.y = (unsigned char) y;
entrada.cb = (unsigned char) cb;
entrada.cr = (unsigned char) cr;
PixelRGB saida = ycbcr_para_rgb(entrada);
printf("\nResultado em RGB:\n");
printf("R = %d\n", saida.r);
printf("G = %d\n", saida.g);
printf("B = %d\n", saida.b);
return 0;
}No terminal, mesmo esquema do outro:
mkdir -p ~/ycbcr-para-rgb-c
cd ~/ycbcr-para-rgb-c
gcc main.c -o converter -lm
./converterSe você digitar algo como:
Y: 76
Cb: 85
Cr: 255A saída vai ser algo próximo de:
Resultado em RGB:
R = 254
G = 0
B = 0Isso representa praticamente um vermelho puro.
Resumo: do RGB ao YCbCr
Vamos recapitular tudo o que aprendemos neste artigo:
1) Espaço de cor é um sistema de coordenadas que define como representar uma cor com números. RGB, YCbCr, HSL, CMYK são espaços diferentes que descrevem as mesmas cores de maneiras diferentes.
2) Luminância (Y) é o componente de brilho da imagem. Nosso olho é extremamente sensível a ela (temos 18x mais bastonetes que cones).
3) Crominância (Cb e Cr) é a informação de cor. Nosso olho percebe crominância com muito menos detalhe do que luminância.
4) O RGB mistura brilho e cor nos mesmos canais, o que torna a compressão inteligente difícil.
5) O YCbCr separa brilho (Y) de cor (Cb, Cr), permitindo comprimir a crominância agressivamente sem que o olho perceba.
6) A conversão usa o padrão BT.601 com coeficientes baseados na sensibilidade real do olho humano (verde domina com 58.7%).
7) A conversão é reversível (com arredondamentos mínimos), então não perdemos dados na ida e volta.
8) Só a separação + redução de crominância já pode cortar 33-50% dos dados antes de qualquer compressão adicional!
No próximo artigo, vamos mergulhar fundo no Chroma Subsampling: o truque que efetivamente corta os dados de crominância pela metade (ou mais).
Vamos entender o que significam aqueles números misteriosos como 4:4:4, 4:2:2 e 4:2:0 que você já deve ter visto por aí em configurações de vídeo.
Até a próxima 😄

