Sharp or Blurry?
A one-line focus check.
One Monday, someone bumps the camera, and the lens slips slightly out of focus. Nothing crashes. Every photo just gets a little softer. For two days, AppleEye quietly gets worse, and nobody knows why. A simple automatic check would have caught that in the first minute.

- Someone bumps the camera
- Every photo gets softer
- Nobody knows why
Measuring sharpness
Blurry images have soft edges, and the Laplacian responds strongly to sharp edges. So if we take the Laplacian, and measure how much it varies, we've got a single number for sharpness. We test it by blurring an image more and more. Run it. The score falls steadily as the blur gets heavier. It's four point seven on the sharp photo, and under one on the heavy blurs. That's a whole focus monitor in one line of maths. Log that number for each frame, and alarm if it drops.
import cv2
from belt import belt
img, _, _ = belt()
def sharpness(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return cv2.Laplacian(gray, cv2.CV_64F).var()
for size in [1, 5, 15, 31]:
blurred = cv2.GaussianBlur(img, (size, size), 0)
print(f"Blur {size:>2}: sharpness {sharpness(blurred):7.1f}")Blur 1: sharpness 4.7 Blur 5: sharpness 2.1 Blur 15: sharpness 0.7 Blur 31: sharpness 0.5
Also uses belt.py
import numpy as np
import cv2
def belt(n=7, seed=1, size=(480, 640), bruises=2, light=1.0):
"""Draw a simulated conveyor belt of apples. Returns the image and the true boxes."""
rng = np.random.default_rng(seed)
h, w = size
img = np.full((h, w, 3), (70, 75, 80), np.uint8)
img = cv2.add(img, rng.normal(0, 6, img.shape).clip(0, 30).astype(np.uint8))
boxes, spots, taken = [], [], []
while len(boxes) < n:
r = int(rng.integers(32, 48))
x, y = int(rng.integers(r + 5, w - r - 5)), int(rng.integers(r + 5, h - r - 5))
if any((x - a) ** 2 + (y - b) ** 2 < (r + c + 8) ** 2 for a, b, c in taken):
continue
taken.append((x, y, r))
red = int(rng.integers(150, 225))
cv2.circle(img, (x, y), r, (35, 40, red), -1, cv2.LINE_AA)
cv2.circle(img, (x - r // 3, y - r // 3), r // 4, (90, 90, min(255, red + 30)), -1, cv2.LINE_AA)
boxes.append((x - r, y - r, x + r, y + r))
for x, y, r in taken[:bruises]:
cv2.ellipse(img, (x + r // 3, y + r // 4), (r // 3, r // 4), 20, 0, 360, (25, 45, 70), -1, cv2.LINE_AA)
spots.append((x, y, r))
img = cv2.GaussianBlur(img, (3, 3), 0)
return np.clip(img * light, 0, 255).astype(np.uint8), boxes, spotsUsing a focus score
The right threshold depends on your scene, as a plain surface has few edges at all. So measure typical values during a good day, and alarm on a sudden drop. Reject blurred frames, rather than classifying them. And on a moving belt, blur often comes from speed, not focus. A shorter exposure and a brighter light will fix that far better than any algorithm.
Summary
One number catches a bumped camera. Next, the mistake that fools nearly every colour based system.
Key takeaways- Laplacian variance measures focus
- Log it for every frame
- Motion blur needs short exposure