3

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.

Community
  • 1
  • 1
user198729
  • 61,774
  • 108
  • 250
  • 348
  • Are you trying to (1) analyze a matrix of pixels to separate foreground and background features to determine a dominant background color, (2) identify the color that has been used to pad an image, (3) retrieve a background color attribute from a MATLAB image/axis/figure object, or (4) something else entirely? An example would make it easier to answer your question. – RTBarnard Apr 10 '10 at 04:15
  • The problem is your definition of "background color". Take this image for example (http://farm4.static.flickr.com/3104/3238518077_1ef13a8e93.jpg). What should be considered as the background color? The color of the sky? The color of the water? Or maybe the color of the mountains? An image is just a set of colored pixels, a matrix containing a bunch of numbers that are interpreted as colors for each point. There is no set "background color". You have to decide for yourself how to define it (i.e. the mean of all the colors in the image, the most frequently occurring color, etc.). – gnovice Apr 10 '10 at 04:16
  • @gnovice ,I can't come up with a good definition for the background color in the image you provided,but can for the image in my post,which is white.Say I don't know how to define it in a general case. – user198729 Apr 10 '10 at 04:26
  • @gnovice ,is it possible to transform the image into a square without requirement to assign the background color explicitly but still keep the result image natural? – user198729 Apr 10 '10 at 04:32
  • @user198729: My third solution in the other question using PADARRAY is the only solution I can think of that picks the background color for you. The only other way to make an image square *without* padding it would be to stretch it along the shorter dimension, but that will distort the image. RTBarnard gives a good example for an automated background color selection routine, but such a thing is very hard to do in a way that handles *all* images well. – gnovice Apr 10 '10 at 16:14
  • Oops,I don't know why RTBarnard's answer is not working exactly,the `modeColor` returned is `0` while actually `1`,making the image background color black: http://www.google.com/intl/en_ALL/images/srpr/logo1w.png – user198729 Apr 10 '10 at 17:05

1 Answers1

5

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
  • 4,374
  • 27
  • 27
  • @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 – user198729 Apr 10 '10 at 11:55
  • 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 Apr 10 '10 at 12:44
  • 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 – user198729 Apr 10 '10 at 14:08
  • 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 – user198729 Apr 10 '10 at 14:10
  • 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 Apr 10 '10 at 14:17
  • `getBackgroundColor` returns both `meanColor` and `modeColor`(what's this?),which one do you think I should use? – user198729 Apr 10 '10 at 14:28
  • 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 Apr 10 '10 at 14:30
  • 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 :) – user198729 Apr 10 '10 at 14:35
  • @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 – user198729 Apr 10 '10 at 17:01
  • 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 Apr 10 '10 at 18:02
  • @RTBarnard ,is it possible to optimize the implementation so that it can also work with indexed image,which is quite often used? – user198729 Apr 11 '10 at 07:03
  • 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 Apr 11 '10 at 07:39
  • 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:( – user198729 Apr 11 '10 at 07:45
  • 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 Apr 11 '10 at 09:22
  • Seems not :( I tried `grayImage = ind2gray(X,map);[img, meanColor, modeColor] = getBackgroundColor(grayImage);` but the result is `meanColor = 252.6370;modeColor = 255;` – user198729 Apr 11 '10 at 14:28
  • For the Google logo, that sounds about right; is that the image you turned to `grayImage`? What results were you expecting to see? – RTBarnard Apr 11 '10 at 15:14
  • 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 – user198729 Apr 11 '10 at 15:20
  • 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 Apr 11 '10 at 16:03
  • Oh,it seems working!But I have the last question: why two `max` is needed here? – user198729 Apr 11 '10 at 16:11
  • `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 Apr 11 '10 at 17:00