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.