Usuń białe tło z obrazu i nadaj mu przezroczystość

82

W Mathematica staramy się wykonać następujące czynności - RMagick usunąć białe tło z obrazu i uczynić go przezroczystym .

Ale w przypadku rzeczywistych zdjęć kończy się to kiepskim wyglądem (jak otoczka wokół obrazu).

Oto, czego próbowaliśmy do tej pory:

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

Oto przykład tego, co to robi.

Oryginalny obraz:

oryginalny obraz

Obraz z białym tłem zastąpionym bez tła (lub, dla celów demonstracyjnych, z różowym tłem):

obraz z przezroczystym tłem - właściwie jest to różowe tło, aby oczyścić problem z halo

Jakieś pomysły na pozbycie się tej aureoli? Poprawiając takie rzeczy jak LevelPenalty, mogę tylko usunąć aureolę kosztem utraty części obrazu.

EDYCJA: Więc mogę porównać rozwiązania dla nagrody, proszę ustrukturyzować twoje rozwiązanie jak powyżej, a mianowicie samodzielną funkcję o nazwie unground - coś, co pobiera obraz i zwraca obraz z przezroczystym tłem.

dreeves
źródło
1
Bardzo dziękuję wszystkim za dotychczasową pomoc! Duża nagroda za to, gdy tylko stackoverflow pozwoli mi je dodać. I zgodnie z duchem przepełnienia stosów, wyrażonym przez założycieli, powinniście okradać się nawzajem, aby Twoja odpowiedź była ostateczna!
dreeves
3
Najpierw 500 nagród, a potem „Zachęcam was wszystkich do hojnego pożyczania od siebie nawzajem, aby w miarę możliwości ulepszyć to!”. - chcesz walki psa, prawda?
Mr.Wizard
@ Mr.Wizard, :) Nie zmyślam jednak, że założyciele (Jeff i Joel) od początku mówili, że jest to zalecane. Chodzi o to, aby najlepsza odpowiedź była naprawdę kompletna i ostateczna. (I oczywiście mam również ukryte motywy w tym przypadku!)
dreeves
2
Dla zbyt ciekawy, tym „FREDRIK” stacji roboczej komputer IKEA za: ikea.com/us/en/catalog/products/60111123
Arnoud Buzing
1
@dreeves, użyłem tineye.com .
Arnoud Buzing

Odpowiedzi:

45

Być może w zależności od wymaganej jakości krawędzi:

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]

wprowadź opis obrazu tutaj

Edytować

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

wprowadź opis obrazu tutaj

Kliknij, aby powiększyć

Edytuj 2

Aby zorientować się w zakresie niedoskonałości halo i tła na obrazie:

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

wprowadź opis obrazu tutaj

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

wprowadź opis obrazu tutaj

Dr Belisarius
źródło
Niestety na moim komputerze twój kod nie daje takiej samej jakości wyniku. Czy img był obrazem 500x500, jak napisano w pytaniu? Jeśli tak, być może sprawa mac / windows ...
Matthias Odisio,
@Matthias Tak, img to kopia / wklej z oryginału. Mma 8.01 w systemie Windows.
Dr. Belisarius
Och ... być może optymalizator daje inny wynik z powodu niewielkiego szumu arytmetycznego. W każdym razie cieszę się, że działa dobrze przy użyciu tego zestawu parametrów.
Matthias Odisio
To nie wygląda na to, że działa. Po prostu zaciera krawędzie.
user541686
48

Ta funkcja implementuje odwrotną mieszankę opisaną przez Mark Ransom, dla dodatkowej małej, ale widocznej poprawy:

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

To jest funkcja usuwania tła. thresholdParametrów służy do wstępnego binaryzację obrazu, minSizeCorrectionjest skomplikowany limit wielkości małych elementów śmieci do usunięcia po binaryzacji.

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

Testowanie funkcji:

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]

Próba

Krótkie wyjaśnienie, jak to działa:

  1. Wybierz swoją ulubioną metodę binariacji, która pozwala uzyskać stosunkowo precyzyjne ostre krawędzie

  2. Zastosuj go do obrazu przeskalowanego w górę, a następnie zmniejsz uzyskany rozmiar maskdo oryginalnego rozmiaru. To daje nam antyaliasing. Większość pracy jest już wykonana.

  3. Aby uzyskać niewielką poprawę, zmieszaj obraz z tłem, używając jasności negatywu jako alfa, a następnie zmieszaj uzyskany obraz z oryginałem w cienkim obszarze wokół krawędzi ( edgemask), aby zmniejszyć widoczność białych pikseli na krawędziach. Obliczany jest kanał alfa odpowiadający tym operacjom (nieco tajemnicze ImageMultiply/Addwyrażenie).

  4. Teraz mamy oszacowanie kanału alfa, więc możemy wykonać mieszanie odwrotne.

Kroki 3 i 4 nie poprawiają się tak bardzo, ale różnica jest widoczna.

Szabolcs
źródło
@belisarius nie chodzi o angielski, wiem, że moje imię wygląda dla większości bardzo nietypowo :-)
Szabolcs
Wygląda na całkiem standardową. Węgierskie nazwisko dla mnie :)
Dr. belisarius 11.11
@belisarius Właściwie to imię, a dokładniej imię, ponieważ w języku węgierskim nazwisko jest pierwsze, a imię ostatnie.
Szabolcs
2
Cień obudowy jest nadal widoczny na drugiej figurze jako szarawy pas na dole ...
Sjoerd C. de Vries
@ SjoerdC.deVries To prawda, ale myślę, że w przypadku tego zadania powinno tak być ... nie ma sposobu, aby stwierdzić, że to cień, a nie część obiektu. Większość zdjęć na Amazon albo miała cienie, albo były nudno trywialne, więc wybrałem to.
Szabolcs
22

Zamierzam mówić ogólnie, a nie konkretnie w odniesieniu do Mathematica. Nie mam pojęcia, czy te operacje są trudne czy trywialne.

Pierwszym krokiem jest oszacowanie poziomu alfa (przezroczystości) pikseli na krawędzi obrazu. W tej chwili używasz ścisłego progu, więc alfa jest albo całkowicie przezroczysta w 0%, albo w 100% całkowicie nieprzezroczysta. Należy zdefiniować zakres między całkowitą bielą tła a kolorami, które są bezsprzecznie częścią obrazu, i ustawić odpowiednią proporcję - jeśli jest bliżej koloru tła to niskie alfa, a jeśli jest bliżej ciemniejszego odcięcia, to jest wysoka alfa. Następnie możesz wprowadzić poprawki na podstawie otaczających wartości alfa - im bardziej piksel jest otoczony przezroczystością, tym większe prawdopodobieństwo, że sam będzie przezroczysty.

Gdy masz już wartości alfa, musisz wykonać mieszanie odwrotne, aby uzyskać właściwy kolor. Gdy obraz jest wyświetlany na tle, jest on mieszany zgodnie z wartością alfa przy użyciu wzoru, c = bc*(1-a)+fc*agdzie bcjest kolorem tła i fckolorem pierwszego planu. W twoim przypadku tło jest białe (255255255) oraz kolor pierwszoplanowy jest nieznana, więc odwrócić wzoru: fc = (c - bc*(1-a))/a. Kiedy a=0formuła wymaga podzielenia przez zero, ale kolor i tak nie ma znaczenia, po prostu użyj czerni lub bieli.

Mark Okup
źródło
3
Świetna odpowiedź. Oszacowanie alfa to właściwie cała dziedzina badań, np. Ai.stanford.edu/~ruzon/alpha
mpenkov
2
Zgoda, świetna odpowiedź; dzięki Mark! Za bounty (kiedy stackoverflow pozwala mi dodać jedno), chociaż planuję wybrać to, które w pełni zaimplementowane rozwiązanie wygląda najlepiej. Jak dotąd belizariusz, myślę.
dreeves
11

Oto próba wdrożenia podejścia Marka Ransoma, z pewną pomocą generowania masek belizariusza:

Zlokalizuj granicę obiektu:

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]

krawędź figury

Ustaw wartości alfa:

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

Odwrotna mieszanka kolorów:

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]

różowe tło

Zauważ, że niektóre krawędzie mają biały meszek? Porównaj to z czerwonym konturem na pierwszym obrazku. Potrzebujemy lepszego detektora krawędzi. Zwiększenie wielkości erozji pomaga w usuwaniu meszku, ale wtedy inne strony stają się zbyt przezroczyste, więc istnieje kompromis w zakresie szerokości maski krawędzi. Jest jednak całkiem nieźle, biorąc pod uwagę, że nie ma operacji rozmycia jako takiej.

Pouczające byłoby uruchomienie algorytmu na różnych obrazach w celu przetestowania jego solidności i sprawdzenia, jak działa automatycznie.

JxB
źródło
Hmmm, dla mnie img2 wygląda lepiej (patrz dół powierzchni stołu) niż img3. Może odwrotna mieszanka kolorów jest niepotrzebna?
JxB
10

Po prostu zabawa jako początkujący - to niesamowite, ile narzędzi jest dostępnych.

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

cormullion
źródło
9

Jestem zupełnie nowy w przetwarzaniu obrazu, ale oto, co otrzymuję po zabawie z nowymi funkcjami morfologicznego przetwarzania obrazu w wersji 8:

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

wizerunek

Alexey Popkov
źródło
3
Myślę, że dreeves próbuje pozbyć się tych postrzępionych linii na krawędziach.
Dr. Belisarius
1
To prawda, to niezła robota zmniejszająca tę aureolę, ale postrzępienie może być przyczyną zerwania umowy. @belisarius, twoja wersja wygląda niesamowicie!
dreeves
@dreeves Myślę, że krawędzie można poprawić (w mojej wersji) stosując transformację odległości po rozmyciu, ale Pan Wiz już to zauważył, więc eksperyment zostawiam jemu.
Dr. Belisarius
Co robi Method -> "Convex"? To nie jest udokumentowane.
Szabolcs
Przepraszam! Zdaję sobie sprawę, że pomyliłem MorphologicalComponents i MorphologicalBinarize, które w rzeczywistości są niezwiązanymi ze sobą funkcjami!
Szabolcs
6

Polecam do tego użyć Photoshopa i zapisać jako PNG.

Angelfilm Entertainment
źródło
5
Słuszna uwaga, ale jakiego algorytmu używa Photoshop, by robić to tak dobrze? (I oczywiście chcemy to zautomatyzować, a nie klikać magiczną różdżką w Photoshopie dla każdego obrazu.)
dreeves
3
Nawiasem mówiąc, myślę, że warto to wskazać (mogłem łatwo być takim wielkim frajerem Mathematica, że ​​Photoshop mógł mi nie przyjść do głowy!). Okazuje się, że jest to nawet możliwe do skryptu w Photoshopie, więc może to być nawet najlepsza możliwa odpowiedź w tym sensie, jeśli Photoshop robi coś naprawdę sprytnego, czego nie można skopiować za pomocą małego programu mathematica.
dreeves
5
Istnieje powód, dla którego Adobe może pobrać 500 smakeroos za swoje oprogramowanie ;-).
Timo
7
Być może mógłbyś opublikować wersję obrazu wygenerowaną przez skrypt PhotoShop (bez ręcznej interwencji :-) w celach informacyjnych - wiedzielibyśmy, co mamy do pokonania ...
cormullion
5

Możliwe kroki, które możesz podjąć:

  • rozszerzyć maskę
  • zamazać to
  • używając maski, ustaw przezroczystość na podstawie odległości od bieli
  • używając maski, dostosuj nasycenie tak, aby kolory wcześniej bardziej białe były bardziej nasycone.
Panie Wizard
źródło
Dobre myśli; Dziękuję Ci! Chciałbym uzyskać do tego kod ogólnego przeznaczenia. Prawdopodobnie za kilka dni dostaniemy dużą nagrodę (kiedy przepełnienie stosu nam na to pozwoli), jeśli chcesz wtedy wrócić. W rzeczywistości zobowiązuję się to zrobić, jeśli jest to jakakolwiek zachęta do nurkowania. :)
dreeves
@dreeves Brzmi dobrze; Nie mam teraz czasu, ale spróbuję do tego wrócić.
Pan Kreator
3

Po prostu zastąp każdy piksel „prawie zbliżony do białego” pikselem o tym samym kolorze RGB i sigmoidalnym gradiencie na kanale przezroczystości. Możesz zastosować liniowe przejście od bryły do ​​przezroczystości, ale sinusoida, sigmoida lub tanh wyglądają bardziej naturalnie, w zależności od ostrości krawędzi, której szukasz, szybko odchodzą od medium do stałego lub przezroczystego, ale nie w stopniowym / binarnym sposób, który masz teraz.

Pomyśl o tym w ten sposób:

Powiedzmy, że każdy z R, G, B ma wartość 0,0-1,0, a następnie przedstawmy biały jako pojedynczą liczbę jako R + G + B = 1,0 * 3 = 3,0.

Usunięcie odrobiny każdego koloru sprawia, że ​​jest on trochę „złamanej bieli”, ale usunięcie odrobiny wszystkich trzech kolorów jest o wiele bardziej osłabione niż trochę z któregokolwiek. Powiedzmy, że pozwalasz na redukcję o 10% na dowolnym kanale: 1,0 * .10 = 0,1, Teraz rozłóż tę stratę na wszystkie trzy i połącz ją między 0 a 1 dla kanału alfa, jeśli jest mniejsza niż 0,1, czyli ( strata = 0,9) => 0 i (strata = 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];

Na przykład:

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

Jedynym niebezpieczeństwem (lub korzyścią?), Które w tym tkwi, jest to, że nie obchodzą go białe, które tak naprawdę SĄ częścią zdjęcia. Usuwa wszystkie białka. Więc jeśli masz zdjęcie białego samochodu, skończy się to na przezroczystych plamach. Ale z twojego przykładu wydaje się, że jest to pożądany efekt.

Gregory Klopper
źródło
Myślę, że pomysł ChanVeseBinarize polega na tym, aby być sprytnym i nie zmieniać białych pikseli jako przezroczystych, chyba że są one częścią większego obszaru bieli, tj. Bardzo prawdopodobne, że będą częścią tła.
dreeves
Problem z „większym obszarem” może być ważny, podczas gdy mały obszar może być nieistotny. W białym samochodzie cała strona byłaby ważna, ale zostałaby oznaczona jako duża plama bieli. Przestrzeń między dwojgiem ludzi na białym tle byłaby mała i miałaby skomplikowane krawędzie, ale trzeba ją usunąć. Musiałbyś mieć sztuczną inteligencję typu Boltzman Machine rozpoznającą typowe kształty i sprawdzającą, czy biel jest przestrzenią lub częścią obiektu, ale jeszcze nas tam nie ma.
Gregory Klopper
1
Możesz także zrobić 2 obrazy z nieco innych perspektyw, a następnie użyć dedukcji wymiarowości z obrazowania stereo, aby dowiedzieć się, które piksele są tłem, na podstawie miejsca wystąpienia okluzji.
Gregory Klopper