Graduação de Cores com Fidelidade Fotográfica
Quando Usar
- O usuário fornece uma imagem de referência e uma foto a ser ajustada, pedindo "ajuste o tom desta foto igual à referência"
- O usuário tem um lote de fotos já editadas e quer solidificar o estilo delas para aplicar em um novo lote
- O usuário deseja "escurecer apenas o céu, sem mexer no chão ou no rosto" — ajustes seletivos por região
- O usuário quer aplicar um determinado Look em lote e exige "não alterar o conteúdo, não redesenhar"
- O usuário precisa verificar se um estilo é fiel: se há posterização, dominância de cor, ou se os realces/sombras foram esmagados
- O usuário envia uma foto perguntando "este ajuste está correto?" ou "qual o problema?", sem imagem de referência
- O material fornecido pode ser log / não convertido para espaço de exibição (quadros S-Log3, V-Log, HLG exportados de RAW)
- O usuário reclama que a edição generativa "mudou o rosto" ou "perdeu resolução" — exatamente o cenário para este skill
Não aplicável: tarefas que exigem mudar a direção da luz, trocar o céu, adicionar objetos ou redesenho semântico. Este skill apenas gera mapeamento de cores, não modifica o conteúdo.
Modelo Central
- Um "estilo" = arquivo
.cubebase + arquivos.cubeopcionais por região + registro de versão + arquivo do lote + tags de assunto, armazenado emcolor-styles/<nome>/ - Aplicar estilo = consulta pixel a pixel à tabela LUT, sobrepondo as LUTs das regiões com base na máscara semântica. A saída tem o mesmo tamanho da imagem original, com SSIM estrutural = 1.0
- Dois modos de criar um estilo:
fit: Aprendizado a partir de pares "imagem original / imagem editada", preciso (ΔE2000 medido de 0,55 a 0,9), precisa de 3 ou mais paresref: Transferência estatística a partir de uma imagem de referência, rápido, mas aproximado (ΔE2000 medido ≈ 4), funciona com uma única imagem, ideal para arranque inicial
- Cada
fité uma "confirmação" que é assimilada automaticamente: determina a qual estilo pertence → se for para um existente, refaz o ajuste com todo o histórico de amostras → arquiva → adiciona tags de assunto. Não treina modelos, não precisa de GPU
O que a Segmentação por Região Resolve
Uma LUT global só consegue responder "para qual cor mapear este pixel". Quando a mesma cor precisa ter resultados diferentes em regiões diferentes — o caso mais típico é o céu acinzentado precisa ser escurecido, enquanto o asfalto, também acinzentado, deve ser preservado — um mapeamento global é matematicamente impossível: os mínimos quadrados produzem um valor médio que não agrada a nenhum dos lados.
Medição real (uma cena de rua nublada, apenas escurecendo o céu em −28%):
| ΔE2000 do Céu | ΔE200o do Não-Céu | |
|---|---|---|
| LUT Global | 11,27 | 0,322 |
| LUT Global + Região Céu | 1,50 | 0,324 |
O erro na região não-céu permanece idêntico, demonstrando que a segmentação não introduz contaminação adicional — apenas complementa a parte que o modelo global não conseguiu aprender.
Dimensões de região: sky (céu) / people (pessoas) / veg (vegetação) / water (água) / ground (solo) / arch (arquitetura). Padrão: sky,people,veg. As máscaras suaves são geradas por um SegFormer-B0 integrado (4,4 MB).
Fluxo de Trabalho
1. Verificar Dependências
python -c "import numpy, PIL; print('ok')"
python -c "import onnxruntime; print('seg ok')" # Necessário para segmentação e tags de assunto
python -c "import tifffile; print('tif ok')" # Necessário para ler/escrever TIFF de 16 bits
python -c "import rawpy; print('raw ok')" # Necessário para ler RAW diretamente, opcional
Se faltar: python -m pip install numpy pillow onnxruntime tifffile rawpy. A falta do onnxruntime não quebra o sistema; ele é rebaixado para apenas color grading global.
2. Inspecionar o Material (Recomendado, Economiza Trabalho)
python <SKILL_DIR>/scripts/grade.py check --in ./photos/original --person
Não precisa de imagem de referência. Responde a duas perguntas:
- Que tipo de material é este?
transfer.verdict=display(exibição) /ambiguous(ambíguo) /log. Aplicarfitdiretamente em material log incorporaria a curva log na LUT, fazendo a imagem parecer razoável, mas errada. A opção--personexecuta a segmentação uma vez, para que a tonalidade da pele seja significativa (não avalia se não houver pessoas). - A imagem final tem defeitos? Luminância mediana, preto absoluto, proporção de áreas queimadas/escuras, dominância de cor, riqueza cromática. Valores que excedem os limites são listados em
warnings.
Os comandos apply e fit já executam essas verificações internamente, portanto, esta etapa é útil uma vez antes da entrega em lote.
3. Submeter um Lote de Material (Ação Central)
python <SKILL_DIR>/scripts/grade.py fit \
--src ./photos/original --tgt ./photos/retouched
--src/--tgt: Pares são combinados pelo nome do arquivo (parte principal). Também é possível usar--pair original editadarepetidamente para pares específicos.- Fotos de tamanhos diferentes no diretório não afetam o processo, mas cada par deve ter o mesmo tamanho.
- Sem
--name: Modo automático. Compara a distância com estilos existentes: se estiver suficientemente próximo, mescla; caso contrário, cria um novo. --name warm_film: Modo explícito. Se o nome já existir, mescla; se não, cria um novo.--regions sky,people,veg: Ativa a segmentação (valor padrão). Use--regions ""para voltar ao modo puramente global.--auto-threshold 1.5: Limiar ΔE para considerar "mesmo estilo".
4. Aplicar
python <SKILL_DIR>/scripts/grade.py apply \
--style warm_film --in ./photos/new --out ./out --strength 1.0
--style auto: Primeiro identifica o assunto da foto de entrada e, em seguida, seleciona o estilo com a tag mais correspondente (o resultado é explicado no campo auto).
Sempre verifique look e warnings antes de entregar. Se look.median_luma mudar de [0,195, 0,302] para [0,55, 0,95], significa que esta foto é muito mais clara do que o material com o qual o estilo foi treinado; ao aplicar o LUT, os realces estourarão. Neste caso, há três opções: reduzir a intensidade com --strength 0.7, usar --match-key on para equalizar a exposição ao nível do estilo (adequado apenas para fotos em sequência com a mesma iluminação), ou aceitar o fundo mais claro. Não tome a decisão pelo usuário sem perguntar.
--match-key off|on|auto (padrão off): Primeiro ajusta o brilho da imagem para o nível mediano do estilo e, em seguida, aplica o LUT. Para fotos com contraluz, pode prejudicar o assunto (veja a "Linha Vermelha da Fidelidade", item 14).
Formato de Saída e Qualidade
Por padrão, dois arquivos são gerados. Não envie apenas o primeiro:
| Produto | Padrão | Uso |
|---|---|---|
<nome>__<estilo>_v<N>.jpg | q98, 4:4:4, preserva ICC/EXIF/DPI originais | Entrega, envio, redes sociais |
<nome>__<estilo>_v<N>__master.png | 8 bits sem perdas, com dither de ±0,5 LSB | Para edições futuras, ampliações, impressão |
--quality 98: Qualidade JPEG. Medição real: q95→q98, PSNR de 48,7→50,6 dB, tamanho de 337→512 KB. Reduzir para q92 economiza metade do tamanho, perde 3 dB, perceptível em degradês de céu.--master png|tif|none:tifusa 16 bits (requertifffile; se não instalado, rebaixa automaticamente para PNG de 8 bits e registra emmaster_note, sem danificar o JPG já gerado). Para lotes, use--master nonepara economizar espaço.- Se o arquivo de origem já for
.png/.tif/.bmp, o mestre não é gerado — o produto principal já é sem perdas. - ICC, EXIF e DPI são transmitidos da imagem original. Perder o ICC é a principal causa de "aparência de baixa qualidade": imagens em P3/AdobeRGB, quando salvas sem tag ICC, são interpretadas como sRGB por qualquer visualizador, resultando em cores opacas e sem brilho.
- O dither é aplicado apenas em formatos sem perdas. O erro de quantização do JPEG já é uma ordem de grandeza maior que 1 LSB; o dither ali só desperdiçaria taxa de bits.
Se o usuário disser "a imagem foi comprimida", verifique primeiro a imagem original, não ajuste os parâmetros. O comando apply indica em source_note: se o JPEG original tiver menos de 0,10 bytes/pixel, é uma cópia retransmitida por aplicativos de mensagens (1080p com apenas pouco mais de 100 KB). Os detalhes já foram perdidos antes desta etapa; a saída, mesmo sem perdas, não pode recuperá-los. Outros dois erros comuns: visualizar a imagem na visualização do chat (a plataforma comprime novamente) e julgar a qualidade com base em uma prévia de composição como final_sheet.jpg (redimensionada pela metade).
5. Verificar
python <SKILL_DIR>/scripts/grade.py verify \
--style warm_film --src ./photos/original/a.jpg --tgt ./photos/retouched/a.jpg \
--threshold 2.0
Com --threshold, se o erro exceder o limite, o comando termina com código de saída 2, permitindo o uso em pipelines.
6. Exportar para Uso de Terceiros
python <SKILL_DIR>/scripts/grade.py export --style warm_film --out ./for_lightroom --full
--full gera também um guia de conexão (camada base cobre toda a imagem → camadas de região são sobrepostas com máscaras suaves → feather de 1 a 2% do quadro).
Estilos com segmentação não podem ser representados por um único .cube: um LUT 3D individual é uma função da cor do pixel; não consegue expressar "a mesma cor, tratamento diferente em regiões diferentes".
7. Reverter e Rastrear
python <SKILL_DIR>/scripts/grade.py batches --style warm_film # Ver quais lotes compõem este estilo
python <SKILL_DIR>/scripts/grade.py undo --style warm_film --batch b20261108_101500
python <SKILL_DIR>/scripts/grade.py styles # Listar todos os estilos + tags + descrição
python <SKILL_DIR>/scripts/grade.py styles --name warm_film # Manifest completo de um único estilo
undo não é uma exclusão de versão: ele remove aquele lote da composição, reajusta com os lotes restantes e gera uma nova versão. As versões antigas são sempre preservadas.
Interpretação da Saída
Todos os comandos imprimem JSON no stdout.
| Campo | Significado | Julgamento |
|---|---|---|
quality.dE2000_mean | Diferença de cor média nos pares de treinamento (incluindo segmentação) | < 2: utilizável; 2–4: aceitável; > 4: o par não é puramente uma relação de cor |
decision | Como esta submissão foi tratada | create-new / merge-nearest / merge-into-named |
auto.distance | Distância do estilo mais próximo | Se for muito menor que o limiar e mesmo assim criou um novo, ou muito maior e mesmo assim mesclou, indica erro de julgamento |
auto.nearest | Três estilos mais próximos e suas distâncias | Útil para entender "por que decidiu assim" |
learned_from | Quantas amostras históricas foram usadas na mesclagem | Se houver apenas new_samples sem archived, é a primeira vez |
diagnostics.coverage_pct | Proporção de pontos da LUT cobertos pelos dados | 30–50% é normal (imagens naturais ocupam apenas parte do espaço de cores) |
diagnostics.lut_clipped_pct_observed | Proporção de dados na região com valor 0/1 | > 2% indica possível perda de realces/sombras |
regions.active | Regiões realmente ativas | Vazio indica que essas regiões não apareceram neste lote |
region_diagnostics.<r>.skipped | Motivo pelo qual a região foi ignorada | too few pixels (poucos pixels) é normal; no residual learned (nenhum resíduo aprendido) indica que a região se comporta igual à global |
description.summary | Descrição do estilo em uma frase | Para o usuário, repita diretamente |
description.region_extra | O que cada região fez a mais em relação à global | Disponível apenas quando há segmentação |
tags | Assuntos identificados automaticamente | portrait (retrato) / landscape (paisagem) / street (rua) / indoor (interno) / night (noite) |
diagnostics.prior_shrink | Intensidade do prior, 1,0 = uso completo | A cada ajuste, testa valores 1,0 / 0,5 / 0,2 / 0,05 e escolhe o melhor; prior_trials exibe os dE de cada teste |
health.monotonic_violations | Número de violações de monotonicidade | Por padrão, forçado a zero; só aparece com --no-monotonic |
fit_quality.geometry_preserved | Se a geometria foi preservada | Deve ser true |
key_match.applied | Se a imagem foi primeiro ajustada à exposição do estilo | Padrão off; se ativado, exibe stops, gamma e mediana ajustados |
transfer.verdict | Se o material é de exibição, log ou ambíguo | log: deve ser convertido antes do fit; ambiguous: o usuário deve confirmar a origem |
warnings[].metric / .note | Problemas detectados na autoavaliação sem referência | Repita a note literalmente para o usuário; array vazio = nenhum defeito encontrado |
look | Luminância mediana / proporção de áreas queimadas / saturação da [original, final] | Use para responder "o que este estilo fez com esta imagem" |
input_guard | Durante fit, se a imagem original não está em espaço de exibição | blocking: false é apenas um aviso, não bloqueia |
input_transfer | Durante apply, se a entrada contém imagens não-display | Fornece veredito por arquivo, igual ao acima |
Linhas Vermelhas da Fidelidade
- RAW é uma decodificação aproximada. O libraw usa o balanço de branco da câmera + matriz de cores genérica, não o DCP da câmera. O mesmo estilo aplicado a câmeras diferentes terá desvios e não será o resultado do Lightroom. Para entrega precisa, o usuário deve exportar TIFF de 16 bits.
- O espaço âncora da LUT é sRGB gamma 2.2. Ao exportar
.cubepara terceiros, informe isso; caso contrário, a cor ficará totalmente errada. - Estilos com segmentação não podem ser representados por um único
.cube. OvN.cubeé apenas a camada base global; os mapeamentos por região estão emvN_<regiao>.cubee precisam de máscaras para combinar. Useexport --full. - Pixels superexpostos passam inalterados. No final da renderização, pixels com o canal mais claro ≥ 0,97 recebem uma transição suave de volta para a imagem original: áreas superexpostas não têm informação de cor, qualquer mapeamento seria invenção. Em contraluz, o disco solar é mapeado pela segmentação como uma mancha azul-acinzentada; com a proteção, ele retorna à cor quente da luz. O custo é que realces superexpostos e altamente saturados não são mais remapeados (teste com cartão de cores: treinamento ΔE 1,15 → 1,21). Use
--no-clip-guardse precisar comprimir também as áreas superexpostas. - Máscaras suaves têm uma zona de transição, propositalmente: dois mapeamentos diferentes se encontrando bruscamente criariam um contorno visível. O custo é que, na borda entre o céu e os galhos das árvores, pode haver alguns pixels de "meio-tom".
- A máscara tem apenas resolução 128×128. Estruturas mais finas que aproximadamente 1/128 do quadro (galhos secos, fios, grades finas) serão incluídas na região correspondente. Essas estruturas geralmente são próximas a silhuetas e não são perceptíveis após o escurecimento; para precisão de pixel, use ferramentas de máscara externas.
- Regiões de baixa confiança não são ajustadas. Céu superexposto e sem cor em contraluz, ou céu recortado por folhagem, onde o modelo não fornece uma máscara confiável — essas partes retornam para a LUT global. É melhor ajustar menos do que ajustar errado.
- Close-ups de animais podem ser classificados como pessoas. O ADE20K classifica um gato em close-up como pessoa (medido em 88% do quadro). Este skill usa a "proporção de tons de pele dentro da região person" como salvaguarda; abaixo de 5%, a região é zerada. Animais de pelagem muito clara ainda podem escapar.
- O modo
refnão deve ser promovido como "réplica exata". Ele apenas alinha a distribuição Lab; o ΔE medido é de aproximadamente 4; para precisão, usefit. - Os números dos controles deslizantes da camada de descrição são estimativas. A coluna
measuredpode ser reproduzida; aestimate_lightroomnão — não há solução única de LUT para controles deslizantes. - A autoavaliação sem referência só detecta "erros óbvios", não julga "se é bonito" . Ela usa alvos estatísticos clássicos (luminância mediana 0,45, preto 0,02, tom de pele 20°). Uma foto noturna deve ter baixa luminância mediana; um pôr do sol deve ser quente. Portanto, cada item tem uma faixa de tolerância; os alertas significam "dê uma olhada", não "está errado". Duas condições rígidas: o tom de pele só é avaliado quando a segmentação detecta pessoas; caso contrário, uma cena de rua quente com 30% da imagem seria interpretada como pele. O equilíbrio de branco só é avaliado quando há pixels neutros suficientes na cena.
- A detecção de log olha para a forma do histograma, não para os metadados. A essa altura, a imagem geralmente já é um PNG ou um resultado da renderização do libraw; o EXIF já foi perdido. Portanto, ele conta elevação do preto / realces comprimidos / faixa dinâmica estreita / baixa saturação. Se três ou mais itens coincidirem, classifica como
log; dois itens,ambiguous. Uma imagem final com estilo dessaturado e achatado pode ser classificada comoambiguous; é um falso positivo conhecido. - O estilo carrega propriedades de exposição; a LUT é confiável apenas dentro da faixa de brilho em que foi treinada. Um look que eleva a mediana de 0,19 para 0,28, quando aplicado a uma foto com mediana 0,55, fará a mediana subir para 0,95 — não é um bug, é que o look não tem margem para tantos realces. Medição real: o ajuste só observou o intervalo 0~0,42 da imagem original (p99 fonte 0,42 / p99 alvo 0,56); ao aplicar a uma imagem com p99 0,72, a parte além é extrapolada linearmente, resultando em realces estourados.
- Não equalize a exposição por padrão para o usuário.
--match-keypode primeiro alinhar a mediana da imagem ao nível do estilo antes de aplicar o LUT, mas para fotos com contraluz, o efeito é reverso: em uma foto real com contraluz (rosto 0,27, fundo 0,62), após alinhar pela mediana geral da imagem, o rosto cai para 0,08, o assunto fica preto. Portanto, ele está desabilitado por padrão e só deve ser ativado quando "a exposição de fotos em sequência com a mesma iluminação e posição de câmera estiver inconsistente". O critério é simples: a relação entre o brilho do assunto e o brilho do fundo nesta foto é consistente com a da foto de referência? - Quando, em um quadro, o assunto é mais escuro que o fundo, e no quadro de referência o assunto é o mais claro, nenhum mapeamento monotônico global pode agradar a ambos. Para satisfazer os dois, só com ajuste local. Nesse caso, priorize o assunto e aceite que o fundo fique mais claro; o contrário esmagaria o rosto.
- A dominância de cor quente existente no material pode se somar ao estilo quente, resultando em laranja-avermelhado. Um retrato com tom escuro e dominância de cor quente de 0,46 stops, ao receber o look quente, faz o rosto passar de marrom-quente para laranja-avermelhado, com o valor de
castchegando a 0,77, criando manchas nas bochechas. Neste caso, não faça primeiro a neutralização do balanço de branco — o único "neutro" da cena é geralmente a parede iluminada pela mesma luz; corrigir por ela tornará a parede esverdeada e o tom de pele amarelo-esverdeado (teste com 0,5 e 0,75 stops, ambos piores). A ordem correta é: primeiro reduza--strength(em torno de 0,6). Se ainda assim não for satisfatório, abandone a colorização e mude para uma curva de luminância pura ou ganho de exposição linear — mexa apenas no brilho, não na tonalidade, preservando o tom quente original. - Cada vez que um JPEG é salvo, ele perde um pouco, e de forma irreversível. A entrega deve ser feita com q98 de uma só vez; se for necessário editar novamente, use o
__master.png, não salve repetidamente sobre o mesmo JPEG. Salvar três vezes com q95, o PSNR cai de 48,7 para 48,6 — parece pouco, mas cada vez adiciona uma camada de blocos em degradês suaves (céu clareado, desfoque de fundo). Para determinar a origem da perda: se a imagem original já foi comprimida (< 0,10 bytes/pixel), a culpa é da fonte; não procure a causa nos parâmetros de codificação.
Linha de Base de Desempenho
| Cenário | Tempo (imagem de 10,8 MP) |
|---|---|
| Renderização puramente global | 3,6 s |
| Renderização com segmentação (2 regiões ativas) | 6,7 s |
| Inferência de segmentação (512 px) + upsampling | 0,7 s |
Um fit (10 pares, incluindo segmentação e tags) | ~12 s |
O gargalo está na leitura pixel a pixel da LUT (8 leituras aleatórias por pixel), não na lógica algorítmica. O uso de múltiplos processos em paralelo é mais eficiente para lotes.
Tarefas Comuns
| O que o usuário diz | O que fazer |
|---|---|
| "Ajuste o tom desta foto para igual à referência" | ref para gerar o estilo → apply em lote |
| "Estas fotos já estão editadas; quero usar este estilo daqui para frente" | fit --src original --tgt editada → apply |
| "Editei outro lote; quero que seja no mesmo estilo" | No fit, use --name apontando para o mesmo estilo; ele assimila e reajusta automaticamente |
| "Escureça apenas o céu, não mexa no chão nem no rosto" | A segmentação já está ativada por padrão; verifique se regions.active contém sky |
| "Este estilo está muito forte" | apply --strength 0.6; não precisa refazer o ajuste |
| "Esta versão está pior que a anterior" | apply --version <número da versão anterior> |
| "O lote anterior estava errado; desfaça" | batches para encontrar o batch_id → undo |
| "Qual estilo devo usar para este lote?" | apply --style auto e veja auto.ranking |
| "O que este estilo realmente fez?" | styles --name X e leia o description.summary |
| "Exportar para usar no DaVinci" | export --full; lembre-se de avisar sobre o espaço sRGB gamma 2.2 e que as camadas de região precisam de máscaras suaves |
| "Contraluz / nascer do sol, o sol não pode ficar cinza" | Já ativado por padrão (proteção de superexposição); se ainda estiver cinza, verifique se o sol está realmente superexposto |
| "Este ajuste está correto? Qual o problema?" | check --in X --person e repita os warnings um a um; não adicione julgamento próprio |
| "Isto é material log?" | check --in X e veja transfer.verdict e transfer.signals |
| "Este estilo vai queimar os realces?" | O look do apply já dá a comparação antes/depois; warnings apontará diretamente |
| "Esta imagem está muito mais clara que a referência; após aplicar, ficou esbranquiçada" | Primeiro, veja a diferença em look.median_luma; reduza --strength ou, para fotos em sequência com a mesma iluminação, use --match-key on |
| "O assunto está mais escuro que o fundo; após aplicar, o assunto ficou preto" | Não ative --match-key; a relação de contraste entre esta foto e a referência é diferente; só ajuste local |
| "A imagem já estava quente; após aplicar o estilo, o rosto ficou alaranjado" | Reduza --strength para 0,6 primeiro; não neutralize o balanço de branco (isso tornaria a parede esverdeada); veja a Regra 16 |
Referências
references/style-schema.md: Estrutura de diretório de estilo, camposstyle.json, semântica de versão e lotereferences/color-pipeline.md: Gerenciamento de cores, detalhes algorítmicos, limitações conhecidas e recomendações de parâmetros