Table of Contents

Histograms and Contrast

An image histogram counts how many pixels fall into each intensity range. Histograms are useful for diagnosing exposure, comparing image distributions, selecting thresholds, and improving contrast.

Calculate a grayscale histogram

This example calculates 256 bins over the complete intensity range of an 8-bit grayscale image:

using OpenCvSharp;

using var source = Cv2.ImRead("input.jpg", ImreadModes.Grayscale);
if (source.Empty())
{
    throw new FileNotFoundException("Could not decode input.jpg.");
}

using var histogram = new Mat();
Cv2.CalcHist(
    images: [source],
    channels: [0],
    mask: default,
    hist: histogram,
    dims: 1,
    histSize: [256],
    ranges: [new Rangef(0, 256)]);

float darkPixelCount = histogram.At<float>(32);
Console.WriteLine($"Pixels with intensity 32: {darkPixelCount}");

The result is a CV_32FC1 matrix with one row per bin. The range end is exclusive, so [0, 256) covers every possible value in an 8-bit image.

Plot the histogram

Normalize the bin counts to the plot height before drawing them:

const int plotWidth = 512;
const int plotHeight = 300;

using var normalized = new Mat();
Cv2.Normalize(
    histogram,
    normalized,
    alpha: 0,
    beta: plotHeight - 1,
    normType: NormTypes.MinMax);

using var plot = new Mat(
    rows: plotHeight,
    cols: plotWidth,
    type: MatType.CV_8UC3,
    s: Scalar.White);

int binWidth = plotWidth / 256;
for (int bin = 1; bin < 256; bin++)
{
    int previousHeight = (int)Math.Round(normalized.At<float>(bin - 1));
    int currentHeight = (int)Math.Round(normalized.At<float>(bin));

    Cv2.Line(
        plot,
        new Point((bin - 1) * binWidth, plotHeight - 1 - previousHeight),
        new Point(bin * binWidth, plotHeight - 1 - currentHeight),
        Scalar.Black,
        thickness: 2);
}

Cv2.ImWrite("histogram.png", plot);

Normalization here is only for display. Preserve the original histogram when its counts or proportions will be used in later calculations.

Limit calculation to a region

CalcHist accepts an 8-bit single-channel mask. Nonzero mask pixels select the source pixels to include:

using var mask = new Mat(source.Size(), MatType.CV_8UC1, Scalar.Black);
Cv2.Rectangle(
    mask,
    new Rect(50, 40, 200, 150),
    Scalar.White,
    thickness: -1);

using var regionHistogram = new Mat();
Cv2.CalcHist(
    images: [source],
    channels: [0],
    mask: mask,
    hist: regionHistogram,
    dims: 1,
    histSize: [256],
    ranges: [new Rangef(0, 256)]);

The mask must have the same width and height as the source image.

Improve grayscale contrast

Global histogram equalization redistributes intensities across the complete image:

using var equalized = new Mat();
Cv2.EqualizeHist(source, equalized);

It works on an 8-bit single-channel source. It can over-amplify noise or produce unnatural results when illumination varies across the image.

Contrast Limited Adaptive Histogram Equalization (CLAHE) processes local tiles and limits amplification:

using var clahe = Cv2.CreateCLAHE(
    clipLimit: 2.0,
    tileGridSize: new Size(8, 8));
using var improved = new Mat();

clahe.Apply(source, improved);
Cv2.ImWrite("improved.png", improved);

Treat clipLimit and tileGridSize as parameters to tune against representative images. Smaller tiles adapt to more local variation but can emphasize noise and tile boundaries.

Color histograms

For a BGR image, channel index 0 is blue, 1 is green, and 2 is red. Separate BGR channel histograms describe the storage channels, but they are often poor measures of perceived color similarity. Convert to a color space such as HSV or Lab first when hue, saturation, or perceptual brightness is the quantity of interest.

Common mistakes

  • Match the histogram range to the source depth and the intended values; [0, 256) is specific to an 8-bit intensity channel.
  • Do not compare raw bin counts from images or masks containing different numbers of pixels. Normalize the histograms first.
  • Histogram similarity does not preserve spatial layout. Two visually different images can have the same histogram.
  • Use a mask for a region of interest when the surrounding background would dominate the distribution.

Official OpenCV references