Image Equalization (Contrast Enhancing) in Python

Image Equalization (Contrast Enhancing) in Python

Introduction

I have been practicing image processing for quite a little - manipulating the images (image matrices to be precise). In doing so, I got to explore the equalizing methods for images so as to enhance the contrast to a certain extent that the manipulated image looks better than the original image. This technique is termed as Histogram Equalization.

Credits of Cover Image - Photo by Andreas Gücklhorn on Unsplash

Often times what happens is when the image is captured, it will not be the same as the natural view. In order to meet the level of natural view, post-processing should be done. Hence Histogram Equalization (Normalization) is one of those techniques to enhance the contrast by tweaking the pixel values of the image.

An example can be seen below - original image and equalized image.

low-high-contrast-image

If we were to plot the image histograms, it would look something like below -

low-high-histograms

Credits - The above images have been taken from the Internet for showing the examples.

Importance of Histogram Equalization

  • This method works better for both bright and dark images, especially in the field of medical science there is higher importance in analyzing the X-ray images.

  • It is also very useful in viewing scientific images like thermal images and satellite images.

Implementation

In this article, I will implement this method both by using the openCV library and from scratch with just NumPy and Matplotlib. Although I would like to do without using NumPy, it would take much time to compute.

Note - For coding from scratch, I will use openCV to read the image and nothing else.

I have taken Lena Image for testing the functions. I have saved the same in my working directory.

Import the Requirements

The packages that we mainly use are:

  • NumPy
  • Matplotlib
  • OpenCV → It is only used for reading the image (in this article).

python_packages.png

import cv2
import numpy as np
from matplotlib import pyplot as plt

Read the Image

def read_this(image_file, gray_scale=False):
    image_src = cv2.imread(image_file)
    if gray_scale:
        image_src = cv2.cvtColor(image_src, cv2.COLOR_BGR2GRAY)
    else:
        image_src = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)
    return image_src

The above function reads the image either in gray_scale or RGB and returns the image matrix.

Code Implementation with Library

For equalizing, we can simply use the equalizeHist() method available in the library cv2. We have two aspects here -

  1. When the image is read in RGB.

    • Separate the pixels based on the color combination. We can use the split() method available in the library cv2.
    • Apply the equalization method for each matrix.
    • Merge the equalized image matrices altogether with the method merge() available in the library cv2.
  2. When the image is read in gray_scale.

    • Just apply the equalization method for the image matrix.
  3. Plot the original image and equalized image.

def equalize_this(image_file, with_plot=False, gray_scale=False):
    image_src = read_this(image_file=image_file, gray_scale=gray_scale)
    if not gray_scale:
        r_image, g_image, b_image = cv2.split(image_src)

        r_image_eq = cv2.equalizeHist(r_image)
        g_image_eq = cv2.equalizeHist(g_image)
        b_image_eq = cv2.equalizeHist(b_image)

        image_eq = cv2.merge((r_image_eq, g_image_eq, b_image_eq))
        cmap_val = None
    else:
        image_eq = cv2.equalizeHist(image_src)
        cmap_val = 'gray'

    if with_plot:
        fig = plt.figure(figsize=(10, 20))

        ax1 = fig.add_subplot(2, 2, 1)
        ax1.axis("off")
        ax1.title.set_text('Original')
        ax2 = fig.add_subplot(2, 2, 2)
        ax2.axis("off")
        ax2.title.set_text("Equalized")

        ax1.imshow(image_src, cmap=cmap_val)
        ax2.imshow(image_eq, cmap=cmap_val)
        return True
    return image_eq

Let's test the above function -

equalize_this(image_file='lena_original.png', with_plot=True)

eql_lena.png

equalize_this(image_file='lena_original.png', with_plot=True, gray_scale=True)

eqlg_lena.png

The above plots are clear and we can say that the equalized images look better than the original images. This was implemented using the cv2 library.

Code Implementation from Scratch

For this, I am using NumPy for all the matrix operations. Again we can do it with for loops, but it will take more time to compute. Even here we have two aspects as before -

  1. When the image is read in RGB.

    • Separate the pixels based on the color combination. We can slice it down using NumPy operations.
    • Apply the equalization method for each matrix.
    • Merge the equalized image matrices altogether with the method dstack(tup=()) available in the library NumPy.
  2. When the image is read in gray_scale.

    • Just apply the equalization method for the image matrix.
  3. Plot the original image and equalized image.

Let's write our own function to compute the image equalization. Image pixel values are normally in the range of 0 to 255. So in total, we will have 256 pixels.

def enhance_contrast(image_matrix, bins=256):
    image_flattened = image_matrix.flatten()
    image_hist = np.zeros(bins)

    # frequency count of each pixel
    for pix in image_matrix:
        image_hist[pix] += 1

    # cummulative sum
    cum_sum = np.cumsum(image_hist)
    norm = (cum_sum - cum_sum.min()) * 255
    # normalization of the pixel values
    n_ = cum_sum.max() - cum_sum.min()
    uniform_norm = norm / n_
    uniform_norm = uniform_norm.astype('int')

    # flat histogram
    image_eq = uniform_norm[image_flattened]
    # reshaping the flattened matrix to its original shape
    image_eq = np.reshape(a=image_eq, newshape=image_matrix.shape)

    return image_eq

Credits - The above code is an inspiration from the article written by Tory Walker.

The above function returns an equalized image matrix when passed the original image matrix as an argument.

Let's write another function that computes the equalization for both the RGB image and the gray_scale image taking the above function in use.

def equalize_this(image_file, with_plot=False, gray_scale=False, bins=256):
    image_src = read_this(image_file=image_file, gray_scale=gray_scale)
    if not gray_scale:
        r_image = image_src[:, :, 0]
        g_image = image_src[:, :, 1]
        b_image = image_src[:, :, 2]

        r_image_eq = enhance_contrast(image_matrix=r_image)
        g_image_eq = enhance_contrast(image_matrix=g_image)
        b_image_eq = enhance_contrast(image_matrix=b_image)

        image_eq = np.dstack(tup=(r_image_eq, g_image_eq, b_image_eq))
        cmap_val = None
    else:
        image_eq = enhance_contrast(image_matrix=image_src)
        cmap_val = 'gray'

    if with_plot:
        fig = plt.figure(figsize=(10, 20))

        ax1 = fig.add_subplot(2, 2, 1)
        ax1.axis("off")
        ax1.title.set_text('Original')
        ax2 = fig.add_subplot(2, 2, 2)
        ax2.axis("off")
        ax2.title.set_text("Equalized")

        ax1.imshow(image_src, cmap=cmap_val)
        ax2.imshow(image_eq, cmap=cmap_val)
        return True
    return image_eq

Let's test the above function -

equalize_this(image_file='lena_original.png', with_plot=True)

eqs_lena.png

equalize_this(image_file='lena_original.png', with_plot=True, gray_scale=True)

eqsg_lena.png

The above plots are clear and we can say that the equalized images look better than the original images. This was implemented from scratch using the NumPy library.

Comparison

Let's compare the equalized image obtained from the cv2 library and the equalized image obtained from the code written from scratch.

ols_lena.png

We can notice there is a slight difference between the library image and scratch image. But both seem to be clear when compared with the original image. Here I complete my article with my own takeaway.

Takeaway

  • Personally, I learned a lot by exploring and implementing different methods applied to increase the image intensity. Especially, trying to implement the code from scratch by both referring and learning.

  • It is always good to use the library methods as they seem to be more optimized and works 100 percent.

  • Image processing is a very crucial subject to learn and one really deserves to try out practicing with so much curiosity and ones' own exploration.

Do give a read to my other articles and let me know your thoughts -

  1. Image Mirroring and Flipping
  2. Image Convulution