Heuristics
VigilCV implements three core image quality heuristics, each with a precise mathematical definition and configurable threshold.
1. Laplacian Variance (Blur Detection)
Formula
The discrete 3×3 Laplacian kernel is applied to the grayscale image:
L = [[0, 1, 0],
[1, -4, 1],
[0, 1, 0]]
The response image R = L * I captures second-order intensity discontinuities (edges and textures). The variance of R is the blur score:
blur_score = Var(L * I_gray)
= E[(L * I)²] - E[L * I]²
Interpretation
blur_score | Image Quality |
|---|---|
| < 50 | Severely blurred or occluded |
| 50–100 | Moderately blurred (fails default threshold) |
| 100–500 | Acceptable sharpness |
| > 500 | High-frequency detail (sharp, textured) |
Implementation Note
VigilCV computes this in pure NumPy without scipy.ndimage to minimize import overhead:
from PIL import Image
import numpy as np
def laplacian_variance(img: Image.Image) -> float:
gray = np.array(img.convert("L"), dtype=np.float32)
# Manual convolution via array slicing
lap = (
gray[:-2, 1:-1] + gray[2:, 1:-1] +
gray[1:-1, :-2] + gray[1:-1, 2:] -
4 * gray[1:-1, 1:-1]
)
return float(lap.var())
2. Shannon Entropy (Texture / Detail Richness)
Formula
Given the 8-bit luminance histogram H[b] (b ∈ [0, 255]):
P(b) = H[b] / N (probability of bin b)
H_Shannon = -∑ P(b) · log₂(P(b)) (for all P(b) > 0)
Maximum entropy is 8.0 bits (uniform distribution across all 256 bins — perfectly random image). Minimum is 0.0 bits (solid single-color image).
Interpretation
| Entropy (bits) | Meaning |
|---|---|
| < 1.0 | Nearly uniform (solid color, lens cap) |
| 1.0–3.0 | Low detail (flat sky, blank wall) |
| 3.0–6.0 | Normal scene complexity |
| > 6.0 | High-frequency texture or noise |
Why Entropy?
Entropy is robust to mild blur (which preserves overall histogram shape) while being highly sensitive to total information collapse. It is a complementary measure to Laplacian variance: a bright, uniformly overexposed image may score high on blur (large uniform regions → low Laplacian) but low on entropy.
3. Exposure Clipping Ratios
Formula
For the luminance channel L ∈ [0, 255]:
underexposure_ratio = |{pixels : L < 10}| / N
overexposure_ratio = |{pixels : L > 245}| / N
Both ratios are in [0, 1]. Default thresholds are 0.20 (20% clipped pixels).
Why These Thresholds?
A 20% clip ratio is a well-established photographic signal for destructive clipping — where detail is irretrievably lost. Images exceeding this fail the exposure gate even if blur and entropy are acceptable, because the model's color-sensitive channels receive a distorted signal.
Configuring Thresholds
All thresholds are exposed as constructor arguments on VisionSentinel:
from vigilcv import VisionSentinel
sentinel = VisionSentinel(
blur_threshold=100.0, # Laplacian variance minimum
min_entropy=3.0, # Shannon entropy minimum (bits)
max_underexposure_ratio=0.20, # Max dark-clipped pixel fraction
max_overexposure_ratio=0.20, # Max saturated pixel fraction
raise_on_fail=True, # Raise or return on failure
)
See the API Reference for the full parameter contract.