CoolFace
Apppublic

Atiya12/Pose-correction-application

sourceHugging Facemitupdated 6d agoView on Hugging Face
0likes
App README

PostureCoach — Online Meeting Pose Monitor

A browser-only computer-vision app that watches your webcam during online meetings and reports what your current framing/pose state isCENTERED IN MEETING, FACE TOO CLOSE, LOOKING AWAY, and so on. No backend, no uploads, no database.

PostureCoach/
├── index.html      markup + MediaPipe imports
├── style.css       visual system
├── script.js       vision pipeline, state machine, dashboard
└── README.md       this file

1. Running it locally

MediaPipe and getUserMedia() both require a secure context, so opening `index.html` by double-clicking will not work (file:// is not secure). Serve the folder:

bash
cd PostureCoach
python3 -m http.server 8000
# then open http://localhost:8000

localhost counts as secure, so the camera prompt will appear. Any static server works (npx serve, VS Code Live Server, etc.).

Click Start camera → allow the permission → click Start meeting session. Sit normally for two seconds while the baseline is measured.


2. Major components

ComponentWhereWhat it does
CONFIGscript.js §1Every threshold in one object. Tune here, not in the logic.
STATES§2Catalogue of the 23 states: display name, user message, tone (colour), and the counter bucket it feeds.
initModels()§6Builds the Pose and FaceDetection solutions and points locateFile at the pinned jsDelivr paths so the .wasm / .tflite assets resolve.
frameLoop()§13A requestAnimationFrame loop that pushes the video element into Pose every frame and into Face Detection on alternate frames. Running our own loop (instead of camera_utils) is what lets startCamera() catch and translate permission errors.
computeMetrics()§7Converts landmarks + face box into ~20 scale-invariant numbers.
smoothed()§7Moving average over the last 6 frames.
maybeCalibrate()§8Records your normal seating geometry as a baseline.
classify()§9Priority cascade returning one state key.
commit()§10Debouncer: a candidate must hold for 320–550 ms before it is displayed.
draw()§11Mirrored video + skeleton + reference lines + face box, tinted by the current state.
renderLive/Session/Alerts§12Dashboard updates. Session counters run on a 500 ms timer, independent of frame rate.

3. Landmark detection

MediaPipe Pose returns 33 body landmarks per frame as {x, y, z, visibility}, where x/y are normalized to the image (0 = left/top, 1 = right/bottom) and visibility is the model's confidence that the point is actually observable. The app uses:

IndexLandmarkUsed for
0noseneck angle, head height, yaw estimate
2 / 5left / right eyehead roll (tilt)
7 / 8left / right earhead yaw (turn)
11 / 12left / right shouldershoulder line, torso, framing, scale
13 / 14left / right elbowshoulder joint angles
23 / 24left / right hiptorso angle

MediaPipe Face Detection (short-range model, tuned for arm's-length webcam use) returns a bounding box per face as {xCenter, yCenter, width, height} plus a score. Pose is single-person; Face Detection genuinely returns multiple boxes, which is the only reason MULTIPLE PEOPLE DETECTED is honest — it is reported from the face pipeline alone.

Left and right always mean your left and right, matching MediaPipe's naming. The preview is mirrored for comfort, so your left appears on the right of the screen.


4. Angle calculations

All 2D vector geometry on normalized coordinates.

`angle(A, B, C)` — the angle formed at B:

v1 = A − B
v2 = C − B
θ  = acos( (v1 · v2) / (|v1| · |v2|) ) × 180/π

Used for the shoulder joint angles: leftShoulderAngle = angle(rightShoulder, leftShoulder, leftElbow).

Vertical deviation — how far a segment departs from upright, = perfectly vertical:

θ = |atan2(Bx − Ax, −(By − Ay))| × 180/π

(The y term is negated because image y grows downward.) Used for:

  • Neck angle = deviation of shoulder midpoint → nose
  • Torso angle = deviation of hip midpoint → shoulder midpoint

Horizontal tilt — signed departure from level:

θ = atan2(By − Ay, Bx − Ax) × 180/π

Used for shoulder tilt (right shoulder → left shoulder) and head roll (right eye → left eye). Positive means the left side sits lower in the image.

Head-height ratio — nose height above the shoulder line, divided by shoulder width:

headGapRatio = (shoulderMidY − noseY) / shoulderWidth

Dividing by shoulder width makes it scale-invariant: it stays roughly constant whether you sit near or far, so HEAD TOO LOW / HEAD TOO HIGH don't fire just because you moved.

Head-turn ratio (yaw estimate) — where the nose falls between the two ears:

dL = |noseX − leftEarX|
dR = |noseX − rightEarX|
turn = (dR − dL) / (dR + dL)     // +1 ≈ fully turned to your left

If only one ear is visible at all, that alone implies a large rotation and the ratio is clamped to ±0.7.


5. State classification

23 states, listed in STATES. Each carries a tone that drives the colour:

ToneColourMeaning
centeredgreenwell framed
infoblueinformational (analyzing, paused)
adjustyellowa small adjustment would help
warnorangeframing needs attention
problemreddetection/camera problem

The words "good posture" and "bad posture" appear nowhere in the UI, the code, or the comments.

Priority cascade

classify() returns on the first match, so a detection failure is never dressed up as a posture observation:

ANALYZING (first 1.4 s)
   ↓
CAMERA VIEW BLOCKED
   ↓
FACE NOT FOUND  /  LOW VISIBILITY
   ↓
MULTIPLE PEOPLE DETECTED
   ↓
LOW VISIBILITY (landmark confidence)
   ↓
FACE TOO CLOSE  /  FACE TOO FAR
   ↓
BODY OUT OF FRAME  /  CAMERA POSITIONING NEEDED
   ↓
NOT IN MEETING POSITION
   ↓
LOOKING AWAY  →  HEAD TURNED L/R
   ↓
LEANING FORWARD / BACK / LEFT / RIGHT
   ↓
HEAD TOO LOW / HIGH  →  HEAD TILTED L/R
   ↓
SHOULDERS MISALIGNED
   ↓
CENTERED IN MEETING

FACE TOO FAR sits below the face check and above everything positional: if a face is detected but tiny, it is FACE TOO FAR, never FACE NOT FOUND.

Face Too Close detection

Three independent signals, any of which triggers it:

  1. 1.Box widthfaceWidth > 0.42 of the frame width.
  2. 2.Box areafaceWidth × faceHeight > 0.17 of the frame. Catches faces that are wide but cropped short, e.g. only the eye-and-nose region filling the view.
  3. 3.Edge overflow — the box extends past the left or right edge (cx ± w/2 outside [-0.03, 1.03]) while already being large. A face spilling out of frame at normal size is a framing problem, not a distance problem, so the size condition prevents a misfire.

Calibration baseline

Leaning forward/back is a relative judgement — it depends on where your chair normally is. During the first 45 qualifying frames of a session the app records the median face width, shoulder width, shoulder midpoint and head-gap ratio. After that:

  • faceWidth / baseline > 1.16 (or the same for shoulder width) → LEANING FORWARD
  • < 0.86LEANING BACK
  • shoulder midpoint drifting > 0.085 from baseline → LEANING LEFT / LEANING RIGHT

Before the baseline exists, forward/back is skipped entirely and side lean falls back to drift from frame centre. Recalibrate clears it; moving your chair or camera is a good reason to press it.

Camera-blocked detection

Every 400 ms the frame is drawn into a 32×24 offscreen canvas and the luminance mean and standard deviation are computed (Y = 0.2126R + 0.7152G + 0.0722B). A feed that is both dark (mean < 42) and flat (σ < 9) is a covered lens rather than a dim room, since a dim room still has structure.


6. Smoothing and debouncing

Three separate mechanisms stop the readout flickering:

  1. 1.Moving average — metrics are averaged over the last 6 frames before classification. Boolean metrics use a majority vote over the same window.
  2. 2.Confirmation windowcommit() holds each candidate state and only displays it once it has persisted for 550 ms (320 ms for critical states like FACE NOT FOUND, so genuine failures still surface fast). A single noisy frame cannot flip the display.
  3. 3.Alert cooldown — the same state cannot post a second alert within 3 s, so a borderline reading can't spam the log.

Plus the `ANALYZING…` window: for the first 1.4 s after starting the camera or a session, no pose state is assigned at all. This is what stops a nonsense classification from the first few frames.


7. Deploying to a static Hugging Face Space

The app needs no Python, Flask, FastAPI, Node.js or database — just three static files.

  1. 1.Go to huggingface.co → sign in → NewSpace.
  2. 2.Give it a name, e.g. posturecoach.
  3. 3.Under Select the Space SDK, choose Static. (Not Gradio, not Streamlit, not Docker.)
  4. 4.Choose Public or Private, then Create Space.
  5. 5.Open the Files tab → Add fileUpload files.
  6. 6.Upload index.html, style.css and script.js into the repository root — not a subfolder. index.html must be at the top level or the Space will show a blank page.
  7. 7.Add a commit message and Commit changes to main. The Space rebuilds in a few seconds.
  8. 8.Open the Space. Hugging Face serves it over HTTPS, so getUserMedia() works.
  9. 9.Click Start camera and accept the browser's camera prompt.

If the camera prompt never appears inside the embedded preview, open the Space in its own tab using the ⋮ → Embed this Space direct URL (https://<user>-<space>.hf.space). Embedded iframes sometimes withhold camera permission; the direct URL does not.

The README is required

Every Space needs a README.md in the repository root whose very first line is `---`, opening a YAML block that declares the SDK. Without it the Space fails to build with "Configuration error — missing configuration in README". This file already carries it:

yaml
---
title: PostureCoach
emoji: 🎥
colorFrom: green
colorTo: blue
sdk: static
app_file: index.html
pinned: false
license: mit
---

Upload this README.md alongside the three code files. If you created the Space through the web form, Hugging Face already generated a README.md — overwrite it, or paste the block above at the top of the existing one.

Rules that trip people up:

  • The --- must be on line 1. A blank line, a heading, or a UTF-8 BOM above it breaks parsing.
  • Use plain spaces for indentation, never tabs.
  • colorFrom / colorTo accept only: red, yellow, green, blue, indigo, purple, pink, gray.
  • sdk: static must match the SDK chosen when the Space was created. If you picked Gradio or Docker by mistake, change it in Settings → Space SDK as well — a mismatch keeps the error alive.
  • Commit the README to the main branch; the Space rebuilds automatically within a few seconds.

8. Testing guide

Start the camera, start a session, and let the baseline settle before running these. Each test lists how to reproduce it, the expected state name, message, and dashboard behaviour.

#TestHow to reproduceExpected stateExpected messageDashboard
1Face not foundStep fully out of frame, or cover your face with a bookFACE NOT FOUNDPlease position your face inside the camera frame.Red; Face distance → "No face detected"; face alert counter +1
2Centered in meetingSit upright, face centred, shoulders visibleCENTERED IN MEETINGYou are well positioned in the camera frame.Green; all rows OK; Meeting Position % climbs
3Not in meeting positionSlide your chair far to one side so your face sits near a frame edgeNOT IN MEETING POSITIONPlease sit in front of the camera and adjust your position.Orange; Camera framing → "Needs adjustment"
4Face too closeLean in until your face fills roughly half the widthFACE TOO CLOSEMove slightly away from the camera.Orange; Face distance → "Too close"; face alert +1
5Face too farSit back 2–3 m from the webcamFACE TOO FARMove a little closer to the camera.Orange; Face distance → "Too far"; not Face Not Found
6Leaning forwardFrom your calibrated position, lean ~20 cm toward the cameraLEANING FORWARDYou are leaning toward the camera.Yellow; position alert +1
7Leaning backLean well back in the chairLEANING BACKYou are leaning back from the camera.Yellow
8Leaning leftShift your torso toward your left without turningLEANING LEFTAdjust your sitting position toward the center.Yellow
9Leaning rightShift your torso toward your rightLEANING RIGHTAdjust your sitting position toward the center.Yellow
10Head tilted leftDrop your left ear toward your left shoulder (>11°)HEAD TILTED LEFTYour head is tilted to the left.Yellow; Head position → "Tilted left"
11Head tilted rightMirror of test 10HEAD TILTED RIGHTYour head is tilted to the right.Yellow
12Head turned leftRotate your head ~35° to your left, torso stillHEAD TURNED LEFTYour head is turned away from the camera.Yellow; Head position → "Turned left"
13Head turned rightMirror of test 12HEAD TURNED RIGHTYour head is turned away from the camera.Yellow
14Looking awayTurn your head close to profile (~60°+)LOOKING AWAYYour head is oriented away from the camera.Yellow; outranks Head Turned
15Shoulders misalignedDrop one shoulder clearly lower than the other (>9°)SHOULDERS MISALIGNEDTry to keep your shoulders more level.Yellow; Shoulder position → "Uneven"; shoulder tilt angle rises
16Head too lowSlump so your head drops toward the shoulder lineHEAD TOO LOWRaise your head and look toward the screen.Yellow; neck angle changes
17Body out of frameMove so one shoulder leaves the frame while your face stays inBODY OUT OF FRAMEMove back into the camera frame.Red
18Camera positioning neededTilt the webcam up so only your head is in view, shoulders croppedCAMERA POSITIONING NEEDEDAdjust your camera so your face and upper body are visible.Orange
19Low visibilityTurn the room lights right down, or backlight yourself heavilyLOW VISIBILITYImprove lighting or move into the camera frame.Orange; Body visibility → "Low"
20Camera blockedCover the lens completely with your thumb or a stickerCAMERA VIEW BLOCKEDCheck whether your camera is covered or obstructed.Red; outranks everything
21Multiple peopleHave a second person lean into the frameMULTIPLE PEOPLE DETECTEDPlease ensure only the meeting participant is in the camera view.Orange; People in frame → 2
22AnalyzingStart the camera, or press Start meeting sessionANALYZING…Analyzing your meeting position.Blue for ~1.4 s, then a real state
23Camera permission deniedBlock the camera in site settings, then Start camera(stays) CAMERA OFFRed notice under the controls explaining how to re-allow it

Debounce check: wave a hand briefly across your face. The state should not flip — the movement is shorter than the 550 ms confirmation window.

Privacy check: open DevTools → Network, run a full session. After the initial MediaPipe model files load from jsDelivr, no further requests should appear. Nothing is sent anywhere.


9. Troubleshooting

SymptomCauseFix
Blank viewfinder, nothing happensOpened via file://Serve over http://localhost or HTTPS
"The vision models could not be loaded"jsDelivr unreachable, or an offline machineCheck the connection; a corporate proxy may block the CDN
Camera prompt never appearsPage isn't a secure context, or an iframe withheld permissionUse HTTPS/localhost; open the HF Space in its own tab
"The camera is in use by another app"Zoom/Teams/Meet already holds the deviceClose the other app, then Start camera
Very low fpsmodelComplexity: 1 on a weak machineSet modelComplexity: 0 in initModels()
Stuck on CAMERA POSITIONING NEEDEDShoulders are genuinely outside the frameTilt the webcam down or sit back until both shoulders are visible
Leaning states fire constantlyBaseline was captured mid-leanPress Recalibrate while sitting normally
Lean left/right feels invertedThe preview is mirrored; labels are anatomicalThis is intended — "left" is your left
States flicker anywayPoor lighting keeps confidence near the thresholdAdd front lighting, or raise confirmMsDefault in CONFIG

10. Scope and honesty

This is a camera-framing demo, not a medical, psychological or behavioural assessment. It does not measure eye gaze, attention, engagement, mental state, health or professionalism.

LOOKING AWAY is an estimate of head orientation derived from where the nose falls between the two ears — no eye-gaze model is implemented, and none is claimed. Meeting Position Percentage is defined as:

Meeting Position % = (Centered in Meeting frames / Total valid frames) × 100

It describes how much of the session you spent well framed for the camera. It is not a posture score.

All processing happens locally in your browser. Webcam video, face images, pose landmarks and derived numbers are never uploaded, and nothing is written to storage.