AI Hints
Home›Computer Vision Tutorial›Getting a Good Image
Computer Vision · Lesson 45 of 111

Fixing Bad Light

Seeing in the dark corners.

⏱ 2 min read▶ Video at 1:12:48🎓 Free
Watch this lesson in the Computer Vision courseWatch on YouTube ↗Full Computer Vision course

It's five o'clock in winter. The sun has dropped, and the far end of the sorting shed has gone dim. AppleEye starts to miss things. Here's a truth that every vision engineer learns the hard way. A better image beats a better model. Before you touch any neural network, make the picture as good as it can be.

Fixing Bad Light
From the video: Fixing Bad Light
  • Five o'clock in winter
  • The shed goes dim
  • A better image beats a better model

Stretching the histogram

We take a photo with only a third of the normal light. The average pixel is about twenty seven out of two hundred and fifty five, which is very dark. Equalize hist spreads the brightness across the full range. But it does that for the entire image at once. Clahe does it in small tiles, and it limits how far each tile can stretch, so that it doesn't blow up the noise. Run it. Global equalisation jumps to one hundred and seventy one, and it turns the belt texture into a harsh glare. Clahe stays gentle, at thirty three, and brings out local detail, without inventing light that wasn't there. For uneven lighting, clahe is nearly always the better choice.

light.pypython
import cv2
import numpy as np
from belt import belt

img, _, _ = belt(light=0.35)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

flat = cv2.equalizeHist(gray)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)).apply(gray)

print("Average brightness:", int(gray.mean()), "->", int(flat.mean()), "and", int(clahe.mean()))
cv2.imwrite("light.png", np.hstack([gray, flat, clahe]))
Output
Average brightness: 27 -> 171 and 33
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, spots

When to use what

So which should you reach for? Global equalisation suits a scan of a document, where everything's equally faded. Clahe suits scenes with shadows, such as medical scans and factory lines. Gamma correction brightens dark tones, without touching bright ones. But remember that none of this adds information. If a sensor captured nothing, then software can't bring it back. The real fix is usually a better lamp.

Global equaliseevenly faded images
CLAHEuneven lighting
Gammalift the dark tones
Better lampthe true fix

Summary

Stretch contrast locally, and fix the light if you can. Next, the most common problem of all on a fast belt. Blur.

Key takeaways
  • Better images beat better models
  • CLAHE handles uneven light
  • Software can't add information
Next up: Sharp or Blurry? — a one-line focus check