Blog / Ingénierie

Les PNG transparents cassent les agrandisseurs IA : le bug du halo sombre

· étude de bug, avec la correction mesurée · sujet : le pipeline ×4 de production

Donne un PNG avec transparence (une photo détourée, un export de sticker) à presque n'importe quel agrandisseur IA et un défaut précis apparaît : un halo sombre qui s'insinue de quelques pixels dans le sujet depuis chaque bord transparent, avec derrière lui un léger anneau clair de dépassement. Il se cache sur les aperçus sombres et saute aux yeux sur les pages blanches. Nous l'avons reproduit sur notre propre pipeline, mesuré sa forme exacte (une tranchée de −94 de luma, large de trois pixels du modèle), remonté jusqu'à l'aller-retour alpha prémultiplié de la plateforme web, et mesuré la correction que nous livrons : un remplissage par diffusion en O(n) qui retire 98 % de l'erreur avant même que le modèle ne tourne.

−93,9erreur de luma, premier anneau de pixelschemin naïf vs référence propre
≈3 pxlargeur de la tranchée à l'échelle du modèlepuis un anneau de dépassement de +17
63×réduction de l'erreur par la correction−62,9 → 1,1 en moyenne, bande de 0–3 px
O(n)coût de la correctionune passe BFS, aucune inférence en plus

Chaque chiffre de cette note a été mesuré sur le code du pipeline de production : mêmes modèles, mêmes calculs. Données brutes par image : e3_alpha.json.

1 · D'où vient le noir

Le canvas 2D du web stocke les pixels prémultipliés : non pas (r, g, b, α) mais (rα, gα, bα, α). La composition adore ce format (les opérateurs de Porter-Duff[1] deviennent de simples multiplications-additions), mais il est destructif à exactement un endroit. Relire les pixels revient à diviser la couleur par α :

r = ⌊ rα / α ⌉, undefined at α = 0(1)

À α = 0, c'est 0/0, et la réponse de la plateforme[2] est qu'il ne reste rien à récupérer : la couleur a été multipliée par zéro à l'instant où elle a touché le canvas. getImageData renvoie un RVB de (0, 0, 0) sous chaque pixel entièrement transparent. Le champ transparent de ton détourage est silencieusement devenu un lac de noir pur.

Un humain ne voit jamais le lac : l'alpha le cache. Un réseau de super-résolution n'est pas un humain : il consomme trois canaux de RVB et pas d'alpha, si bien qu'il voit une photographie plongée dans du vantablack, et chaque pixel de sortie près de la frontière est calculé à partir d'un champ récepteur qui trempe dans le lac. Le noir ne reste pas en place ; il diffuse vers l'intérieur à travers la pile de convolutions[3].

2 · Mesurer le halo

naïf (canvas RVB=0)diffusion alpha (la nôtre)
-100-75-50-2502513581216distance L1 dans le sujet (px, échelle du modèle)Δ luma vs référenceparfait
Figure 1. Le halo, profilé. Chemin naïf (violet) : une tranchée de −94 de luma dans le premier anneau de pixels à l'intérieur du sujet, −90 à 2 px, −54 à 3 px, puis un anneau de dépassement de +17 à 5 px qui décroît jusqu'à zéro vers ~13 px ; une contamination sombre à travers un champ récepteur fini, aiguisée en contour par le GAN. Chemin de production (vert) : jamais pire que 1,5 niveau de luma. Distances à l'échelle du modèle ; ×4 pour les pixels de sortie.
Trois panneaux : l'entrée détourée montrée sur un damier, l'agrandissement naïf avec un liseré sombre autour de l'ovale, et l'agrandissement propre avec diffusion alpha
Figure 2. Entrée avec transparence (gauche), chemin naïf (milieu), chemin de production (droite), composités sur un fond clair. Le liseré du panneau du milieu se lit comme un léger « bord sale » à la taille d'une vignette, ce qui est exactement pourquoi ce bug est livré si souvent : il se cache jusqu'à ce qu'un utilisateur pose le sticker sur un fond clair.

Une mesure réellement contre-intuitive : nous nous attendions à ce que le modèle hallucine des déchets à l'intérieur de la zone transparente, qu'il « améliore le vide ». Il ne le fait pas. Noir uni en entrée, noir uni en sortie (énergie de gradient 0,1). Le mal n'est pas ce que le modèle invente dans le vide ; c'est ce que le vide fait au sujet à travers la frontière. C'est important, parce que cela signifie que masquer la sortie ne peut pas te sauver : les pixels abîmés sont à l'intérieur de la zone visible, sous α > 0.

3 · La correction : inonder le vide d'une couleur plausible

Si le modèle doit voir du RVB sous α = 0, que ce soit un RVB qui prolonge le sujet. Avant l'inférence, nous exécutons une diffusion alpha : une inondation en largeur d'abord à sources multiples depuis chaque pixel non transparent, où chaque pixel transparent prend la couleur moyenne de ses 4-voisins déjà colorés :

c(p) = mean{ c(q) : q ∈ N₄(p), q colored }, p in BFS order(2)

Amorcer la file avec l'anneau de frontière et traiter en FIFO garantit que chaque pixel défilé a au moins un voisin coloré (celui qui l'a enfilé a terminé avant), si bien qu'une seule passe en O(n) colore tout le vide : distance illimitée, trous enclavés compris. Les octets d'alpha ne sont jamais touchés ; les pixels semi-transparents gardent leur propre couleur. Le modèle voit alors un prolongement lisse au lieu d'une falaise, et l'alpha (agrandi séparément) est réappliqué à la sortie. Effet mesuré : l'erreur de la bande de 0–3 px tombe de -62,9 à 1,1 niveau de luma, une réduction de 63×, sans coût d'exécution mesurable.

Détail de bord des reconstructions naïve et avec diffusion, plus une carte de chaleur de différence amplifiée montrant le halo collé à la frontière alpha
Figure 3. Détail de bord : le chemin naïf (gauche) à côté du chemin avec diffusion (milieu), et à droite la |différence| entre les deux reconstructions, amplifiée ×8. Le halo suit la frontière alpha et rien d'autre ; loin du bord, les sorties sont identiques, ce qui est la correction se comportant exactement comme prévu.

4 · Ce que cela signifie pour tes images

Si tu utilises ce site : rien ; les PNG transparents empruntent le chemin mesuré ci-dessus dans le pipeline cloud comme dans le moteur de repli du navigateur, puisque les deux partagent la même bibliothèque pure. Si tu construis des outils d'image : considère tout modèle RVB seul comme allergique aux zéros prémultipliés : diffuse avant l'inférence (l'inondation ci-dessus tient en ~30 lignes sans dépendance), ou aplatis sur une couleur unie et accepte le biais de l'aplat. Et si tu diagnostiques un mystérieux bord sombre dans la sortie de l'agrandisseur de quelqu'un, tu connais désormais l'empreinte : une tranchée de quelques pixels de large, un anneau de dépassement derrière elle, et une corrélation parfaite avec la frontière alpha. Les trois chiffres à retenir : −94, 3 px, 63×.

Références

  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