Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Colour-based tracking of a single object. Instead of naming a colour, you outline the object in a few frames and the notebook learns its colour from the pixels inside those outlines. The example tracks the yellow water polo ball in the trimmed video, restricted to when it is in the air.

To track something else: set the video in Step 0, pick frames where the object is visible in Step 2, outline it in Step 3, and run the rest. Steps 10–11 assume a round object of known size in free flight (a thrown ball); skip them for other objects.

Step 0: Settings

The video to track, a short label used in the output file names, and how many evenly spaced frames to preview in Step 1.

Last updated: 2026-10-15

Open in Colab Open in GitHub Codespaces

Step 1: Load the video

Reads all frames of the video into memory and shows N_PREVIEW evenly spaced frames. Note the numbers of frames where the object is clearly visible.

222 frames, 1420x400, 59.94 fps
<Figure size 2000x900 with 12 Axes>

Step 2: Pick sample frames

From the preview above, list 1–4 frames where the object is clearly visible in SAMPLE_FRAMES. Choose frames in different conditions (e.g. on the water and in flight, near and far, light and shade) so the learned colour covers all of them. If the track is later lost in some frames (Step 6), add one of those frames here and outline it too. Each frame is shown large with a grid in pixel coordinates (x to the right, y downwards) to read off where the object is.

Why type coordinates? A simple click-to-outline interface (e.g. OpenCV’s cv2.selectROI window) would be quicker, but it opens a window on the screen of the computer running Python. In a notebook running on Colab or any other remote Jupyter server that computer has no screen, so the window cannot appear. Typed coordinates work everywhere.

Step 3: Outline the object

For each sample frame, enter 3 to 6 (x, y) points that go around the object in order (clockwise or anticlockwise) in OUTLINES. Place the points just inside the object’s edge: any background inside the polygon would be learned as object colour. The plot zooms in on each outline with a finer grid; adjust the points and rerun until the red polygon covers only the object.

Step 4: Learn the object’s colour range

Why HSV? Video frames store each pixel as amounts of blue, green and red (BGR). Shadow and lighting change all three together, so the same object in shade has quite different BGR values. HSV describes a colour in three more useful parts:

  • Hue (H): which colour it is, as a position on the colour wheel. OpenCV uses 0–179 (degrees ÷ 2): red ≈ 0 and 179, yellow ≈ 30, green ≈ 60, blue ≈ 120. The wheel wraps around, so red sits at both ends.

  • Saturation (S, 0–255): how vivid the colour is; 0 is grey, white or black.

  • Value (V, 0–255): how bright it is; 0 is black.

Lighting mostly changes S and V and leaves H nearly the same, so a range of H, S and V values describes an object’s colour much better than a range of BGR values.

How the range is learned. All pixels inside the outlines are collected. Their centre hue is averaged around the colour wheel, and the range kept for H, S and V runs from the PCT-th to the (100 − PCT)-th percentile, so a few stray background pixels inside an outline don’t stretch it. MARGIN widens it slightly so that the object still matches under somewhat different lighting in other frames. color_mask then marks every pixel within this range. The blob size limits used in Step 5 (MIN_AREA, MAX_AREA) come from the outline areas.

Check. For each sample frame, the printout gives the share of the outlined pixels that match (should be high) and the number of matching pixels elsewhere (should be low). The histograms show the sampled pixels with the learned range shaded, and the frames show matching pixels in magenta. If the object is missed, raise MARGIN or outline more frames; if background matches, lower MARGIN or outline more tightly.

Step 5: Find blob candidates

Applies color_mask to each frame, removes static objects of the same colour (pixels that match in more than half of the frames, e.g. flags or markings), and finds connected blobs between MIN_AREA and MAX_AREA pixels. Prints how many candidates each frame has; ideally 1.

frames with >=1 candidate: 132 / 222
candidates per frame (first 130): [1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Chooses one candidate per frame: the one closest to the position predicted from the blob’s recent velocity, rejecting jumps larger than MAX_JUMP px. Tracking stops after MAX_MISS consecutive frames without a match. The plots overlay the trajectory and the detection on sample frames for a visual check.

tracked frames: 121 | range: 0 - 120
<Figure size 2200x800 with 8 Axes>

Step 7: Check the height over time

Plots the blob’s vertical position per frame (up = higher). For the water polo ball this shows when it leaves the water and when tracking ends, which sets the airborne start rule in the next step.

<Figure size 1200x300 with 1 Axes>
[276.0, 255.0, 223.0, 193.0, 168.0, 151.0, 141.0, 136.0, 135.0, 137.0, 105.0, 94.0, 94.0, 98.0, 106.0, 129.0, nan]

Step 8: Keep the relevant part and save the track

With AIRBORNE_ONLY = True (thrown-ball videos) the frames where the ball rests on the water are dropped: the track starts at the first frame where the ball is more than 15 px above its starting height. Set it to False to keep the whole track. Saves frame, x, y, x_orig, y_orig, t_s to <video>_<LABEL>_track.csv, where x_orig/y_orig are coordinates in the original untrimmed frame (trim offset CROP_OFFSET; use (0, 0) for an untrimmed video). Also plots the kept trajectory.

Airborne frames 64-120 (57 points)
Saved: 01-F-01-TL-Back-2_trimmed_ball_track.csv
<Figure size 1400x400 with 1 Axes>

Step 9: Render a video with the tracked blob and trail

Draws a circle on the blob in each kept frame, a fading trail of its last 30 positions, and the current (x, y) readout. The video is saved next to the input as <video>_<LABEL>_tracked.mp4 and re-encoded to H.264 with ffmpeg (if available) so it plays in most players.

Tracked video saved as "01-F-01-TL-Back-2_trimmed_ball_tracked.mp4" in /Users/souvikmandal/Documents/S06_Teaching_Mentoring_Talks/LS100/2026_Sem2/data/raw/water-polo_maya

Step 10: Ball diameter in pixels and metric scale

For a round object of known size; skip for other objects. Measures the ball’s apparent diameter in each kept frame from a looser colour mask around the tracked position (the learned range widened by LOOSE_EXTRA), using the ellipse’s minor axis (perpendicular to motion blur). The median over frames gives px_per_m = diameter_px / BALL_DIAMETER_M. The ball is in its own plane of motion, so this scale applies at the ball’s depth.

count    57.0
mean     35.3
std       5.4
min      14.4
25%      34.0
50%      35.6
75%      36.8
max      56.8

Median diameter 35.6 px -> 169.6 px/m (5.9 mm/px)
<Figure size 1800x900 with 18 Axes>

Step 11: Ball speed in m/s

For a thrown ball; skip for other objects. Uses the ellipse centre as the ball position (frames with an implausible diameter fall back to the colour-blob centroid), converts to metres, and finds the release frame as the first frame where the ball moves faster than RELEASE_PX_PER_FRAME. During free flight, the horizontal motion is a straight line and the vertical motion a parabola, so x(t)=x0+vxtx(t)=x_0+v_xt and y(t)=y0+vyt−12gfitt2y(t)=y_0+v_yt-\tfrac12 g_{fit}t^2 (y up) are fitted to the flight frames. Air drag is ignored. g_fit should be close to 9.81 m/s² if the scale and track are good. Speeds are in the image plane only; motion toward or away from the camera is not captured. Set RELEASE_FRAME/END_FRAME manually to override the automatic choice.

Flight frames 96-118 (23 points, 0.37 s)
Release speed: 12.9 m/s (46 km/h)  vx=12.7, vy=2.2 m/s, angle=10 deg
Mean speed (path length / time): 12.3 m/s
Fitted g: 10.4 m/s^2 (expected 9.81)
Fit residual (RMS): 28 mm in y, 89 mm in x
<Figure size 1400x400 with 2 Axes>