Debouncing, but for a hand
This concept came from the original requirements for Nsila, and it reminded me of something I already knew from React Native: debouncing.
Debouncing a search box makes the app react to what the user meant, not to every keystroke on the way there. Live sign recognition has a similar problem. The model gives a prediction for each frame, and while a hand moves into position those predictions can jump between letters. If every prediction counted, one word would come out as a stream of wrong letters.
The difference is that a search box waits for the input to stop changing, while here the app also has to judge whether noisy input is really staying the same. So it takes a few checks:
- Look at the last half second of predictions, not just the latest one
- Show a letter only if it wins the majority of that window and averages at least 0.6 probability
- Add it to the text only after it has stayed the current letter for 0.6 seconds by default, which the user can change in settings
- Lock it after that, so holding your hand still does not type the same letter repeatedly
Allowing double letters
That lock creates a new problem: names like Bello have a double L. So the lock releases when a different confident letter appears, or when the hand leaves the frame for a short moment. To sign a double letter, you drop your hand briefly and sign it again. If no hand comes back for about 1.5 seconds, the app adds a space.
Keep it easy to test
All of this lives in one plain Kotlin class with no camera code in it. It takes a timestamp and a set of probabilities, and returns a state. That means a unit test can feed it a scripted sequence of predictions and check exactly when a letter commits, with no phone or hand involved.
The thresholds are only starting points, and they still need tuning with real users. The idea is the same as debouncing, though: react to what the person meant, not to every intermediate signal.