Skip to content

Repository files navigation

VeloCV

Track any object in a video and measure its real-world speed and acceleration — from a single camera, no special equipment.

Draw a box around an object in one frame. VeloCV segments and tracks it through the rest of the clip, reconstructs its actual 3D motion (not just pixel motion), and overlays live speed and acceleration on the video. For objects in free flight (a thrown or dropped ball), it fits a real physics model to the trajectory and reports how closely the recovered gravity matches the true value — 9.81 m/s² — as a built-in accuracy check.

demo

Real footage, not synthetic: a ball tossed in a hallway, tracked and fit to a physics model in real time. The "9.81 m/s²" on screen is the fixed physics assumption used to compute the trajectory, not a happy coincidence — the actual measured/validated result for this clip is reported below.


How it works

video ──┬─> user draws a box around the object on one frame
         └─> a YOLO pass suggests the category (person/car/ball/…) and a
             sensible pose-stable dimension + default size to confirm
                    │
                    v
     SAM2 (Segment Anything 2) segments and tracks the object's exact
     mask through every frame, forward and backward from the prompt
                    │
                    v
     each frame: mask → sub-pixel centroid + pose-stable dimension
     (diameter for a ball, height for a person/car — whichever doesn't
     change as the object rotates)
                    │
                    v
   pinhole camera model (checkerboard-calibrated, or a documented lower-
   accuracy "estimated" fallback) back-projects 2D pixels to 3D position
                    │
          ┌─────────┴─────────┐
          v                    v
   free-flight segment    otherwise: per-frame position,
   detected? fit a 6-      Savitzky-Golay smoothed →
   parameter ballistic     velocity, acceleration
   model (gravity fixes
   absolute scale) →
   launch velocity,
   fitted g vs 9.81 m/s²
          │                    │
          └─────────┬──────────┘
                    v
        annotated output video + metrics.json + plots

Why this is genuinely 3D, not just pixel tracking: a camera flattens depth. VeloCV recovers it two ways. For an object in free flight, gravity's known, universal curvature pins down absolute scale from the 2D track alone — no object size needed. Otherwise, the object's own known real-world size (diameter/height) combined with how large it appears in each frame gives a depth estimate that updates continuously, which is what lets the tool handle an object moving toward or away from the camera, not just side-to-side.

Why mass isn't estimated: under ideal projectile motion, trajectory alone cannot recover mass — a bowling ball and a tennis ball thrown identically follow the exact same path (Galileo's equivalence principle). Claiming otherwise would be the kind of thing that falls apart under a follow-up question, so it's left out.


Validation — real numbers, from real footage

No synthetic dataset substitutes for this: since gravity is a known, universal constant, a real recorded throw or drop is its own ground truth. Four clips were recorded on an iPhone (checkerboard-calibrated, 0.64px reprojection error — see Calibration), all analyzed by the exact same pipeline used above.

Clip Mode Result Error vs. 9.81 m/s²
semiarc_toss Flight (ballistic fit) Fitted g = 8.95 m/s², launch speed 3.22 m/s, 1.42px reprojection residual 8.72%
ball_drop General (per-frame) Mean acceleration 9.03 m/s² (median 8.80) 7.91% (mean) / 10.3% (median)
straight_toss General (per-frame) Correct free-flight speed curve (peak-to-peak 5.03 → 0.31 → 4.51 m/s) no independent ground truth for this one
arc_toss General (per-frame) Correct free-flight speed curve (4.75 → 0.74 → 4.17 m/s) no independent ground truth for this one

The two flight-type measurements land at ~8-9% error recovering a real physical constant — a legitimate, unglamorized number: a bit above the project's best-case theoretical estimate (2-5%, under ideal conditions), which is exactly what real footage (real motion blur, a hand-thrown toss, an imperfectly-measured object) should be expected to look like versus a lab-perfect synthetic test.

straight_toss and arc_toss don't have their own numeric ground truth (no tape-measure/stopwatch check was done for those two), but both show the correct physical shape — speed decreasing to a minimum at the arc's apex and increasing symmetrically back down — which is a real, if qualitative, correctness check.

Flight-mode residual fit (semiarc_toss) — how well the fitted 3D trajectory reprojects back onto the observed 2D track:

residuals

Velocity over time (semiarc_toss, flight mode) and (ball_drop, general mode):

velocity ball drop velocity

Two real things this validation run surfaced (documented, not hidden)

  1. Phone autofocus hunting mid-recording tripped the camera-motion safety check. The pipeline has a guard that refuses to output real-world units if it detects the camera moving. Autofocus refocusing caused enough temporary blur/optical shift to trigger it on the ball_drop clip. Verified independently — tracked a fixed doorframe feature across the clip; it returns to within 1px of its starting position by the last frame, which real camera movement would not do — and overrode the guard with that evidence documented directly in the clip's own warnings, not silently.
  2. A systematic SAM2 behavior: the very last trackable frame before an object leaves the frame or gets caught consistently shows an undersized mask (confirmed independently across 3 of the 4 clips — each one's apparent object size collapsed abruptly on its final frame only). Excluding that one artifact frame turned physically-impossible speed spikes (as high as 104 m/s for a hand toss) into the clean, textbook parabolic curves shown above. A real, generalizable finding worth an automated check in a future pass (see Planned improvements).

Calibration

Two independent, one-time calibration steps — a camera property and an object property, unrelated to each other:

  1. Camera intrinsics — a printed checkerboard, recorded in the same video mode/zoom as the real capture (phones crop the sensor differently between photo and video modes, so a photo's EXIF focal length doesn't transfer). Recovers focal length and real lens distortion via cv2.calibrateCamera. This run: 135/136 sampled frames detected, 0.64px reprojection error.
  2. Object real-world size — a photo of the object next to a coin or credit card (or, for something too small/large for that to be practical, a direct measurement/published spec). Full step-by-step protocol, including why each step matters and answers to questions that came up in practice, is in docs/capture-protocol.md.

Skipping camera calibration falls back to an "estimated" tier (a generic assumed field of view) — usable, but honestly labeled as lower accuracy. That penalty is asymmetric: lateral position is mathematically unaffected by a wrong focal-length guess whenever the object's size is known (proven by direct derivation against the actual code, not just claimed); only the depth (toward/away-from-camera) axis and lens-distortion correction are actually at stake.


Quick start

pip install -r requirements.txt

# One-time: calibrate your camera (record a checkerboard video first)
python -m velocv calibrate --video checkerboard.mov --pattern 9x6 --square-mm 20 --out camera.yaml

# One-time per object: get its real size from a reference photo
python -m velocv size-ref --object-px 300 --reference-px 428 --reference card

# Run
python -m velocv run --video clip.mov --select --frame 0 --fps 30 \
    --camera camera.yaml --dim diameter --size 0.0762 --mode auto --out results/

Prefer a browser UI over the CLI? python app.py launches a local Gradio web demo (velocv/ui/webapp.py) at http://127.0.0.1:7860, running at the estimated-intrinsics tier for casual, drag-and-drop use with no calibration step required.


Stated limitations (honestly, not buried)

  • Rolling shutter on phone sensors is mitigated by high-fps capture, not eliminated.
  • Flight-mode's gravity-axis fit assumes a level camera by default (a gravity-direction fit relaxes this automatically given enough frames).
  • Camera and capture video must share the same zoom/lens setting as the calibration recording.
  • Aerodynamic objects (frisbees, badminton shuttles) deviate from the pure ballistic model; large fit residuals trigger an automatic fallback to general mode with an explicit notice, not a silently wrong number.
  • Mass estimation is intentionally excluded (see How it works).
  • CPU-only: SAM2 has no real-time deadline in this offline pipeline, but it's not fast — a few minutes for a several-second clip on a laptop CPU.

Planned improvements (not yet done)

Concrete, prioritized ideas for a future pass, not aspirational hand-waving:

  1. Measure the reference object with calipers directly rather than a stated/assumed size — the single biggest lever, since size error propagates ~linearly into every downstream number.
  2. Lock phone focus/exposure before recording to prevent the autofocus hunting documented above.
  3. Automate detection of the last-frame SAM2 mask-undersizing artifact in the pipeline itself, instead of manual post-hoc trimming.
  4. Tune the flight-segment search's window/stride/min-length thresholds for short real-world clips — only 1 of 4 clips in this validation run reached the more-accurate flight-mode fit; the other three defaulted to the noisier general-mode fallback partly on frame-count grounds.
  5. Average multiple independent tosses for a statistically tighter headline accuracy number (the flight-mode result above is a single clip).

Tech stack

Python 3.11+ · PyTorch + SAM2 (Apache-2.0) · Ultralytics YOLO (category classification) · OpenCV · NumPy/SciPy · Matplotlib · Gradio

License

MIT — see LICENSE.

About

Monocular 3D speed & acceleration tracker — track any object in video, get real-world speed/acceleration from a single camera, physics-validated against gravity

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages