Difference in histograms of a colored image using plt.plot v/s plt.hist [Python]

Viewed 332

I used the code below to generate histograms of a colored image using 2 methods :

Method 1 :-

  1. Using cv2.calcHist() function to calculate the frequency
  2. Using plt.plot() to generate a line plot of the frequency

Method 2 :-

  1. Using plt.hist() function to calculate and generate the histogram (I added bin=250 so that the 2 histograms are consistent)

Observation : Both histograms are roughly similar. The 1st histogram (using plt.plot) looks pretty smooth. However the 2nd histogram (using plt.hist) has additional spikes and drops.

Question : Since the image only has int values there shouldn't be inconsistent binning. What is the reason for these additional spikes and drops in the histogram-2 ?

    blue_bricks = cv2.imread('Computer-Vision-with-Python/DATA/bricks.jpg')
    
    fig = plt.figure(figsize=(17,10))
    color = ['b','g','r']
    
    # Histogram Type-1
    fig.add_subplot(2,2,1)
    
    for i,c in enumerate(color): 
        hist = cv2.calcHist([blue_bricks], mask=None, channels=[i], histSize=[256], ranges=[0,256])
        plt.plot(hist,color=c)    
    plt.title('Histogram-1')
    
    # Histogram Type-2
    fig.add_subplot(2,2,2)
    
    for i,c in enumerate(color):
        plt.hist(blue_bricks[:,:,i].flatten(),color=c, alpha=0.5, bins=250)
    plt.title('Histogram-2')

enter image description here

1 Answers

bins=250 creates 251 equally spaced bin edges between the lowest and highest values. These don't align well with the discrete values. When the difference between highest and lowest is larger than 250, some bins will be empty. When the difference is smaller than 250, some bins will get the values for two adjacent numbers, creating a spike. Also, when superimposing histograms, it is handy that all histograms use exactly the same bin edges.

You need the bins to be exactly between the integer values, setting bins=np.arange(-0.5, 256, 1) would achieve such. Alternatively, you can use seaborn's histplot(...., discrete=True).

Here is some code with smaller numbers to illustrate what's happening.

import matplotlib.pyplot as plt
import numpy as np

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(12, 3))

for ax in (ax1, ax2, ax3, ax4):
    if ax in [ax1, ax3]:
        x = np.arange(1, 10)
    else:
        x = np.arange(1, 12)
    if ax in [ax1, ax2]:
        bins = 10
    else:
        bins = np.arange(0.5, x.max() + 1, 1)
    _, bin_edges, _ = ax.hist(x, bins=bins, ec='white', lw=2)
    ax.vlines(bin_edges, 0, 2.5, color='crimson', ls='--')
    ax.scatter(x, [2.2] * len(x), color='lime', s=50)
    ax.set_title((f"{bins} bins" if type(bins) == int else "discrete bins") + f', {len(x)} values')
    ax.set_xticks(x)
    ax.set_yticks([0, 1, 2])
plt.tight_layout()
plt.show()

histogram with non-aligned bins

Related