Reputation: 159
I have a grayscale image that I want to apply gamma correction to. Manually, I adjust the middle slider in photoshop levels without touching the black or white points until the median value of the histogram reaches 127, or somewhere close to it. I want to do the same through python.
I looked for a formula that takes the gamma value as input and produces an output image. However, I only found one that adjusts the low and high values and not the gamma(or midtones).
Here, R is the RGB red value of pixels in my image as a numpy array, H is the high input and L the low input. I start with L=0 and H=255, and keep passing different values to the function until the image returned has median 127 on the histogram.
def get_levelcorrected_image(R,H,L):
temp=((R-L)*255.0)/(H-L)
temp[temp<0]=0
temp[temp>255]=255
temp[temp==255]=0 #added this line because for some reason there were a lot of white pixels where it should have been dark
final = temp.astype('uint8')
return final
I've tried searching the gimp documentation for a formula behind the gamma slider, and found a code that looked like this.
if (gray)
{
gdouble input;
gdouble range;
gdouble inten;
gdouble out_light;
gdouble lightness;
/* Calculate lightness value */
lightness = GIMP_RGB_LUMINANCE (gray->r, gray->g, gray->b);
input = gimp_levels_config_input_from_color (channel, gray);
range = config->high_input[channel] - config->low_input[channel];
if (range <= 0)
goto out;
input -= config->low_input[channel];
if (input < 0)
goto out;
/* Normalize input and lightness */
inten = input / range;
out_light = lightness / range;
/* See bug 622054: picking pure black or white as gamma doesn't
* work. But we cannot compare to 0.0 or 1.0 because cpus and
* compilers are shit. If you try to check out_light using
* printf() it will give exact 0.0 or 1.0 anyway, probably
* because the generated code is different and out_light doesn't
* live in a register. That must be why the cpu/compiler mafia
* invented epsilon and defined this shit to be the programmer's
* responsibility.
*/
if (out_light <= 0.0001 || out_light >= 0.9999)
goto out;
/* Map selected color to corresponding lightness */
config->gamma[channel] = log (inten) / log (out_light);
config->gamma[channel] = CLAMP (config->gamma[channel], 0.1, 10.0);
g_object_notify (G_OBJECT (config), "gamma");
I know that the gamma input value should be between 0.1 and 10, and that it's probably a log or quadratic function. I'm not sure if this is the right code fragment, as it seems to take the high, low and pixel values and produce a gamma value (I may be mistaken) whereas I want to input the gamma value and get a corrected image.
The problem is that while the image produced with this method is close to what I want, it's not the same as moving the gamma slider in say photoshop or gimp.
I'm an amateur at image manipulation with code, and this is my first question at StackOverFlow, so please forgive me for anything stupid I may have asked.
Upvotes: 2
Views: 1289
Reputation: 207670
I think this is correct, but please try it out thoroughly before putting into production:
#!/usr/bin/env python3
import numpy as np
import math
from PIL import Image
# Load starting image and ensure greyscale. Make into Numpy array for doing maths.
im = Image.open('start.png').convert('L')
imnp = np.array(im)/255
# DEBUG: print(imnp.max())
# DEBUG: print(imnp.min())
# DEBUG: print(imnp.mean()
# Calculate new gamma
gamma=math.log(imnp.mean())/math.log(0.5)
# Apply new gamma to image
new = ((imnp**(1/gamma))*255).astype(np.uint8)
# Convert back to PIL from Numpy and save
Image.fromarray(new).save('result.png')
It turns this:
into this:
If your images are large, it might be worth making a LUT (Look Up Table) of the 256 possible greyscale values and applying that rather than doing powers and things.
Or, if you don't feel like writing any Python, you can just use ImageMagick which is installed on most Linux distros and is available for macOS and Windows. So, just in Terminal (or Command Prompt on Windows):
magick input.png -auto-gamma result.png
Upvotes: 1