MathJax 3

Saturday, 31 July 2021

Colour image histogram in Python

If you have missed the previous post on image channel decomposition, follow this link and then come back here. 

Colour image histogram

We can have two main types of colour image histogram: luminance histogram and component histogram. The principle used is the same as for the grey-level histogram in both the types, with one main difference between them:  the first type is obtained from a grey-scale version of the colour image, while the second type, from the image's three separate channels. 

Component histogram

To plot the component histogram of a colour image, after decomposing the image's channels into three separate images, each one containing only the colour for each channel, we must convert these images to grayscale. This step should account for the difference in human perception between red, green and blue, not just average the three channels' intensity, as explained here.

In the following picture, we can see the result of the above procedure executed on a 24 bits RGB image:

 

clockwise from top left:original image, red, blue and green channel

 

 

Some problems with the contrast are soon very evident, especially with the blue channel.

Once we have the arrays of the intensity values for each channel, we can apply the same steps to each channel's intensity array, as we would do for a grayscale image. That would produce the histogram for each one of the image's channels, as we can see in the next picture:

 

 
 
 
 
Interpreting the histogram
 
By analyzing the histogram above, it's evident that what it seems, after a quick look, a pretty and well-taken picture, in reality, it's instead a poor quality one and with a lot of room for improvements.
The red and the blue's dynamic range, ad example, it's too low. A little better for the greens, but still not enough intensity values. As a consequence, the contrast of the image lacks variance, and hence the colours look washed-up. 
However, we can use for colour images the same contrast improvement technique we learned here, like histogram equalization, which we will learn how to implement in the next post, together with other histogram operations.
 
 
The full source code for colour histogram:
 
from PIL import Image, ImageTk
import tkinter as tk
import sys

def rgb2gray(input_image):
   if
input_image.mode != 'RGB':
       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))
               pix = int(pix[0]*0.299 +
                         pix[1]*0.587 +
                         pix[2]*0.114)
               output_image.putpixel((x,y), pix)
       return output_image


def histogram(input_image):
   
if input_image.mode != 'L' and input_image.mode != 'P':
        return None
    else:
        IHIST = [0 for i in range(256)]
        HIST = [0 for i in range(256)]
        SUM = 0        
        for x in range(input_image.width):
            for y in range(input_image.height):
                pix = input_image.getpixel((x, y))
                IHIST[pix] = IHIST[pix]+1
                SUM += 1
        for i in range(256):
            HIST[i] = float(IHIST[i]/SUM)
            
        return HIST


def draw_histogram(canvas, IHIST, hist_w, hist_h, color, bins=False):
   hist_max = max(IHIST)## get the max value
   ## Normalize between 0 and hist_h
   for i in range(256):
      IHIST[i] = float(IHIST[i]/hist_max) * hist_h
   ## A bin is a bar of the histogram
   bin_w = round(float(hist_w/256))
   offset = hist_w + 20 ## where we draw the first
   prev_x = offset
   prev_y = hist_h
   for i in range(256):
      if bins:
         canvas.create_line(bin_w*i+offset, hist_h,
            bin_w*i+offset, hist_h-IHIST[i],fill=color)
      else:
         canvas.create_line(prev_x, prev_y,
            bin_w*i+offset, hist_h-IHIST[i],fill=color)
         prev_x = bin_w*i+offset
         prev_y =  hist_h-IHIST[i]

def get_channel(input_image, channel_key):
   if input_image.mode != 'RGB':
       return None
   else:
      channels_indices = {'R':0 ,'G':1 ,'B':2}
      output_image = Image.new('RGB', (input_image.width,
                                      input_image.height))
      ch_idx = channels_indices[channel_key]
      for x in range(input_image.width):
         for y in range(input_image.height):
            pix = input_image.getpixel((x, y))
            pix = pix[ch_idx]
            if ch_idx == 0:
               output_image.putpixel((x,y), (pix,0,0))
            elif ch_idx == 1:
               output_image.putpixel((x,y), (0,pix,0))
            elif ch_idx == 2:
               output_image.putpixel((x,y), (0,0,pix))
                  
       return output_image

if __name__ == "__main__":
   root = tk.Tk()
   img = Image.open("retriver.png")
   if img == None:
      print("Error opening image file"),
      sys.exit(1)

   width = img.width*2+20
   height = img.height+20
   root.title("RGB Image histogram")
   root.geometry(f'{width}x{height}')
   
   output_R = get_channel(img, 'R')  
   output_G = get_channel(img, 'G')
   output_B = get_channel(img, 'B')

   gray_R = rgb2gray(output_R)
   gray_G = rgb2gray(output_G)
   gray_B = rgb2gray(output_B)
   
   IHISTR = histogram(gray_R)
   IHISTG = histogram(gray_G)
   IHISTB = histogram(gray_B)
   
   hist_w = img.width
   hist_h = img.height
   
   if output_R != None and output_G!= None and output_B!=None:
      output_R = ImageTk.PhotoImage(output_R)
      output_G = ImageTk.PhotoImage(output_G)
      output_B = ImageTk.PhotoImage(output_B)
      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")

      draw_histogram(canvas, IHISTR, hist_w, hist_h, "#f00")
      draw_histogram(canvas, IHISTG, hist_w, hist_h, "#0f0")
      draw_histogram(canvas, IHISTB, hist_w, hist_h, "#00f")
      
      canvas.place(x=0, y=0)
      canvas.pack()

      root.mainloop()
   else:
        print("Input image must be in 'RGB' colour space")
 
 
The code has been already explained in the previous posts. But, if something is not clear, ask for it in the comment section.

No comments:

Post a Comment