Adjusting contrast of image purely with numpy

Question:

I am trying write a contrast adjustment for images in gray scale colors but couldn’t find the right way to do it so far. This is what I came up with:

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from scipy import misc
def fix_contrast(image):
    minimumColor = np.amin(image)
    maximumColor = np.amax(image)
    #avg = (minimumColor - maximumColor)/2 first attempt
    avg = np.mean(image) #second attempt
    colorDownMatrix = image < avg # also tried
    colorUpMatrix = image > avg 
    #also tried:   colorUpMatrix = image > avg * 1.2
    # and : colorDownMatrix = image < avg* 0.3

    image = image - minimumColor*colorDownMatrix
    image = image + maximumColor*colorUpMatrix
    lessThen0 = image<0
    moreThen255 = image>255
    image[lessThen0] = 0
    image[moreThen255] = 255
    return image    

My general attempt was to decrease elements towards 0 the pixels that their are “closer” to 0 and to increase elements towards 255 the elements that are “closer” to 255. I tried to measure closeness by mean function but before that by the arithmetic average, but all my attempts didn’t get me to any good result.

the image I am working on :
enter image description here

the result I am aiming for:
goal picture

Am I anywhere close to the solution? Any hints/ tips will be great

Asked By: barshopen

||

Answers:

You need to apply a mapping curve like this:

contrast

It makes the dark tones darker, the light tones lighter, and increases the range of the medium shades.

To achieve that, I’d find the minimum and maximum, then create a lookup table that expands the narrow remaining range into a whole range between 0 and 255. After that, I’d apply the lookup table.

This will certainly leave some blocking, because the ranges of nice gradients of the source were compressed in a lossy way. To fix it, you might consider applying a “smart blur” algorithm that blurs only pixels that have low contrast between them, and does not touch those with high contrast. (I don’t see a nice link with a numpy-friendly algorithm, though).

Answered By: 9000

I’m learning Python and numpy and thought I’d try to implement a "LookUp Table" (LUT). It works, and the output image has the full range from black to white, but I’m happy to receive suggestions for improvement.

#!/usr/local/bin/python3
import numpy as np
from PIL import Image

# Open the input image as numpy array, convert to greyscale and drop alpha
npImage=np.array(Image.open("cartoon.png").convert("L"))

# Get brightness range - i.e. darkest and lightest pixels
min=np.min(npImage)        # result=144
max=np.max(npImage)        # result=216

# Make a LUT (Look-Up Table) to translate image values
LUT=np.zeros(256,dtype=np.uint8)
LUT[min:max+1]=np.linspace(start=0,stop=255,num=(max-min)+1,endpoint=True,dtype=np.uint8)

# Apply LUT and save resulting image
Image.fromarray(LUT[npImage]).save('result.png')

enter image description here

Answered By: Mark Setchell

The easiest way to increase the contrast (i.e. pull apart darker and brighter pixels), is just to “stretch out” the current existing range (144 to 216) over the entire spectrum (0 to 255):

Setup, same way as in this answer.

import numpy as np
from PIL import Image

pixvals = np.array(Image.open("image.png").convert("L"))

And then expand the range

pixvals = ((pixvals - pixvals.min()) / (pixvals.max()-pixvals.min())) * 255
Image.fromarray(pixvals.astype(np.uint8))

The result is effectively the same as in this answer, just with slightly less code:
enter image description here

Now, in this image, that might be enough. However some images might have a few pixels that are really close to 0 or 255, which would render this method ineffective.

Here numpy.percentile() comes to the rescue. The idea is to “clip” the range in which pixels are allowed to exist.

minval = np.percentile(pixvals, 2)
maxval = np.percentile(pixvals, 98)
pixvals = np.clip(pixvals, minval, maxval)
pixvals = ((pixvals - minval) / (maxval - minval)) * 255
Image.fromarray(pixvals.astype(np.uint8))

Which results in a little bit higher contrast, since all values below 2% and above 98% are effectively removed. (Play with these values as you see fit)
enter image description here

Answered By: Johan Dettmar

You may need auto transforms using Tensroflows!

image = plt.imread('F:\temp\20220117\GO48R.png')
model_normalize = tf.keras.models.Sequential([  
            tf.keras.layers.InputLayer(input_shape=(256, 256)),
            tf.keras.layers.Normalization(mean=3., variance=2.),
            tf.keras.layers.Normalization(mean=4., variance=6.),])

predictions_red = model_normalize.predict(np.reshape(red, (1, 128, 128)))
predictions_green = model_normalize.predict(np.reshape(green, (1, 128, 128)))
predictions_blue = model_normalize.predict(np.reshape(blue, (1, 128, 128))) 
    
predictions = np.ones((128, 128, 3)) - 1
predictions[:,:,0] = predictions_red
predictions[:,:,1] = predictions_blue
predictions[:,:,2] = predictions_green

predictions = np.reshape(predictions, (128,128,3))
predictions = ( predictions - np.min(predictions) ) * 10
plt.figure(figsize=(1,2))
plt.subplot(1,2,2)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(predictions)
plt.xlabel('predictions')
    
plt.subplot(1,2,1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(image)
plt.xlabel('image')
    
plt.show()
plt.close()

As someone asking about blur and contrast: Normalized and Contrast

Without Networks it contrast all

Answered By: Jirayu Kaewprateep
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.