Overview
A Python backend that turns a webcam stream into a stable numeric prediction. MediaPipe's Hand Landmarker locates 21 landmarks on a single hand, those coordinates become a 63-value feature vector, a scikit-learn model classifies the sign, and a mode-based smoothing window converts noisy per-frame output into a prediction that is steady enough to act on. Both the raw and the stabilised prediction are exposed, so the consumer can choose responsiveness or reliability.
- Landmarks per hand
- 21
- Feature vector length
- 6321 landmarks × 3 coordinates
- Smoothing window
- 10frames, mode-based
Problem
Per-frame classification of a live video stream is unusable straight out of the model. A hand in motion, a moment of partial occlusion, or a single blurred frame produces a prediction that flickers between neighbouring classes several times a second.
The recognition problem was largely solved by the landmark model. The engineering problem was turning a sequence of individually plausible guesses into one answer a consumer can act on.
Constraints
- Must run at interactive frame rates on ordinary consumer hardware, with no GPU assumed
- One hand only, which bounds the feature vector and keeps the model small
- Ten classes, signs 1 through 10
- Latency budget tight enough that the output still feels like a response to the user's gesture
Role and contribution
Software Engineer — computer vision and backend
- Built the capture and detection pipeline on OpenCV and MediaPipe
- Designed the landmark-to-feature-vector transformation
- Trained and persisted the sign classifier
- Implemented the mode-based smoothing window and the raw/stable output split
- Structured the backend into replaceable modules per pipeline stage
System design
Architecture
Inference pipeline
A webcam frame is captured with OpenCV and passed to the MediaPipe Hand Landmarker, which returns 21 landmarks for a single detected hand. Those landmarks are flattened into a 63-value feature vector and classified by a scikit-learn model loaded from disk with joblib, producing a raw per-frame prediction. The raw prediction enters a 10-frame window whose mode becomes the stable prediction. The Flask API exposes both the raw and the stable value.
- Vision
- OpenCV
- MediaPipe Tasks Hand Landmarker
- Model
- scikit-learn
- joblib
- Service
- Python 3.11
- Flask
From landmarks to features
The Hand Landmarker returns 21 points for a detected hand, each with three coordinates. Flattened, that is a fixed 63-value vector per frame — small enough for a classical model to classify in well under the frame budget, and stable enough in structure that no per-frame reshaping is needed.
Using landmarks rather than raw pixels is what makes the model this cheap. The expensive perception work is done by the landmarker; the classifier only has to separate ten shapes in a low-dimensional space.
Raw versus stabilised prediction
The raw prediction is the classifier's opinion about the current frame. It is immediate and it flickers. The stable prediction is the mode of the last ten raw predictions: a single frame of noise cannot move it, but a genuine change of sign moves it once the majority of the window agrees.
Both values are returned. A consumer that wants responsiveness — a live debug overlay, for example — can read the raw value; a consumer that will take an action reads the stable one. Choosing for the consumer would have been the wrong call, because the right trade-off depends on what the prediction is used for.
Modular structure
Each stage — capture, detection, feature extraction, classification, smoothing, API — is a separate module with a narrow interface. The practical benefit showed up during tuning: the smoothing window could be changed, and the classifier swapped, without touching capture or detection.
Known limitation: no wrist-relative normalisation
The current version feeds landmark coordinates to the classifier without first translating them relative to the wrist. The model therefore sees a sign made at the left of the frame as different from the same sign made at the right, and has to spend capacity learning that they are equivalent.
This is a real weakness, not a stylistic one: it makes accuracy depend on where the hand sits in frame and on distance from the camera. The fix is to translate all landmarks into a wrist-anchored frame of reference and scale by hand size before classification, which should improve generalisation without changing the pipeline's shape.
Key decisions and trade-offs
Decision
Stabilise output with a mode-based smoothing window
- Context
- Frame-by-frame classifications fluctuate, even when the hand is holding one sign.
- Alternatives considered
- Return every raw prediction directly
- Require N identical consecutive frames before emitting a change
- Average class probabilities across frames
- Chosen approach
- The mode of a rolling 10-frame window, exposed alongside the raw prediction.
- Reason
- The mode is robust to isolated outliers without demanding a run of perfect frames, and it needs no probability calibration from the classifier.
- Trade-off
- Output lags a genuine change of sign by roughly half a window. Exposing the raw value as well means a consumer that cannot afford that delay is not forced to accept it.
- Outcome
- Isolated misclassified frames no longer reach consumers of the stable prediction.
Decision
Classify landmarks rather than raw image pixels
- Context
- The alternative is a convolutional model trained on hand images.
- Alternative considered
- A CNN over cropped hand regions
- Chosen approach
- MediaPipe landmarks flattened into a 63-value vector, classified by a scikit-learn model.
- Reason
- Landmarks carry the geometry the task actually depends on and discard lighting, background and skin tone. The result runs at interactive rates on a CPU and trains on a small dataset.
- Trade-off
- Total dependence on the landmarker: if it fails to detect a hand, there is no fallback path to a prediction.
Result
- A working real-time pipeline from webcam to stabilised prediction on ordinary CPU hardware
- Per-frame flicker eliminated from the stable output without a perceptible loss of responsiveness
- A modular backend where the model and the smoothing strategy can be replaced independently
Lessons learned
- The model was the easy part. Making its output usable over time was the actual engineering.
- Exposing both the raw and the smoothed value avoided guessing which trade-off the consumer needs.
- Choosing a good intermediate representation — landmarks over pixels — bought more than any amount of model tuning would have.
- Skipping wrist-relative normalisation was a shortcut that quietly costs accuracy; the fix belongs in the feature stage, not the model.
Next improvements
- Translate landmarks relative to the wrist and scale by hand size before classification
- Evaluate accuracy across hand positions and distances to quantify the current normalisation gap
- Handle the no-hand-detected case as an explicit class rather than an absence of prediction
- Extend beyond a single hand and beyond ten static signs