22
$\begingroup$

Does Mathematica include a function to correct the white balance of an image? ImageAdjust would seem to be the first go-to choice, but I haven't found a way to correct white balance without changing other image parameters (brightness, contrast, etc.). For example, this is a collage of the same photo with three different white balance settings: mars white balance

Update: Here is an example of the photos I'm working with: https://i.stack.imgur.com/EW1yo.jpg As you can see, the camera and location is always the same, but the time of day and the weather varies.

$\endgroup$
4
  • $\begingroup$ Please clarify what you mean with "white balanced without changing other image parameters (brightness, contrast, etc.)". Does it mean that white areas (like the window frames in your examples) should look white at sunshine and gray at low light? They can't become white at low light without increasing the brightness. $\endgroup$
    – Karsten7
    Mar 14, 2015 at 14:34
  • $\begingroup$ Should the "JellyBeans" look like gray balanced jBeans? $\endgroup$
    – Karsten7
    Mar 14, 2015 at 14:38
  • $\begingroup$ @Karsten7. Yes, they should remain gray at low light. $\endgroup$
    – shrx
    Mar 14, 2015 at 14:39
  • $\begingroup$ I added a code that uses only the white areas of your photographs for the color balancing instead of the whole image to my answer. $\endgroup$
    – Karsten7
    Mar 16, 2015 at 3:14

3 Answers 3

19
$\begingroup$

Histogram Mean Color Balance

Your example photographs

imgList = Import /@ {"http://i.imgur.com/qzFttRD.jpg", 
            "http://i.imgur.com/fh9tAK1.jpg", "http://i.imgur.com/b2kWM3y.jpg",
            "http://i.imgur.com/amNRIhh.jpg"};
ImageAssemble[imgList~Partition~2]

examplePhotographs

The following code balances the colors of an image by transforming the histogram of each color channel to the same common mean.

meanColorBalance[img_] := Module[{rgb, mean, dist, tDist},
  rgb = ColorSeparate[img, "RGB"];
  dist = HistogramDistribution[Flatten@ImageData[#]] & /@ rgb;
  mean = Mean[Mean /@ dist];
  tDist = TransformedDistribution[x - (Mean@# - mean), x \[Distributed] #] & /@ dist;
  Inner[HistogramTransform, rgb, tDist, ColorCombine[{##}, "RGB"] &]]

mcbList = meanColorBalance /@ imgList;
ImageAssemble[mcbList ~Partition~ 2]

colorBalancedPhotographs

Applied to the "Unprocessed Color" Mars image

meanColorBalance@Import["https://i.stack.imgur.com/bUvXg.png"]

meanColorBalancedMars


White Area Mean Balanced Colors

The last processed photograph can be used to determine the positions of the pixels that belong to white areas of the image.

whitePixels = PixelValuePositions[mcbList[[-1]], White, 0.2][[3000 ;;]];

The first 3000 pixel positions aren't used, as these are in the background and therefore their exact position might easily change from picture to picture.

HighlightImage[mcbList[[-1]], whitePixels, Method -> {"Compose", 0.8}]

whiteAreaPixels

With the following code the histogram of each color channel is shifted to get a color neutral gray for the whitePixels.

whiteAreaBalanced[img_, whitePixelsPosition_] := Module[{rgb, meanShift},
  rgb = ColorSeparate[img, "RGB"];
  meanShift = Mean[(# - Mean /@ #) &@PixelValue[img, whitePixelsPosition]];
  Inner[Image[ImageData[#1] - #2] &, rgb, meanShift, ColorCombine[{##}, "RGB"] &]]

Finally, a comparison of the original photographs (left column) with the meanColorBalanced (middle column) and the whiteAreaBalanced photographs.

ImageAssemble[{#, meanColorBalance[#], whiteAreaBalanced[#, whitePixels]} & /@ imgList]

comparison



First approach using HistogramTransform with manual tweaking

Let's number the images i1 to i3

{i1, i2, i3} = {Import["https://i.stack.imgur.com/bUvXg.png"], 
                Import["https://i.stack.imgur.com/mhKLQ.png"], 
                Import["https://i.stack.imgur.com/BYku2.png"]}

enter image description here

Using HistogramTransform seems to be the easiest way to get close to a white balanced color image

HistogramTransform[i1, NormalDistribution[0.461, 0.207], 2]

enter image description here

HistogramTransform[i2, NormalDistribution[0.461, 0.207], 2]

enter image description here

This approach can be fine tuned

{r, g, b} = ColorSeparate[i1, "RGB"];

Manipulate[
 i4= ColorCombine[{HistogramTransform[r, NormalDistribution[rm, s], 2], 
      HistogramTransform[g, NormalDistribution[gm, s], 2], 
      HistogramTransform[b, NormalDistribution[bm, s], 2]}, "RGB"],
 {{rm, 0.4165}, 0.35, 0.55}, {{gm, 0.374}, 0.35, 0.55}, 
 {{bm, 0.387}, 0.35, 0.55}, {{s, 0.213}, 0.05, 0.75}]

enter image description here

An additional ImageAdjust

ImageAdjust[i4, {-0.098, -0.027, 1.0975}]

enter image description here

gets pretty close to the "White Balanced" image.

If the upper right corner of the image is chosen to be white, the image can be further processed with

Mean@Flatten[
 Map[{rc, gc, bc}*# &, ImageData@ImageCrop[i4, {32, 22}, {Left, Bottom}], {2}], 1]

Solve[% == {1, 1, 1}, {rc, gc, bc}] // Flatten
newI = Image@Map[({rc, gc, bc} /. %)*# &, ImageData[i4], {2}]

enter image description here

But this result is further away from the "White Balanced" reference image.

$\endgroup$
5
  • $\begingroup$ Nice! Where do the magic numbers (0.461, 0.207) come from? $\endgroup$
    – shrx
    Mar 13, 2015 at 21:55
  • $\begingroup$ These numbers come from some Manipulate magic, guessing the starting values by looking at ImageHistogram@i3. $\endgroup$
    – Karsten7
    Mar 13, 2015 at 22:07
  • $\begingroup$ Unfortunately, this approach does not conserve the image's brightness and contrast. You can test for example with ExampleData[{"TestImage", "JellyBeans"}]. $\endgroup$
    – shrx
    Mar 14, 2015 at 10:14
  • $\begingroup$ I will be working with images with very different histograms, so I am looking for a general solution. $\endgroup$
    – shrx
    Mar 14, 2015 at 12:34
  • $\begingroup$ Camera and location will always be the same, but the photos are taken every hour. So it needs to work with different weather and time of day. Some test images: imgur.com/a/2S5nx $\endgroup$
    – shrx
    Mar 14, 2015 at 13:16
10
$\begingroup$

Simple white balance

As described in the Wikipedia article, the simplest white balance is to rescale the RGB channels to make white objects have white pixels. Here's a simple method where I define a white point in the original image by the 0.995 quantile of each colour channel:

image = Import["https://i.stack.imgur.com/bUvXg.png"];

white = Quantile[#, 0.995] & /@ Transpose[Flatten[ImageData[image], 1]]
(* {0.8, 0.737255, 0.545098} *)

ImageAssemble[{{image, ImageApply[#/white &, image]}}]

enter image description here

Histogram matching

In a comment to Karsten 7's answer you provided a link to some example images of the same scene taken under different conditions. To co-balance these images I propose that you isolate a region of interest and use that as a reference for HistogramTransformInterpolation.

I resized the originals to make the code faster:

files = {"http://i.imgur.com/qzFttRD.jpg", "http://i.imgur.com/fh9tAK1.jpg",
         "http://i.imgur.com/b2kWM3y.jpg", "http://i.imgur.com/amNRIhh.jpg"};

images = ImageResize[Import[#], 400] & /@ files;

ImageAssemble[Partition[images, 2]]

enter image description here

I define a region to do the histogram matching against, using image 4 as the reference:

matchRegion = ImageTake[#, {140, 240}] &;
ref = matchRegion[images[[4]]]

enter image description here

Then the adjustment is like this. It attempts to match the histograms in the reference region, allowing other parts of the image (e.g. the sky) to change colour as required:

adjust[im_] := Module[{r, g, b},
  {r, g, b} = HistogramTransformInterpolation[matchRegion[im], ref];
  ImageApply[{r[#[[1]]], g[#[[2]]], b[#[[3]]]} &, im]]

ImageAssemble[Partition[adjust /@ images, 2]]

enter image description here

If I had taken a region of sky for the reference image instead, the results would all have a similar shade of blue sky but the houses would be very different.

Colour balancing

The procedure above changes the overall image brightness because it is changing the image histogram to match the reference image. Here is an attempt to change the colour balance without altering the brightness. I use a 3x3 matrix to transform the {r,g,b} triplets without changing the value of r+g+b. Technically this is not quite right, because the perceived brightness is not simply r+g+b (I believe the green component carries greater weight).

I estimate the colour correction needed by simply comparing the mean of the pixel values between two images. I've used the same sub-region as above, and this time used image 1 as the reference.

meanc[im_] := Mean[Flatten[ImageData[matchRegion[im]], 1]]

matchto[n_] := Module[{a, b, c, m},
    {a, b, c} = meanc[images[[n]]]/meanc[#];
    m = (1/2) {{2 a, 1 - b, 1 - c}, {1 - a, 2 b, 1 - c}, {1 - a, 1 - b, 2 c}};
    ImageApply[m.# &, #]] & /@ images

ImageAssemble[Partition[matchto[1], 2]]

enter image description here

For comparison here is the result using image 3 as the reference:

ImageAssemble[Partition[matchto[3], 2]]

enter image description here

$\endgroup$
2
  • $\begingroup$ In the night photo, the houses appear lighter after the white balance adjustment. $\endgroup$
    – shrx
    Mar 14, 2015 at 14:42
  • $\begingroup$ @shrx, I've added a new method which doesn't affect the brightness. $\endgroup$ Mar 14, 2015 at 16:49
10
$\begingroup$

Mathematica 10.2 introduced the function ColorBalance, which can be used to correct the white balance of an image.

1) Example Photos

Original Photos

imgList = 
  Import /@ {"http://i.imgur.com/qzFttRD.jpg", "http://i.imgur.com/fh9tAK1.jpg", 
    "http://i.imgur.com/b2kWM3y.jpg", "http://i.imgur.com/amNRIhh.jpg"};

ImageAssemble@imgList

original photos

Balance that simulates the effect of neutral lighting

ColorBalance /@ imgList // ImageAssemble

neutral lighting

Adjusting color so that the mean color at the positions specified by whitePixels is mapped to white

whitePixels = PixelValuePositions[ColorBalance@imgList[[-1]], White, 0.2][[3000 ;;]];

ColorBalance[#, whitePixels] & /@ imgList // ImageAssemble

whitePixelsColorBalance

Comparison with whiteAreaBalanced from the other answer

whiteAreaBalanced[#, whitePixels] & /@ imgList // ImageAssemble

whiteAreaBalanced

Comparison with meanColorBalance from the other answer

ImageAssemble[meanColorBalance /@ imgList]

meanColorBalance


2) Mars Photo

img = Import["https://i.stack.imgur.com/bUvXg.png"]

Mars

ColorBalance[img, Method -> #] & /@ {Automatic, "GrayScaling", 
   "RGBScaling", "LMSScaling", "ChromaticityScaling"} // ImageAssemble

marsColorBalance

RGBColor @@ Mean@Flatten[ImageData@ImageCrop[img, {32, 22}, {Left, Bottom}], 1]

ColorBalance[img, % -> White, Method -> "ChromaticityScaling"]

marsCS

$\endgroup$
4
  • $\begingroup$ Looking forward to testing this when we update our licence. $\endgroup$
    – shrx
    Jul 16, 2015 at 19:59
  • $\begingroup$ Indeed, the results look promising, very similar to the effect achieved with your meanColorBalance processing. $\endgroup$
    – shrx
    Jul 16, 2015 at 20:08
  • $\begingroup$ @shrx You're the only one who can judge which balancing gets closed to the real impression. It's especially hard to guess how the sky in the third image looks in reality. It has a strange color when using ColorBalance[#, whitePixels]. $\endgroup$
    – Karsten7
    Jul 16, 2015 at 20:32
  • $\begingroup$ I like my whiteAreaBalanced[#, whitePixels] more than the ColorBalance[#, whitePixels]. Especially the roof in the second photo looks vary grayish when using ColorBalance[#, whitePixels]. $\endgroup$
    – Karsten7
    Jul 16, 2015 at 20:44

Your Answer

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

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