# Custom White Balancing with picamera

I need a method to set the White Balance of the raspberry pi camera by "teaching it". Such as it is in DSLR cameras, I'd like to show a white picture to the camera and say "OK, this is white, now adjust your AWB accordingly".

I think the first step is to set the `awb_mode` to `off` and then, capture photos continously playing around with `awb_gains`. The problem is, I don't know how to decide "OK these values are fine, let's keep with them."

Anybody done such thing? I'm using the picamera Python module btw.

You could write a little loop that assumes that the camera is pointed at something which is mostly white (e.g. a sheet of paper) and then iterate over various combinations of the red and blue gains (probably increments of 0.1 between, say, 0.5 and 2.5) until you find the combination that produces an image in which most of the pixels are as close to grey (i.e. equal values for R, G, and B) as possible. It probably wouldn't be very quick (as it'd involve taking an picture, tweaking values, taking another picture, and so on) so I'd recommend using a low-res resize, and video-port captures. Still, it should do the trick.

The following is a crude demo; it sets the AWB to something ludicrous (both gains 0.5; this is just to demonstrate the convergence) and then allows 30 attempts to move each gain in either direction by 0.1 to try and get the average colours of the resulting capture to match the green channel:

``````import picamera
import picamera.array
import numpy as np

with picamera.PiCamera() as camera:
camera.resolution = (1280, 720)
camera.awb_mode = 'off'
# Start off with ridiculously low gains
rg, bg = (0.5, 0.5)
camera.awb_gains = (rg, bg)
with picamera.array.PiRGBArray(camera, size=(128, 72)) as output:
# Allow 30 attempts to fix AWB
for i in range(30):
# Capture a tiny resized image in RGB format, and extract the
# average R, G, and B values
camera.capture(output, format='rgb', resize=(128, 72), use_video_port=True)
r, g, b = (np.mean(output.array[..., i]) for i in range(3))
print('R:%5.2f, B:%5.2f = (%5.2f, %5.2f, %5.2f)' % (
rg, bg, r, g, b))
# Adjust R and B relative to G, but only if they're significantly
# different (delta +/- 2)
if abs(r - g) > 2:
if r > g:
rg -= 0.1
else:
rg += 0.1
if abs(b - g) > 1:
if b > g:
bg -= 0.1
else:
bg += 0.1
camera.awb_gains = (rg, bg)
output.seek(0)
output.truncate()
``````

In my tests it usually gets close to a decent solution in 10 or so steps, and then wobbles around a couple of values. There's almost certainly betters ways of doing this (starting with more sensible values, varying one at a time, using YUV captures instead, decreasing the increments as the values converge, terminating when acceptably close, etc.) but this should be enough to demonstrate the principle.

Sadly I cannot comment yet, so I have to do it as a seperate answer to this one

The Code from Dave Jones can be made faster convergent with this diff calculation (and more precise)

``````            # different (delta +/- 2)
if abs(r - g) > 2:
rg -= 0.01 * (r - g)
if abs(b - g) > 2:
bg -= 0.01 * (b - g)
# make sure the values do not get out of bounds
(rg, bg) = np.maximum((0, 0), (rg, bg))
(rg, bg) = np.minimum((8, 8), (rg, bg))
camera.awb_gains = (rg, bg)
output.seek(0)
output.truncate()
``````

For me this code converges in around 3-5 steps.

``````R: 0.50, B: 0.50 = ( 0.28, 157.60,  3.37)
R: 2.07, B: 2.04 = (158.43, 134.18, 210.09)
R: 1.83, B: 1.28 = (122.17, 119.14, 131.89)
R: 1.80, B: 1.16 = (121.19, 120.09, 118.13)
R: 1.80, B: 1.18 = (120.91, 120.35, 120.99)
R: 1.80, B: 1.18 = (121.14, 120.37, 120.76)
``````