Sasani Perera · Follow
7 min read · Jul 14, 2023
Image histograms provide valuable insights into the distribution of pixel intensities in an image. By understanding the histogram, you can gain information about the image’s contrast, brightness, and overall tonal distribution. This knowledge can be useful for tasks like image enhancement, image segmentation, and feature extraction.
This article aims to provide a clear and comprehensive guide to learning how to perform image histogram calculations using OpenCV. By understanding and applying histogram analysis techniques, you are enabled to enhance image quality, perform thresholding operations, analyze colour compositions, extract useful features, and visualize and understand images more effectively.
Every image is composed of individual pixels, which are like tiny dots on a grid. Let’s say we have an image with a size of 250 columns and 100 rows, totalling 2500 pixels. Each of these pixels can have a different colour value, represented by a number ranging from 0 to 255 (for a greyscale image as we discussed in the earlier article).
To visualize the distribution of colour values within the image, we can create a histogram. This histogram acts as a set of bar graphs that displays the number of pixels having the same colour value. By comparing the heights of the bars, we can easily identify which colour values are more prominent or occur more frequently in the image. This graphical representation provides valuable insights into the overall colour composition and distribution of an image.
Now remember, pixel intensity
0 → Black
255 → White
So the image must contain more Black pixels if our histogram is shifted to the left (left-skewed) and the image must contain more White pixels if our histogram is shifted to the right (right-skewed).
So I am sure that you thoroughly understand that,
More black → darker image
More white → brighter image
Now let us do the Histogram Calculation in OpenCV.
First, we’ll load the image and visualize it.
#import necessary libraries
import cv2
import numpy as np
import matplotlib.pyplot as plt#using opencv to read an image
#BGR Image
image = cv2.imread("C:/users/public/pictures/nature.jpg")
#visualizing
cv2.namedWindow("BGR Image", cv2.WINDOW_NORMAL);
cv2.imshow("BGR Image",image);
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Before we plot the histogram, we can separate the colour channels in this image.
B = image[:,:,0] #blue layer
G = image[:,:,1] #green layer
R = image[:,:,2] #red layer
Now we calculate and find the histograms for each layer using OpenCV function cv.calcHist() and plot those histograms, using OpenCV and Matplotlib functions
cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
● images: it is the source image of type uint8 or float32. it should be given in square brackets, ie, “[img]”.
● channels: it is also given in square brackets. It is the index of the channel for which we calculate the histogram. For example, if the input is a grayscale image, its value is [0]. For colour images, you can pass [0], [1] or [2] to calculate histograms of blue, green or red channels respectively.
● mask: mask image. To find the histogram of the full image, it is given as “None”. But if you want to find a histogram of a particular region of the image, you have to create a mask image for that and give it as a mask.
● histSize: this represents our BIN count. Need to be given in square brackets. For full scale, we pass [256].
● ranges: this is our RANGE. Normally, it is [0,256].
B_histo = cv2.calcHist([image],[0], None, [256], [0,256])
G_histo = cv2.calcHist([image],[1], None, [256], [0,256])
R_histo = cv2.calcHist([image],[2], None, [256], [0,256])
Now we plot these in subplots using matplotlib.
You may try this on images of different settings.
COMPLETE CODE
#import necessary libraries
import cv2
import numpy as np
import matplotlib.pyplot as plt#using opencv to read an image
#BGR Image
image = cv2.imread("C:/users/public/pictures/nature.jpg")
#seperating colour channels
B = image[:,:,0] #blue layer
G = image[:,:,1] #green layer
R = image[:,:,2] #red layer
#calculating histograms for each channel
B_histo = cv2.calcHist([image],[0], None, [256], [0,256])
G_histo = cv2.calcHist([image],[1], None, [256], [0,256])
R_histo = cv2.calcHist([image],[2], None, [256], [0,256])
#visualizing histograms
plt.subplot(2, 2, 1)
plt.plot(B_histo, 'b')
plt.subplot(2, 2, 2)
plt.plot(G_histo, 'g')
plt.subplot(2, 2, 3)
plt.plot(R_histo, 'r')
#visualizing image
cv2.namedWindow("BGR Image", cv2.WINDOW_NORMAL);
cv2.imshow("BGR Image",image);
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Then we can extend this idea to identify overexposed (too bright) images and underexposed (too dark) images.
Let us see the histograms of these images.
Clearly, one histogram is left-skewed which the image is underexposed and another histogram is right-skewed which the image is overexposed.
Here, we can clearly understand if an image is underexposed or overexposed just by looking at its histograms.
Consider an underexposed or overexposed image whose pixel values are confined to some specific range of values only.
For eg: the brighter image will have all pixels confined to high values.
But a good image will have pixels from all regions of the image. So you need to stretch this histogram to either end. This normally improves the contrast of the image.
When performing histogram equalization on colour images we usually apply the process separately to the Red, Green, and Blue components of the RGB colour values in the image.
First, we read the image and split the image into three colour layers.
import cv2
import numpy as np
import matplotlib.pyplot as plt#using opencv to read an image
#BGR Image
image = cv2.imread("C:/users/public/pictures/underexposed_image.jpg")
#seperating colour channels
B = image[:,:,0] #blue layer
G = image[:,:,1] #green layer
R = image[:,:,2] #red layer
Then we use cv.equalizeHist() to equalize each of the colour layers’ histograms. Visualize them using Matplotlib and OpenCV.
b_equi = cv2.equalizeHist(B)
g_equi = cv2.equalizeHist(G)
r_equi = cv2.equalizeHist(R)plt.imshow(b_equi)
plt.title("b_equi")
plt.show()
plt.imshow(g_equi)
plt.title("g_equi")
plt.show()
plt.imshow(r_equi)
plt.title("r_equi")
plt.show()
With the equalized colour layers’ we calculate each colour’s histogram using cv.calcHist(). And then plot them all.
B_histo = cv2.calcHist([b_equi],[0], None, [256], [0,256])
G_histo = cv2.calcHist([g_equi],[0], None, [256], [0,256])
R_histo = cv2.calcHist([r_equi],[0], None, [256], [0,256])plt.subplot(2, 2, 1)
plt.plot(G_histo, 'g')
plt.subplot(2, 2, 2)
plt.plot(R_histo, 'r')
plt.subplot(2, 2, 3)
plt.plot(B_histo, 'b')
You must have noticed that we have used only [0]s in the ‘channels’ place. In the previous case, we used all [0], [1] and [2]. This is due to, the availability of split channels. Therefore there’s only 1 channel. Hence for all histograms, ‘channels’ is [0]
Optionally, you can get the histograms of each channel in the original image and plot them with the equalized colour layers.
#calculate histograms for each channel seperately
#Equilized channels
B_histo = cv2.calcHist([b_equi],[0], None, [256], [0,256])
G_histo = cv2.calcHist([g_equi],[0], None, [256], [0,256])
R_histo = cv2.calcHist([r_equi],[0], None, [256], [0,256])
#Original channels
BO_histo = cv2.calcHist([image],[0], None, [256], [0,256])
GO_histo = cv2.calcHist([image],[1], None, [256], [0,256])
RO_histo = cv2.calcHist([image],[2], None, [256], [0,256])#visualize the channel histograms seperately
plt.figure(figsize=(10,12), )
plt.subplot(3, 2, 1)
plt.title("Green Original")
plt.plot(GO_histo, 'g')
plt.subplot(3, 2, 2)
plt.title("Green Equilized")
plt.plot(G_histo, 'g')
plt.subplot(3, 2, 3)
plt.title("Red Original")
plt.plot(RO_histo, 'r')
plt.subplot(3, 2, 4)
plt.title("Red Equilized")
plt.plot(R_histo, 'r')
plt.subplot(3, 2, 5)
plt.title("Blue Original")
plt.plot(BO_histo, 'b')
plt.subplot(3, 2, 6)
plt.title("Blue Equilized")
plt.plot(B_histo, 'b')
Moving on to the next step, what we have now are only layers. To get an image out of these, we need to merge these.
equi_im = cv2.merge([b_equi,g_equi,r_equi])
Now let us see the equalized image and the original image side by side.
cv2.namedWindow("Original Image", cv2.WINDOW_NORMAL);
cv2.imshow("Original Image",image);
cv2.namedWindow("New Image", cv2.WINDOW_NORMAL);
cv2.imshow("New Image",equi_im);cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
COMPLETE CODE
import cv2
import numpy as np
import matplotlib.pyplot as plt#using opencv to read an image
#BGR Image
image = cv2.imread("C:/users/public/pictures/underexposed_image.jpg")
#seperating colour channels
B = image[:,:,0] #blue layer
G = image[:,:,1] #green layer
R = image[:,:,2] #red layer
#equilize each channel seperately
b_equi = cv2.equalizeHist(B)
g_equi = cv2.equalizeHist(G)
r_equi = cv2.equalizeHist(R)
#calculate histograms for each channel seperately
B_histo = cv2.calcHist([b_equi],[0], None, [256], [0,256])
G_histo = cv2.calcHist([g_equi],[0], None, [256], [0,256])
R_histo = cv2.calcHist([r_equi],[0], None, [256], [0,256])
#merge thechannels and create new image
equi_im = cv2.merge([b_equi,g_equi,r_equi])
#visualize the equilized channels seperately
plt.imshow(b_equi)
plt.title("b_equi")
plt.show()
plt.imshow(g_equi)
plt.title("g_equi")
plt.show()
plt.imshow(r_equi)
plt.title("r_equi")
plt.show()
#visualize the channel histograms seperately
plt.subplot(2, 2, 1)
plt.plot(G_histo, 'g')
plt.subplot(2, 2, 2)
plt.plot(R_histo, 'r')
plt.subplot(2, 2, 3)
plt.plot(B_histo, 'b')
#visualize the original and equilized images
cv2.namedWindow("Original Image", cv2.WINDOW_NORMAL);
cv2.imshow("Original Image",image);
cv2.namedWindow("New Image", cv2.WINDOW_NORMAL);
cv2.imshow("New Image",equi_im);
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Thanks For Reading, Follow For More.
Happy learning!
Connect with me on Linkedin