# White balance correction with Mathematica

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:

Update: Here is an example of the photos I'm working with: http://imgur.com/a/2S5nx As you can see, the camera and location is always the same, but the time of day and the weather varies.

• 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. Mar 14, 2015 at 14:34
• Should the "JellyBeans" look like gray balanced jBeans? Mar 14, 2015 at 14:38
• @Karsten7. Yes, they should remain gray at low light.
– shrx
Mar 14, 2015 at 14:39
• 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. Mar 16, 2015 at 3:14

# Histogram Mean Color Balance

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]


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]


Applied to the "Unprocessed Color" Mars image

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


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


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]


## First approach using HistogramTransform with manual tweaking

Let's number the images i1 to i3

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


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]


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


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


An additional ImageAdjust

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


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


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

• Nice! Where do the magic numbers (0.461, 0.207) come from?
– shrx
Mar 13, 2015 at 21:55
• These numbers come from some Manipulate magic, guessing the starting values by looking at ImageHistogram@i3. Mar 13, 2015 at 22:07
• Unfortunately, this approach does not conserve the image's brightness and contrast. You can test for example with ExampleData[{"TestImage", "JellyBeans"}].
– shrx
Mar 14, 2015 at 10:14
• I will be working with images with very different histograms, so I am looking for a general solution.
– shrx
Mar 14, 2015 at 12:34
• 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
– shrx
Mar 14, 2015 at 13:16

## 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["http://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]}}]


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


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

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


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



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


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

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


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

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


Balance that simulates the effect of neutral lighting

ColorBalance /@ imgList // ImageAssemble


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


Comparison with whiteAreaBalanced from the other answer

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


Comparison with meanColorBalance from the other answer

ImageAssemble[meanColorBalance /@ imgList]


## 2) Mars Photo

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


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


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

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


• Looking forward to testing this when we update our licence.
– shrx
Jul 16, 2015 at 19:59
• Indeed, the results look promising, very similar to the effect achieved with your meanColorBalance processing.
– shrx
Jul 16, 2015 at 20:08
• @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]. Jul 16, 2015 at 20:32
• I like my whiteAreaBalanced[#, whitePixels] more than the ColorBalance[#, whitePixels]. Especially the roof in the second photo looks vary grayish when using ColorBalance[#, whitePixels]. Jul 16, 2015 at 20:44