Skip to content
TomatokeftesPublic

About

Mikiri: local video analysis for badminton movement, racket and shuttle

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Mikiri

Mikiri (見切り, "reading the shuttle") is a local video analysis app for badminton, formerly JumpLab. It covers jumps, lunges and recovery; court position and footwork; rallies and split steps; racket and stroke description; and coaching advice built on all of it. What is still to come is in docs/feature-roadmap.md.

Local video biomechanics for badminton movements. Load a phone clip, track the athlete with RTMPose on the CPU, scrub frame by frame with a skeleton overlay, read joint angles over time, mark takeoff and landing (or let Mikiri suggest them), fix bad keypoints by dragging them, and export a self-describing pack you can hand to a person or an AI assistant. No cloud, no telemetry; the only network access is the one-off model download.

Mikiri is a single-camera 2D tool: it measures angles in the image plane from a side-on view. See "Limitations" before trusting a number.

Tracked clip with skeleton overlay, events and angle plots

60 second quick start

Requirements: Python 3.11 or newer on Windows, macOS or Linux. ffmpeg is bundled (via imageio-ffmpeg), nothing else to install.

pip install -e .
mikiri

The browser opens at http://127.0.0.1:8765. Then:

  1. Open video: the system's own file dialog opens. On a machine with no display to put one on, paste a path or upload instead. Duration, fps and resolution appear above the frame; rotation metadata is respected.
  2. Trim: drag the blue handles on the timeline or press I / O on the current frame.
  3. Run tracking: click the athlete in the first frame (or "Track largest person"). The first run downloads the models (about 150 MB) and tells you where from. Progress bar and Cancel are in the dialog. Once the court is calibrated the dialog also offers the player at the other end: they are told apart by which side of the net they stand on and drawn in grey on the video and the court map, and their contacts feed the rally chain. For doubles tick "Two players on each side": partners are told apart by continuity, and the frames where they overlap are marked rather than guessed. The line under the transport says what to do next -- track, calibrate the court, track the other end, answer the review, save -- and the right-hand column is four pages: Movement, Match, Over time and Project. A page that has nothing yet says what would fill it.
  4. Scrub: Left/Right one frame, Shift+arrows ten frames, Space play/pause at 0.1x to 1x. Click any plot or the timeline to jump.
  5. Events: press a preset (split step, deepest load, takeoff, contact, landing, recovered) to mark the current frame, or "Auto-detect takeoff / landing" and accept the suggestions. Flight time appears immediately.
  6. Fix keypoints: tick "Edit keypoints" (E), drag a joint, optionally "Interpolate between" two corrected frames, undo with Ctrl+Z.
  7. Export pack: choose whether to include the videos, then find the folder and zip in the dialog. Save (Ctrl+S) writes <video>.mikiri.json next to the clip; opening the clip again restores everything.

demo/ holds one clip: demo.mp4, a synthetic jump the tests use, which mikiri make-demo regenerates. Real footage lives in benchmarks/ and is described by benchmarks/clips.json -- a phone recording of jump smashes from behind-diagonal, a screen recording of a match seen from behind, a side-on smash and a Lin Dan rally. The videos themselves are never committed; see benchmarks/README.md. Real clips contain other players: use the "Track largest person" default or click the athlete in the Run dialog, and expect to flip "Swap L/R" for footage from behind.

Real footage: jump smash tracked from behind

What you get

Editing a keypoint

  • Skeleton overlay colour-coded left (green) / right (orange), with a "Swap L/R" toggle for clips filmed from behind.

  • Plots of knee, hip, elbow, shoulder and ankle angles (left and right), trunk lean, segment angles versus the ground, hip height, ankle heights and stance width, with a cursor synced to the scrubber and event lines.

  • Movement profiles: jump smash, scissor-kick smash, block jump, lunge, split step and recovery. The profile sets the event buttons, the automatic suggestions (flight, hop or lunge detectors) and an Assessment table that compares the clip's metrics with reference bands and gives a cue when a value is outside its band. Bands are coaching heuristics; edit them in movements.json in the Mikiri folder. See docs/movements.md.

    Assessment against the jump smash reference bands

  • Automatic takeoff / landing suggestions from the lowest foot's trajectory against a perspective-aware ground model (works when the athlete runs towards the camera), with a confidence figure and a list of every other airborne phase in the clip; you confirm or move them.

  • Keypoint correction with interpolation across a frame range, optional Savitzky-Golay or Butterworth smoothing (off by default; corrected points stay put), undo/redo.

  • Scale calibration from the athlete's height or two clicked points, so hip height, loading depth and jump height are also reported in cm.

  • Side-by-side comparison of two open projects with scrubbers aligned on an event (for example takeoff); the second clip's angles are overlaid dashed.

  • Batch mode: mikiri batch FOLDER tracks and exports every clip in a folder.

Export dialog

The export pack

<name>/
  angles.csv        one row per frame: frame, time_s, every angle, hip_y, ankle_L_y,
                    ankle_R_y, stance_width, per-angle confidence, corrected/lost flags
  keypoints.csv     all 26 keypoints per frame, pixel coordinates + confidence
  events.json       name, frame, time_s, source, confidence
  keyframes/        PNG overlay at each event frame, plus sheet.png (every Nth frame)
  overlay.mp4       skeleton over the video, H.264 yuv420p (optional)
  stick.mp4         stick figure only (optional)
  summary.md        metadata, event table, angles at each event, flight time,
                    jump height, angle definitions, limitations
  project.mikiri.json  reopen the analysis in Mikiri
<name>.zip          the same, zipped

summary.md reports the flight time between takeoff and landing, the jump height implied by it, hip rise and loading depth, and explains every column so the pack stands on its own. Angle conventions are documented in docs/angles.md.

Court calibration and footwork

Click the four court corners you can see (or the near half, or the near baseline plus the short-service line) once per clip and Mikiri maps the player's feet onto a top-down court in metres: a court map with the track, heat map and current position next to the video, footwork metrics (distance, average and peak speed, direction changes, base position, time per zone, coverage shape), and positions.csv plus court.png in the export pack. The camera must be static.

Click the two net-post tops as well. They are verticals of known height (1.55 m) standing at court points Mikiri already knows, so two extra clicks pin down where the camera is. That matters more than it sounds: an ankle keypoint sits about 7.5 cm above the floor, and mapping it as if it were on the floor pushes it away from the camera by up to 92 cm at the far baseline from a 1.6 m tripod. That error is systematic, not noise, so it never averages out. Knowing the camera corrects it, and also yields jump clearance and stroke contact height in metres.

Mikiri propagates its own measurement error rather than asserting an accuracy: the court map draws 95% error ellipses, and the heat map is blurred by that same uncertainty, so the far court is drawn as the smear it really is. Details and accuracy notes in docs/footwork.md; the plan for shuttle tracking is in docs/roadmap.md.

Court map with the footwork track

Racket

The racket head is estimated one racket length beyond the wrist along the forearm and drawn as a white line, with head speed per frame, contact-point height and swing direction in the smash assessment and racket_* columns in angles.csv. It is an estimate with known limits; read docs/racket.md before quoting a speed.

Coaching

Once the court is calibrated, a Coaching panel turns the numbers into at most three things to work on, each with the evidence behind it and how much that evidence is worth. Pick your level (beginner, club, national, elite) and the bands tighten; edit coaching.json in the Mikiri folder if you disagree with them. Cues are phrased for what should happen on the court rather than what a joint should do, and a data-quality cue fires when the measurement itself is not good enough to draw a conclusion from. See docs/coaching.md, and docs/feature-roadmap.md for the researched plan of what is worth building next and what is deliberately not.

Strokes

Mikiri finds each contact and describes it: contact height (overhead, sidearm, underarm), forehand or backhand, and which part of the court it was played from. Contacts appear as ticks on the timeline and in a panel that seeks to them, and as strokes.csv in the export pack.

It stops short of naming the shot from the body alone. Smash, clear and drop share nearly identical overhead kinematics and separate only on racket-face angle and what the shuttle does afterwards, neither of which a single 30 fps camera can see. Rather than guess, Mikiri reports what it can defend and leaves the shot name unset until shuttle evidence supports one. docs/strokes.md explains the reasoning.

Find shuttle in the Strokes panel goes looking for that evidence. Rather than track the shuttle across a whole rally -- which is unreliable on consumer footage -- it searches the few frames after each contact the swing detector already found, starting from where the racket head was. The launch direction and speed it recovers are exactly what separate a smash from a clear from a drop, and contacts where nothing is found simply keep their description.

Shot names also need a side-on camera. The launch is measured in the image, and from behind the baseline a smash is foreshortened until it reads as a drop, so Mikiri checks the recovered camera view and declines to name rather than name it wrong. Film from the side for shot names, from behind and high for footwork. See docs/shuttle.md.

Rallies and split steps

A clip is not a rally, so Mikiri cuts it into bouts of continuous work and reports the rests separately: rally length, distance per rally, metres per minute in play, shots per rally, and -- only when the clip still contains its rests -- work density and effective playing time. That last pair is withheld on footage already trimmed to the rallies, where it would measure the editing rather than the player. docs/rally.md.

Inside the bouts it looks for split steps: of the moves the player made, how many were prepared by a hop, and how long after landing the first step came. It does not attempt split-step timing against the opponent, which needs an opponent this app may not be tracking, and it is honest that what it detects is a preparatory hop of some kind rather than a certified split step. The reasoning, and what the number is worth, is in docs/footwork.md.

The Rallies panel on a short clip, refusing the figures it cannot support

Serves, outcomes and the score

With the court calibrated and both players tracked, Mikiri reads who won each rally off who serves the next one, keeps the score, and checks every serve against the rules of the game: contacts alternate, the server stands in the right court on an even score and the left on an odd one, the receiver stands diagonally opposite. Where the rules are broken it asks a question instead of stating a result, and every fact it does state says whether you confirmed it, the rules derived it, or a detector guessed it. A result is never stated on guesses alone. docs/match.md.

What was played, and over time

From the chain, a Tactics panel gives rally lengths and, per side, the shot mix, the points split by who hit last, and the serve; a percentage is shown only above a minimum count, and every figure says how it was known. Saving the project keeps the session, grouped by the player label in the project options, and the Over time panel reads the open clip against the same player's earlier ones -- the tactical figures and the movement figures the coaching module follows -- withholding a trend that rests on one session. docs/tactics.md.

Doubles

With two players tracked on a side, a Formation panel gives the shape the pair holds -- side-by-side, front-and-back, or between -- from the line between the partners, the distance between them, and whether the pair took the right shape after its own shots: side-by-side after a lift, front-and-back after a downward shot. It needs no idea of which partner is which, and no shot is attributed to one partner yet. Nothing here has seen real doubles footage; docs/doubles.md says what has.

Filming guidelines

There are two different set-ups here, and they pull in opposite directions. Side-on is right for joint angles; raised and behind the court is right for footwork. Pick the one that matches the question you are asking.

For joint angles (jumps, lunges, technique):

  • Side-on. Put the camera perpendicular to the direction of the jump so the knees, hips and trunk move in the image plane. Filming from behind or at an angle makes angles meaningless and confuses left and right.
  • Camera at hip to chest height, on a tripod or leaning on something steady, 4 to 5 m back.

For footwork and court position:

  • As high as you can get. Depth error falls off inversely with camera height, and a raised camera also stops the near player hiding the far one. A stand or a balcony beats a tripod by a wide margin.
  • As close as framing allows. Depth error grows with the square of distance while height only helps linearly, so higher-and-closer beats further-back. Back off only as far as it takes to fit the court in.
  • Get both net posts in frame, base and top, so the camera can be located.
  • Expect the far court to be less precise than the near court whatever you do. Mikiri shows you how much less.
  • Headroom above the jump and the whole body in frame throughout, including the feet at the deepest load.
  • 120 fps or more (240 fps if the phone offers it in good light). Takeoff and landing are sub-frame events at 30 fps.
  • Good, even light and a background that contrasts with the athlete. Avoid strong backlight and other people crossing behind.
  • Keep the original file. WhatsApp and similar re-encode and drop frames; Mikiri uses the frame index as its primary key and derives time from the measured frame rate, but nothing recovers dropped frames.

Limitations of single-camera 2D analysis

  • An angle is only correct when the joint's plane of motion is parallel to the camera sensor. Out-of-plane motion (a rotated trunk, a hip that turns in) foreshortens segments and changes the apparent angle; the same jump filmed from a slightly different spot gives different numbers.
  • There is no depth from the image alone. Pixel distances become centimetres through the scale calibration, and only in the plane of the athlete. Court positions are different: the floor is a known plane, so those are metres everywhere, with an error that grows with distance from the camera and is reported rather than assumed.
  • Shot types (smash, clear, drop) are not classified from pose, because they cannot be. See docs/strokes.md.
  • Left and right come from the pose model and can be mirrored when the athlete faces away or turns; use "Swap L/R" and note the camera side.
  • Fast feet blur at 30 to 60 fps; ankle and toe keypoints jitter. The auto events use a smoothed signal and hysteresis and report a confidence, but you should confirm them on the frames.
  • The pose model was trained on people, not on every racket position; hands and feet behind the body are the first things it loses. Fix them by hand.
  • Results are best used comparatively: the same athlete, filmed the same way, over time.

Settings

Settings (gear button) cover the pose backend (rtmlib RTMPose by default, MediaPipe PoseLandmarker as an alternative), the RTMPose size (lightweight / balanced / performance), local model files if you do not want any download, the processing downscale (default longest side 1280 px, so long 240 fps clips stay fast and are never loaded into RAM at once), how often the person detector runs, the confidence threshold below which angles are blanked, and the ONNX Runtime thread count (0 = auto; on CPUs with many cores the auto value is markedly faster than ONNX Runtime's own default). Settings and caches live in %LOCALAPPDATA%\Mikiri (or MIKIRI_HOME).

Settings

Troubleshooting

  • No Python at '"...\python.exe"' when running pip or mikiri inside the venv. The virtual environment's base interpreter moved or is blocked (the shipped .venv was created by uv from a Python living under %APPDATA%\uv, which some corporate policies refuse to execute). Recreate the environment from any normally installed Python 3.11 or newer and reinstall:

    rmdir /s /q .venv && py -3.11 -m venv .venv && .venv\Scripts\activate.bat && pip install -e .

    Replace py -3.11 with C:\Python313\python.exe (or whatever py -0p lists) if 3.11 is not installed.

  • OSError: No such file or directory during pip install with a very long path. Windows still limits paths to 260 characters unless long paths are enabled; keep the project in a short folder such as C:\Users\<you>\Desktop\Badminton App.

  • First tracking run is slow or fails to download models. The two RTMPose files (about 150 MB) come from download.openmmlab.com with a Hugging Face mirror as fallback; on a machine without internet, download them elsewhere and point Settings at the .onnx files.

Command line

mikiri                       # start the app (default port 8765, opens a browser)
mikiri serve --port 9000 --no-browser --open path/to/clip.mp4
mikiri batch path/to/folder --out path/to/exports --no-videos
mikiri make-demo             # regenerate demo/demo.mp4

Development

pip install -e .[dev]
python -m playwright install chromium
ruff check src tests
pytest -m "not slow and not ui"      # fast unit tests
pytest -m ui                          # Playwright smoke test (also refreshes the screenshots)
pytest -m slow                        # real RTMPose pipeline on the demo clip and on the real footage (minutes on CPU)

scripts/real_clip_screenshots.py drives a running server with Playwright on any clip (trim, click the athlete, accept events, screenshot); it produced the real-footage screenshot above. scripts/render_benchmark.py <clip id> renders a benchmark clip with the analysis drawn on it -- skeleton, the top-down court, the bout, the contacts and the split steps -- which is the quickest way to see whether a number is describing what actually happened.

pytest with no arguments runs all three and is the check to run before committing; the UI test rewrites the screenshots in docs/screenshots, so discard that churn unless the interface actually changed.

Layout: src/mikiri (package), src/mikiri/server (FastAPI + static front end), tests, docs, demo (the synthetic clip), benchmarks (real footage and what each clip is for). Every non-obvious choice is written up in DECISIONS.md, and CLAUDE.md is the orientation for anyone (or anything) picking the codebase up cold.

License

MIT, see LICENSE. The pose models are downloaded on first run and come under their own terms.

About

Mikiri: local video analysis for badminton movement, racket and shuttle

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages