Skip to main content

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_EMA and TRACER_SMOOTH_WINDOW in src/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

Install as a CLI tool (editable install — picks up code changes immediately):

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 overlays
  • side_by_side.mp4 — if both views were provided
  • report.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 (in src/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 calls analyze_video() on both clips and diffs the summary dicts. 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

swing_analyzer-0.1.1.tar.gz (55.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swing_analyzer-0.1.1-py3-none-any.whl (57.8 kB view details)

Uploaded Python 3

File details

Details for the file swing_analyzer-0.1.1.tar.gz.

File metadata

  • Download URL: swing_analyzer-0.1.1.tar.gz
  • Upload date:
  • Size: 55.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for swing_analyzer-0.1.1.tar.gz
Algorithm Hash digest
SHA256 141d364e4c13a43ea445d27eea1c7ee741e423f9a5b2f28ffc3982175a01c857
MD5 ef9be4ea6146e770c83db28c48369a14
BLAKE2b-256 fc3356287710db52dd84b35fa29a456cad34c7e5a1f2a7012218041ab7cdb28a

See more details on using hashes here.

File details

Details for the file swing_analyzer-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: swing_analyzer-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 57.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for swing_analyzer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2f3d47d56c7f65cf74fd23874f77f28fe0430216661c38d6c541c9a7cc882b67
MD5 b2301841bb4a0bb9f570c945dba0c2f9
BLAKE2b-256 ea18450266d31a28bebcc1e70176d1310d0fdb1aa865e9b65c551793d41cd868

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page