| OpenCV 5.0.0alpha Open Source Computer Vision

Loading...
Searching...
No Matches
Histogram Calculation

Prev Tutorial: Histogram Equalization
Next Tutorial: Histogram Comparison

Original author | Ana Huamán
Compatibility | OpenCV >= 3.0

# Goal

In this tutorial you will learn how to:

- Use the OpenCV function cv::split to divide an image into its correspondent planes.
- To calculate histograms of arrays of images by using the OpenCV function cv::calcHist
- To normalize an array by using the function cv::normalize

Note
  In the last tutorial (Histogram Equalization) we talked about a particular kind of histogram called Image histogram. Now we will considerate it in its more general concept. Read on!

## What are histograms?

- Histograms are collected counts of data organized into a set of predefined bins
- When we say data we are not restricting it to be intensity values (as we saw in the previous Tutorial Histogram Equalization). The data collected can be whatever feature you find useful to describe your image.
- Let's see an example. Imagine that a Matrix contains information of an image (i.e. intensity in the range \(0-255\)):

- What happens if we want to count this data in an organized way? Since we know that the range of information value for this case is 256 values, we can segment our range in subparts (called bins) like:

  \[\begin{array}{l} [0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\ range = { bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} } \end{array}\]

  and we can keep count of the number of pixels that fall in the range of each \(bin_{i}\). Applying this to the example above we get the image below ( axis x represents the bins and axis y the number of pixels in each of them).

- This was just a simple example of how an histogram works and why it is useful. An histogram can keep count not only of color intensities, but of whatever image features that we want to measure (i.e. gradients, directions, etc).
- Let's identify some parts of the histogram:
  1. dims: The number of parameters you want to collect data of. In our example, dims = 1 because we are only counting the intensity values of each pixel (in a greyscale image).
  2. bins: It is the number of subdivisions in each dim. In our example, bins = 16
  3. range: The limits for the values to be measured. In this case: range = [0,255]
- What if you want to count two features? In this case your resulting histogram would be a 3D plot (in which x and y would be \(bin_{x}\) and \(bin_{y}\) for each feature and z would be the number of counts for each combination of \((bin_{x}, bin_{y})\). The same would apply for more features (of course it gets trickier).

## What OpenCV offers you

For simple purposes, OpenCV implements the function cv::calcHist , which calculates the histogram of a set of arrays (usually images or image planes). It can operate with up to 32 dimensions. We will see it in the code below!

# Code

- What does this program do?
  - Loads an image
  - Splits the image into its R, G and B planes using the function cv::split
  - Calculate the Histogram of each 1-channel plane by calling the function cv::calcHist
  - Plot the three histograms in a window

C++

- Downloadable code: Click here
- Code at glance:
  #include "opencv2/highgui.hpp"
  #include "opencv2/imgcodecs.hpp"
  #include "opencv2/imgproc.hpp"
  #include <iostream>
  using namespace std;
  using namespace cv;
  int main(int argc, char** argv)
  {
  CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
  Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
  if( src.empty() )
  {
  return EXIT_FAILURE;
  }
  vector<Mat> bgr_planes;
  split( src, bgr_planes );
  int histSize = 256;
  float range[] = { 0, 256 }; //the upper boundary is exclusive
  const float* histRange[] = { range };
  bool uniform = true, accumulate = false;
  Mat b_hist, g_hist, r_hist;
  calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, histRange, uniform, accumulate );
  calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, histRange, uniform, accumulate );
  calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, histRange, uniform, accumulate );
  int hist_w = 512, hist_h = 400;
  int bin_w = cvRound( (double) hist_w/histSize );
  Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
  normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  for( int i = 1; i < histSize; i++ )
  {
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
  Scalar( 255, 0, 0), 2, 8, 0 );
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
  Scalar( 0, 255, 0), 2, 8, 0 );
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
  Scalar( 0, 0, 255), 2, 8, 0 );
  }
  imshow("Source image", src );
  imshow("calcHist Demo", histImage );
  waitKey();
  return EXIT_SUCCESS;
  }
  cv::CommandLineParser
  Designed for command line parsing.
  Definition utility.hpp:890
  cv::Mat
  n-dimensional dense array class
  Definition mat.hpp:951
  cv::Mat::at
  _Tp & at(int i0=0)
  Returns a reference to the specified array element.
  cv::Mat::empty
  bool empty() const
  Returns true if the array has no elements.
  cv::Point_< int >
  cv::Scalar_< double >
  cv::String
  std::string String
  Definition cvstd.hpp:151
  CV_8UC3
  #define CV_8UC3
  Definition interface.h:101
  cvRound
  int cvRound(double value)
  Rounds floating-point number to the nearest integer.
  Definition fast_math.hpp:200
  cv::imshow
  void imshow(const String &winname, InputArray mat)
  Displays an image in the specified window.
  cv::waitKey
  int waitKey(int delay=0)
  Waits for a pressed key.
  highgui.hpp
  main
  int main(int argc, char *argv[])
  Definition highgui_qt.cpp:3
  imgcodecs.hpp
  imgproc.hpp
  cv
  Definition core.hpp:107
  std
  STL namespace.

Java

- Downloadable code: Click here
- Code at glance:
  import java.util.ArrayList;
  import java.util.List;
  import org.opencv.core.Core;
  import org.opencv.core.CvType;
  import org.opencv.core.Mat;
  import org.opencv.core.MatOfFloat;
  import org.opencv.core.MatOfInt;
  import org.opencv.core.Point;
  import org.opencv.core.Scalar;
  import org.opencv.highgui.HighGui;
  import org.opencv.imgcodecs.Imgcodecs;
  import org.opencv.imgproc.Imgproc;
  class CalcHist {
  public void run(String[] args) {
  String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
  Mat src = Imgcodecs.imread(filename);
  if (src.empty()) {
  System.err.println("Cannot read image: " + filename);
  System.exit(0);
  }
  List<Mat> bgrPlanes = new ArrayList<>();
  Core.split(src, bgrPlanes);
  int histSize = 256;
  float[] range = {0, 256}; //the upper boundary is exclusive
  MatOfFloat histRange = new MatOfFloat(range);
  boolean accumulate = false;
  Mat bHist = new Mat(), gHist = new Mat(), rHist = new Mat();
  Imgproc.calcHist(bgrPlanes, new MatOfInt(0), new Mat(), bHist, new MatOfInt(histSize), histRange, accumulate);
  Imgproc.calcHist(bgrPlanes, new MatOfInt(1), new Mat(), gHist, new MatOfInt(histSize), histRange, accumulate);
  Imgproc.calcHist(bgrPlanes, new MatOfInt(2), new Mat(), rHist, new MatOfInt(histSize), histRange, accumulate);
  int histW = 512, histH = 400;
  int binW = (int) Math.round((double) histW / histSize);
  Mat histImage = new Mat( histH, histW, CvType.CV_8UC3, new Scalar( 0,0,0) );
  Core.normalize(bHist, bHist, 0, histImage.rows(), Core.NORM_MINMAX);
  Core.normalize(gHist, gHist, 0, histImage.rows(), Core.NORM_MINMAX);
  Core.normalize(rHist, rHist, 0, histImage.rows(), Core.NORM_MINMAX);
  float[] bHistData = new float[(int) (bHist.total() * bHist.channels())];
  bHist.get(0, 0, bHistData);
  float[] gHistData = new float[(int) (gHist.total() * gHist.channels())];
  gHist.get(0, 0, gHistData);
  float[] rHistData = new float[(int) (rHist.total() * rHist.channels())];
  rHist.get(0, 0, rHistData);
  for( int i = 1; i < histSize; i++ ) {
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(bHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(bHistData[i])), new Scalar(255, 0, 0), 2);
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(gHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(gHistData[i])), new Scalar(0, 255, 0), 2);
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(rHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(rHistData[i])), new Scalar(0, 0, 255), 2);
  }
  HighGui.imshow( "Source image", src );
  HighGui.imshow( "calcHist Demo", histImage );
  HighGui.waitKey(0);
  System.exit(0);
  }
  }
  public class CalcHistDemo {
  public static void main(String[] args) {
  // Load the native OpenCV library
  System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  new CalcHist().run(args);
  }
  }

Python

- Downloadable code: Click here
- Code at glance:
  from __future__ import print_function
  from __future__ import division
  import cv2 as cv
  import numpy as np
  import argparse
  parser = argparse.ArgumentParser(description='Code for Histogram Calculation tutorial.')
  parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
  args = parser.parse_args()
  src = cv.imread(cv.samples.findFile(args.input))
  if src is None:
  print('Could not open or find the image:', args.input)
  exit(0)
  bgr_planes = cv.split(src)
  histSize = 256
  histRange = (0, 256) # the upper boundary is exclusive
  accumulate = False
  b_hist = cv.calcHist(bgr_planes, [0], None, [histSize], histRange, accumulate=accumulate)
  g_hist = cv.calcHist(bgr_planes, [1], None, [histSize], histRange, accumulate=accumulate)
  r_hist = cv.calcHist(bgr_planes, [2], None, [histSize], histRange, accumulate=accumulate)
  hist_w = 512
  hist_h = 400
  bin_w = int(round( hist_w/histSize ))
  histImage = np.zeros((hist_h, hist_w, 3), dtype=np.uint8)
  cv.normalize(b_hist, b_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
  cv.normalize(g_hist, g_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
  cv.normalize(r_hist, r_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
  for i in range(1, histSize):
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(b_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(b_hist[i]) ),
  ( 255, 0, 0), thickness=2)
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(g_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(g_hist[i]) ),
  ( 0, 255, 0), thickness=2)
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(r_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(r_hist[i]) ),
  ( 0, 0, 255), thickness=2)
  cv.imshow('Source image', src)
  cv.imshow('calcHist Demo', histImage)
  cv.waitKey()
  cv::split
  void split(const Mat &src, Mat *mvbegin)
  Divides a multi-channel array into several single-channel arrays.
  cv::normalize
  void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
  Normalizes the norm or value range of an array.
  cv::samples::findFile
  cv::String findFile(const cv::String &relative_path, bool required=true, bool silentMode=false)
  Try to find requested data file.
  cv::imread
  CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
  Loads an image from a file.
  cv::line
  void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
  Draws a line segment connecting two points.
  cv::calcHist
  void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
  Calculates a histogram of a set of arrays.

# Explanation

- Load the source image

  C++
  CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
  Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
  if( src.empty() )
  {
  return EXIT_FAILURE;
  }
  Java
  String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
  Mat src = Imgcodecs.imread(filename);
  if (src.empty()) {
  System.err.println("Cannot read image: " + filename);
  System.exit(0);
  }
  Python
  parser = argparse.ArgumentParser(description='Code for Histogram Calculation tutorial.')
  parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
  args = parser.parse_args()
  src = cv.imread(cv.samples.findFile(args.input))
  if src is None:
  print('Could not open or find the image:', args.input)
  exit(0)

- Separate the source image in its three R,G and B planes. For this we use the OpenCV function cv::split :

  C++
  vector<Mat> bgr_planes;
  split( src, bgr_planes );
  Java
  List<Mat> bgrPlanes = new ArrayList<>();
  Core.split(src, bgrPlanes);
  Python
  bgr_planes = cv.split(src)

  our input is the image to be divided (this case with three channels) and the output is a vector of Mat )

- Now we are ready to start configuring the histograms for each plane. Since we are working with the B, G and R planes, we know that our values will range in the interval \([0,255]\)

- Establish the number of bins (5, 10...):

  C++
  int histSize = 256;
  Java
  int histSize = 256;
  Python
  histSize = 256

- Set the range of values (as we said, between 0 and 255 )

  C++
  float range[] = { 0, 256 }; //the upper boundary is exclusive
  const float* histRange[] = { range };
  Java
  float[] range = {0, 256}; //the upper boundary is exclusive
  MatOfFloat histRange = new MatOfFloat(range);
  Python
  histRange = (0, 256) # the upper boundary is exclusive

- We want our bins to have the same size (uniform) and to clear the histograms in the beginning, so:

  C++
  bool uniform = true, accumulate = false;
  Java
  boolean accumulate = false;
  Python
  accumulate = False

- We proceed to calculate the histograms by using the OpenCV function cv::calcHist :

  C++
  Mat b_hist, g_hist, r_hist;
  calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, histRange, uniform, accumulate );
  calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, histRange, uniform, accumulate );
  calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, histRange, uniform, accumulate );
  Java
  Mat bHist = new Mat(), gHist = new Mat(), rHist = new Mat();
  Imgproc.calcHist(bgrPlanes, new MatOfInt(0), new Mat(), bHist, new MatOfInt(histSize), histRange, accumulate);
  Imgproc.calcHist(bgrPlanes, new MatOfInt(1), new Mat(), gHist, new MatOfInt(histSize), histRange, accumulate);
  Imgproc.calcHist(bgrPlanes, new MatOfInt(2), new Mat(), rHist, new MatOfInt(histSize), histRange, accumulate);
  Python
  b_hist = cv.calcHist(bgr_planes, [0], None, [histSize], histRange, accumulate=accumulate)
  g_hist = cv.calcHist(bgr_planes, [1], None, [histSize], histRange, accumulate=accumulate)
  r_hist = cv.calcHist(bgr_planes, [2], None, [histSize], histRange, accumulate=accumulate)
- where the arguments are (C++ code):
  - &bgr_planes[0]: The source array(s)
  - 1: The number of source arrays (in this case we are using 1. We can enter here also a list of arrays )
  - 0: The channel (dim) to be measured. In this case it is just the intensity (each array is single-channel) so we just write 0.
  - Mat(): A mask to be used on the source array ( zeros indicating pixels to be ignored ). If not defined it is not used
  - b_hist: The Mat object where the histogram will be stored
  - 1: The histogram dimensionality.
  - histSize: The number of bins per each used dimension
  - histRange: The range of values to be measured per each dimension
  - uniform and accumulate: The bin sizes are the same and the histogram is cleared at the beginning.

- Create an image to display the histograms:

  C++
  int hist_w = 512, hist_h = 400;
  int bin_w = cvRound( (double) hist_w/histSize );
  Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
  Java
  int histW = 512, histH = 400;
  int binW = (int) Math.round((double) histW / histSize);
  Mat histImage = new Mat( histH, histW, CvType.CV_8UC3, new Scalar( 0,0,0) );
  Python
  hist_w = 512
  hist_h = 400
  bin_w = int(round( hist_w/histSize ))
  histImage = np.zeros((hist_h, hist_w, 3), dtype=np.uint8)

- Notice that before drawing, we first cv::normalize the histogram so its values fall in the range indicated by the parameters entered:

  C++
  normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  Java
  Core.normalize(bHist, bHist, 0, histImage.rows(), Core.NORM_MINMAX);
  Core.normalize(gHist, gHist, 0, histImage.rows(), Core.NORM_MINMAX);
  Core.normalize(rHist, rHist, 0, histImage.rows(), Core.NORM_MINMAX);
  Python
  cv.normalize(b_hist, b_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
  cv.normalize(g_hist, g_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
  cv.normalize(r_hist, r_hist, alpha=0, beta=hist_h, norm_type=cv.NORM_MINMAX)
- this function receives these arguments (C++ code):
  - b_hist: Input array
  - b_hist: Output normalized array (can be the same)
  - 0 and histImage.rows: For this example, they are the lower and upper limits to normalize the values of r_hist
  - NORM_MINMAX: Argument that indicates the type of normalization (as described above, it adjusts the values between the two limits set before)
  - -1: Implies that the output normalized array will be the same type as the input
  - Mat(): Optional mask

- Observe that to access the bin (in this case in this 1D-Histogram):

  C++
  for( int i = 1; i < histSize; i++ )
  {
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
  Scalar( 255, 0, 0), 2, 8, 0 );
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
  Scalar( 0, 255, 0), 2, 8, 0 );
  line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ),
  Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
  Scalar( 0, 0, 255), 2, 8, 0 );
  }
  Java
  float[] bHistData = new float[(int) (bHist.total() * bHist.channels())];
  bHist.get(0, 0, bHistData);
  float[] gHistData = new float[(int) (gHist.total() * gHist.channels())];
  gHist.get(0, 0, gHistData);
  float[] rHistData = new float[(int) (rHist.total() * rHist.channels())];
  rHist.get(0, 0, rHistData);
  for( int i = 1; i < histSize; i++ ) {
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(bHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(bHistData[i])), new Scalar(255, 0, 0), 2);
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(gHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(gHistData[i])), new Scalar(0, 255, 0), 2);
  Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(rHistData[i - 1])),
  new Point(binW * (i), histH - Math.round(rHistData[i])), new Scalar(0, 0, 255), 2);
  }
  Python
  for i in range(1, histSize):
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(b_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(b_hist[i]) ),
  ( 255, 0, 0), thickness=2)
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(g_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(g_hist[i]) ),
  ( 0, 255, 0), thickness=2)
  cv.line(histImage, ( bin_w*(i-1), hist_h - int(r_hist[i-1]) ),
  ( bin_w*(i), hist_h - int(r_hist[i]) ),
  ( 0, 0, 255), thickness=2)

  we use the expression (C++ code):

  b_hist.at<float>(i)

  where \(i\) indicates the dimension. If it were a 2D-histogram we would use something like:

  b_hist.at<float>( i, j )

- Finally we display our histograms and wait for the user to exit:

  C++
  imshow("Source image", src );
  imshow("calcHist Demo", histImage );
  waitKey();
  Java
  HighGui.imshow( "Source image", src );
  HighGui.imshow( "calcHist Demo", histImage );
  HighGui.waitKey(0);
  Python
  cv.imshow('Source image', src)
  cv.imshow('calcHist Demo', histImage)
  cv.waitKey()

# Result

1. Using as input argument an image like the one shown below:

1. Produces the following histogram: