Golf Swing Analyzer (v1)
Analyzes a golf swing video using pose estimation (MediaPipe) and overlays:
- Dots on body landmarks (shoulders, elbows, wrists, hips, knees, ankles)
- Lines connecting them into a skeleton
- Angle triangles (e.g. spine tilt vs. vertical), color-coded green/orange/red depending on how close to instructional target ranges
- Wrist trail showing the swing arc/path over recent frames (smoothed — the
tracked point is EMA-damped and the drawn line is moving-averaged, so the tracer
reads as a clean curve rather than jaggy/shaky segments; tune with
TRACER_EMAandTRACER_SMOOTH_WINDOWinsrc/swing_analyzer/analyze.py) - Swing plane line (down-the-line view) extrapolated through shoulders/hands
It also does lightweight swing-phase detection (address / top of backswing / impact / finish) and outputs a metrics report.
Setup
Requires Python 3.9–3.12. This tool pins mediapipe==0.10.14 (see below),
which has no wheels for Python 3.13+ — pip install will fail with an error
like Package 'swing-analyzer' requires a different Python if your default
python/pip is 3.13 or newer (e.g. a fresh Windows install with only the
latest Python). Install a 3.9–3.12 build and use that specifically, ideally in
a virtual environment:
# macOS/Linux
python3.12 -m venv .venv
source .venv/bin/activate
# Windows (py launcher picks a specific version even if a newer one is default)
py -3.12 -m venv .venv
.venv\Scripts\activate
Then, from inside the project folder, with the venv active:
pip install -e .
This installs a swing command with three subcommands: analyze, compare,
full. Run swing --help, or swing <subcommand> --help, for the full flag
list at any time.
Note: pin mediapipe==0.10.14 — newer 0.10.3x releases dropped the legacy
mediapipe.solutions API in favor of a Tasks API that requires a separately
downloaded model file. 0.10.14 keeps the simple bundled-model API this tool
uses.
Outputs always land in ./output/ relative to wherever you run swing,
not the install location — so cd to your videos first.
Usage
Single view:
swing analyze --face-on my_swing.mp4
Down-the-line view:
swing analyze --dtl my_swing_dtl.mp4
Both angles at once (produces a synced side-by-side video too):
swing analyze --face-on face.mp4 --dtl dtl.mp4
Compare mode
Diff two swings against each other — e.g. same swing with different club/shaft builds, or before/after a change:
swing compare --a old_shaft.mp4 --b new_shaft.mp4 \
--label-a "Stock shaft" --label-b "Ventus Blue 7X"
Add --view dtl if both clips are down-the-line rather than face-on.
Outputs in ./output/: annotated_<a>.mp4, annotated_<b>.mp4,
compare_side_by_side.mp4, and compare_report.md (metrics table + per-swing
assessment).
Face blur (anonymize)
Pass --blur-face to pixelate the golfer's face in the output (uses the tracked
face landmarks, so the mosaic follows the head through the swing):
# compare mode — blurs the first swing (--a) only; the reference is left as-is
swing compare --a my_swing.mp4 --b reference.mp4 --blur-face
swing compare --a my_swing.mp4 --b other.mp4 --blur-face --blur-face-b # blur both
# standalone
swing analyze --face-on my_swing.mp4 --blur-face
In compare mode --blur-face anonymizes only your clip (--a); add
--blur-face-b if you also want the reference blurred.
The blur box is temporally smoothed — EMA-averaged frame to frame to reduce
jitter, and held through short tracking gaps (up to FACE_BLUR_HOLD_FRAMES, ~0.4s)
so it doesn't flicker off on a blurry frame where pose tracking briefly drops.
Both FACE_BLUR_SMOOTHING and FACE_BLUR_HOLD_FRAMES are constants in
src/swing_analyzer/analyze.py.
Auto-zoom (framing match)
Clips shot from different distances make one golfer look much smaller than the other. Before syncing, each clip's body size is measured (the nose→ankles box as a fraction of frame height, median over the swing) and the clip is zoomed in and panned down to a standard framing:
- Standard zoom box — the golfer's body is zoomed to fill ~50% of the frame
height (
STANDARD_BODY_FRACTION). In compare mode the tighter of the two clips raises this so both clips stay matched to each other (e.g. your range clip zooms in to match a tour-pro reference). - Standard ground line — the clip is panned down so the feet (and the ball,
which sits at the feet) land ~85% down the frame (
STANDARD_FEET_Y), so every clip shares the same ground line and the ball/feet are always in view.
This runs in both compare and standalone (swing analyze) modes. It only
ever zooms in (never adds borders) and crops at the original aspect ratio, so the
body is never stretched. The two standards are constants at the top of
src/swing_analyzer/analyze.py — override them there (or pass target_fraction to
match_framing()); there is deliberately no command-line flag.
Slow-motion & tempo sync
Every clip is checked on load: swing tempo is measured from the downswing (top-of-backswing → impact, the fastest and most consistent part of a swing). A real-time downswing is ~0.2–0.35s; anything that plays back faster than ~0.8s is treated as real-time and slowed down so the downswing spans ~1.5s, while a clip that's already slow-mo is left as-is. In compare mode both clips are slowed to that same tempo so they play at matched speed.
The compare_side_by_side.mp4 then does a piecewise time-warp — backswing,
downswing, and follow-through are each resampled to a shared length — so
top-of-backswing and impact line up across the two clips, not just the first
frame.
Guardrails keep pathological clips sane: slowdown is capped at 8×, a retimed clip
never plays longer than 20s, and the side-by-side trims excess
backswing/follow-through context. Tunables live at the top of src/swing_analyzer/analyze.py
(SLOWMO_*). This all leans on the heuristic phase detector, so it works best
on a clean, tightly-trimmed single-swing clip — feed it a 20s highlight reel
and top/impact detection (and therefore the tempo/sync) gets unreliable.
Output
All in ./output/:
annotated_<name>.mp4— video with overlaysside_by_side.mp4— if both views were providedreport.md— phase frame numbers, key metrics, and flagged issues
Tips for good input video
- Frame the whole swing (address to finish) in view, camera roughly waist-to-head height
- Face-on: camera directly facing you, perpendicular to target line
- Down-the-line: camera behind you, aligned with the target line
- Plain background helps pose detection; avoid other people/objects in frame
- 30fps+ is fine; higher fps (60/120) gives smoother phase detection around impact
Known limitations (v1)
- 2D single-camera pose estimation — no true 3D depth, so some angles (especially swing plane in face-on view) are approximate.
- Phase detection is a simple heuristic based on wrist height/velocity, not a robust event detector — works best with a clear address position held briefly and a normal tempo swing.
- No clubhead or clubshaft tracking (MediaPipe tracks body only) — swing plane line is a proxy through hands, not the actual shaft.
- No ball flight / clubhead speed data — that needs radar/launch-monitor input.
- Target ranges in
TARGETS(insrc/swing_analyzer/analyze.py) are rough instructional benchmarks, not personalized — worth tuning to your own baseline over time rather than treating as absolute truth.
Extending
- Compare mode (
src/swing_analyzer/compare.py) is scaffolded — it's a thin wrapper that callsanalyze_video()on both clips and diffs thesummarydicts. Natural next steps: frame-aligned overlay (warp swing B's timeline onto A's phase markers so both hit "top of backswing" at the same frame), a ghost/overlay video (draw both skeletons on one frame, semi-transparent), or exporting the comparison table as CSV for tracking across many sessions. - To track club-specific data (shaft lean, clubface angle) you'd need either a marker on the club visible to a second detector, or a dedicated launch monitor/radar unit — video-only pose estimation can't see the club reliably.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swing_analyzer-0.1.2.tar.gz.
File metadata
- Download URL: swing_analyzer-0.1.2.tar.gz
- Upload date:
- Size: 55.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
366aa58e30b023729a4c44ca1cd245c75734b4d6e88529b7a5352f512be8a7e9
|
|
| MD5 |
727e3c1e6a072aaeff481b003c72b23a
|
|
| BLAKE2b-256 |
8152c87a655ea7e58a97b60a8a0c653335beae6f94aa748611ece86dbebbe366
|
File details
Details for the file swing_analyzer-0.1.2-py3-none-any.whl.
File metadata
- Download URL: swing_analyzer-0.1.2-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88482ebf3980f47331130bd59b161c2418071e65732630256ee8de22eb05b6c8
|
|
| MD5 |
2d84da700d376d5bd2c304d8d6405165
|
|
| BLAKE2b-256 |
4615ddda91cd6b5fd0a502b8bcad1223a33565ad9bf71aeb92638aba1c95fc81
|