AI Hints
Home›Computer Vision Tutorial›Hidden Details
Computer Vision · Lesson 84 of 111

JPEG and Your Model

What compression does.

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

Your training photos came straight from a good camera. But in production, the images get sent across the network, heavily compressed to save bandwidth. Everything looks the same to you. Does it look the same to a model?

JPEG and Your Model
From the video: JPEG and Your Model
  • Training photos were clean
  • Production images are compressed
  • Does a model notice?

Quality against size

We save the same image at four quality levels, then measure the file size, and how far the result is from the original. Run it. Going from ninety five to fifty cuts the file to less than a fifth, with hardly any error. That's the sweet spot. Below that, the savings shrink, and the damage grows quickly. At quality five, the error's ten times worse, and you've saved almost nothing more. So what should you do? Find the point just before the drop, and include compression in your training augmentation, at the level that you'll meet in production.

jpeg.pypython
import cv2
from belt import belt

img, _, _ = belt()

for q in [95, 50, 15, 5]:
    ok, data = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, q])
    back = cv2.imdecode(data, 1)
    print(f"Quality {q:>2}: {len(data) / 1000:5.1f} KB, error {cv2.absdiff(img, back).mean():.2f}")
Output
Quality 95:  42.3 KB, error 1.10
Quality 50:   7.6 KB, error 1.80
Quality 15:   6.5 KB, error 4.71
Quality  5:   6.0 KB, error 10.77
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

Other hidden changes

Your image pipeline can change things in other ways. Phones apply sharpening and beauty filters. Video streams use their own heavy compression. Colour order can flip, as OpenCV uses blue, green, red, and everyone else uses red, green, blue. Image orientation might be stored as a tag and ignored. Save a sample of real production images, and compare them with your training data.

BGR versus RGBa classic bug
Phone processingsharpening, filters
Rotation tagsoften ignored
Compare samplesproduction vs training

Summary

Train on images like the ones that you'll really see. Next, what does each neuron actually look at?

Key takeaways
  • JPEG 50 is often a sweet spot
  • Add compression to training
  • Watch BGR versus RGB
Next up: Receptive Fields — what a neuron can see