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.
No comments:
Post a Comment