82 votes

Supprimer le fond blanc d'une image et la rendre transparente

Nous essayons de faire ce qui suit dans Mathematica - RMagick supprime le fond blanc de l'image et le rend transparent .

Mais avec des photos réelles, cela finit par donner un mauvais résultat (comme un halo autour de l'image).

Voici ce que nous avons essayé jusqu'à présent :

unground0[img_] := With[{mask = ChanVeseBinarize[img, TargetColor->{1.,1.,1.}]},
  Rasterize[SetAlphaChannel[img, ImageApply[1-#&, mask]], Background->None]]]

Voici un exemple de ce que cela fait.

Image originale :

original image

Image dont le fond blanc a été remplacé par un fond vide (ou, pour les besoins de la démonstration, un fond rose) :

image with transparent background -- actually a pink background here, to make the halo problem obvious

Des idées pour se débarrasser de ce halo ? En modifiant des éléments comme LevelPenalty, je n'arrive à faire disparaître le halo qu'au prix d'une perte partielle de l'image.

EDIT : Afin que je puisse comparer les solutions pour la prime, veuillez structurer votre solution comme ci-dessus, à savoir une fonction autonome nommée unground-something qui prend une image et renvoie une image avec un fond transparent.

1 votes

Merci beaucoup à tous pour l'aide apportée jusqu'à présent ! Une grosse prime est prévue dès que stackoverflow me permettra d'en ajouter une. Et, conformément à l'esprit de stackoverflow tel qu'il a été formulé par les fondateurs, vous devriez vous sentir libre de voler les uns des autres pour faire de votre réponse la réponse définitive !

3 votes

D'abord 500 de prime et ensuite "Je vous encourage tous à emprunter généreusement les uns des autres pour l'améliorer si possible !". -- vous voulez un combat de chiens, n'est-ce pas ?

0 votes

@Mr.Wizard, :) Je n'invente rien, les fondateurs (Jeff et Joel) ont dit depuis le début que c'était encouragé. L'idée est que la meilleure réponse soit vraiment complète et définitive. (Et évidemment, j'ai aussi des arrière-pensées dans ce cas !)

48voto

Szabolcs Points 12411

Cette fonction met en œuvre le mélange inverse décrit par Mark Ransom, pour une amélioration supplémentaire, petite mais visible :

reverseBlend[img_Image, alpha_Image, bgcolor_] :=
 With[
  {c = ImageData[img], 
   a = ImageData[alpha] + 0.0001, (* this is to minimize ComplexInfinitys and considerably improve performance *)
   bc = bgcolor},

  ImageClip@
   Image[Quiet[(c - bc (1 - a))/a, {Power::infy, 
       Infinity::indet}] /. {ComplexInfinity -> 0, Indeterminate -> 0}]
  ]

Il s'agit de la fonction de suppression de l'arrière-plan. Le site threshold est utilisé pour la binarisation initiale de l'image, le paramètre minSizeCorrection permet d'ajuster la taille limite des petites composantes indésirables à supprimer après la binarisation.

removeWhiteBackground[img_, threshold_: 0.05, minSizeCorrection_: 1] :=
  Module[
  {dim, bigmask, mask, edgemask, alpha},
  dim = ImageDimensions[img];
  bigmask = 
   DeleteSmallComponents[
    ColorNegate@
     MorphologicalBinarize[ColorNegate@ImageResize[img, 4 dim], threshold], 
    Round[minSizeCorrection Times @@ dim/5]];
  mask = ColorNegate@
    ImageResize[ColorConvert[bigmask, "GrayScale"], dim];
  edgemask = 
   ImageResize[
    ImageAdjust@DistanceTransform@Dilation[EdgeDetect[bigmask, 2], 6],
     dim];
  alpha = 
   ImageAdd[
    ImageSubtract[
     ImageMultiply[ColorNegate@ColorConvert[img, "GrayScale"], 
      edgemask], ImageMultiply[mask, edgemask]], mask];
  SetAlphaChannel[reverseBlend[img, alpha, 1], alpha]
  ]

Test de la fonction :

img = Import["http://i.stack.imgur.com/k7E1F.png"];

background = 
  ImageCrop[
   Import["http://cdn.zmescience.com/wp-content/uploads/2011/06/\
forest2.jpg"], ImageDimensions[img]];

result = removeWhiteBackground[img]

ImageCompose[background, result]
Rasterize[result, Background -> Red]
Rasterize[result, Background -> Black]

Sample

Brève explication de son fonctionnement :

  1. Choisissez votre méthode de binarisation préférée qui produit des bords nets relativement précis.

  2. Appliquez-le à une image à échelle supérieure, puis réduisez l'échelle de l'image obtenue. mask à la taille originale. Cela nous donne l'anticrénelage. Le gros du travail est fait.

  3. Pour une petite amélioration, fusionnez l'image sur le fond en utilisant la luminosité de son négatif comme alpha, puis fusionnez l'image obtenue sur l'original dans une fine région autour des bords ( edgemask ) pour réduire la visibilité des pixels blancs sur les bords. Le canal alpha correspondant à ces opérations est calculé (l'option quelque peu cryptique ImageMultiply/Add expression).

  4. Maintenant, nous avons une estimation du canal alpha et nous pouvons faire un mélange inversé.

Les étapes 3 et 4 n'améliorent pas tant que ça la situation, mais la différence est visible.

0 votes

@belisarius ce n'est pas une question d'anglais, je sais que mon nom est très inhabituel pour la plupart :-)

0 votes

Cela ressemble à un nom de famille hongrois assez ordinaire pour moi :)

0 votes

@belisarius En fait, il s'agit d'un prénom, ou plus précisément d'un prénom, car en hongrois le nom de famille vient en premier et le prénom en dernier.

45voto

belisarius Points 45827

Peut-être, en fonction de la qualité des bords dont vous avez besoin :

img = Import@"http://i.stack.imgur.com/k7E1F.png";
mask = ChanVeseBinarize[img, TargetColor -> {1., 1., 1.}, "LengthPenalty" -> 10]
mask1 = Blur[Erosion[ColorNegate[mask], 2], 5]
Rasterize[SetAlphaChannel[img, mask1], Background -> None]

enter image description here

Modifier

Stealing a bit from @Szabolcs

img2 = Import@"http://i.stack.imgur.com/k7E1F.png";
(*key point:scale up image to smooth the edges*)
img = ImageResize[img2, 4 ImageDimensions[img2]];
mask = ChanVeseBinarize[img, TargetColor -> {1., 1., 1.}, "LengthPenalty" -> 10];
mask1 = Blur[Erosion[ColorNegate[mask], 8], 10];
f[col_] := Rasterize[SetAlphaChannel[img, mask1], Background -> col, 
                     ImageSize -> ImageDimensions@img2]
GraphicsGrid[{{f@Red, f@Blue, f@Green}}]

enter image description here

Cliquez pour agrandir

Edit 2

Juste pour avoir une idée de l'étendue de la halo et les imperfections de l'arrière-plan de l'image :

img = Import@"http://i.stack.imgur.com/k7E1F.png";
Join[{img}, MapThread[Binarize, {ColorSeparate[img, "HSB"], {.01, .01, .99}}]]

enter image description here

ColorNegate@ImageAdd[EntropyFilter[img, 1] // ImageAdjust, ColorNegate@img]

enter image description here

0 votes

Malheureusement, sur ma machine, votre code ne produit pas du tout la même qualité de résultat. Est-ce que img était l'image 500x500 comme indiqué dans la question ? Si oui, c'est peut-être un problème entre Mac et Windows...

0 votes

@Matthias Oui, l'image est un copier/coller de l'original. Mma 8.01 sur Windows.

0 votes

Oh... peut-être que l'optimiseur produit un résultat différent en raison d'un minuscule bruit arithmétique. Quoi qu'il en soit, je suis heureux que cela fonctionne bien pour vous en utilisant ce jeu de paramètres.

22voto

Mark Ransom Points 132545

Je vais parler de manière générale, sans me référer spécifiquement à Mathematica. Je n'ai aucune idée si ces opérations sont difficiles ou triviales.

La première étape consiste à estimer un niveau alpha (transparence) pour les pixels du bord de l'image. Pour l'instant, vous utilisez un seuil strict, donc l'alpha est soit 0% totalement transparent, soit 100% totalement opaque. Vous devriez définir une plage entre le blanc total de l'arrière-plan et les couleurs qui font indiscutablement partie de l'image, et définir une proportion appropriée - si la couleur est plus proche de celle de l'arrière-plan, il s'agit d'un alpha faible, et si elle est plus proche du seuil le plus sombre, il s'agit d'un alpha élevé. Ensuite, vous pouvez effectuer des ajustements en fonction des valeurs alpha environnantes : plus un pixel est entouré de transparence, plus il est susceptible d'être lui-même transparent.

Une fois que vous avez les valeurs alpha, vous devez faire un mélange inversé pour obtenir la bonne couleur. Lorsqu'une image est affichée sur un fond, elle est mélangée en fonction de la valeur alpha à l'aide de la formule suivante c = bc*(1-a)+fc*a donde bc est la couleur de fond et fc est la couleur d'avant-plan. Dans votre cas, l'arrière-plan est blanc (255,255,255) et la couleur d'avant-plan est inconnue, nous inversons donc la formule : fc = (c - bc*(1-a))/a . Quand a=0 la formule prévoit une division par zéro, mais la couleur n'a pas d'importance de toute façon, alors utilisez simplement le noir ou le blanc.

3 votes

Bonne réponse. L'estimation de l'alpha est en fait un domaine de recherche à part entière. ai.stanford.edu/~ruzon/alpha

2 votes

D'accord, excellente réponse ; merci Mark ! Pour la prime (lorsque stackoverflow me permettra d'en ajouter une), j'ai l'intention de choisir la solution la plus aboutie. Jusqu'à présent, je pense à celle de Belisarius.

11voto

JxB Points 697

Voici un essai de mise en œuvre de l'approche de Mark Ransom, avec l'aide de la génération de masques de belisarius :

Localisez la limite de l'objet :

img1 = SetAlphaChannel[img, 1];
erosionamount=2;
mb = ColorNegate@ChanVeseBinarize[img, TargetColor -> {1., 1., 1}, 
      "LengthPenalty" -> 10];
edge = ImageSubtract[Dilation[mb, 2], Erosion[mb, erosionamount]];

ImageApply[{1, 0, 0} &, img, Masking ->edge]

figure edge

Définissez les valeurs alpha :

edgealpha = ImageMultiply[ImageFilter[(1 - Mean[Flatten[#]]^5) &, 
   ColorConvert[img, "GrayScale"], 2, Masking -> edge], edge];
imagealpha = ImageAdd[edgealpha, Erosion[mb, erosionamount]];
img2 = SetAlphaChannel[img, imagealpha];

Mélange de couleurs inversé :

img3 = ImageApply[Module[{c, \[Alpha], bc, fc},
   bc = {1, 1, 1};
   c = {#[[1]], #[[2]], #[[3]]};
   \[Alpha] = #[[4]];
   If[\[Alpha] > 0, Flatten[{(c - bc (1 - \[Alpha]))/\[Alpha], \[Alpha]}], {0., 0., 
   0., 0}]] &, img2];

Show[img3, Background -> Pink]

pink background

Vous avez remarqué que certains des bords ont un duvet blanc ? Comparez cela avec le contour rouge de la première image. Nous avons besoin d'un meilleur détecteur de bords. L'augmentation de la quantité d'érosion aide à réduire le flou, mais les autres côtés deviennent trop transparents, il y a donc un compromis sur la largeur du masque de bord. C'est assez bon, cependant, considérant qu'il n'y a pas d'opération de flou, en soi.

Il serait instructif d'exécuter l'algorithme sur une variété d'images pour tester sa robustesse, pour voir dans quelle mesure il est automatique.

0 votes

Hmmm, pour moi l'image 2 semble meilleure (voir le bas de la surface du tableau) que l'image 3. Peut-être que le mélange de couleurs inversé est inutile ?

10voto

cormullion Points 1117

Je m'amuse juste en tant que débutant - c'est incroyable le nombre d'outils disponibles.

b = ColorNegate[
    GaussianFilter[MorphologicalBinarize[i, {0.96, 0.999}], 6]];
c = SetAlphaChannel[i, b];
Show[Graphics[Rectangle[], Background -> Orange, 
     PlotRangePadding -> None], c]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X