AI Hints
Home›Computer Vision Tutorial›Segmentation and Video
Computer Vision · Lesson 22 of 111

Separating Touching Objects

The watershed trick.

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

When apples touch on a conveyor, a threshold sees one big blob. Count it, and you get one apple instead of five. There's a clever classic fix, inspired by water filling up a landscape.

Separating Touching Objects
From the video: Separating Touching Objects
  • Touching apples form one blob
  • One instead of five
  • Think of water filling a valley

Watershed

First, the distance transform. For each white pixel, how far is it from the background? Each object's centre becomes a peak. We threshold those peaks, which gives separate seeds, one per object. Then watershed floods out from the seeds, until the floods meet. Those meeting lines separate the objects. Run it. Touching coins become separate objects. No training, no GPU, and it runs in milliseconds.

watershed.pypython
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage import data

rgb = cv2.cvtColor(data.coins(), cv2.COLOR_GRAY2BGR)
gray = data.coins()
_, b = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
b = cv2.morphologyEx(b, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations=2)
dist = cv2.distanceTransform(b, cv2.DIST_L2, 5)
_, sure = cv2.threshold(dist, 0.4 * dist.max(), 255, 0)
n, markers = cv2.connectedComponents(sure.astype(np.uint8))
markers = cv2.watershed(rgb, markers + 1)

print("Separate objects:", n - 1)
fig, ax = plt.subplots(1, 2, figsize=(9, 3.2))
ax[0].imshow(dist, cmap="magma"); ax[0].set_title("Distance from background")
ax[1].imshow(markers, cmap="tab20"); ax[1].set_title("Split objects")
for a in ax: a.axis("off")
plt.tight_layout(); plt.savefig("watershed.png", dpi=110)
Output
Separate objects: 11

Grading a bruise

Now let's put this to work for AppleEye. We draw a simulated apple with a dark bruise. Using HSV, we find fruit pixels, then the dark ones inside the fruit. The ratio gives the bruise area. Run it. It reports the share, and it applies a grading rule. That's a real business decision, made from pixels. On real apples, you'd replace the colour rule with a trained segmentation model, but the final logic is the same.

bruise.pypython
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = np.full((240, 240, 3), 30, np.uint8)
cv2.circle(img, (120, 120), 90, (200, 40, 40), -1)
cv2.ellipse(img, (150, 90), (26, 18), 20, 0, 360, (95, 50, 40), -1)

hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
fruit = hsv[..., 1] > 80
dark = fruit & (hsv[..., 2] < 140)

share = dark.sum() / fruit.sum()
print(f"Bruise covers {share:.1%} of the apple")
print("Grade:", "reject" if share > 0.05 else "accept")

fig, ax = plt.subplots(1, 2, figsize=(7, 3.4))
ax[0].imshow(img); ax[1].imshow(dark, cmap="gray")
for a in ax: a.axis("off")
plt.tight_layout(); plt.savefig("bruise.png", dpi=110)
Output
Bruise covers 6.0% of the apple
Grade: reject

Summary

Distance peaks separate touching objects, and a mask turns into a grade. So far, we've looked at single images. But AppleEye watches a moving belt. Next, video.

Key takeaways
  • Distance transform finds centres
  • Watershed splits blobs
  • Masks become decisions
Next up: Motion and Video — how to see movement