Model card: standalone use without OpenVoiceOS
Browse files
README.md
CHANGED
|
@@ -79,6 +79,61 @@ pip install --pre ovos-ww-plugin-wakeforge
|
|
| 79 |
A model file from this repository also loads by path: set `model` to the local `.onnx` file. The plugin reads
|
| 80 |
the featurizer and the default threshold from the model's metadata.
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
## Choosing a threshold
|
| 83 |
|
| 84 |
Set `"threshold"` in the hotword config to trade missed wake words against false activations. A lower value
|
|
|
|
| 79 |
A model file from this repository also loads by path: set `model` to the local `.onnx` file. The plugin reads
|
| 80 |
the featurizer and the default threshold from the model's metadata.
|
| 81 |
|
| 82 |
+
## Use in your own code
|
| 83 |
+
|
| 84 |
+
The models need only `numpy`, `onnxruntime` and `huggingface_hub`; nothing from OpenVoiceOS. Each model is a small classifier that reads WakeHuBERT-tiny features, so you run two ONNX files: the featurizer, then the wake-word model.
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
import numpy as np
|
| 88 |
+
import onnxruntime as ort
|
| 89 |
+
from huggingface_hub import hf_hub_download
|
| 90 |
+
|
| 91 |
+
word = "jarvis"
|
| 92 |
+
head = ort.InferenceSession(hf_hub_download("OpenVoiceOS/wakehubert-wakewords", f"models/wakehubert_{word}.onnx"))
|
| 93 |
+
meta = head.get_modelmeta().custom_metadata_map
|
| 94 |
+
featurizer_file = "wakehubert_int8.onnx" if meta["pretrained_featurizer"] == "wakehubert-int8" else "wakehubert.onnx"
|
| 95 |
+
feat = ort.InferenceSession(hf_hub_download("TigreGotico/wakehubert-tiny", featurizer_file))
|
| 96 |
+
threshold = float(meta["default_threshold"])
|
| 97 |
+
|
| 98 |
+
WINDOW = 24000 # 1.5 s of 16 kHz audio: the window the models were trained on (75 feature frames)
|
| 99 |
+
BLOCK = 1280 # score every 80 ms
|
| 100 |
+
DEBOUNCE_BLOCKS = 25 # ignore 2 s after a detection
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class WakeWordDetector:
|
| 104 |
+
def __init__(self):
|
| 105 |
+
self.buf = np.zeros(WINDOW, np.float32)
|
| 106 |
+
self.cooldown = 0
|
| 107 |
+
|
| 108 |
+
def push(self, chunk):
|
| 109 |
+
"""chunk: 1280 float32 samples at 16 kHz in -1..1. Returns (score, detected)."""
|
| 110 |
+
self.buf = np.concatenate([self.buf, chunk.astype(np.float32)])[-WINDOW:]
|
| 111 |
+
features = feat.run(None, {"waveform": self.buf[None]})[0] # [1, 75, 128]
|
| 112 |
+
logit = head.run(None, {"features": features})[0][0]
|
| 113 |
+
score = float(1.0 / (1.0 + np.exp(-logit)))
|
| 114 |
+
self.cooldown = max(0, self.cooldown - 1)
|
| 115 |
+
detected = score >= threshold and self.cooldown == 0
|
| 116 |
+
if detected:
|
| 117 |
+
self.cooldown = DEBOUNCE_BLOCKS
|
| 118 |
+
return score, detected
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
From a microphone, for example with `sounddevice`:
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
import sounddevice as sd
|
| 125 |
+
|
| 126 |
+
detector = WakeWordDetector()
|
| 127 |
+
with sd.InputStream(samplerate=16000, channels=1, dtype="float32", blocksize=BLOCK) as stream:
|
| 128 |
+
while True:
|
| 129 |
+
chunk, _ = stream.read(BLOCK)
|
| 130 |
+
score, detected = detector.push(chunk[:, 0])
|
| 131 |
+
if detected:
|
| 132 |
+
print(f"{word} detected (score {score:.2f})")
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
Each block featurizes the last 1.5 s on its own, exactly as the models were trained and scored. The featurizer is causal, the per-block cost is about 1–2 ms on one CPU core for the int8 featurizer, and one featurizer run can feed any number of wake-word models: run `feat` once per block and pass the same `features` to each model. Replace `threshold` to change sensitivity (see below). Checked against the plugin on real recordings: the snippet fires on the same clips.
|
| 136 |
+
|
| 137 |
## Choosing a threshold
|
| 138 |
|
| 139 |
Set `"threshold"` in the hotword config to trade missed wake words against false activations. A lower value
|