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-independent tracking: click the ball once on one frame, and SAM 2 (Segment Anything Model 2, via the ultralytics package) segments and follows it through the rest of the video. Then the ball speed in m/s is computed as in track_ball.ipynb. Runs on CUDA (e.g. Colab), Apple Metal (mps) or CPU.

Step 1: Load the video

Reads all frames into memory and selects the compute device.

Last updated: 2026-10-15

Open in Colab Open in GitHub Codespaces

222 frames, 1420x400, 59.94 fps, device=mps

Step 2: Choose the frame and click point

Set PROMPT_FRAME to a frame where the ball is clearly visible, and PROMPT_POINT to (x, y) pixel coordinates on the ball (read from the grid). SAM 2 tracks forward from this frame, so choose the earliest frame you need, for example just before release. Re-run this cell until the red cross sits on the ball.

<Figure size 1400x394.366 with 1 Axes>

Step 3: Run SAM 2 on the video

Writes the frames from PROMPT_FRAME onward to a temporary clip and runs the SAM 2 video predictor with the clicked point as a positive prompt. The model weights (SAM2_WEIGHTS) are downloaded automatically on first use; sam2.1_t.pt (tiny) is faster, sam2.1_b.pt (base) is more accurate. If the chosen device fails, it falls back to CPU. The result is one segmentation mask per frame.

Ultralytics 8.4.27 🚀 Python-3.12.12 torch-2.11.0 MPS (Apple M2 Max)
Downloading https://github.com/ultralytics/assets/releases/download/v8.4.0/sam2.1_b.pt to 'sam2.1_b.pt': 100% ━━━━━━━━━━━━ 154.4MB 34.5MB/s 4.5s 4.5s<0.0s
WARNING ⚠️ 
Inference results will accumulate in RAM unless `stream=True` is passed, which can cause out-of-memory errors for large
sources or long-running streams and videos. See https://docs.ultralytics.com/modes/predict/ for help.

Example:
    results = model(source=..., stream=True)  # generator of Results objects
    for r in results:
        boxes = r.boxes  # Boxes object for bbox outputs
        masks = r.masks  # Masks object for segment masks outputs
        probs = r.probs  # Class probabilities for classification outputs

126 frames segmented

Step 4: Masks to ball positions and a visual check

For each frame, takes the mask’s centroid as the ball position and its area-equivalent diameter d=2A/πd = 2\sqrt{A/\pi} as the ball size in pixels. Frames where SAM 2 returns no mask are NaN. The plot shows the mask outline on sample frames and the mask area over time; a sudden jump in area or position means the mask has drifted onto something else (hand, splash, goal).

masks found in 26 / 126 frames
<Figure size 2200x900 with 12 Axes>
<Figure size 1400x300 with 2 Axes>

Step 5: Ball speed in m/s

The ball’s known diameter (0.21 m) gives the scale, but the camera looks along the throw, so the ball’s apparent size shrinks as it flies away. A single constant scale would therefore underestimate its later speed. Instead, the scale is taken per frame from a linear trend of the mask diameter: X=(x−cx) D/dX=(x-c_x)\,D/d, Y=−(y−cy) D/dY=-(y-c_y)\,D/d in metres, where DD is the ball diameter and dd its diameter in pixels. Frames where the mask is much smaller than typical (ball partly hidden) are excluded, and the release frame is the first frame where the ball moves faster than RELEASE_PX_PER_FRAME.

Free flight is fitted as 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. g_fit should be near 9.81 m/s² if the scale and track are good. The reported speed is in the image plane. If you know the camera focal length in pixels (FOCAL_PX), the depth Z=f D/dZ=f\,D/d is also computed, giving the 3D speed. Air drag is ignored, and the camera is assumed level with its principal point at the image centre. Set RELEASE_FRAME/END_FRAME to override the automatic choice.

Ball diameter 42.1 px at release -> 35.1 px at the end (1.20x change in size)
Flight frames 99-119 (21 points, 0.33 s)
Release speed (image plane): 12.1 m/s (44 km/h)  vx=12.1, vy=1.2 m/s, angle=5 deg
Fitted g: 7.5 m/s^2 (expected 9.81)
Fit residual (RMS): 9 mm in y, 27 mm in x
<Figure size 1800x400 with 3 Axes>