web-dev-qa-db-fra.com

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 avoir l'air moche (comme avoir 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 avec le fond blanc remplacé par aucun fond (ou, à des fins de démonstration ici, un fond rose):

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

Des idées pour vous débarrasser de ce halo? En ajustant des choses comme LevelPenalty, je ne peux que faire disparaître le halo au détriment de la perte d'une partie de l'image.

EDIT: Je peux donc comparer les solutions pour la prime, veuillez structurer votre solution comme ci-dessus, à savoir une fonction autonome nommée unground-quelque chose qui prend une image et renvoie une image avec un fond transparent. Merci beaucoup, tout le monde!

81
dreeves

En fonction de la qualité Edge 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

Modifier 2

Juste pour avoir une idée de l'étendue des halo et des imperfections d'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

44
Dr. belisarius

Cette fonction implémente 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 d'arrière-plan. Le paramètre threshold est utilisé pour la binarisation initiale de l'image, le minSizeCorrection sert à ajuster la taille limite des petits composants 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 binariaztion préférée qui produit des arêtes vives relativement précises

  2. Appliquez-le à une image mise à l'échelle, puis réduisez l'échelle mask obtenue à la taille d'origine. Cela nous donne l'anticrénelage. La plupart du travail est fait.

  3. Pour une petite amélioration, mélangez l'image sur l'arrière-plan en utilisant la luminosité de son négatif comme alpha, puis mélangez l'image obtenue sur l'original dans une fine zone 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'expression quelque peu cryptique ImageMultiply/Add).

  4. Maintenant, nous avons une estimation du canal alpha afin que nous puissions faire un mélange inverse.

Les étapes 3 et 4 ne s'améliorent pas beaucoup, mais la différence est visible.

48
Szabolcs

Je vais parler de façon générique, pas spécifiquement en référence à 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 sur le bord de l'image. En ce moment, vous utilisez un seuil strict, donc l'alpha est soit 0% totalement transparent ou 100% totalement opaque. Vous devez définir une plage entre le blanc total de l'arrière-plan et les couleurs qui font incontestablement partie de l'image, et définir une proportion appropriée - si sa couleur est plus proche de l'arrière-plan, son alpha est faible, et si elle est plus proche de la coupure plus sombre, alors c'est un alpha élevé. Après cela, vous pouvez effectuer des ajustements en fonction des valeurs alpha environnantes - plus un pixel est entouré de transparence, plus il est susceptible d'être transparent lui-même.

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

22
Mark Ransom

Voici un essai d'implémentation 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

Remarquez comment certains 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 bord. L'augmentation de la quantité d'érosion facilite le flou, mais les autres côtés deviennent alors trop transparents, il y a donc un compromis sur la largeur du masque de bord. C'est plutôt bien, étant donné 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 à quel point il est automatique.

11
JxB

Il suffit de jouer 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]

10
cormullion

Je suis complètement nouveau dans le traitement d'image, mais voici ce que j'obtiens après avoir joué avec les nouvelles fonctions de traitement d'image morphologique de la version 8:

mask = DeleteSmallComponents[
   ColorNegate@
    Image[MorphologicalComponents[ColorNegate@img, .062, 
      Method -> "Convex"], "Bit"], 10000];
Show[Graphics[Rectangle[], Background -> Red, 
  PlotRangePadding -> None], SetAlphaChannel[img, ColorNegate@mask]]

image

9
Alexey Popkov

Je recommande d'utiliser Photoshop pour cela et d'enregistrer au format PNG.

Mesures possibles que vous pourriez prendre:

  • dilater le masque
  • le brouiller
  • à l'aide du masque, définissez la transparence par la distance par rapport au blanc
  • en utilisant le masque, ajustez la saturation de sorte que les couleurs précédemment plus blanches soient plus saturées.
5
Mr.Wizard

Il suffit de remplacer tout pixel "presque proche du blanc" par un pixel de la même couleur RVB et un dégradé sigmoïde sur le canal de transparence. Vous pouvez appliquer une transition linéaire du solide au transparent, mais Sinusoid ou Sigmoid ou Tanh semblent plus naturels, selon la netteté du bord que vous recherchez, ils s'éloignent rapidement du médium pour être solides ou transparents, mais pas en pas/binaire manière, qui est ce que vous avez maintenant.

Pense-y de cette façon:

Disons que R, G, B sont chacun 0,0-1,0, puis représentons le blanc comme un nombre unique comme R + G + B = 1,0 * 3 = 3,0.

Retirer un peu de chaque couleur le rend un peu "blanc cassé", mais en retirer un peu des 3, c'est le retirer beaucoup plus qu'un peu. Disons que vous autorisez une réduction de 10% sur n'importe quel canal: 1.0 * .10 = .1, répartissez maintenant cette perte sur les trois et liez-la entre 0 et 1 pour le canal alpha, si elle est inférieure à 0,1, de sorte que ( perte = 0,9) => 0 et (perte = 1,0) => 1:

threshold=.10;
maxLoss=1.0*threshold;
loss=3.0-(R+G+B);
alpha=If[loss>maxLoss,0,loss/maxLoss];
(* linear scaling is used above *)
(* or use 1/(1 + Exp[-10(loss - 0.5maxLoss)/maxLoss]) to set sigmoid alpha *)
(* Log decay: Log[maxLoss]/Log[loss]
      (for loss and maxLoss <1, when using RGB 0-255, divide by 255 to use this one *)

setNewPixel[R,G,B,alpha];

Pour référence:

maxLoss = .1;
Plot[{ 1/(1 + Exp[-10(loss - 0.5maxLoss)/maxLoss]),
       Log[maxLoss]/Log[loss],
       loss/maxLoss
     }, {loss, 0, maxLoss}]

Le seul danger (ou avantage?) Que vous ayez à cela, c'est que cela ne se soucie pas des blancs qui font réellement partie de la photo. Il supprime tous les blancs. De sorte que si vous avez une photo de voiture blanche, elle finira par avoir des patchs transparents. Mais d'après votre exemple, cela semble être un effet souhaité.

3
Gregory Klopper