MathJax 3

Saturday, 17 July 2021

Contrast stretching in Python

Contrast can be defined as the spread in the value of brightness, within a specific range, in an image. An image with a high contrast contains a low number of shades or colours, so high contrast isn't always good, like in pictures of people or scenery, for example, as these might have lost informative details. But, this property of images can be helpful if we need to isolate some feature that could be revealed or made clear by increasing the contrast. And, in the opposite case, we could reduce the contrast for making an area more uniform and smooth. 

Contrast stretching

The formula to modify the contrast is similar to that for brightness given in the previous post, where we use multiplication, with the difference that, to avoid saturation and too much brightening, it is common to first subtract some value from the pixels values and then perform the multiplication:

 

   P0(x,y) = (P1(x,y) - 100) * 2.0 


where P1 is the pixel at (x,y) position in the input image, and P0 is the pixel of the output one. For RGB images, the formula is to apply to all the channels. Note that I chosen 2.0 as the value for the multiplication because it was the value that, to my eyes, returned a better result. I could never stress enough the importance of our personal judgement when we try to get an optimal result. 

Here is the code implementing contrast stretching in a greyscale image:

 

from PIL import Image, ImageTk
import tkinter as tk

 

def contrast(input_image, value):
    if input_image.mode != 'L' and input_image.mode != 'P':
        return None
    else:
        output_image = Image.new('L', (input_image.width,
                                     input_image.height))

        for x in range(input_image.width):
            for y in range(input_image.height):
                pix = input_image.getpixel((x, y))
                output_image.putpixel((x,y), int((pix-100) *  value))
    
        return output_image    

 
def main():
    root = tk.Tk()
    img = Image.open("retriver_gray.png", formats=['PNG'])

    width = img.width*2+20
    height = img.height
    root.geometry(f'{width}x{height}')
    
    output_im = contrast(img, 2.0)
     
    if output_im != None:
        output_im = ImageTk.PhotoImage(output_im)
        input_im = ImageTk.PhotoImage(img)
     
        canvas = tk.Canvas(root, width=width, height=height, bg="#ffffff")
        canvas.create_image(width/4-1, height/2-1, image=input_im, state="normal")
        canvas.create_image(20+3*img.width/2-1, img.height/2-1, image=output_im, state="normal")
        canvas.place(x=0, y=0)
        canvas.pack()
        
        root.mainloop()
    else:
        print("Input image's mode must be 'L' or 'P'"\
              "Check https://pillow.readthedocs.io/en/"\
              "latest/handbook/concepts.html#concept-modes")
 
if __name__ == "__main__":
    main()


This is the result produced:


The following is a bonus for those of you who came here expecially for the code: contrast stretching for RGB images: 


def contrast_rgb(input_image, value):
    if input_image.mode == 'L' or input_image.mode == 'P':
        output_image = Image.new('L', (input_image.width,
                                     input_image.height))

        for x in range(input_image.width):
            for y in range(input_image.height):
                pix = input_image.getpixel((x, y))
                value = value != None and value or 0
                output_image.putpixel((x,y), int((pix-100) *  value))
    elif input_image.mode == 'RGB':
        output_image = Image.new('RGB', (input_image.width,
                                         input_image.height))

        for x in range(input_image.width):
            for y in range(input_image.height):
                pix = input_image.getpixel((x, y))
                output_image.putpixel((x,y), (int((pix[0]-100) *  value),
                                              int((pix[1]-100) *  value),
                                              int((pix[2]-100) *  value)))
    else:
        return None
    return output_image


The result:



If you enjoy this content and would like to see more of it, or if you wish to know about a specific algorithm, please comment and also share this series. If you haven't read the older posts, head for the first one at this link. or read the next.

No comments:

Post a Comment