AI Hints
Home›Computer Vision Tutorial›Vision Projects Two
Computer Vision · Lesson 81 of 111

Dominant Colours

Grading fruit with clustering.

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

Fruit buyers talk about colour grades. How much of the fruit is deep red, and how much is pale? Rather than deciding on thresholds by hand, we can let the image tell us which colours it contains. That's a neat use of a tool that you've already met. Clustering.

Dominant Colours
From the video: Dominant Colours
  • Buyers talk about colour grades
  • Don't set thresholds by hand
  • Let the image tell us

K-means on pixels

Each pixel is just a point, with three numbers. So we can run K means on the pixels, and find three groups of similar colours. We take every twentieth pixel, to keep it fast. Run it. Three colours come out, with their shares. Grey belt, at eighty nine percent. Deep apple red, at ten percent. And one percent pale highlight. In one line, we've got a summary of the image. Run this inside each apple's mask, and the share of deep red becomes a measure of ripeness, for each fruit. This is also how design tools pull a colour palette from a photo.

palette.pypython
import cv2
import numpy as np
from sklearn.cluster import KMeans
from belt import belt

img, _, _ = belt(n=6)
pixels = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).reshape(-1, 3)[::20]

km = KMeans(3, n_init=5, random_state=0).fit(pixels)
share = np.bincount(km.labels_) / len(pixels)
for c, s in sorted(zip(km.cluster_centers_.astype(int).tolist(), share), key=lambda p: -p[1]):
    print(f"RGB {c}: {s:.0%}")
Output
RGB [82, 77, 71]: 89%
RGB [199, 40, 35]: 10%
RGB [228, 88, 88]: 1%
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

Clustering pixels summarises colour. Next, finding damage in material that has a repeating pattern.

Key takeaways
  • Pixels are points in colour space
  • K-means finds main colours
  • Shares give a grade
Next up: Finding Flaws in Fabric — when the pattern is the clue