MathJax 3

Monday, 16 August 2021

Histogram matching(specification) inplementation in Python


In today note, we will continue our exploration of histogram techniques by learning about histogram matching, also called histogram specification.

Histogram equalization is already a particular case of histogram matching, but with some substantial differences.
In histogram matching, we increase or decrease the contrast of an image using the inverse transform of a specified histogram. But where do we get the histogram from, you may ask?
We can either generate one or get one from another image that satisfies our requirements for the image which contrast we want to modify. 


Let's assume we want to increase the brightness of the image below:



this image is quite dark and lacks contrast. Now, let's consider the next one:



the one above it's brighter than the first image, so it must also have a histogram with a range of values higher when compared to the first.  Let's see a visual confirmation of that:



 

now we are sure that the histogram of the image on the right it's a good specification to match for the image on the left as it contains a wider range of high intensity values. Now, we have all we need for brightening our image by histogram matching.

Histogram matching steps

The first step is to equalize the desired histogram and the histogram of our image:

 

    HISTSPEC = [0 for i in range(256)]
    SUM = 0.0
   
    _, eq_im = histogram_equalization(input_image)

    
    for I in range(256):
        SUM = 0.0
        for J in range(256):
            SUM += SPEC[J] # SPEC is passed to the function
        HISTSPEC[I] = int(255*SUM+0.5)


then, we can proceed to find the inverse transformation of the desired histogram:


    INVHIST = [0 for i in range(256)] 
    for I in range(256):
        minval = abs(I-HISTSPEC[0])
        minj = 0
        for J in range(256):
            if (abs(I - HISTSPEC[J]) < minval):
                minval = abs(I - HISTSPEC[J])
                minj = J
            INVHIST[I] = minj

 

finally applying  this to our image:

 

    for x in range(input_image.width):
        for y in range(input_image.height):
            pix = eq_im.getpixel((x, y))
            output_image.putpixel((x, y),INVHIST[pix])


let's check the results:




the black histogram is that of the original image, the blue one is that of the image used to get the match, and the orange is the histogram of the resulting image. We had a noticeable improvement. The dynamic range of the resulting image is a lot wider than it was in the original, with a consequent increased contrast and brightness. The image on the left is the resulting image.

Below, a side by side comparation of the original image and the same after the matching process:

 

 

The complete code for this post:

 

 from PIL import Image, ImageTk, ImageFilter
import tkinter as tk

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 histogram_equalization(input_image):
    if input_image.mode != 'L' and input_image.mode != 'P':
        return None
    else:
        HISTEQ = [0 for i in range(256)]
        SUM = 0.0
        HIST = histogram(input_image)
        for i in range(256):
            SUM = 0.0
            for j in range(i+1):
                SUM += HIST[j]
            HISTEQ[i] = int(255*SUM+.5)
        output_image = Image.new('L', (input_image.width,
                                        input_image.height))
        for y in range(input_image.height):
            for x in range(input_image.width):
                pix = input_image.getpixel((x, y))
                output_image.putpixel((x, y), HISTEQ[pix])            
        return HISTEQ, output_image
    
def histogram_matching(input_image, SPEC):
    HISTSPEC = [0 for i in range(256)]
    INVHIST = [0 for i in range(256)]
    SUM = 0.0
    ## Equalize input_image's histogram
    _, eq_im = histogram_equalization(input_image)

    ## Equalize the specified histogram
    for I in range(256):
        SUM = 0.0
        for J in range(I):
            SUM += SPEC[J]
        HISTSPEC[I] = int(255*SUM+0.5)
        
    ## Find the inverse transformation
    ## of the specified histogram     
    for I in range(256):
        minval = abs(I-HISTSPEC[0])
        minj = 0
        for J in range(256):
            if (abs(I - HISTSPEC[J]) < minval):
                minval = abs(I - HISTSPEC[J])
                minj = J
            INVHIST[I] = minj
            
    output_image = Image.new('L', (input_image.width,
                                   input_image.height))

    ## Apply the inverse transformation
    ## to the gray-level values of th pixels
    ## in the equalized image
    for x in range(input_image.width):
        for y in range(input_image.height):
            pix = eq_im.getpixel((x, y))
            output_image.putpixel((x, y),INVHIST[pix])

    return INVHIST,
output_image                
        
    
def draw_histogram(canvas, IHIST, hist_w, hist_h, color, rangeT=(0,256,1), bins=False):
   '''
    canvas: the Tk canvas
    IHIST:  the histogram to draw
    hist_w: histogram width
    hist_h: histogram height
    color:  plot colour
    rangeT: tuple used for histogram steps
    bins:   if False draws a line instead of bars
   '''
   
   hist_max = max(IHIST)
   ## 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/300))
   offset = hist_w + 20 ## where we draw the first
   prev_x = offset
   prev_y = hist_h
   for i in range(rangeT[0],rangeT[1],rangeT[2]):
      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]


if __name__=="__main__":
    root = tk.Tk()
    root.title("HISTOGRAM MATCHING")
    imgT = Image.open("retriver_gray.png")
    HISTS = histogram(imgT) ## specified histogram
    
    img = Image.open("rose.jpg") ## input image
    
    print(img.mode)
    width = img.width*2
    height = img.height
    root.geometry(f'{width}x{height}')
    
    HISTOR = histogram(img) ## input image's histogram
    HISTEQ, out_im = histogram_matching(img, HISTS)
    HISTEQ = histogram(out_im)
 
    if HISTEQ != None:
        hist_w = img.width
        hist_h = img.height

        hist_max = max(HISTEQ) ## get the max value
        ## Normalize between 0 and hist_h
        for i in range(256):
            HISTEQ[i] = float(HISTEQ[i]/hist_max) * hist_h
        
        output_im = ImageTk.PhotoImage(out_im)
     
        canvas = tk.Canvas(root, width=width, height=height, bg="#ffffff")
        canvas.create_image(width/4-1, height/2-1, image=output_im, state="normal")
        out_im.save("rose_match2.jpg")

        draw_histogram(canvas, HISTS, hist_w, hist_h, color='#08f',bins=True)
        draw_histogram(canvas, HISTOR, hist_w, hist_h, color='#000',bins=True)
        draw_histogram(canvas, HISTEQ, hist_w, hist_h, color='#f80',bins=True)
        
             
        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 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.

No comments:

Post a Comment