'How do I do the equivalent of Gimp's Colors, Auto, White Balance in Python-Fu?
the only function I can find is : gimp-color-balance, which takes the applicable parameters : preserve-lum(osity), cyan-red, magenta-green, and yellow-blue.
I'm not sure what values to pass for these parameters to duplicate the menu option in the title.
Solution 1:[1]
To complete the answer of @banderlog013, I think the Gimp Doc specifies that the end pixels of each channel are first discarded, then the remaining ranges are stretched. I believe the right code is :
img = cv2.imread('test.jpg')
balanced_img = np.zeros_like(img) #Initialize final image
for i in range(3): #i stands for the channel index
hist, bins = np.histogram(img[..., i].ravel(), 256, (0, 256))
bmin = np.min(np.where(hist>(hist.sum()*0.0005)))
bmax = np.max(np.where(hist>(hist.sum()*0.0005)))
balanced_img[...,i] = np.clip(img[...,i], bmin, bmax)
balanced_img[...,i] = (balanced_img[...,i]-bmin) / (bmax - bmin) * 255
I obtain good results with it, try it out !
Solution 2:[2]
According to GIMP doc, we need to discard pixel colors at each end of the Red, Green and Blue histograms which are used by only 0.05% of the pixels in the image and stretch the remaining range as much as possible (Python code):
import numpy as np
import cv2 # opencv-python
import matplotlib.pyplot as plt
img = cv2.imread('test.jpg')
x = []
# get histogram for each channel
for i in cv2.split(img):
hist, bins = np.histogram(i, 256, (0, 256))
# discard colors at each end of the histogram which are used by only 0.05%
tmp = np.where(hist > hist.sum() * 0.0005)[0]
i_min = tmp.min()
i_max = tmp.max()
# stretch hist
tmp = (i.astype(np.int32) - i_min) / (i_max - i_min) * 255
tmp = np.clip(tmp, 0, 255)
x.append(tmp.astype(np.uint8))
# combine image back and show it
s = np.dstack(x)
plt.imshow(s[::,::,::-1])
The result is pretty the same as after GIMP's 'Colors -> Auto -> White Balance'
UPD: we need np.clip() because OpenCV and numpy differently casts int32 to uint8:
# Numpy
np.array([-10, 260]).astype(np.uint8)
>>> array([246, 4], dtype=uint8)
# but we need just [0, 255]
Solution 3:[3]
From what I understand after a quick look at the source code (and more or less confirmed with a test image), these are unrelated and under the hood,Colors>Auto>White Balance:
- obtains the histogram for each channel
- get the values that determine the bottom and top 0.6%
- stretches the range of values for that channel using these two values as the black and white points using an internal call that is very similar to "Levels".
Proof with a synthetic image:
Before:
After:
All this isn't hard to do in Python.
Solution 4:[4]
How to essentially get the equivalent of GIMP's Colors --> Auto --> White Balance feature:
Tested on Ubuntu 20.04.
Download the below code from my eRCaGuy_hello_world repo here: python/auto_white_balance_img.py.
Install dependencies:
pip3 install opencv-python # for cv2
pip3 install numpy
Now here is some fully-functional code, unlike some of the other answers here which are snippets and lacking things like import statements. I'm borrowing from @Canette Ouverture's answer here, and @banderlog013's answer here.
Create file auto_white_balance_img.py:
#!/usr/bin/python3
import cv2
import numpy as np
file_in = 'test.jpg'
file_in_base = file_in[:-4] # strip file extension
file_in_extension = file_in[-4:]
img = cv2.imread(file_in)
# From @banderlog013's answer: https://stackoverflow.com/a/54864315/4561887
x = []
# get histogram for each channel
for i in cv2.split(img):
hist, bins = np.histogram(i, 256, (0, 256))
# discard colors at each end of the histogram which are used by only 0.05%
img_out1 = np.where(hist > hist.sum() * 0.0005)[0]
i_min = img_out1.min()
i_max = img_out1.max()
# stretch hist
img_out1 = (i.astype(np.int32) - i_min) / (i_max - i_min) * 255
img_out1 = np.clip(img_out1, 0, 255)
x.append(img_out1.astype(np.uint8))
# From @Canette Ouverture's answer: https://stackoverflow.com/a/56365560/4561887
img_out2 = np.zeros_like(img) # Initialize final image
for channel_index in range(3):
hist, bins = np.histogram(img[..., channel_index].ravel(), 256, (0, 256))
bmin = np.min(np.where(hist>(hist.sum()*0.0005)))
bmax = np.max(np.where(hist>(hist.sum()*0.0005)))
img_out2[...,channel_index] = np.clip(img[...,channel_index], bmin, bmax)
img_out2[...,channel_index] = ((img_out2[...,channel_index]-bmin) /
(bmax - bmin) * 255)
# Write new files
cv2.imwrite(file_in_base + '_out1' + file_in_extension, img_out1)
cv2.imwrite(file_in_base + '_out2' + file_in_extension, img_out2)
Make auto_white_balance_img.py executable:
chmod +x auto_white_balance_img.py
Now set the file_in variable in the file above to your desired input image path, then run it with:
python3 auto_white_balance_img.py
# OR
./auto_white_balance_img.py
Assuming you have set file_in = 'test.jpg', it will produce these two files:
test_out1.jpg# The result from @banderlog013's answer heretest_out2.jpg# The result from @Canette Ouverture's answer here
Solution 5:[5]
I use this function to auto white balance images. Unlike Gimp function, it does not normalize image contrast. So it is useful with low contrast images too.
import numpy as np
from imageio import imread
import matplotlib.pyplot as plt
def auto_white_balance(im, p=.6):
'''Stretch each channel histogram to same percentile as mean.'''
# get mean values
p0, p1 = np.percentile(im, p), np.percentile(im, 100-p)
for i in range(3):
ch = im[:,:,i]
# get channel values
pc0, pc1 = np.percentile(ch, p), np.percentile(ch, 100-p)
# stretch channel to same range as mean
ch = (p1 - p0) * (ch - pc0) / (pc1 - pc0) + p0
im[:,:,i] = ch
return im
def test():
im = imread('imageio:astronaut.png')
# distort white balance
im[:,:,0] = im[:,:,0] *.6
im[:,:,1] = im[:,:,1] *.8
plt.imshow(im)
plt.show()
im2 = auto_white_balance(im)
im2 = np.clip(im2, 0, 255) # or 0, 1 for float images
plt.imshow(im2)
plt.show()
if __name__ == "__main__":
test()
If you want equivalent of Gimp function, use fixed values instead: p0, p1 = 0, 255
Solution 6:[6]
K, cool. Figured out how to script one up.
Use it if you like. Does alright by me.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Canette Ouverture |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | |
| Solution 5 | |
| Solution 6 |


