Entfernen Sie den weißen Hintergrund von einem Bild und machen Sie es transparent

82

In Mathematica versuchen wir Folgendes zu tun: RMagick entfernt den weißen Hintergrund aus dem Bild und macht es transparent .

Bei tatsächlichen Fotos sieht es jedoch mies aus (wie ein Heiligenschein um das Bild).

Folgendes haben wir bisher versucht:

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

Hier ist ein Beispiel dafür, was das bewirkt.

Original Bild:

Original Bild

Bild mit weißem Hintergrund ohne Hintergrund (oder zu Demonstrationszwecken hier mit rosa Hintergrund):

Bild mit transparentem Hintergrund - eigentlich ein rosa Hintergrund hier, um das Halo-Problem offensichtlich zu machen

Irgendwelche Ideen, um diesen Heiligenschein loszuwerden? Wenn ich Dinge wie LevelPenalty optimiere, kann ich den Heiligenschein nur auf Kosten des Verlusts eines Teils des Bildes verschwinden lassen.

BEARBEITEN: Damit ich Lösungen für das Kopfgeld vergleichen kann, strukturieren Sie bitte Ihre Lösung wie oben beschrieben, nämlich eine in sich geschlossene Funktion namens unground - etwas, das ein Bild aufnimmt und ein Bild mit transparentem Hintergrund zurückgibt.

träumt
quelle
1
Vielen Dank für die bisherige Hilfe an alle! Sobald der Stapelüberlauf es mir ermöglicht, eine hinzuzufügen, kommt eine große Prämie auf mich zu. Und gemäß dem von den Gründern artikulierten Geist des Stackoverflow sollten Sie sich frei fühlen, voneinander zu stehlen, um Ihre Antwort zur endgültigen zu machen!
Dreeves
3
Zuerst 500 Kopfgeld und dann "Ich ermutige Sie alle, sich großzügig voneinander zu leihen, um es nach Möglichkeit zu verbessern!" - Sie wollen einen Hundekampf, nicht wahr?
Mr.Wizard
@ Mr.Wizard, :) Ich denke mir das aber nicht aus, dass die Gründer (Jeff und Joel) von Anfang an gesagt haben, dass das ermutigt ist. Die Idee ist, dass die Top-Antwort wirklich vollständig und endgültig ist. (Und natürlich habe ich auch in diesem Fall Hintergedanken!)
Dreeves
2
Für die allzu Neugierigen ist dies der IKEA-Computerarbeitsplatz "FREDRIK": ikea.com/us/en/catalog/products/60111123
Arnoud Buzing,
1
@dreeves, ich habe tineye.com verwendet .
Arnoud Buzing

Antworten:

45

Je nach Kantenqualität benötigen Sie möglicherweise:

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]

Geben Sie hier die Bildbeschreibung ein

Bearbeiten

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}}]

Geben Sie hier die Bildbeschreibung ein

klicken um zu vergrößern

Bearbeiten 2

Nur um eine Vorstellung vom Ausmaß der Halo- und Hintergrundfehler im Bild zu bekommen:

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

Geben Sie hier die Bildbeschreibung ein

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

Geben Sie hier die Bildbeschreibung ein

Dr. Belisarius
quelle
Leider erzeugt Ihr Code auf meinem Computer überhaupt nicht die gleiche Ergebnisqualität. War img das 500x500 Bild wie in der Frage gepostet? Wenn ja, vielleicht eine Mac / Windows-Sache ...
Matthias Odisio
@Matthias Ja, das Bild ist ein Kopieren / Einfügen aus dem Original. Mma 8.01 unter Windows.
Dr. Belisarius
Oh ... vielleicht liefert der Optimierer aufgrund des winzigen arithmetischen Rauschens ein anderes Ergebnis. Wie auch immer, ich bin froh, dass es mit diesen Parametern gut für Sie funktioniert.
Matthias Odisio
Das sieht nicht so aus, als würde es funktionieren. Es verwischt nur die Ränder.
user541686
48

Diese Funktion implementiert die von Mark Ransom beschriebene umgekehrte Mischung für eine zusätzliche kleine, aber sichtbare Verbesserung:

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}]
  ]

Dies ist die Funktion zum Entfernen des Hintergrunds. Der thresholdParameter wird für die anfängliche Binärisierung des Bildes verwendet. Er minSizeCorrectiondient zum Optimieren der Größenbeschränkung für kleine Junk-Komponenten, die nach der Binärisierung entfernt werden sollen.

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]
  ]

Testen der Funktion:

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]

Stichprobe

Kurze Erklärung, wie es funktioniert:

  1. Wählen Sie Ihre bevorzugte Binariaztion-Methode, die relativ präzise scharfe Kanten erzeugt

  2. Wenden Sie es auf ein maskvergrößertes Bild an und verkleinern Sie das erhaltene Bild auf die Originalgröße. Dies gibt uns Antialiasing. Der größte Teil der Arbeit ist erledigt.

  3. Um eine kleine Verbesserung zu erzielen, mischen Sie das Bild mit der Helligkeit des Negativs als Alpha auf den Hintergrund und mischen Sie dann das erhaltene Bild in einem dünnen Bereich um die Kanten ( edgemask) über das Original , um die Sichtbarkeit weißer Pixel an den Kanten zu verringern. Der diesen Operationen entsprechende Alphakanal wird berechnet (der etwas kryptische ImageMultiply/AddAusdruck).

  4. Jetzt haben wir eine Schätzung des Alphakanals, damit wir eine umgekehrte Mischung durchführen können.

Die Schritte 3 und 4 verbessern sich nicht so sehr, aber der Unterschied ist sichtbar.

Szabolcs
quelle
@ Belisarius es geht nicht um Englisch, ich weiß, mein Name sieht für die meisten sehr ungewöhnlich aus :-)
Szabolcs
Sieht aus wie ein hübscher Standard. Ungarischer Familienname für mich :)
Dr. belisarius
@belisarius Eigentlich ist es ein Vorname oder genauer ein Vorname, da auf Ungarisch der Nachname an erster Stelle und der Vorname an letzter Stelle steht.
Szabolcs
2
Der Schatten des Falles ist noch in der 2. Figur als graues Band unten zu sehen ...
Sjoerd C. de Vries
@ SjoerdC.deVries Das stimmt, aber ich denke, für diese Aufgabe sollte es so sein ... es gibt keine Möglichkeit zu sagen, dass es ein Schatten ist und nicht Teil des Objekts. Die meisten Bilder bei Amazon hatten entweder Schatten oder waren langweilig trivial, also habe ich mich für dieses entschieden.
Szabolcs
22

Ich werde generisch sprechen, nicht speziell in Bezug auf Mathematica. Ich habe keine Ahnung, ob diese Operationen schwierig oder trivial sind.

Der erste Schritt besteht darin, einen Alpha-Pegel (Transparenzpegel) für die Pixel am Bildrand zu schätzen. Im Moment verwenden Sie einen strengen Schwellenwert, sodass das Alpha entweder zu 0% vollständig transparent oder zu 100% vollständig undurchsichtig ist. Sie sollten einen Bereich zwischen dem Gesamtweiß des Hintergrunds und den Farben definieren, die unbestreitbar Teil des Bildes sind, und einen angemessenen Anteil festlegen. Wenn die Farbe näher am Hintergrund liegt, ist das Alpha niedrig, und wenn sie näher am dunkleren Grenzwert liegt, ist dies der Fall ein hohes Alpha. Danach können Sie Anpassungen basierend auf den umgebenden Alpha-Werten vornehmen. Je mehr ein Pixel von Transparenz umgeben ist, desto wahrscheinlicher ist es, dass es selbst transparent ist.

Sobald Sie Alpha-Werte haben, müssen Sie eine umgekehrte Mischung durchführen, um die richtige Farbe zu erhalten. Wenn ein Bild über einem Hintergrund angezeigt wird, wird es gemäß dem Alpha-Wert unter Verwendung der Formel gemischt, c = bc*(1-a)+fc*awobei bcdie Hintergrundfarbe und fcdie Vordergrundfarbe verwendet werden. In Ihrem Fall ist der Hintergrund weiß (255,255,255) und die Vordergrundfarbe ist unbekannt, daher kehren wir die Formel um : fc = (c - bc*(1-a))/a. Wenn a=0die Formel eine Division durch Null erfordert, die Farbe jedoch keine Rolle spielt, verwenden Sie einfach Schwarz oder Weiß.

Mark Ransom
quelle
3
Gute Antwort. Alpha-Schätzung ist eigentlich ein ganzes Forschungsfeld, zB ai.stanford.edu/~ruzon/alpha
mpenkov
2
Einverstanden, gute Antwort; danke Mark! Für die Prämie (wenn ich durch Stackoverflow eine hinzufügen kann), obwohl ich vorhabe, mit der vollständig implementierten Lösung zu arbeiten, die am besten aussieht. Bisher Belisarius, denke ich.
Dreeves
11

Hier ist ein Versuch, Mark Ransoms Ansatz mit Hilfe der Maskengenerierung von Belisarius umzusetzen:

Suchen Sie die Grenze des Objekts:

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]

Figurenkante

Stellen Sie die Alpha-Werte ein:

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

Umgekehrte Farbmischung:

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]

rosa Hintergrund

Beachten Sie, wie einige der Kanten weiße Flaum haben? Vergleichen Sie das mit dem roten Umriss im ersten Bild. Wir brauchen einen besseren Kantendetektor. Das Erhöhen der Erosionsmenge hilft beim Flaum, aber dann werden andere Seiten zu transparent, sodass die Breite der Kantenmaske beeinträchtigt wird. Es ist jedoch ziemlich gut, wenn man bedenkt, dass es per se keine Unschärfeoperation gibt.

Es wäre lehrreich, den Algorithmus auf einer Vielzahl von Bildern auszuführen, um seine Robustheit zu testen und festzustellen, wie automatisch er ist.

JxB
quelle
Hmmm, für mich sieht img2 besser aus (siehe unten auf der Tischoberfläche) als img3. Vielleicht ist die umgekehrte Farbmischung nicht notwendig?
JxB
10

Nur als Anfänger herumspielen - es ist erstaunlich, wie viele Tools verfügbar sind.

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

cormullion
quelle
9

Ich bin völlig neu in der Bildverarbeitung, aber hier ist, was ich nach einigem Spielen mit neuen morphologischen Bildverarbeitungsfunktionen von Version 8 bekomme:

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

Bild

Alexey Popkov
quelle
3
Ich denke, Dreeves versucht, diese gezackten Linien an den Rändern loszuwerden.
Dr. Belisarius
1
Das macht zwar einen guten Job, um diesen Heiligenschein zu reduzieren, aber die Zackigkeit könnte ein Deal Breaker sein. @belisarius, deine Version sieht ziemlich toll aus!
Dreeves
@dreeves Ich denke, die Kanten können (in meiner Version) durch die Verwendung einer Abstandstransformation nach der Unschärfe verbessert werden, aber das wurde bereits von Mr. Wiz bemerkt, also überlasse ich das Experiment ihm.
Dr. Belisarius
Was macht Method -> "Convex"das Es ist nicht dokumentiert.
Szabolcs
Es tut mir Leid! Mir ist klar, dass ich MorphologicalComponents und MorphologicalBinarize verwechselt habe, die tatsächlich nicht verwandte Funktionen sind!
Szabolcs
6

Ich empfehle, dafür Photoshop zu verwenden und als PNG zu speichern.

Angelfilm Unterhaltung
quelle
5
Guter Punkt, aber welchen Algorithmus verwendet Photoshop, um dies so gut zu machen? (Und natürlich wollen wir dies automatisieren, nicht mit dem Zauberstab in Photoshop für jedes Bild
herumklicken
3
Übrigens denke ich, dass dies eine hilfreiche Sache ist (ich hätte leicht so groß wie ein Mathematica-Nerd sein können, dass mir Photoshop vielleicht nicht in den Sinn gekommen ist!). Und es stellt sich heraus, dass es in Photoshop sogar skriptfähig ist, sodass dies möglicherweise sogar die bestmögliche Antwort in diesem Sinne ist, wenn Photoshop etwas wirklich Kluges tut, das mit einem kleinen Mathematica-Programm nicht dupliziert werden kann.
Dreeves
5
Es gibt einen Grund, warum Adobe 500 Smakeroos für ihre Software berechnen kann ;-).
Timo
7
Vielleicht könnten Sie eine Version des Bildes posten, das von einem PhotoShop-Skript generiert wurde (kein manueller Eingriff :-) als Referenz - wir würden wissen, was wir schlagen müssen ...
cormullion
5

Mögliche Schritte, die Sie unternehmen könnten:

  • die Maske erweitern
  • verwischen Sie es
  • Stellen Sie mit der Maske die Transparenz anhand des Abstands von Weiß ein
  • Stellen Sie die Sättigung mithilfe der Maske so ein, dass die zuvor weißeren Farben gesättigter sind.
Mr.Wizard
quelle
Gute Gedanken; Danke! Würde gerne einen Allzweckcode dafür bekommen. Wir werden wahrscheinlich in ein paar Tagen ein großes Kopfgeld aufbringen (wenn Stackoverflow es uns erlaubt), wenn Sie dann zurückkommen möchten. Tatsächlich verpflichte ich mich hiermit, dies zu tun, wenn dies ein Anreiz zum Eintauchen ist. :)
Dreeves
@dreeves Hört sich gut an für mich; Ich habe jetzt keine Zeit, aber ich werde versuchen, darauf zurückzukommen.
Mr.Wizard
3

Ersetzen Sie einfach jedes Pixel, das "fast weiß" ist, durch ein Pixel mit derselben RGB-Farbe und einem Sigmoid-Farbverlauf auf dem Transparenzkanal. Sie können einen linearen Übergang von fest zu transparent anwenden, aber Sinus oder Sigmoid oder Tanh sehen natürlicher aus. Abhängig von der Schärfe der gesuchten Kante bewegen sie sich schnell vom Medium zu fest oder transparent, jedoch nicht schrittweise / binär Art und Weise, was Sie jetzt haben.

Denk darüber so:

Nehmen wir an, R, G, B sind jeweils 0,0-1,0, dann stellen wir Weiß als einzelne Zahl als R + G + B = 1,0 * 3 = 3,0 dar.

Wenn Sie ein wenig von jeder Farbe herausnehmen, wird es ein wenig "cremefarben", aber wenn Sie ein wenig von allen 3 herausnehmen, wird es viel mehr entfernt als ein wenig von irgendjemandem. Angenommen, Sie erlauben eine Reduzierung um 10% auf einen Kanal: 1.0 * .10 = .1. Verteilen Sie diesen Verlust nun auf alle drei und binden Sie ihn für den Alpha-Kanal zwischen 0 und 1, wenn er kleiner als .1 ist, so dass ( Verlust = 0,9) => 0 und (Verlust = 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];

Als Referenz:

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

Die einzige Gefahr (oder der einzige Nutzen?), Die Sie dabei haben, besteht darin, dass es nicht um Weiße geht, die tatsächlich Teil des Fotos sind. Es entfernt alle Weißen. Wenn Sie also ein Bild von einem weißen Auto haben, werden transparente Flecken darin sein. Aber aus Ihrem Beispiel scheint dies ein gewünschter Effekt zu sein.

Gregory Klopper
quelle
Ich denke, die Idee von ChanVeseBinarize ist es, klug zu sein und weiße Pixel nicht transparent zu machen, es sei denn, sie sind Teil eines größeren weißen Bereichs, dh sehr wahrscheinlich Teil des Hintergrunds.
Dreeves
Das Problem mit "größerer Fläche" ist, dass dies wichtig sein könnte, während kleine Fläche unwichtig sein könnte. Bei einem weißen Auto wäre die gesamte Seite wichtig, würde aber als großer weißer Fleck markiert. Der Abstand zwischen zwei Personen vor einem weißen Hintergrund wäre klein und mit komplexen Kanten, aber er muss gehen. Eine KI im Boltzman-Maschinenstil müsste gängige Formen erkennen und prüfen, ob das Weiß ein Raum oder ein Teil des Objekts ist, aber wir sind noch nicht da.
Gregory Klopper
1
Sie können auch 2 Bilder aus leicht unterschiedlichen Perspektiven aufnehmen und dann mithilfe der Dimensionsableitung aus der Stereobildgebung herausfinden, welche Pixel Hintergrund sind, basierend darauf, wo Okklusionen auftreten.
Gregory Klopper