AI Hints
Home›Computer Vision Tutorial›Measuring the World
Computer Vision · Lesson 52 of 111

Measuring Size and Shape

From pixels to millimetres.

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

Supermarkets pay more for large apples. So every fruit has to be graded by size. And a misshapen fruit goes to the juice line. The camera sees pixels, but the customer pays for millimetres. So we need to convert.

Measuring Size and Shape
From the video: Measuring Size and Shape
  • Large apples earn more
  • Misshapen ones go to juice
  • Pixels to millimetres

Contours and grades

We've calibrated the camera, so we know that one pixel covers about point nine of a millimetre. Find contours gives the outline of each apple. The smallest enclosing circle gives a diameter. And roundness compares area with perimeter. A perfect circle scores one. Run it. Each apple gets a size in millimetres, a roundness, and a grade. Roundness sits at about point nine, because real outlines made of pixels are slightly jagged. So a limit of about point seven five would catch the misshapen ones. And that's a working fruit grader, with no neural network at all.

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

img, boxes, _ = belt(n=6)
mask = cv2.inRange(cv2.cvtColor(img, cv2.COLOR_BGR2HSV), (0, 90, 40), (12, 255, 255))
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((15, 15), np.uint8))

mm_per_pixel = 0.9
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in sorted(contours, key=cv2.contourArea, reverse=True)[:4]:
    (x, y), r = cv2.minEnclosingCircle(c)
    area, perimeter = cv2.contourArea(c), cv2.arcLength(c, True)
    roundness = 4 * np.pi * area / perimeter ** 2
    grade = "Large" if r * 2 * mm_per_pixel >= 75 else "Medium"
    print(f"{r * 2 * mm_per_pixel:5.1f} mm  roundness {roundness:.2f}  {grade}")
Output
 85.6 mm  roundness 0.90  Large
 85.6 mm  roundness 0.90  Large
 85.3 mm  roundness 0.90  Large
 74.6 mm  roundness 0.90  Medium
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

Getting measurements right

Accuracy depends on calibration. Keep the camera at a fixed height, or a moved camera will change every measurement. Lens distortion bends straight lines near the edges, so correct it first. Objects at different heights appear at different sizes. And always check your system against a real object, of a known size, every day.

Fixed heightor scale changes
Fix distortionedges bend
Daily referencea known object
Report uncertaintyplus or minus

Summary

Calibrated pixels give you real measurements. Next, a classic algorithm for finding perfect shapes.

Key takeaways
  • One pixel equals a known distance
  • Roundness measures shape
  • Check with a reference object
Next up: Finding Circles and Lines — the Hough transform