AI Hints
Home›Computer Vision Tutorial›Geometry in Practice
Computer Vision · Lesson 56 of 111

Fixing Perspective

Making a tilted camera square.

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

There's no room to mount the camera straight above the belt, so it's fixed on a pole, at an angle. Now apples at the far end look smaller, and all the size measurements are wrong. But there's a remarkable fact. If everything lies on a flat surface, one simple matrix can undo the tilt completely.

Fixing Perspective
From the video: Fixing Perspective
  • The camera sits at an angle
  • Far apples look smaller
  • One matrix undoes the tilt

Four points are all you need

A homography maps one flat plane onto another. Mark four points on the belt in the photo, then say where they should be in a square view. That's enough to calculate the whole mapping. Then each pixel can be moved. It's how document scanner apps straighten a page, and how sports graphics draw lines on a pitch.

Warping and unwarping

We simulate a tilted camera, by warping a flat image with four chosen corners. Then we fix it by calculating the opposite mapping, from the same four points. Finally, we compare the result with the original. Run it. The average difference is under half a brightness level, out of two hundred and fifty five, so we've recovered the flat view almost perfectly. On a real system, you'd mark four known points on the belt once, during setup, and then the same matrix fixes every frame.

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

img, _, _ = belt(seed=3)
tilt = np.float32([[60, 40], [590, 10], [630, 470], [10, 440]])
flat = np.float32([[0, 0], [640, 0], [640, 480], [0, 480]])

camera = cv2.warpPerspective(img, cv2.getPerspectiveTransform(flat, tilt), (640, 480))
fixed = cv2.warpPerspective(camera, cv2.getPerspectiveTransform(tilt, flat), (640, 480))

print("Average difference after fixing:", round(cv2.absdiff(img, fixed).mean(), 1))
cv2.imwrite("persp.png", np.hstack([camera, fixed]))
Output
Average difference after fixing: 0.4
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

Summary

Four points can square up any flat surface. Next, the same idea can join several photos into one.

Key takeaways
  • Homographies map flat planes
  • Four points define one
  • Set it up once
Next up: Stitching Images — one wide view from two cameras