Blog / Engenharia

PNGs transparentes quebram ampliadores por IA: o bug do halo escuro

· estudo de bug, com a correção medida · sujeito: o pipeline ×4 de produção

Alimente quase qualquer ampliador por IA com um PNG com transparência (uma foto recortada, um adesivo exportado) e um defeito específico aparece: um halo escuro avançando alguns pixels para dentro do sujeito a partir de cada borda transparente, com um anel claro e tênue de sobressinal atrás dele. Ele se esconde em pré-visualizações escuras e salta aos olhos em páginas brancas. Nós o reproduzimos no nosso próprio pipeline, medimos sua forma exata (um fosso de −94 de luma, com três pixels de modelo de largura), o rastreamos até a ida e volta do alfa pré-multiplicado da plataforma web e medimos a correção que entregamos: um flood fill O(n) que remove 98% do erro antes de o modelo sequer rodar.

−93.9erro de luma, primeiro anel de pixelscaminho ingênuo vs referência limpa
≈3 pxlargura do fosso na escala do modelodepois um anel de sobressinal de +17
63×redução do erro com a correção−62.9 → 1.1 de média, faixa de 0–3 px
O(n)custo da correçãouma passagem de BFS, sem inferência extra

Cada número desta nota foi medido no código do pipeline de produção: os mesmos modelos, a mesma matemática. Registros brutos por imagem: e3_alpha.json.

1 · De onde vem o preto

O canvas 2D da web armazena os pixels pré-multiplicados: não (r, g, b, α), mas (rα, gα, bα, α). A composição adora esse formato (os operadores de Porter–Duff[1] viram uma única multiplicação-adição), mas ele é com perdas em exatamente um lugar. Ler os pixels de volta significa dividir a cor por α:

r = ⌊ rα / α ⌉, indefinido em α = 0(1)

Em α = 0 isso é 0/0, e a resposta da plataforma[2] é que não sobrou nada para recuperar: a cor foi multiplicada por zero no instante em que tocou o canvas. getImageData devolve RGB (0, 0, 0) sob todo pixel totalmente transparente. O campo transparente do seu recorte silenciosamente virou um lago de preto puro.

Um humano nunca vê o lago: o alfa o esconde. Uma rede de super-resolução não é um humano: ela consome três canais de RGB e nenhum alfa, então vê uma fotografia mergulhada em vantablack, e cada pixel de saída perto da fronteira é calculado a partir de um campo receptivo que mergulha no lago. O preto não fica parado; ele se difunde para dentro pela pilha de convoluções[3].

2 · Medindo o halo

ingênuo (canvas RGB=0)sangria de alfa (a nossa)
-100-75-50-2502513581216distância L1 para dentro do sujeito (px, escala do modelo)Δ luma vs referênciaperfeito
Figura 1. O halo, em perfil. Caminho ingênuo (violeta): um fosso de −94 de luma no primeiro anel de pixels dentro do sujeito, −90 a 2 px, −54 a 3 px, depois um anel de sobressinal de +17 a 5 px decaindo a zero por volta de ~13 px; contaminação escura através de um campo receptivo finito, afiada pela GAN em uma borda. Caminho de produção (verde): nunca pior que 1.5 níveis de luma. Distâncias na escala do modelo; ×4 para pixels de saída.
Três painéis: a entrada recortada mostrada sobre um tabuleiro de xadrez, a ampliação ingênua com uma borda escura ao redor da oval e a ampliação limpa com sangria de alfa
Figura 2. Entrada com transparência (esquerda), caminho ingênuo (meio), caminho de produção (direita), compostos sobre um fundo claro. A borda do painel do meio parece uma sutil “borda suja” em tamanho de miniatura, que é exatamente por que este bug entra em produção tão frequentemente: ele se esconde até um usuário colocar o adesivo sobre um fundo claro.

Uma medição genuinamente contraintuitiva: esperávamos que o modelo alucinasse lixo dentro da região transparente, “aprimorar o vazio”. Ele não faz isso. Preto chapado entra, preto chapado sai (energia de gradiente 0.1). O dano não é o que o modelo inventa no vazio; é o que o vazio faz ao sujeito através da fronteira. Isso importa porque significa que mascarar a saída não pode salvar você: os pixels danificados estão dentro da região visível, sob α > 0.

3 · A correção: inundar o vazio com cor plausível

Se o modelo precisa ver RGB sob α = 0, que seja um RGB que continue o sujeito. Antes da inferência rodamos a sangria de alfa (alpha bleeding): uma inundação em largura com múltiplas fontes a partir de cada pixel não transparente, em que cada pixel transparente recebe a cor média dos seus 4-vizinhos já coloridos:

c(p) = média{ c(q) : q ∈ N₄(p), q colorido },  p em ordem de BFS(2)

Semear a fila com o anel da fronteira e processar em FIFO garante que todo pixel retirado da fila tem pelo menos um vizinho colorido (quem o enfileirou terminou primeiro), então uma única passagem O(n) colore o vazio inteiro: distância ilimitada, buracos fechados incluídos. Os bytes de alfa nunca são tocados; pixels semitransparentes mantêm a própria cor. O modelo então vê uma continuação suave em vez de um penhasco, e o alfa (ampliado separadamente) é reaplicado na saída. Efeito medido: o erro na faixa de 0–3 px cai de -62.9 para 1.1 níveis de luma, uma redução de 63×, sem custo mensurável de tempo de execução.

Detalhe da borda das reconstruções ingênua e com sangria, mais um mapa de calor da diferença amplificada mostrando o halo abraçando a fronteira do alfa
Figura 3. Detalhe da borda: o caminho ingênuo (esquerda) ao lado do caminho com sangria (meio) e, à direita, a |diferença| entre as duas reconstruções, amplificada ×8. O halo acompanha a fronteira do alfa e nada mais; longe da borda as saídas são idênticas, que é a correção se comportando exatamente como projetada.

4 · O que isso significa para as suas imagens

Se você usa este site: nada; PNGs transparentes seguem o caminho medido acima tanto no pipeline na nuvem quanto no motor de reserva no navegador, já que os dois compartilham a mesma biblioteca pura. Se você constrói ferramentas de imagem: trate todo modelo só de RGB como alérgico a zeros pré-multiplicados: faça a sangria antes da inferência (a inundação acima tem ~30 linhas sem dependências), ou aplique um fundo de cor sólida e aceite o viés desse fundo. E se você está diagnosticando uma misteriosa borda escura na saída do aprimorador de alguém, agora conhece a impressão digital: um fosso de alguns pixels de largura, um anel de sobressinal atrás dele e uma correlação perfeita com a fronteira do alfa. Os três números para lembrar: −94, 3 px, 63×.

Referências

  1. Porter, T., & Duff, T. (1984). Compositing Digital Images. SIGGRAPH '84, Computer Graphics 18(3), 253–259. (The premultiplied-alpha compositing algebra.) dl.acm.org
  2. WHATWG HTML Living Standard, the 2D canvas element: premultiplied backing stores and the lossy getImageData round-trip for non-opaque pixels. html.spec.whatwg.org
  3. Wang, X., Xie, L., Dong, C., & Shan, Y. (2021). Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. ICCV Workshops. arXiv:2107.10833. arxiv.org
  4. Lange, D. (1936). “Migrant Mother” (Destitute pea pickers in California), Farm Security Administration. Public domain, via Wikimedia Commons. commons.wikimedia.org
  5. ONNX Runtime: the inference engine both our server (CPU) and in-browser (WebGPU/WASM) engines run on. onnxruntime.ai