Recursive AI

Get image histogram statistics in C#

OneImage is a free, open-source image library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

A histogram counts pixels in value ranges. Use it with channel statistics to measure an image without changing its pixels.

Terminal
dotnet add package RecursiveAI.OneImage

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneImage;
 
// samples.tif is in the example zip.
Image image = Image.Load("samples.tif");
Console.WriteLine($"Bin 0 count: {image.Histogram()[0, 0]}");
Console.WriteLine($"Mean: {image.Statistics().Channels[0].Mean}");
Text
Bin 0 count: 2
Mean: 127.5

Unzip the example, then dotnet run.

The full program

samples.tif is a small two-page grayscale TIFF (in the zip). Each page is 2 × 2 pixels: page 0 has rows [0, 0] and [255, 255]; page 1 has rows [64, 64] and [128, 128]. The full program reads each page separately and sums its histogram bins.

C#
using OneImage;
using System.Globalization;
 
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Image image = Image.Load("samples.tif");
for (int i = 0; i < image.FrameCount; i++)
{
    ImageHistogram histogram = image.Frames[i].Histogram();
    ChannelStatistics stats = image.Frames[i].Statistics().Channels[0];
    long total = 0;
    foreach (long count in histogram.Channel(0)) total += count;
    Console.WriteLine($"Frame {i}: {total} samples; bin 0={histogram[0, 0]}; bin 255={histogram[0, 255]}");
    Console.WriteLine($"Min={stats.Min}, Max={stats.Max}, Mean={stats.Mean:F2}, StdDev={stats.StdDev:F2}");
}

dotnet run prints:

Text
Frame 0: 4 samples; bin 0=2; bin 255=2
Min=0, Max=255, Mean=127.50, StdDev=127.50
Frame 1: 4 samples; bin 0=0; bin 255=0
Min=64, Max=128, Mean=96.00, StdDev=32.00

Analysis decodes pixels immediately; corrupt image data throws ImageFormatException, and Lab images are refused.

Variations

Analyze another frame with fewer bins. Page 1 has two samples in the 64–127 range and two in the 128–191 range.

C#
using OneImage;
 
Image image = Image.Load("samples.tif");
var histogram = image.Frames[1].Histogram(bins: 4);
for (int bin = 0; bin < 4; bin++)
    Console.WriteLine($"{bin}: {histogram[0, bin]}");
Text
0: 0
1: 2
2: 2
3: 0

The short answer

Use Image.Histogram() for bin counts and Image.Statistics() for per-channel count, minimum, maximum, mean, and population standard deviation. They read pixels without modifying the image. The default histogram has 256 bins per channel.

Both methods on Image analyze frame 0 only. For an animated image or multipage TIFF, call the methods on image.Frames[i], as the full program does. Ignoring this distinction can give a correct result for the wrong page.

What does an image histogram tell you?

It tells you how often pixel values occur, not where they occur. Two images can have identical histograms with completely different arrangements of pixels. A histogram can help you choose a threshold or notice concentrations of dark and light samples. The University of Edinburgh’s intensity histogram explanation illustrates threshold selection and why illumination gradients can make a single threshold unsuitable.

OneImage returns counts rather than a chart image. In the sample’s first page, bin 0 contains two pixels and bin 255 contains two. Summing all bins yields four, the page’s pixel count. There is only one stored channel because this fixture is grayscale. On an RGB image, channels are red, green and blue; alpha, when present, is last. CMYK channels follow their stored C, M, Y, K order.

The analysis uses stored samples. It does not convert profiles, compute perceptual brightness, or undo premultiplication. An RGB red-channel histogram is not a luminance histogram. If you need analysis in a particular color space, make that interpretation explicit before measuring.

How to get the histogram of an image?

Load the image and call Histogram(). Index the result with [channel, bin] to read a count, or use Channel(channel) to walk the channel’s bins. The full program uses that span to calculate the total without assuming a particular distribution.

For 8-bit samples, the bin index is floor(value × bins / 256). With four bins, values 0–63 fall in bin 0, 64–127 in bin 1, 128–191 in bin 2, and 192–255 in bin 3. The variation demonstrates this mapping on the second TIFF page. The allowed bin count is 1 through 65,536.

Analysis computes the entire selected frame immediately, unlike a lazy edit pipeline. Do not use it as a header-only file probe. If you need only dimensions, identify the image instead of requesting its pixels. For animations, frame pixels are fully composited; TIFF pages can each have their own dimensions. Compare totals against the selected frame, not an assumed shared size.

How are image histogram statistics calculated?

Statistics() reports summaries in the stored channel’s units. Eight-bit channels use 0–255, 16-bit channels use 0–65,535, and floating-point channels use 0–1 as their full scale. The FullScale property identifies that scale. Do not compare raw means across these representations without accounting for units.

The example’s first page has mean 127.5 and population standard deviation 127.5. Every sample is equally far from the mean. Its second page has mean 96 and population standard deviation 32. These are population values, not the sample estimate that divides squared deviations by one less than the count.

By default, transparent pixels remain part of the measurement. Both analysis APIs offer skipTransparent: true to ignore pixels whose alpha is zero; partially transparent pixels are not discarded by that setting. See the analysis documentation for the stored-value semantics. Measuring a histogram does not equalize it: histogram equalization is a separate operation that changes pixels.

All oneimage recipes