You can feel a squat, but you can't see one. Depth feels deeper than it is, and fatigue quietly turns a controlled descent into a drop. A trainer would catch it — but nobody has a trainer watching every set in the living room. And pointing a camera at yourself, inside your home, creates an obvious privacy problem if that video goes anywhere.
FormWild runs the entire pipeline — capture, pose estimation, rep detection, coaching — on your own machine, and tells you the one thing worth hearing about each rep as it happens: "Go deeper — 103°, aim for 100°." "Chest up." "Slow the descent."
What it doesStand side-on to your webcam and squat. FormWild tracks 17 body keypoints in real time, draws the skeleton over the live camera, counts reps with a hysteresis state machine (a mid-rep pause is not a phantom rep), and scores every rep on three things a single 2D camera can measure honestly: depth (knee angle vs. a 100° target), tempo (rushed descents get called), and torso angle (chest up). A depth gauge tracks the current rep against the target line while it is still happening; a beep marks each counted rep so you never need to look at the screen mid-set. Sessions export to a plain CSV on your own disk.
Then I tested it the honest way: standing in front of it. Across three live sessions, the counter matched reality every time (14/14, 5/5, 8/8), and the reps that earned the "go deeper" cue were exactly the deliberately shallow ones.
Pose inference runs locally, in-process (ONNX Runtime on the CPU — MoveNet SinglePose Lightning, a 9 MB model downloaded once from a public repo with no account). No frame is ever written to disk; nothing is uploaded anywhere. The repository's .gitignore refuses *.png outright so a captured webcam frame cannot even be committed by accident. Session history is a CSV you can open in any spreadsheet and delete whenever you want. For a camera pointed at someone's home, this is the whole point.
Honest engineering: what it refuses to measureThe most important design decision was removal. An early build scored knee valgus (knees caving inward) and left/right asymmetry — and a test caught it firing on a rep with perfectly straight legs. The cause is geometric, not a bug: knee depth is only measurable in profile, valgus only from the front, and one 2D camera cannot be in both places. So v1 measures three things properly and documents why the others are absent. The skeleton renderer follows the same rule: it never draws a bone from an unreliable keypoint, and joint opacity carries confidence, so a marginal detection looks marginal instead of authoritative.
Modern Java 26, end to endA virtual thread runs the capture → inference → analysis pipeline and publishes an immutable render state to a Swing repaint timer — no locks, no torn frames. The FFM (Foreign Function & Memory) API memory-maps the ONNX model. Gatherers.windowSliding does median smoothing over the keypoint stream — median, not mean, because rep detection is about when the angle reversed, and a mean drags one mistracked frame into its neighbours. Records model every immutable value; sealed types with exhaustive pattern-matching switches model the analysis outcomes. Measured: ~30 fps with 8 ms mean inference on a plain laptop CPU — the camera is the bottleneck, not the model. 24 automated tests run without a webcam, and a --diagnose mode proves the pipeline headless, because a GUI cannot be verified over SSH.
Reproducibility is built in: the pom.xml explicitly targets Java 26, and the committed build.log records a successful Java 26 build.
Recreate it1. Install a JDK 26 (java --version should say 26) and Apache Maven 3.9+.
2. Clone github.com/darcy0408/formwild.
3. Run scripts\fetch-model.ps1 once — it downloads the 9 MB MoveNet pose model. No account needed.
4. Build with mvn clean package — the 24 tests run here; none need a webcam.
5. Start coaching: run.cmd --coach. Stand side-on, about 2 m back, whole body in frame, room lit. (Or run.cmd --diagnose 5 for a headless throughput check.) Full details in the README.
What's nextA yoga mode is closer than it looks — the same pipeline feeds a pose library instead of a rep detector. And the 100° depth target is one constant in SquatAnalyzer.java if your coach should be kinder.








Comments