Bryan Dunphy
Bryan Dunphy

Reputation: 851

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.

Upvotes: 8

Views: 4186

Answers (6)

ffsedd
ffsedd

Reputation: 305

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

Upvotes: 0

Gabriel Staples
Gabriel Staples

Reputation: 52777

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:

  1. test_out1.jpg # The result from @banderlog013's answer here
  2. test_out2.jpg # The result from @Canette Ouverture's answer here

Upvotes: 0

banderlog013
banderlog013

Reputation: 2505

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]

Upvotes: 2

Canette Ouverture
Canette Ouverture

Reputation: 56

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 !

Upvotes: 4

Doyousketch2
Doyousketch2

Reputation: 2147

K, cool. Figured out how to script one up.
Use it if you like. Does alright by me.

https://github.com/doyousketch2/eAWB

pic

Upvotes: -2

xenoid
xenoid

Reputation: 8904

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:

enter image description here

After:

enter image description here

All this isn't hard to do in Python.

Upvotes: 0

Related Questions