views:

398

answers:

1

This is yet another questions springed from this one

How do I programatically get the background color of an image?

Example:

alt text

For the above image,the background color is white.

+3  A: 

As discussed in the comments for the question itself, the notion of "background color" is rather subjective, so it's not really possible to write an algorithm to guarantee the desired result for all inputs.

That said, however, I think I understand what you're trying to accomplish, and I've written a couple of MATLAB functions that are quite successful at identifying a probable background color for a number of input images I've tried.

The heuristic I used is based on the observation that, generally speaking, the background color of an image is likely to be regions of low-frequency information, while the foreground is likely to be high-frequency. (Note that when this isn't the case, my getBackgroundColor function will fail miserably.) So what I do is isolate the high-frequency information in the frequency domain, transform it back to the spatial domain, "spread-out" the selected pixels so as to cover broad high-frequency regions, and then to simply remove these pixels.

There are plenty of places in the code that can be tightened and fiddled with to improve performance for your particular application, but it seems to work nicely for a wide variety of test cases as is.

getBackgroundColor.m:

function [img, meanColor, modeColor] = getBackgroundColor (img)
%
% function [img, meanColor, modeColor] = getBackgroundColor (img)
%
%    img   -   Either a string representing the filename of an image to open
%              or an image itself.  If the latter, it must be either a
%              3-dimensional matrix representing an RGB image or a 2-dimensional
%              matrix representing a grayscale image.

if ischar(img)
  img = imread(imageFile);
end
img = double(img);

% Handle RGB and Grayscale separately.
if ndims(img)==3
  % There are probably some spiffy ways to consolidate this sprawl
  % so that the R, G, and B channels are not being processed
  % independently, but for the time being, this does work.
  red   = getBG(img(:, :, 1));
  green = getBG(img(:, :, 2));
  blue  = getBG(img(:, :, 3));

  % For each channel, remove the "foreground" regions identified in
  % each of the other channels.
  red(isnan(green)) = NaN;
  red(isnan(blue)) = NaN;

  green(isnan(red)) = NaN;
  green(isnan(blue)) = NaN;

  blue(isnan(red)) = NaN;
  blue(isnan(green)) = NaN;

  % Compute the mean and mode colors.
  meanColor = [ ...
      mean(mean( red(~isnan(red)) )) ...
      mean(mean( green(~isnan(green)) )) ...
      mean(mean( blue(~isnan(blue)) )) ];
  modeColor = [ ...
      mode(mode( red(~isnan(red)) )) ...
      mode(mode( green(~isnan(green)) )) ...
      mode(mode( blue(~isnan(blue)) )) ];

  % Update each the foreground regions of each channel and set them
  % to their mean colors.  This is only necessary for visualization.
  red(isnan(red)) = meanColor(1);
  green(isnan(green)) = meanColor(2);
  blue(isnan(blue)) = meanColor(3);

  img(:, :, 1) = red;
  img(:, :, 2) = green;
  img(:, :, 3) = blue;
else
  img = getBG(img);
  meanColor = mean(mean( img( ~isnan(img) ) ));
  modeColor = mode(mode( img( ~isnan(img) ) ));
  img(isnan(img)) = meanColor;
end

% Convert the image back to integers (optional)
img = uint8(img);

% Display the results before returning
display(meanColor)
display(modeColor)



  function image = getBG (image)
      mask = getAttenuationMask(size(image), min(size(image)) / 2, 0, 1);

      % Assume that the background is mostly constant, so isolate the high-frequency
      % parts of the image in the frequency domain and then transform it back into the spatial domain
      fftImage = fftshift(fft2(image));
      fftImage = fftImage .* mask;
      invFftImage = abs(ifft2(fftImage));

      % Expand the high-frequency areas of the image and fill in any holes.  This should
      % cover all but the (hopefully) low frequency background areas.
      edgeRegion = imfill(imdilate(invFftImage, strel('disk', 4, 4)), 'holes');

      % Now remove the parts of the image that are covered by edgeRegion
      edgeMean = mean(mean(edgeRegion));
      image(edgeRegion>edgeMean) = NaN;
  end
end

getAttenuationMask.m:

function mask = getAttenuationMask (maskSize, radius, centerValue, edgeValue)
%
% function mask = getAttenuationMask (maskSize, radius, centerValue, edgeValue)
%

if nargin==2
  centerValue = 1;
  edgeValue = 0;
end

width = maskSize(1);
height = maskSize(2);

mx = width / 2;
my = height / 2;

mask=zeros(maskSize);

for i=1:width
  for j=1:height
      d = sqrt( (i-mx)^2 + (j-my)^2 );
      if (d >= radius)
        d = edgeValue;
      else
        d = (centerValue * (1 - (d / radius))) + (edgeValue * (d / radius));
      end

      mask(i, j) = d;
  end
end
RTBarnard
@RTBarnard,thanks for the reply!Can you take a look at this question to see if it is possible to transform the image into a square without requirement to assign the background color explicitly but still keep the result image natural? http://stackoverflow.com/questions/2605202/how-to-automate-the-padding-for-arbitary-images-using-matlab
In what way is the accepted answer from @gnovice deficient? It appears to me that any (or all) of his proposed solutions could work in tandem with my `getBackgroundColor` function: call my function to get an appropriate padding color, then call one of the padding functions with that color. Maybe I'm misunderstanding what you're after.
RTBarnard
This kind of solution which depends on background color doesn't work well with images that doesn't have an obvious background color,like this one:http://farm4.static.flickr.com/3104/3238518077_1ef13a8e93.jpg
Also,I don't know why `getBackgroundColor` takes so much time to process an image,and I doesn't see `white` is given as a result for this image:http://www.google.com/intl/en_ALL/images/srpr/logo1w.png
Both of the images you linked in your previous two comments were among my test images. The sky image yields a nice, sensible blue; the Google logo yields almost-white (for the mean) and pure white (for the mode). Additionally, on my 4-core 3GHz Mac Pro, the Google image takes well under a second to process and the sky image takes about 1.5 seconds to process. One thing that may be causing hiccups for you could be the color format, though. Be sure the images are either grayscale or 24-bit RGB. The latter loads as a 3-dimensional array from `imread`.
RTBarnard
`getBackgroundColor` returns both `meanColor` and `modeColor`(what's this?),which one do you think I should use?
After removing the foreground as best as possible, `getBackgroundColor` computes `meanColor` and `modeColor`. `meanColor` is the average color of the remaining pixels. `modeColor` is the color that appears most often among the remaining pixels. In most of the images I tested, `modeColor` was slightly closer to what I would have hoped for, but if the frequency of that particular color is relatively low, `meanColor` would probably be a better bet. Does this help?
RTBarnard
Thanks for the great info! Let me upvote on your answer and see for a while if there is a solution that doesn't depends on background color,hopefully from @gnovice :)
@RTBarnard ,`modeColor` is not correct,it should be `1` but your `getBackgroundColor` returns `0`,changing the background color from white to black. http://www.google.com/intl/en_ALL/images/srpr/logo1w.png
The problem with that image is that it uses indexed color. My algorithm needs either grayscale or direct color mapping. I've used ImageMagick to perform a conversion of the image you reference, and once done, it works fine. Try any of the following images: http://interfix.arane.us/stackoverflow/google.jpg, http://interfix.arane.us/stackoverflow/sky.jpg, http://interfix.arane.us/stackoverflow/sky-gray.tif.
RTBarnard
@RTBarnard ,is it possible to optimize the implementation so that it can also work with indexed image,which is quite often used?
Probably so, but I think the easiest solution would simply be to convert indexed color to direct color for the algorithm, then rebuild the color index afterwards. Unfortunately, I won't have access to a MATLAB license again until Tuesday so I can't test any alternative ideas out until then. The major obstacle I see is that with indexed color, neighboring pixels that appear similar do not necessarily share similar numeric values. Every aspect of the algorithm depends upon this property, so even if I did have a MATLAB license, I don't immediately have any suggestions.
RTBarnard
The problem is `getBackgroundColor` takes a file name as the parameter,so `grayImage = ind2gray(X,map);` won't work(I can't pass `grayImage` as the parameter), the bottom line is: I don't want to generate another image in the disk:(
I've edited my response to accommodate your concern. The `getBackgroundColor` function can now accept either an image or a filename. If a character array is provided, it assumes that the provided argument is a filename and then loads the image. Otherwise, it assumes that a direct-color grayscale or RGB image is provided. Does this do the job for you?
RTBarnard
Seems not :( I tried `grayImage = ind2gray(X,map);[img, meanColor, modeColor] = getBackgroundColor(grayImage);` but the result is `meanColor = 252.6370;modeColor = 255;`
For the Google logo, that sounds about right; is that the image you turned to `grayImage`? What results were you expecting to see?
RTBarnard
Yes,it is.I think it should be `1`,as indicated in this answer by `ones(nPad,c,3)`:http://stackoverflow.com/questions/2589851/how-can-i-implement-this-visual-effect-in-matlab/2589957#2589957
In the example you cite, the image is converted using `ind2rgb` which will produce a `double` array with (I believe) values in the interval [0, 1], whereas `ind2gray` produces an array of the same type as input with values (at least in this case) in the interval [0, 255]. If you prefer to work in the real interval [0, 1], use `grayImage = double(ind2gray(X, map)); grayImage = grayImage / max(max(grayImage));`.
RTBarnard
Oh,it seems working!But I have the last question: why two `max` is needed here?
`grayImage` is 2-dimensional, so a single `max(grayImage)` gives a 1-dimensional array of the maximum values in each column. The second call to `max` computes the maximum of those values.
RTBarnard