System designExplainer

Maximum likelihood histogram classification: Bayes for pixel windows

Gaussian maximum likelihood classification assigns a pixel to a class from its window histogram: Bayes formula, covariance estimates, worked example.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01The decision rule in one line
  2. 02Why minimum distance is the special case
  3. 03Estimating the means and covariances
  4. 04Worked example: two classes, two bins
  5. 05Sixty-four bins, several window sizes and a reject gate
  6. 06Where maximum likelihood wins and where it breaks
  7. 07What it runs on next to the line
  8. 08Checks before you trust it
  9. 09Questions

You classify every pixel by the histogram of the window centred on it, and so far the winner has been the class whose mean histogram sits closest. That minimum-distance rule is a special case of maximum likelihood classification, which scores each class with its full Gaussian model — mean, covariance and prior — and assigns the pixel to the smallest score. What follows is the rule in codable form, a worked example with the arithmetic shown, the training-sample counts that make the covariance estimable, and the cases where the extra maths does not pay.

The decision rule in one line

You want, for each class Ci, the probability of the class given the window histogram h. Bayes' rule turns that around: P(Ci|h) = p(h|Ci)·P(Ci)/p(h). The denominator p(h) is the same for every class, so it cancels; the decision needs only p(h|Ci)·P(Ci), how typical h is of class i, times how common class i is. Model each class's histograms as a multivariate Gaussian with mean μi and covariance Σi, take logarithms, drop the terms constant across classes, and the whole classifier compresses to:

g_i(h) = ln|Σ_i| + (h − μ_i)ᵀ Σ_i⁻¹ (h − μ_i) − 2 ln P(C_i) assign h to the class with the smallest g_i

Each term earns its place. ln|Σi| is a complexity penalty: a class spread over many directions pays a larger determinant, so a vague class cannot win just because everything is vaguely near its mean. The quadratic term is the squared Mahalanobis distance — Euclidean distance measured in units of the class's own spread along each direction, which lets a long, tilted cluster beat a tight one. The last term credits the classes that actually appear on your line: with 80% good parts, 'good' wins every exact tie. A histogram is one summary of a window; when histograms beat raw pixel comparison, and when they do not, is measured in image similarity: histograms or pixel values?.

Why minimum distance is the special case

Set every class covariance to the same sphere (Σi = σ²I) and every prior to the same value, and gi collapses to ||h − μi||² plus constants: minimum distance to the mean. Your current classifier therefore assumes, without saying so, that all classes are equally spread, equally round and equally frequent.

Those assumptions are testable with a few labelled windows. Measure the per-class standard deviations: if the largest is more than about three times the smallest, or your yield log is nowhere near 50/50, maximum likelihood will change decisions — usually on exactly the windows you argue about at the review meeting.

Estimating the means and covariances

Training is two formulas per class. Every labelled window contributes one feature vector: the 64-bin histogram of the window, each entry the fraction of the window's pixels falling in that bin. Reducing a 256-level image to 64 bins is the binning step described in the color quantization guide.

μ_i = mean of class i's training histograms Σ_i = (1/(n_i − 1)) Σ_k (h_k − μ_i)(h_k − μ_i)ᵀ

Two cautions, both with numbers. With d = 64 features, Σi holds 64×65/2 = 2,080 free numbers, and the classic rule of thumb from remote sensing — where this classifier has run for decades — asks for about 10d ≈ 640 windows per class, spread across parts, batches and lighting. Below roughly 3d ≈ 200 windows the estimate turns noisy or singular: add a ridge λI with λ near 10⁻³ × the mean feature variance, use Ledoit–Wolf shrinkage, or tie Σi across classes and pool the deviations. A 5×5 window drops only 25 counts into 64 bins, so most bins sit at zero; merge bins or step up to a 10×10 window before training. Labels come from wherever your ground truth does; when they are judgment calls, keep them consistent as the subjective-label classification guide describes.

Worked example: two classes, two bins

Two texture classes, two histogram bins so the arithmetic fits on the page: bin 12 (dark-line energy) and bin 40 (background). Training over 10×10 windows gives:

good scratched
μ [0.30, 0.10] [0.12, 0.28]
Σ (diagonal) diag(0.04, 0.01) diag(0.01, 0.04)
ln|Σ| −7.824 −7.824
prior P(Ci) 0.80 0.20
−2 ln P(Ci) +0.446 +3.219

A new window normalizes to h = [0.20, 0.18] — 20 of its 100 pixels in bin 12, 18 in bin 40. Against 'good': h − μ = [−0.10, +0.08], so the Mahalanobis term is (−0.10)²/0.04 + (0.08)²/0.01 = 0.25 + 0.64 = 0.89. Against 'scratched': h − μ = [+0.08, −0.10], giving 0.64 + 0.25 = 0.89 again. The scores:

  • g_good = −7.824 + 0.89 + 0.446 = −6.49
  • g_scratched = −7.824 + 0.89 + 3.219 = −3.72

Smallest score wins, so the window is good. Notice what happened: the squared Euclidean distances are both 0.0164, so your minimum-distance rule ties and shrugs. The posterior odds are exp((g_scratched − g_good)/2) = e^1.386 ≈ 4, so P(good|h) = 0.80: the 80/20 mix decided the case, worth 2.77 on the score. Had the priors been 50/50, the scores would tie exactly — the honest reading is that these two bins carry no information about this window, not that the classifier is timid.

Sixty-four bins, several window sizes and a reject gate

At d = 64 this is matrix-library work. Factor each Σi once (Cholesky); scoring one window against one class is a forward substitution of about d² = 4,096 multiply-adds. Two classes over a 1 MP frame is on the order of 8 × 10⁹ multiply-adds — a fraction of a second on a multi-core x86 CPU with vectorized code, and out of reach for a per-pixel Python loop, which is its own subject (slow Python OpenCV code: fix per-pixel loops).

For a reject path, gate on the Mahalanobis term alone: for a true member it follows a χ² distribution with d degrees of freedom, and the 99th percentile at d = 64 is about 93. A window whose every class exceeds 93 goes to an operator instead of a wrong class; about 1% of good windows trip the gate by construction, which makes drift easy to count. This is the classical route into open-set inspection — how it differs from modern anomaly detection is mapped in the anomaly detection vs classification guide.

Run one classifier per window size. A 5×5 and a 15×15 window put different sampling noise behind the same normalized occupancy, so each size keeps its own μi, Σi and prior, and the pixel's decision is the smallest gi across sizes.

Where maximum likelihood wins and where it breaks

It earns its keep in four situations: elongated or tilted clusters, where the covariance rotates the distance to match the data; unbalanced production mixes, where the prior term turns a 95/5 yield split into correct default decisions; open worlds, where the χ² gate catches appearances no class was trained on; and correlated features, where Euclidean distance double-counts the same information. Histogram descriptors with a learned classifier are standard practice far beyond defect sorting — the histogram of oriented gradients descriptor for pedestrian detection is the same pattern at feature scale.

It breaks in three, each with a fix:

  • A class with two genuine appearances (brushed and polished 'good') underfits one Gaussian. Split it into subclasses, or use a per-class Gaussian mixture and sum the component densities.
  • Many near-empty bins create tiny variances that blow up the quadratic term. Keep the shrinkage from the training section permanently, or cut to the 20 or so bins whose between-class variance actually differs.
  • Illumination drift moves the histograms. Update μi with a running mean, re-fit Σi monthly, and log the runner-up score.

When does it not pay? Two tight, round, equally frequent classes make maximum likelihood and minimum distance return identical decisions — keep the simpler rule. And if the real goal is pass/fail per part rather than labelling every pixel, the binary image classification guide starts closer to that task.

What it runs on next to the line

The classifier is plain matrix code in your own C++, Python or library of choice, so the hardware question is CPU, memory and camera ports rather than a GPU. On the line that usually means a fanless vision controller beside the cell instead of a tower PC: x86 compute, several camera LAN ports and built-in light-controller channels in the same enclosure.

The MV-VC2140-128G40-1T pairs an Intel N-series processor with 8 GB of memory and four built-in light channels — the compact option for a single camera and one classifier.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

Cells with several cameras and PoE cabling step up: the MV-VC5525-256G70-NN carries an Intel Core i5 processor, 32 GB of memory, PoE camera ports and eight light channels.

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

If you would rather buy a classifier than write one, compare the on-device options in the smart cameras category with the camera finder, and check that the toolchain exposes the classifier type you need. The controllers above sit in the vision controllers category.

Checks before you trust it

  • Hold out 20% of the labelled windows per class and report per-class recall; with an 80/20 mix, a classifier that says 'good' always already scores 80% overall.
  • Count rejects on good production: the 99% gate should reject about 1% by construction; 5% means the Gaussian does not fit the class, not that the threshold is wrong.
  • Run your old nearest-mean rule alongside for one shift and inspect every disagreement — each is either the prior doing its job or a covariance estimated from too few windows.
  • Freeze binning and normalization before training; the classifier learns exactly the pipeline you hand it, so a changed bin edge invalidates μi and Σi.
  • Re-estimate the priors from the yield log whenever the product mix changes; the 2.77-point prior credit in the worked example moves with it.

More algorithm pieces sit on the machine-vision systems topic page. Size the controller and cameras in the system configurator, or contact the applications team with your bin count, window sizes and per-class window counts — we will check the sample math before you start collecting.

Questions

How many training windows does each class need?

Use the classic remote-sensing rule of about 10 windows per feature: 640 windows per class at 64 bins, collected across parts, batches and lighting shifts. Below roughly 3×d, about 200 windows, the 64×64 covariance cannot be estimated stably — add a ridge on the diagonal, use Ledoit–Wolf shrinkage, or tie the covariance across classes. Overlapping windows inflate the count without adding information, so spread them over many images.

Why switch from minimum distance if the distances tie?

Because the Gaussian terms decide ties that Euclidean distance cannot. In the worked example both classes sit exactly 0.89 Mahalanobis units from the window, yet the 80/20 production mix makes 'good' four times more likely and swings the score by 2.77. If your measured class spreads agree within about a factor of two and the mix is near 50/50, expect almost no changed decisions; the switch pays when either assumption fails.

Can the classifier reject windows that belong to no class?

Yes. Gate on the Mahalanobis term alone: for a true member of a 64-feature class it follows a χ² distribution with 64 degrees of freedom, whose 99th percentile is about 93. Reject a window when every class exceeds the gate, and expect roughly 1% of good windows to trip it by construction. With 10 features the same 99% gate sits at about 23.2.

What if one class has two distinct appearances?

A single Gaussian underfits a two-mode class and inflates the determinant until it wins wrongly. Either split the class into two subclasses and train each separately, or give the class a Gaussian mixture: the Bayes rule is unchanged, you just sum the component densities weighted by their mixing weights. Two or three components cover most industrial appearance splits.

Does the number of histogram bins change the decision?

It changes both the features and the sample demand. At 256 bins the covariance alone holds 256×257/2 = 32,896 free numbers and the 10× rule asks for about 2,560 windows per class, which most lines cannot label honestly. Start at 32–64 bins and grow only when the confusion matrix blames feature resolution rather than training-sample count.