Automatic exercise repetition counter from video — prototype
Automatic exercise repetition counter from video/camera. Detects repeating motion patterns without a predefined exercise list. Patterns are learned on the fly and persist between sessions.
You point a camera at a person exercising. The system:
Each unique movement becomes a pattern. Patterns are saved to a database — next time you do the same exercise, it’s recognized immediately.
Camera frame
↓
MediaPipe detects 33 body landmarks (x, y, z + visibility)
↓
System tracks y-coordinate of each major joint over a 2-second sliding window
↓
Finds the 5 joints that move the most (dominant joints)
↓
Builds a composite signal by averaging those joints, then smooths it
↓
Autocorrelation finds the repetition period (how many frames per cycle)
↓
Extracts one cycle, normalizes to 0..1
↓
Compares against known patterns using DTW (Dynamic Time Warping)
↓
Match found? → Count rep with Schmitt trigger
No match? → After 0.5 sec delay, save as new pattern
Two movements are considered different exercises if:
So arm raises and squats will always be separate patterns, even if they look vaguely similar in signal shape.
A pattern captures:
The signature evolves slightly over time (80% old + 20% new on each match) to adapt to natural variation. But the dominant joints are locked — this prevents different exercises from merging into one pattern.
The composite signal oscillates between 0 and 1 for each rep. A Schmitt trigger with two thresholds prevents double-counting:
When exercises appear in a repeating sequence, that’s a routine.
Example: arm raises → squats → bends → arm raises → squats → bends = routine [#1 → #2 → #3], 2 sets.
A routine is detected after ≥2 complete passes through the same sequence of exercises.
Two independent processes communicate via SQLite (WAL mode):
┌──────────────┐ ┌──────────────┐
│ Writer │ │ Analyzer │
│ │ │ │
│ Camera/Video │ │ C++ core: │
│ → MediaPipe │ SQLite │ signal math │
│ → landmarks │────────→│ DTW matching │
│ → DB write │ │ rep counting │
│ │ │ → events DB │
└──────────────┘ └──────────────┘
│
↓
┌──────────────────┐
│ Web Dashboard │
│ │
│ FastAPI + WS │
│ Stick figure │
│ Exercise cards │
│ Timeline chart │
└──────────────────┘
Why this split:
PoseDetector is a Python Protocol. MediaPipe is one implementation. On iOS, you’d swap it for Apple Vision framework. The C++ core doesn’t care where landmarks come from.
# Install (builds C++ extension automatically)
pip install -e ".[web]"
# Web dashboard — auto-detects camera
python -m demo.web.server
# → http://localhost:8000
# Web dashboard with specific camera
python -m demo.web.server --camera 0
# Web dashboard with video file
python -m demo.web.server path/to/video.mp4
# CLI mode
python -m demo.cli --camera 0
python -m demo.cli path/to/video.mp4
| Term | Meaning | Example |
|---|---|---|
| Pattern | One type of detected movement | Arm raises, squats |
| Rep | One repetition within a pattern | One arm raise up-and-down |
| Count | Total reps for a pattern | 12 arm raises = count 12 |
| Routine | Ordered sequence of patterns that repeats | [arm raises → squats → bends] |
| Set | One full pass through a routine | Did all 3 exercises once = 1 set |
Key parameters in AnalyzerConfig (see Tuning Guide):
| Parameter | Default | What It Does |
|---|---|---|
window_frames |
60 | Sliding window size (~2 sec at 30fps) |
min_period / max_period |
10 / 60 | Allowed rep cycle range in frames |
period_strength |
0.3 | Autocorrelation threshold — lower = more sensitive |
dtw_threshold |
0.8 | Max DTW distance for pattern match — lower = stricter |
counter_up / counter_down |
0.7 / 0.3 | Schmitt trigger thresholds |
smooth_window |
5 | Signal smoothing window |
exercises-counter/
├── cpp/ # C++ core (pure math, no dependencies)
│ ├── include/exco/ # Headers: geometry, signal, pattern, counter
│ ├── src/ # Implementation
│ ├── bindings/ # pybind11 Python bindings
│ └── tests/ # doctest unit tests
├── python/exco/ # Python layer
│ ├── pose/ # Pose detection (MediaPipe backend)
│ ├── db.py # SQLite read/write
│ ├── writer.py # Video → pose → DB
│ ├── analyzer.py # DB → C++ core → events
│ └── routine.py # Routine detection
├── demo/
│ ├── cli.py # CLI demo
│ └── web/ # FastAPI web dashboard
└── docs/ # Architecture, tuning, specs