42
$\begingroup$

For example, using ExampleData[{"TestImage", "Girl2"}]:

Mathematica graphics

what's a general way to make the background transparent? I've tried various combinations of EdgeDetect and Threshold and ImageAdd but can't figure it out. Thank you.

$\endgroup$
7
  • $\begingroup$ Maybe something useful here: stackoverflow.com/questions/8041703/… $\endgroup$
    – Chris Degnen
    Commented Aug 7, 2012 at 20:40
  • $\begingroup$ @ChrisDegnen: that is a very useful post in general but simply running it on the girl image instead of the one given doesn't work because it's not a single colored background. Plus, parts of the girl herself have the same color as the background... $\endgroup$
    – Philip Maymin
    Commented Aug 7, 2012 at 22:26
  • $\begingroup$ @Verde: I'd be just as happy with a white background, it's not about the transparency issues of that post per se, just how to remove the background without affecting the head of the girl. Thanks. $\endgroup$
    – Philip Maymin
    Commented Aug 7, 2012 at 22:28
  • $\begingroup$ @Verbeia -- I, uh, um... don't know. That's certainly where I was aiming for. I guess I don't really know the difference between the two sites. Do you suggest I take it down from here and put it up there? PS that is great code at the link, but it also doesn't work immediately on the girl, even moving the locator around. $\endgroup$
    – Philip Maymin
    Commented Aug 8, 2012 at 11:01
  • 1
    $\begingroup$ You can flag to have it migrated if you want. Your question is about programming so it is on topic on StackOverflow, but really any Mathematica-related question is better off on the Mathematica specific site now. $\endgroup$
    – Verbeia
    Commented Aug 8, 2012 at 12:03

4 Answers 4

41
$\begingroup$

============>> 2-liner <<============

Let's import the image and crop boundary artifacts:

img = ImageCrop[ExampleData[{"TestImage", "Girl2"}], {200, 200}]

enter image description here

We have quite uniform background. So with Mathematica functions such as RegionBinarize where you can specify background test pixel, your task is about just a few lines:

ImageAdd[img,ColorNegate@Blur[Erosion[Dilation[DeleteSmallComponents[
      ColorNegate@RegionBinarize[img, {{10, 190}}, 0.1]], 3], 3], 2]]

enter image description here

Below is just a deeper insight on how it all owrks on pixel value level.

============>> Deeper Insight <<============

Now lets get the ImageData and see how the RGB pixel values are distributed on a diagonal across the image:

data = ImageData[img];
ListPlot[Transpose[data[[#, #]] & /@ Range[200]]]

enter image description here

Because background is located above approximately 0 to 60, i'll pick number 30 (1st tuning parameter) as a test pixel and plot EuclideanDistance of all other pixels from that test pixel:

ListPlot[EuclideanDistance[#, data[[30, 30]]] & /@ (data[[#, #]] & 
/@Range[200]), Filling -> 0]

enter image description here

Based on this we can say that roughly background is everything below threshold 0.1 (2nd tuning parameter) and replace all those pixels with white {1,1,1} pixel:

ndata = data /. {x_, y_, z_} /; 
    EuclideanDistance[{x, y, z}, data[[30, 30]]] < .1 -> {1, 1, 1};

Based on that create mask and to avoid pixelation at the edge Blur it at some radius value (3rd tuning parameter)

nimg = Blur[ColorNegate@Erosion[DeleteSmallComponents[
     Dilation[ColorNegate@Binarize[Image[ndata], .99], 3]], 3], 5]

enter image description here

And finally mask the original image by adding the thresholded version of it:

ImageAdd[img, nimg]

enter image description here

In the same way you could add an alpha channel to background making it transparent. To play with the whole procedure make an app to tune in the 3 parameters described above. Note the little piece of background in the left bottom corner is removed now by tuning background pixel parameter.

Manipulate[ImageAdd[img, Blur[ColorNegate@Erosion[DeleteSmallComponents[
      Dilation[ColorNegate@Binarize[Image[data /. {x_, y_, z_} /; 
             EuclideanDistance[{x, y, z}, data[[px, px]]] < th -> {1, 
             1, 1}], .99], 3]], 3], bl]]
 , {{px, 30, "background"}, 1, 60, 1, Appearance -> "Labeled", ImageSize -> Small}
 , {{th, .1, "threshold"}, .01, .2, Appearance -> "Labeled", ImageSize -> Small}
 , {{bl, 2, "blur"}, 0, 10, 1, Appearance -> "Labeled", ImageSize -> Small}]

enter image description here

Additional parameters to add could be Binarize function ranges. Binarize[image,{Subscript[t, 1],Subscript[t, 2]}] creates a binary image by replacing all values in the range Subscript[t, 1] through Subscript[t, 2] with 1 and others with 0. ~ from Documentation.

$\endgroup$
8
  • $\begingroup$ There is a piece of background under your right armpit :) $\endgroup$ Commented Aug 15, 2012 at 9:38
  • $\begingroup$ @Verde removed now by playing with background pixel parameter ;-) $\endgroup$ Commented Aug 15, 2012 at 9:46
  • $\begingroup$ you can make your two liner a one liner easily... $\endgroup$
    – rm -rf
    Commented Aug 15, 2012 at 19:34
  • $\begingroup$ Great answer, Vitaliy - I'm working through it and appreciating and learning from it. But a question: what do you mean by "Because background is located above approximately 0 to 60"? $\endgroup$
    – cormullion
    Commented Aug 16, 2012 at 17:41
  • $\begingroup$ @cormullion In the first ListPlot flat portion (background) of brown, red, blue graphs spans approximately numbers 0 to 60 on horizontal axes of the ListPlot. Those are pixel indexes of the image. Does this help? $\endgroup$ Commented Aug 16, 2012 at 17:54
20
$\begingroup$
f[im_, n_] := Nest[Erosion[Dilation[#, 2], 2] &, im, n];
(*Get your image and Crop Nuisances*)
i1 = ImageTake[ExampleData[{"TestImage", "Girl2"}], 2 {10, -10}, {10, -10}];
(*Get edges and extend edges to border*)
b3 = Erosion[ColorNegate@f[EdgeDetect[i1, 1], 10], 1];
(*identify area& Get background pixels*)
b5 = Position[#, SortBy[Tally@Flatten@#, -#[[2]] &][[1, 1]]] &@ MorphologicalComponents@b3;
(*make a mask*)
b6 = Array[{0, 0, 0} &, Reverse@ImageDimensions@i1];
(b6[[##]] = {1, 1, 1}) & @@@ b5;
(*ready*)
b7 = ColorNegate@ImageMultiply[ColorNegate@Blur@Dilation[Image@b6, 1], ColorNegate@i1]

Mathematica graphics

Mathematica graphics

Edit

It works quite well mostly

Mathematica graphics Mathematica graphics

The problems are due to the background not being connected or not being the biggest morphological component.

Edit Using RM's suggestion

Mathematica graphics

Not perfect, but better. (blondes still can't make it unharmed)

f[im_, n_] := Nest[Erosion[Dilation[#, 1], 1] &, im, n];
(*Get your image and Crop Nuisances*)
Table[(
  i1 = ImageTake[j, 2 {10, -10}, {10, -10}];
  (*Get edges and extend edges to border*)
  b3 = Erosion[ColorNegate@f[EdgeDetect[i1, 1], 1], 1];
  b4 = (MorphologicalComponents@b3);
  (*identify area& Get background pixels*)
  (*select the component with most pixels at the border*)
  bckg = SortBy[
     Tally@Flatten@Join[#[[{1, -1}]], (Transpose@#)[[{1, -1}]]] &@
      b4, -#[[2]] &][[1, 1]];
  (*Its mean color*)
  meanBkg = Mean@Extract[ImageData[i1], Position[#, bckg] &@b4];
  (*identify other bckgnd components*)
  bckgs = 
   Flatten@MapIndexed[
       If[#1 < .08, #2, Sequence @@ {}] &, #] &@(EuclideanDistance[
        meanBkg, #] & /@ (Mean@
          Extract[ImageData[i1], Position[b4, #]] & /@ 
        Range@Max@Flatten@b4));
  (*replace by main bckgnd color*)
  b4 = b4 /. (Rule[#, bckg] & /@ bckgs);
  (*get all bckgnds together*)
  b5 = Position[b4, bckg];
  (*make a mask*)
  b6 = Array[{0, 0, 0} &, Reverse@ImageDimensions@i1];
  (b6[[##]] = {1, 1, 1}) & @@@ b5;
  (*ready*)
  b7 = ColorNegate@
    ImageMultiply[ColorNegate@Blur@Dilation[Image@b6, 1], 
     ColorNegate@i1]), {j, h}]
$\endgroup$
11
  • $\begingroup$ cool graphic - helps a lot to see how it's working! $\endgroup$
    – cormullion
    Commented Aug 16, 2012 at 17:42
  • $\begingroup$ What is b4? Not defined. $\endgroup$ Commented Aug 20, 2012 at 5:06
  • $\begingroup$ @PhilipMaymin Sorry, it was a leftover of a previous version. Corrected. Thanks! $\endgroup$ Commented Aug 20, 2012 at 5:11
  • $\begingroup$ your algorithm doesn't like blondes and golden hair? Looks like one more kicked into the CW bin... soon anyway :) $\endgroup$
    – rm -rf
    Commented Aug 20, 2012 at 5:18
  • 1
    $\begingroup$ aha! I hadn't actually noticed that the figures in the first column have backgrounds with a radial lighting effect. Pretty impressive. $\endgroup$
    – rm -rf
    Commented Aug 20, 2012 at 8:45
4
$\begingroup$

The other two excellent answers to this question were created before the arrival of version 10.0 and the RemoveBackground function. Out of curiosity I tried this new function on the test image, to see if it delivered on its promise.

g = ImagePad[ExampleData[{"TestImage", "Girl2"}], -10]

original

The function is set up to work without settings, presumably for use with images like this, with no obvious problem areas:

RemoveBackground[g]

remove background 1

but the result is a bit disappointing. An obvious option is try next is "Uniform", not her uniform, but an option which identifies "a region of almost uniform colour". By default, all settings define the background to be removed:

RemoveBackground[g, "Uniform"]

remove background 2

but it looks the same. Perhaps "Uniform" was the default after all... Let's try to specify a color. Gray looks a close match:

RemoveBackground[g, Gray]

remove background 3

but that's worse, if anything. How about "Dark" - "a darker background":

RemoveBackground[g, "Dark"]

remove background 4

which isn't very good, finding the hair and bow tie. I suppose "Bright" will do no better?

RemoveBackground[g, "Bright"]

remove background 5

Perhaps trying some markers to indicate where the background is will be the solution?

RemoveBackground[g, {"Background" , {{35,178},{35,128}}}]

remove background 6

It's not really helping.

There are a few intriguing options left to try. How about "Blurred", which finds in-focus and out-of-focus areas? Perhaps the background is blurred...

RemoveBackground[g, {"Blurred", 10}]

remove background 7

That's perhaps the best so far.

I don't know if I'm doing something wrong, or if this TestImage isn't a suitable test image. Or perhaps there are teething problems in version 10.0.0.0?

$\endgroup$
1
  • $\begingroup$ Better, but still rather terrible: RemoveBackground[g, {"Background", {{{35, 178}, {35, 128}}, 0.05}}] $\endgroup$
    – Michael E2
    Commented Jul 20, 2014 at 16:22
4
$\begingroup$

RemoveBackground works well only when the foreground does not have the same (or very similar) color as the background. Here is an alternative approach using GrowCutComponents. Markers were generated using Mask Tool from Image Toolbar.

g = ImagePad[ExampleData[{"TestImage", "Girl2"}], -10];

Remove background using GrowCutComponents

$\endgroup$
2
  • 1
    $\begingroup$ Nice. How would you get the markers non-interactively? $\endgroup$
    – cormullion
    Commented Jul 22, 2014 at 9:37
  • $\begingroup$ Where is the Image Toolbar in version 11? $\endgroup$
    – Martin R
    Commented Mar 1, 2017 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.