Improve gesture recognition robustness and latency - #9
zayan-sheikh wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Summary
This PR introduces significant improvements to gesture recognition accuracy (+7.95 pp) and latency (66.7% faster) through pose normalization, weighted features, and inverse-distance voting. The comprehensive benchmark demonstrates robustness against scale and noise variations.
Critical Issues
Two numerical stability defects must be fixed before merge:
- Numerical overflow in inverse-distance voting (line 39): Perfect matches produce vote weights of ~1M, causing instability
- Division by zero in shoulder scaling (line 50-56): Missing or corrupted shoulder landmarks cause classification failure
Both issues will cause runtime crashes or incorrect predictions in production when processing real-world landmark data.
Verification Required
After addressing the critical issues, please re-run:
npm testto ensure all tests passnpm run benchmark:recognitionto verify accuracy improvements are maintained
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
| const votes = new Map(model.classes.map((label) => [label, 0])); | ||
| for (const neighbor of nearest) { | ||
| votes.set(neighbor.label, votes.get(neighbor.label) + 1); | ||
| const voteWeight = 1 / (Math.sqrt(neighbor.distance) + 1e-6); |
There was a problem hiding this comment.
🛑 Numerical Instability Risk: When neighbor.distance equals 0 (perfect match), the vote weight becomes 1/1e-6 ≈ 1,000,000, which can dominate all other votes and cause numerical overflow in vote tallies. Replace with safer inverse-square weighting or cap the maximum vote weight.
| const voteWeight = 1 / (Math.sqrt(neighbor.distance) + 1e-6); | |
| const voteWeight = 1 / (neighbor.distance + 1e-6); |
| function getShoulderScale(features) { | ||
| const leftShoulder = 11 * 3; | ||
| const rightShoulder = 12 * 3; | ||
| const dx = features[leftShoulder] - features[rightShoulder]; | ||
| const dy = features[leftShoulder + 1] - features[rightShoulder + 1]; | ||
| const dz = features[leftShoulder + 2] - features[rightShoulder + 2]; | ||
| return Math.max(Math.hypot(dx, dy, dz), 1e-6); |
There was a problem hiding this comment.
🛑 Division by Zero Risk: If both shoulders have identical coordinates (corrupted/missing landmarks), Math.hypot(dx, dy, dz) returns 0, making queryScale and sampleScale equal to 1e-6. This causes division operations in line 29 to produce extremely large values (~1e5 scale) that distort the distance calculation and break classification.
| function getShoulderScale(features) { | |
| const leftShoulder = 11 * 3; | |
| const rightShoulder = 12 * 3; | |
| const dx = features[leftShoulder] - features[rightShoulder]; | |
| const dy = features[leftShoulder + 1] - features[rightShoulder + 1]; | |
| const dz = features[leftShoulder + 2] - features[rightShoulder + 2]; | |
| return Math.max(Math.hypot(dx, dy, dz), 1e-6); | |
| function getShoulderScale(features) { | |
| const leftShoulder = 11 * 3; | |
| const rightShoulder = 12 * 3; | |
| const dx = features[leftShoulder] - features[rightShoulder]; | |
| const dy = features[leftShoulder + 1] - features[rightShoulder + 1]; | |
| const dz = features[leftShoulder + 2] - features[rightShoulder + 2]; | |
| const distance = Math.hypot(dx, dy, dz); | |
| if (distance < 1e-3) { | |
| throw new Error('Invalid pose: shoulder landmarks are too close or identical'); | |
| } | |
| return distance; | |
| } |
|
Addressed the numerical-stability review feedback in
|
What changed
npm run benchmark:recognitionMeasured result
The benchmark uses seed 1847 and 9,000 leave-one-sample-out cases (180 source samples × 50 variants). Every tested pose is excluded from its own training set, randomly scaled from 0.65× to 1.45×, and given Gaussian coordinate jitter with σ=0.01.
Accuracy by gesture
dup2endFuncneutoneplusrunFuncstartFuncundozerooneis the only class with a small regression in this synthetic stress set (-0.6 pp); the overall model still removes 715 of 842 baseline errors. This benchmark measures robustness against camera-distance and landmark jitter using the existing dataset. It is reproducible evidence, but it is not a substitute for a larger real-world multi-person validation set.Verification
npm test(10/10 passing)npm run benchmark:recognitionnpm run check(0 errors, 0 warnings)npm run build