Skip to main content

Overview

Posture detection is powered by TensorFlow.js and the MoveNet model, which detects human body keypoints in real-time from webcam video. The system tracks the user’s right eye position and compares it to a baseline “good posture” position.

MoveNet Model

Model Configuration

The extension uses the SINGLEPOSE_LIGHTNING variant of MoveNet for fast, real-time detection:
const detectorConfig = {
  modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING,
};

detector = await poseDetection.createDetector(
  poseDetection.SupportedModels.MoveNet,
  detectorConfig
);
SINGLEPOSE_LIGHTNING is optimized for speed over accuracy, making it ideal for real-time browser-based pose detection. It detects 17 keypoints including eyes, nose, shoulders, elbows, wrists, hips, knees, and ankles.

Loading the Model

The model is loaded when the Options component mounts:
const loadMoveNet = async () => {
  const detectorConfig = {
    modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING,
  };
  detector = await poseDetection.createDetector(
    poseDetection.SupportedModels.MoveNet,
    detectorConfig
  );

  // Start detection loop at 100ms intervals
  setInterval(() => {
    return detect(detector);
  }, DETECTION_RATE);
};
Key Parameters:
  • DETECTION_RATE: 100ms (10 detections per second)
  • Model loads asynchronously on component mount
  • Detection loop runs continuously while page is open

Detection Pipeline

The detect() Function

The core detection function runs every 100ms and orchestrates the entire detection pipeline:
const detect = async (model: { estimatePoses: (arg0: any) => any }) => {
  if (
    typeof camRef.current !== 'undefined' &&
    camRef.current !== null &&
    camRef.current.video.readyState === 4
  ) {
    // Get video properties
    const video = camRef.current.video;
    const videoWidth = camRef.current.video.videoWidth;
    const videoHeight = camRef.current.video.videoHeight;

    // Set video dimensions
    camRef.current.video.width = videoWidth;
    camRef.current.video.height = videoHeight;

    // Estimate poses from video frame
    const poses = await model.estimatePoses(video);

    // Validate pose data
    if (
      !poses ||
      !poses[0] ||
      !poses[0].keypoints ||
      poses[0].keypoints.length < 3
    )
      return;

    // Process pose and draw visualization
    handlePose(poses);
    drawCanvas(
      poses,
      video,
      videoWidth,
      videoHeight,
      canvasRef,
      GOOD_POSTURE_POSITION.current
    );
  }
};
Pipeline Steps:
  1. Verify video is ready (readyState === 4)
  2. Extract video dimensions
  3. Run pose estimation on current video frame
  4. Validate that keypoints were detected
  5. Process pose data to determine posture quality
  6. Draw visual feedback on canvas overlay
readyState === 4 means the video has enough data to play. The ready states are:
  • 0: HAVE_NOTHING - no information
  • 1: HAVE_METADATA - duration and dimensions known
  • 2: HAVE_CURRENT_DATA - current frame available
  • 3: HAVE_FUTURE_DATA - enough data to play a little
  • 4: HAVE_ENOUGH_DATA - enough data to play through

The handlePose() Function

Analyzes detected keypoints and determines if posture is good or bad:
const handlePose = async (poses: { keypoints: { y: number }[] }[]) => {
  try {
    // Track right eye position (keypoint index 2)
    let rightEyePosition = poses[0].keypoints[2].y;
    currentPosturePosition.current = rightEyePosition;

    if (!rightEyePosition) return;

    // Set baseline on first detection
    if (GOOD_POSTURE_POSITION.current == null) {
      handlePosture({ baseline: currentPosturePosition.current });
    }

    // Check if current position exceeds deviation threshold
    if (
      Math.abs(
        currentPosturePosition.current - GOOD_POSTURE_POSITION.current
      ) > GOOD_POSTURE_DEVIATION.current
    ) {
      handlePosture({ posture: 'bad' });
    }

    // Within acceptable range
    if (
      Math.abs(
        currentPosturePosition.current - GOOD_POSTURE_POSITION.current
      ) < GOOD_POSTURE_DEVIATION.current
    ) {
      handlePosture({ posture: 'good' });
    }
  } catch (error) {
    console.error(error);
  }
};
Detection Logic:
  • Uses keypoint index 2 (right eye) for posture tracking
  • First detection sets the baseline “good posture” position
  • Calculates absolute difference between current and baseline position
  • Default deviation threshold: 25 pixels
  • Sends posture status (‘good’ or ‘bad’) to background script
The right eye was chosen as the tracking point because it’s reliably detected by MoveNet and provides a stable reference for head position. As users slouch, their head typically moves down, increasing the Y-coordinate of the eye position.

Posture Parameters

Baseline Position

let GOOD_POSTURE_POSITION = useRef<any>(null);
  • Stores the Y-coordinate of the right eye when tracking starts
  • Set automatically on first detection
  • Can be reset by clicking “Reset Posture” button
  • Persists until manually reset or page reload

Deviation Threshold

let GOOD_POSTURE_DEVIATION = useRef(25);
  • Default: 25 pixels
  • Configurable via Popup component
  • Determines how much vertical movement is tolerated
  • Lower values = stricter posture requirements

Detection Rate

const DETECTION_RATE = 100; // milliseconds
  • Pose detection runs every 100ms (10 FPS)
  • Balances responsiveness with performance
  • Constant, not configurable by users

Drawing Utilities

Canvas Rendering

The draw_utils.ts module provides functions to visualize pose detection on a canvas overlay:
Main drawing function that orchestrates all visual elements:
export const drawCanvas = (
  poses: { keypoints: any }[],
  video: any,
  videoWidth: any,
  videoHeight: any,
  canvas: any,
  goodPostureBaseLine: any
) => {
  if (canvas.current == null) return;
  const ctx = canvas.current.getContext('2d');

  canvas.current.width = videoWidth;
  canvas.current.height = videoHeight;

  if (poses[0].keypoints != null) {
    drawKeypoints(poses[0].keypoints, ctx, goodPostureBaseLine);
    drawGoodPostureHeight(poses[0].keypoints, ctx, goodPostureBaseLine);
  }
};
Calls:
  1. drawKeypoints() - Draw detected body points
  2. drawGoodPostureHeight() - Draw baseline and current position lines

MoveNet Keypoints

MoveNet detects 17 keypoints with indices:
0: nose
1: left_eye
2: right_eye          ← Used for posture tracking
3: left_ear
4: right_ear
5: left_shoulder
6: right_shoulder
7: left_elbow
8: right_elbow
9: left_wrist
10: right_wrist
11: left_hip
12: right_hip
13: left_knee
14: right_knee
15: left_ankle
16: right_ankle
Each keypoint includes x, y coordinates and a score (confidence value 0-1). Only keypoints with score >= 0.3 are considered reliable and drawn on the canvas.

Performance Considerations

  • Model Size: SINGLEPOSE_LIGHTNING is lightweight (~3MB)
  • Detection Speed: ~100ms per frame on modern hardware
  • GPU Acceleration: Uses WebGL backend for TensorFlow.js
  • Resource Usage: Minimal CPU when camera is off
  • Battery Impact: Moderate when tracking is active
To improve performance:
  • Use lower resolution webcam feed
  • Increase DETECTION_RATE interval (e.g., 200ms instead of 100ms)
  • Disable canvas drawing when not needed
  • Use SINGLEPOSE_LIGHTNING instead of SINGLEPOSE_THUNDER model

Build docs developers (and LLMs) love