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.

Thursday, 12 August 2021

How to fix the webcam not working with opencv on Windows 10


If my blog is not ranked higher than stackoverflow.com, a good chance exists that you have been reading this SO's thread before landing here. Well, if you landed on my blog, it signifies SO didn't help you. If so, then keep reading.

 I installed OpenCV for the first time about 15 years ago. Since I can remember,  using a webcam has always been the most tricky part of working with it. Don't get me wrong, OpenCV is a magnificent open-source library, really of a high standard. But when it comes to closed source drivers, it may suffer from some compatibility problems, as it happens with nearly all open-source software.
There are other times instead when the causes for malfunctioning webcam lye outside of OpenCV source code.

As you may know already, Windows 10 offers some privacy setting which enables the user to exclude any program from using the webcam.
The good news is that these settings might be all that is preventing your webcam from working.
Open Settings and hit Privacy. On the left pane, hit Camera, go in the main panel and scroll all down. If you followed along, you should see this:

 



Make sure the last switch is on. Now, close the Settings and try out your OpenCV code.
Everything works, right?

With no shame, I admit that I reinstalled OpenCV 4.3.5 several times before realizing that switch had been turned off by Windows, for who knows which reasons.

I hope this helped you.

Saturday, 7 August 2021

RGB image histogram equalization in Python

In RGB images, a correlation exists between the properties of the colours, so to equalize an RGB image's histogram is not possible to directly apply the histogram equalization technique used for greyscale on each channel. The Colour's intensity needs to be isolated. That's why, in the standard method, the image is converted first to HSV/HSL or Lab colour space. After this step, histogram equalization is applied to the value channel, as if it was an 8 bits grayscale image. In this way, hue and saturation will remain untouched, and so the image's colour balance.

The following picture shows the original RGB image converted to HSV and its value channel histogram:
 

 

The picture below shows the equalized histogram and the effect of histogram equalization on the image:



The source code for this post:


from PIL import Image, ImageTk
import tkinter as tk

def hsv2gray(input_image):
   if input_image.mode != 'HSV':
      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 = pix[2]
            output_image.putpixel((x,y), pix)
      return output_image
 
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(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, 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()
   img = Image.open("retriver.png")

   width = img.width*2+20
   height = img.height+20
   root.title("RGBtoHSV Image - Value channel equalized histogram")
   root.geometry(f'{width}x{height}')  

   output_image = img.convert('HSV')

   values_image = hsv2gray(output_image)
   HISTEQ = histogram(values_image)
   ##HISTEQ, values_image = histogram_equalization(values_image)
   ##HISTEQ = histogram(values_image)
   
   output_image2 = Image.new('HSV', (img.width,
                                     img.height))
 
   for x in range(img.width):
      for y in range(img.height):
         pix = values_image.getpixel((x, y))
         hsv_pix = output_image.getpixel((x,y))
         output_image2.putpixel((x,y), (hsv_pix[0], hsv_pix[1], pix))
    
   hist_w = img.width
   hist_h = img.height
 
   if output_image!=None:
      output_image = ImageTk.PhotoImage(output_image2)

      canvas = tk.Canvas(root, width=width, height=height, bg="#ffffff")
      canvas.create_image(width/4-1, height/2-1, image=output_image, state="normal")
      
      draw_histogram(canvas, HISTEQ, hist_w, hist_h, "#000", bins=True)
            
      canvas.place(x=0, y=0)
      canvas.pack()

      root.mainloop()
   else:
      print("Input image must be in 'RGB' colour space")


I will not go through all the code because I discussed it in the previous posts. I want only to explain this new function used to extract the value channel from the HSV image:

 

 def hsv2gray(input_image):
  if input_image.mode != 'HSV':
     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 = pix[2]
           output_image.putpixel((x,y), pix)
     return output_image

 

 the function makes sure it is receiving an HSV image:
  

if input_image.mode != 'HSV':
     return None"


if so, it creates a new one in grayscale that will use as output:

 

     output_image = Image.new('L', (input_image.width,
                          input_image.height))


then it loops over the input image's pixels and stores the value channel in the newly created image, which it is then returned:

 

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

 

Histogram equalization it's a simple and powerful tool to improve the contrast of both colour and grayscale images. There are other histogram techniques at our disposal, like histogram matching, which we'll learn in the next post. Stay tuned.


All posts in this serie