As I have mentioned in the previous post, it's possible to process an image's histogram to spread the distribution of grey level values. This procedure is called histogram equalization. By equalizing the histogram, we redistribute the image's grey level values uniformly so that the number of pixels at any value becomes almost equivalent. More formally, this technique gives a linear trend to the cumulative probability function associated with the image.
Suppose that we have an image f(x,y), and its histogram h(i). The cumulative distribution function of h(i) is as follow:
it can be proved that the above transform makes y = c(i) to follow a uniform distribution. Thus, for a 256 gray-level image, the following equation defines the transformation to equalize its histogram:
where n is the total number of pixels in the image.
This operation increases the contrast of the image, which becomes much clear and filled with details.
Histogram equalization in Python
As a first thing, we need a function to compute the image's histogram, about which you can read in the previous post:
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
then we can write our histogram equalization function:
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):
SUM += HIST[j]
HISTEQ[i] = int(255*SUM+0.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
The function above computes the histogram of the input image. Then, from this histogram, then the cumulative histogram is obtained (first nested loop). Finally, a new image is created and populated using the grey levels pixel values stored by the cumulative histogram (second nested loop). This image is then returned together with the histogram.
Below we can see the original greyscale image and its histogram:
original image and its histogram |
the next picture shows the image after histogram equalization and its new histogram:
Picture A |
Even though the plot doesn't show a scale, it's easy to see how the grey level values of the pixels are now more spread than in the previous image.
In the next picture we can see the plot of the cumulative histogram:
Picture B |
The above histogram is that returned by the histogram_equalization function.
If we compute and plot the above picture's cumulative histogram, we obtain the results shown in the next picture:
Picture C |
we can see how the resulting cumulative histogram approximates a uniformly distributed image.
Histogram equalization implementation in Python
To obtain the results visible in the above pictures, we only need the two fumctions given at the beginning of this post, but we need to alternate them in the following manner:
Picture A:
HISTEQ, out_im = histogram_equalization(img)
HISTEQ = histogram(out_im)
Picture B:
HISTEQ, out_im = histogram_equalization(img)
Picture C:
HISTEQ, out_im = histogram_equalization(img)
HISTEQ, out_im = histogram_equalization(out_im)
the following is the complete code for this post:
from PIL import Image, ImageTk
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 draw_histogram(canvas, IHIST, hist_w, hist_h):
## A bin is a bar of the histogram
bin_w = round(float(hist_w/512))
offset = hist_w + 20 ## where we draw the first bin
for i in range(256):
canvas.create_line(bin_w*i+offset, hist_h,
bin_w*i+offset, hist_h-IHIST[i])
if __name__=="__main__":
root = tk.Tk()
root.title("IMAGE GREY-LEVEL HISTOGRAM EQUALIZED")
img = Image.open("retriver_gray.png")
width = img.width*2
height = img.height
root.geometry(f'{width}x{height}')
HISTEQ, out_im = histogram_equalization(img)
HISTEQ = histogram(out_im)
## HISTEQ, out_im = histogram_equalization(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")
draw_histogram(canvas, HISTEQ, hist_w, hist_h)
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")
That's all for now. I hope you're enjoying and finding interesting this series on image processing in Python. Please, comment, share and get in touch if you find any error or if you would like to request a specific algorithm. In the next post we will learn about more histogram operations and colour image's histogram. Stay tuned.
No comments:
Post a Comment