ballform
A computer vision project that looks at shooting form, gives feedback, and rates shots in a real 1-on-1 or 5 on 5 game. I optimized the GPU kernel and video pipeline to cut processing time by 88% on a test clip.
ballform /
Faster video analysis
88.2%less total timevs. original CPU run
The same 286-frame clip went from 172.68s to 20.46s — 8.44× faster overall.
Measured on the same M3 Pro across September 22–25. The detector backend, model loading, and rim setup changed along the way. The last two runs use an automatically detected rim; marking it manually adds about 4 seconds in the latest tests. These are individual runs showing the total measured improvement across versions.
latest changes across four clips · 25.1% less time on average
The review video now gets drawn and encoded in one pass. When the GPU is free from side-crop ball detection, it also handles the right crop's pose.
clip 3074 · 286 frames27.8% less time
clip 2fcb · 175 frames25.4% less time
clip 2c7e · 204 frames24.4% less time
clip 5a58 · 301 frames22.9% less time
Before and after the two latest changes. Each clip was run once per configuration with the rim detected automatically. The 25.1% average here covers this update only.
what each change saved
Each row adds to the one above. These tests used a rim marked on frame 0, adding about 4 seconds of tracking to every run. That makes them slower than the automatic-rim tests above.
On clip 3074 with the rim detected automatically, review-video time fell from 4.34s to 1.52s. Most of that saving came from removing two extra encodes and overlapping encoding with drawing.
earlier optimizations · September 22–24
These earlier runs used the ONNX / Core ML ball detector. The final setup in this batch took 76.7% less time than the original CPU run.
The last two steps keep the models loaded, then switch pose to fp16 Core ML with the Neural Engine requested.
The original overlapped run took 83.50s. A rerun on September 24 took 99.90s; the final setup in this batch cut that by 59.7%.
detector and pose timings
Measured with each model warmed up and running on its own. Includes preprocessing, inference, and readback. Pose uses one full frame and two crops. Core ML settings request compute units; they don't confirm where every operation runs.
One 286-frame, 1920x1080 clip on an Apple M3 Pro, 36 GB unified memory. One run per configuration, measured across two days. These are full analysis times; models were preloaded for the last two runs.
The later runs used a rim marked on frame 0, shifting the detected make from frame 243 to 245. The shot score was 97.9 with PyTorch pose and 97.4 with fp16 pose, which changed measured separation by 0.7%.
Measured on 2026-09-25 on an Apple M3 Pro, 36 GB unified memory, with models preloaded. Ball detection uses MLX + Metal; pose uses fp16 Core ML.
Release times, make frames, shooters, defenders, and outcomes stayed the same across all four clips. Clip 2fcb's score changed from 91.3 to 89.8 with the GPU pose keypoints; the other scores stayed the same.


intended to run on apple silicon with 16gb ram (only tested on 36gb). Ball detection uses an optimized MLX + Metal kernel, with fp16 Core ML pose work split between the Neural Engine and GPU.