ウェイクワードエンジンモジュールテスト(失敗)

ウェイクワードエンジンモジュールテスト

import pyaudio
import numpy as np
from openwakeword.model import Model

class SimpleWakeWordDetector:
    def __init__(self, model_path="alexa_v0.1.onnx", threshold=0.5):
        self.model_path = model_path
        self.threshold = threshold
        self.format = pyaudio.paInt16
        self.channels = 1
        self.rate = 16000
        self.chunk = 1024

        self.model_name = model_path  # モデル名 = ファイル名
        self.model = Model(
            wakeword_models=[self.model_path],
            inference_framework="onnx"
        )

        self.audio = pyaudio.PyAudio()
        self.stream = self.audio.open(
            format=self.format,
            channels=self.channels,
            rate=self.rate,
            input=True,
            frames_per_buffer=self.chunk
        )

    def listen_for_wakeword(self):
        print(f"ウェイクワード待機中...({self.model_path})")
        prev_detect = False

        while True:
            data = self.stream.read(self.chunk, exception_on_overflow=False)
            audio = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0

            self.model.predict(audio)
            score = self.model.get_last_prediction(self.model_name)

            print(f"score: {score:.3f}", end='\r')

            detect = score > self.threshold
            if detect and not prev_detect:
                print(f"\nWakeword Detected! (score: {score:.3f})")
                return True

            prev_detect = detect

 touch simple_wakeword.py

で作成

touch wordtest.py

from simple_wakeword import SimpleWakeWordDetector

# モジュール初期化(Alexa用モデルとしきい値指定)
wakeword_detector = SimpleWakeWordDetector(
    model_path="alexa_v0.1.onnx",
    threshold=0.5
)

# 検知まで待機
detected = wakeword_detector.listen_for_wakeword()

if detected:
    print("こんにちは")

しかし反応がない

とりあえずウェイクワードは保留とし
先にRAGと llama index を行う

ウェイクワードエンジンと ollama gemma3 4b の組み合わせ(失敗)

ウェイクワードエンジンと ollama gemma3 4b の組み合わせ

とりあえずopenwakeword で alexa なら動作するので
次に音声入力が起動するように組み合わせる

これならAlexa と言ったら
音声入力を開始
という

常にマイクを監視
「ねえラマ」などのウェイクワードが話されたら録音開始
認識→質問→読み上げを実行
終了後、またウェイクワード待ちに戻る

ができるはず

import pyaudio
import numpy as np
from openwakeword.model import Model
import sys

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

model_name = "alexa_v0.1.onnx"

model = Model(
  wakeword_models=[model_name],
  inference_framework="onnx"
)

print("Listening for wakeword \"Alexa\"...")
print()

prev_detect=False

while True:
    audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)

    prediction = model.predict(audio)

    scores = model.prediction_buffer[model_name]
    curr_score = format(scores[-1], '.20f')
    detect = True if float(curr_score) > 0.5 else False

    if detect:
        if detect != prev_detect:
            print(f"Detected!({curr_score[:5]})")
            prev_detect=True
    else:
        prev_detect=False
を元にGPTの提案したコードを書き換える

touch module/module_wakeword.py
で

import pyaudio
import numpy as np
from openwakeword.model import Model

class WakeWordDetector:
    def __init__(self, model_path="alexa_v0.1.onnx", threshold=0.5):
        self.model_name = model_path
        self.threshold = threshold

        # モデル初期化
        self.model = Model(
            wakeword_models=[self.model_name],
            inference_framework="onnx"
        )

        # PyAudio設定
        self.format = pyaudio.paInt16
        self.channels = 1
        self.rate = 16000
        self.chunk = 1024

        self.audio = pyaudio.PyAudio()
        self.stream = self.audio.open(
            format=self.format,
            channels=self.channels,
            rate=self.rate,
            input=True,
            frames_per_buffer=self.chunk
        )

    def listen_for_wakeword(self):
        print(f"ウェイクワード待機中...(モデル: {self.model_name})")

        prev_detect = False

        while True:
            audio_chunk = self.stream.read(self.chunk, exception_on_overflow=False)
            audio_np = np.frombuffer(audio_chunk, dtype=np.int16)

            _ = self.model.predict(audio_np)

            score = self.model.prediction_buffer[self.model_name][-1]
            detect = score > self.threshold

            if detect and not prev_detect:
                print(f"ウェイクワード検出!(スコア: {score:.3f})")
                return True  # 検出されたら終了

            prev_detect = detect

✅ 特徴と使い方
* alexa_v0.1.onnx をモデルとして使う(変更可能)
* listen_for_wakeword() を呼び出すと、検出されるまでループし、検出されたら return True

✅ モデルファイルが models/ にある場合の使い方

wakeword_detector = WakeWordDetector(model_path="models/alexa_v0.1.onnx", threshold=0.5)

✅ 使い方例(main側)

from module.module_wakeword import WakeWordDetector

wakeword_detector = WakeWordDetector("models/alexa_v0.1.onnx", threshold=0.5)
wakeword_detector.listen_for_wakeword()

これらを元に

touch main6.py

を作成し

from module.module_audio_to_text import AudioToTextCorrector
from module.module_speaker import Speaker
from module.module_wakeword import WakeWordDetector
from ollama import chat, ChatResponse

# モデル名(Ollama用)
OLLAMA_MODEL = 'gemma3:4b'

# 各モジュールの初期化
speaker = Speaker()
audio_to_text = AudioToTextCorrector("config.json")
wakeword_detector = WakeWordDetector(model_path="models/alexa_v0.1.onnx", threshold=0.5)

def ask_ollama(prompt: str) -> str:
    try:
        response: ChatResponse = chat(model=OLLAMA_MODEL, messages=[
            {'role': 'user', 'content': prompt}
        ])
        return response.message.content.strip()
    except Exception as e:
        print(f"Ollamaエラー: {e}")
        return "エラーが発生しました。"

def main():
    while True:
        # ① ウェイクワードを検出するまで待機
        wakeword_detector.listen_for_wakeword()

        # ② 音声を認識してテキスト化(+日本語補正)
        corrected_text = audio_to_text.record_and_correct(timeout_seconds=10)

        if corrected_text is None:
            print("無音またはタイムアウトで中断。再びウェイクワード待ちに戻ります。")
            continue

        print("\n【認識・補正したテキスト】")
        print(corrected_text)

        # ③ Ollama(gemma3:4b)へ質問
        ollama_reply = ask_ollama(corrected_text)

        print("\n【gemma3:4bの返答】")
        print(ollama_reply)

        # ④ 読み上げ
        speaker.speak(ollama_reply)

if __name__ == "__main__":
    main()

でウェイクワード対応させる

実行すると

[2025-05-14 06:23:54.568] [ctranslate2] [thread 311016] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/gemma/main6.py", line 12, in <module>
    wakeword_detector = WakeWordDetector(model_path="models/alexa_v0.1.onnx", threshold=0.5)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/aw10s/gemma/module/module_wakeword.py", line 11, in __init__
    self.model = Model(
                 ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/utils.py", line 686, in wrapped
    return func(*args, **new_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/model.py", line 97, in __init__
    raise ValueError("Could not find pretrained model for model name '{}'".format(i))
ValueError: Could not find pretrained model for model name 'models/alexa_v0.1.onnx'

これは
openWakeWord の Model(…) に渡されたモデルパスが正しく認識されていない

openwakeword.Model(…) に渡す wakeword_models は、**ファイル名ではなく「モデル名 or 辞書形式」**で渡す必要がある

なので

self.model = Model(
    wakeword_models=[self.model_name],  # これはNG
    inference_framework="onnx"
)

self.model = Model(
    wakeword_models={ "alexa": self.model_name },
    inference_framework="onnx"
)

にする

import pyaudio
import numpy as np
from openwakeword.model import Model

class WakeWordDetector:
    def __init__(self, model_path="models/alexa_v0.1.onnx", threshold=0.5):
        self.model_name = "alexa"
        self.threshold = threshold

        # モデル名とファイルパスを辞書で渡す
        self.model = Model(
            wakeword_models={self.model_name: model_path},
            inference_framework="onnx"
        )

        # PyAudio設定
        self.format = pyaudio.paInt16
        self.channels = 1
        self.rate = 16000
        self.chunk = 1024

        self.audio = pyaudio.PyAudio()
        self.stream = self.audio.open(
            format=self.format,
            channels=self.channels,
            rate=self.rate,
            input=True,
            frames_per_buffer=self.chunk
        )

    def listen_for_wakeword(self):
        print(f"ウェイクワード待機中...(アレクサ)")

        prev_detect = False

        while True:
            audio_chunk = self.stream.read(self.chunk, exception_on_overflow=False)
            audio_np = np.frombuffer(audio_chunk, dtype=np.int16)

            _ = self.model.predict(audio_np)

            score = self.model.prediction_buffer[self.model_name][-1]
            detect = score > self.threshold

            if detect and not prev_detect:
                print(f"「アレクサ」検出!(スコア: {score:.3f})")
                return True

            prev_detect = detect

が全体コード

今度は

[2025-05-14 06:28:28.142] [ctranslate2] [thread 314783] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/gemma/main6.py", line 12, in <module>
    wakeword_detector = WakeWordDetector(model_path="models/alexa_v0.1.onnx", threshold=0.5)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/aw10s/gemma/module/module_wakeword.py", line 11, in __init__
    self.model = Model(
                 ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/utils.py", line 686, in wrapped
    return func(*args, **new_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/model.py", line 90, in __init__
    for ndx, i in enumerate(wakeword_models):
RuntimeError: dictionary changed size during iteration

コード生成がうまくいかないため

 test_openwakeword.py 

のコードをモジュール化する

module/module_wakeword_simple.py

内容は

import pyaudio
import numpy as np
from openwakeword.model import Model

class SimpleWakeWordDetector:
    def __init__(self, model_path="alexa_v0.1.onnx", threshold=0.5):
        self.model_path = model_path
        self.threshold = threshold
        self.format = pyaudio.paInt16
        self.channels = 1
        self.rate = 16000
        self.chunk = 1024

        # モデル名(ファイル名)でアクセスするためのキー
        self.model_name = model_path

        # OpenWakeWord モデルの読み込み
        self.model = Model(
            wakeword_models=[self.model_path],
            inference_framework="onnx"
        )

        # マイク初期化
        self.audio = pyaudio.PyAudio()
        self.stream = self.audio.open(
            format=self.format,
            channels=self.channels,
            rate=self.rate,
            input=True,
            frames_per_buffer=self.chunk
        )

    def listen_for_wakeword(self):
        print(f"Listening for wakeword \"{self.model_name}\"...")

        prev_detect = False

        while True:
            data = self.stream.read(self.chunk, exception_on_overflow=False)
            audio = np.frombuffer(data, dtype=np.int16)

            self.model.predict(audio)
            scores = self.model.prediction_buffer[self.model_name]
            curr_score = scores[-1]
            detect = curr_score > self.threshold

            if detect and not prev_detect:
                print(f"Wakeword Detected! (score: {curr_score:.3f})")
                return True

            prev_detect = detect

使い方は

from module.module_wakeword_simple import SimpleWakeWordDetector

wake_detector = SimpleWakeWordDetector(model_path="models/alexa_v0.1.onnx", threshold=0.5)

while True:
    wake_detector.listen_for_wakeword()
    print("処理を実行します...")
    # → 音声認識など次の処理へ

しかし、そもそもの前提として
Modelディレクトリにはモデルが存在していない

touch download.py  

内容は

import openwakeword

openwakeword.utils.download_models()


実行すると

python download.py 
embedding_model.tflite: 100%|█████████████████| 1.33M/1.33M [00:00<00:00, 9.92MiB/s]
embedding_model.onnx: 100%|███████████████████| 1.33M/1.33M [00:00<00:00, 7.13MiB/s]
melspectrogram.tflite: 100%|██████████████████| 1.09M/1.09M [00:00<00:00, 6.30MiB/s]
melspectrogram.onnx: 100%|████████████████████| 1.09M/1.09M [00:00<00:00, 6.54MiB/s]
silero_vad.onnx: 100%|████████████████████████| 1.81M/1.81M [00:00<00:00, 7.82MiB/s]
alexa_v0.1.tflite: 100%|████████████████████████| 855k/855k [00:00<00:00, 5.67MiB/s]
alexa_v0.1.onnx: 100%|██████████████████████████| 854k/854k [00:00<00:00, 5.36MiB/s]
hey_mycroft_v0.1.tflite: 100%|██████████████████| 860k/860k [00:00<00:00, 6.84MiB/s]
hey_mycroft_v0.1.onnx: 100%|████████████████████| 858k/858k [00:00<00:00, 6.52MiB/s]
hey_jarvis_v0.1.tflite: 100%|█████████████████| 1.28M/1.28M [00:00<00:00, 6.96MiB/s]
hey_jarvis_v0.1.onnx: 100%|███████████████████| 1.27M/1.27M [00:00<00:00, 6.26MiB/s]
hey_rhasspy_v0.1.tflite: 100%|██████████████████| 416k/416k [00:00<00:00, 4.76MiB/s]
hey_rhasspy_v0.1.onnx: 100%|████████████████████| 204k/204k [00:00<00:00, 3.06MiB/s]
timer_v0.1.tflite: 100%|██████████████████████| 1.74M/1.74M [00:00<00:00, 7.98MiB/s]
timer_v0.1.onnx: 100%|████████████████████████| 1.74M/1.74M [00:00<00:00, 8.96MiB/s]
weather_v0.1.tflite: 100%|████████████████████| 1.15M/1.15M [00:00<00:00, 6.99MiB/s]
weather_v0.1.onnx: 100%|██████████████████████| 1.15M/1.15M [00:00<00:00, 6.44MiB/s]

となってダウンロードされているがパスが不明

GPTで

ls ~/.cache/openwakeword/models/

で存在するというが

ls: /Users/snowpool/.cache/openwakeword/models/: No such file or directory

となるのでこれではない

 touch show_model_path.py

from openwakeword.utils import default_cache_dir
import os

print("モデル保存先:")
print(os.path.join(default_cache_dir(), "models"))

で実行する

しかし

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/gemma/show_model_path.py", line 1, in <module>
    from openwakeword.utils import default_cache_dir
ImportError: cannot import name 'default_cache_dir' from 'openwakeword.utils' (/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/utils.py)

となる

参考サイトとして
https://zenn.dev/kun432/scraps/1a987de4943c65
によれば

 pip show openwakeword | grep Location

で調べることができる

Location: /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages

モデルはsite-packagesの下にダウンロードされる

Treeコマンドはインストールされていないので

 brew install tree

でインストール

しかしエラーになるので

arch -arm64 brew install tree

でインストールする

tree /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword
/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-311.pyc
│   ├── custom_verifier_model.cpython-311.pyc
│   ├── data.cpython-311.pyc
│   ├── metrics.cpython-311.pyc
│   ├── model.cpython-311.pyc
│   ├── train.cpython-311.pyc
│   ├── utils.cpython-311.pyc
│   └── vad.cpython-311.pyc
├── custom_verifier_model.py
├── data.py
├── metrics.py
├── model.py
├── resources
│   └── models
│       ├── alexa_v0.1.onnx
│       ├── alexa_v0.1.tflite
│       ├── embedding_model.onnx
│       ├── embedding_model.tflite
│       ├── hey_jarvis_v0.1.onnx
│       ├── hey_jarvis_v0.1.tflite
│       ├── hey_mycroft_v0.1.onnx
│       ├── hey_mycroft_v0.1.tflite
│       ├── hey_rhasspy_v0.1.onnx
│       ├── hey_rhasspy_v0.1.tflite
│       ├── melspectrogram.onnx
│       ├── melspectrogram.tflite
│       ├── silero_vad.onnx
│       ├── timer_v0.1.onnx
│       ├── timer_v0.1.tflite
│       ├── weather_v0.1.onnx
│       └── weather_v0.1.tflite
├── train.py
├── utils.py
└── vad.py

これで場所が把握できたのでモデルをコピーする

mkdir -p models
cp /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/alexa_v0.1.onnx models/

これで実行したけど
アレクサ
と言っても検知しない

試しに

python test_openwakeword.py

を実行してもダメだった

openwakeWordと EffecientWord-Net について

ウェイクワードエンジンの導入

openwakeWordと EffecientWord-Net について調べる

1. OpenWakeWord
開発元: Picovoice(またはオープンソースコミュニティ)
特徴
* オープンソースで利用可能
* Pythonで簡単に組み込める
* 自作ウェイクワードの訓練が比較的簡単(マイク音声から自作ワードを学習可能)
* 低スペックなデバイス(例:Raspberry Pi)でも動作可能
* 英語中心だが、日本語でも利用できるようカスタムワードを収録可
メリット
* 手軽に組み込みやすい
* 小型音声アシスタントやプロトタイプ向けに最適
* 小型モデルなのでリアルタイム処理に強い
利用例
* 「ねえラマ!」など任意のフレーズで起動する家庭用AIスピーカー
* 子供の声でも誤検知しにくいように調整可能

2. EfficientWord-Net
開発元: ByteDance Research(論文あり)
特徴
* 学習済みモデルを使用した高精度ウェイクワード検出モデル
* EfficientNetアーキテクチャベースで構築されており、精度と軽量性を両立
* 公開されている論文ベースでは、LibriSpeechやGoogle Speech Commandsなどのベンチマークで優秀な精度を記録
メリット
* 音声ノイズ環境でも精度が高い
* 高速かつ高精度な推論が可能
* 多数のウェイクワードを同時に検出するシナリオに強い
注意点
* 実装がやや高度で、デバイスへの組み込みにはある程度の知識が必要
* ソースコードが論文公開のみで、実装例が少ないこともある

| 比較項目 | OpenWakeWord | EfficientWord-Net |
| ——— | ——————- | ——————– |
| オープンソース | はい | 一部のみ(論文あり) |
| カスタムワード対応 | あり(簡易に追加可能) | 理論上可能だが要学習 |
| リアルタイム性 | 高い(軽量) | 高いがややGPU依存 |
| 対応言語 | 任意(自作可能、日本語対応も工夫で可) | 主に英語想定 |
| 実装のしやすさ | 非常に簡単(Pythonで完結) | やや難(自前でモデル導入や音響処理必要) |
| 小型デバイス対応 | Raspberry Pi等でOK | 要スペック(モバイルでも可能) |

EfficientWord-Netはリアルタイム性
高いがややGPU依存だと
OpenWakeWordの方が今後ラズパイとかで動く方が良いかも

OpenWakeWordは自分の声でトリガーワードを録音し、モデルを訓練して追加可能
なお自分の声でなくても voicevox などで生成した音声でもいけるらしい

ただし要求するマシンスペックがメモリ8GBのため
Google Colab を使うことにする

ウェイクワード検出ライブラリ openWakeWord を使ってみた
での動作環境は ubuntu

オープンソースのウェイクワード検出ライブラリ「openWakeword」を試す
だと
Apple Silicon
だと問題があるらしい
自力でビルドすればいけるらしいが

GPTだと
Zennのkun432氏による記事「オープンソースのウェイクワード検出ライブラリ『openWakeword』を試す」で報告されていた問題は、現在では解決されています。
当初、Apple Silicon(M1/M2)搭載のMacでOpenWakeWordを使用する際、TensorFlow Liteランタイム(tflite-runtime)が提供されておらず、エラーが発生していました。
しかし、OpenWakeWordはONNX形式のモデルもサポートしており、onnxruntimeを使用することで、Apple Silicon環境でも問題なく動作します。

ってあるけど本当かは試さないと不明

pip install openwakeword onnxruntime sounddevice

でインストール

M1環境では tflite-runtime は使わず、onnxruntime を使うらしい

次に起動テスト

touch test_openwakeword.py

内容を

import sounddevice as sd
from openwakeword.model import Model

# ONNXモードを明示
oww_model = Model(
    wakeword_models=["hey_jarvis"],
    inference_framework="onnx"
)

sample_rate = 16000
duration = 0.5  # 秒

print("ウェイクワード検出開始(Ctrl+Cで終了)")

try:
    while True:
        audio = sd.rec(int(sample_rate * duration), samplerate=sample_rate, channels=1, dtype='float32')
        sd.wait()
        prediction = oww_model.predict(audio.flatten())

        score = prediction.get("hey_jarvis", 0)
        if score > 0.5:
            print(f"検出!スコア: {score:.2f}")
except KeyboardInterrupt:
    print("終了しました")

これを実行すると

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/gemma/test_openwakeword.py", line 5, in <module>
    oww_model = Model(
                ^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/utils.py", line 686, in wrapped
    return func(*args, **new_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/model.py", line 153, in __init__
    self.models[mdl_name] = ort.InferenceSession(mdl_path, sess_options=sessionOptions,
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 469, in __init__
    self._create_inference_session(providers, provider_options, disabled_optimizers)
  File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 530, in _create_inference_session
    sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
onnxruntime.capi.onnxruntime_pybind11_state.NoSuchFile: [ONNXRuntimeError] : 3 : NO_SUCHFILE : Load model from /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx failed:Load model /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx failed. File doesn't exist

となる

OpenWakeWord が使用しようとしているモデルファイル:

/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx

が存在せず、onnx モデルの自動ダウンロードに失敗していることが原因

https://zenn.dev/kun432/scraps/1a987de4943c65
にも書いてあるけど
どうやらモデルファイルをダウンロードする必要がある

GPTでモデルダウンロードURLを表示されるが
存在しないURLだったので解決策を調べる

https://zenn.dev/kun432/scraps/1a987de4943c65
を参考に

touch download.py  

内容は

import openwakeword

openwakeword.utils.download_models()


実行すると

python download.py 
embedding_model.tflite: 100%|█████████████████| 1.33M/1.33M [00:00<00:00, 9.92MiB/s]
embedding_model.onnx: 100%|███████████████████| 1.33M/1.33M [00:00<00:00, 7.13MiB/s]
melspectrogram.tflite: 100%|██████████████████| 1.09M/1.09M [00:00<00:00, 6.30MiB/s]
melspectrogram.onnx: 100%|████████████████████| 1.09M/1.09M [00:00<00:00, 6.54MiB/s]
silero_vad.onnx: 100%|████████████████████████| 1.81M/1.81M [00:00<00:00, 7.82MiB/s]
alexa_v0.1.tflite: 100%|████████████████████████| 855k/855k [00:00<00:00, 5.67MiB/s]
alexa_v0.1.onnx: 100%|██████████████████████████| 854k/854k [00:00<00:00, 5.36MiB/s]
hey_mycroft_v0.1.tflite: 100%|██████████████████| 860k/860k [00:00<00:00, 6.84MiB/s]
hey_mycroft_v0.1.onnx: 100%|████████████████████| 858k/858k [00:00<00:00, 6.52MiB/s]
hey_jarvis_v0.1.tflite: 100%|█████████████████| 1.28M/1.28M [00:00<00:00, 6.96MiB/s]
hey_jarvis_v0.1.onnx: 100%|███████████████████| 1.27M/1.27M [00:00<00:00, 6.26MiB/s]
hey_rhasspy_v0.1.tflite: 100%|██████████████████| 416k/416k [00:00<00:00, 4.76MiB/s]
hey_rhasspy_v0.1.onnx: 100%|████████████████████| 204k/204k [00:00<00:00, 3.06MiB/s]
timer_v0.1.tflite: 100%|██████████████████████| 1.74M/1.74M [00:00<00:00, 7.98MiB/s]
timer_v0.1.onnx: 100%|████████████████████████| 1.74M/1.74M [00:00<00:00, 8.96MiB/s]
weather_v0.1.tflite: 100%|████████████████████| 1.15M/1.15M [00:00<00:00, 6.99MiB/s]
weather_v0.1.onnx: 100%|██████████████████████| 1.15M/1.15M [00:00<00:00, 6.44MiB/s]

となってダウンロードされる

再度

python test_openwakeword.py
ウェイクワード検出開始(Ctrl+Cで終了)
^C終了しました

と起動はするけど
ジャービス
ヘイ ジャービス
でも反応がない

https://zenn.dev/kun432/scraps/1a987de4943c65
のコードを参考に実験する

import pyaudio
import numpy as np
from openwakeword.model import Model
import sys

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

model_name = "alexa_v0.1.onnx"

model = Model(
  wakeword_models=[model_name],
  inference_framework="onnx"
)

print("Listening for wakeword \"Alexa\"...")
print()

prev_detect=False

while True:
    audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)

    prediction = model.predict(audio)

    scores = model.prediction_buffer[model_name]
    curr_score = format(scores[-1], '.20f')
    detect = True if float(curr_score) > 0.5 else False

    if detect:
        if detect != prev_detect:
            print(f"Detected!({curr_score[:5]})")
            prev_detect=True
    else:
        prev_detect=False


Alexaから変更する
これは家の中にあるアレクサと被るため
hey jarvisで動作確認する

とりあえず GPTでコード解説

マイク設定と音声入力の初期化

import pyaudio
import numpy as np
from openwakeword.model import Model
import sys

FORMAT = pyaudio.paInt16       # 16bit PCM
CHANNELS = 1                   # モノラル
RATE = 16000                   # サンプリング周波数 16kHz
CHUNK = 1024                   # 一度に読み取るサンプル数

audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

これで
マイクから音声をリアルタイムで取得するためのストリームを開始

次にOpenWakeWord モデルの初期化
model_name = "alexa_v0.1.onnx"
model = Model(
  wakeword_models=[model_name],
  inference_framework="onnx"
)

今回は”alexa_v0.1.onnx” モデルをONNXランタイムで使用
macOS (M1など) では onnx が推奨

そして
メインループで音声解析

print("Listening for wakeword \"Alexa\"...")

while True:
    audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)

この部分で
マイクから CHUNK = 1024 サンプル(= 約64ms)分を読み込み
int16 に変換してNumPy配列にします(OpenWakeWordの入力形式)

そしてウェイクワードの検出

    prediction = model.predict(audio)

現在のフレームに対してウェイクワードスコアを計算

    scores = model.prediction_buffer[model_name]
    curr_score = format(scores[-1], '.20f')
    detect = True if float(curr_score) > 0.5 else False

過去のスコアをバッファから取得し、最新のスコア([-1])を確認
0.5 を超えていたら検出と判定

一度だけ表示する仕組みとして

    if detect:
        if detect != prev_detect:
            print(f"Detected!({curr_score[:5]})")
            prev_detect=True
    else:
        prev_detect=False

前回検出と今回の結果が異なるときだけ表示することで、連続検出を防ぐ
例:「アレクサ」と言う → スコアが上がって「Detected!」が一度表示

これを元に音声モデルを変更

import pyaudio
import numpy as np
from openwakeword.model import Model
import sys

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

model_name = "hey_jarvis_v0.1.onnx"

model = Model(
  wakeword_models=[model_name],
  inference_framework="onnx"
)

print("Listening for wakeword \"hey_jarvis\"...")
print()

prev_detect=False

while True:
    audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)

    prediction = model.predict(audio)

    scores = model.prediction_buffer[model_name]
    curr_score = format(scores[-1], '.20f')
    detect = True if float(curr_score) > 0.5 else False

    if detect:
        if detect != prev_detect:
            print(f"Detected!({curr_score[:5]})")
            prev_detect=True
    else:
        prev_detect=False

実行したら

python test_openwakeword.py
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/gemma/test_openwakeword.py", line 1, in <module>
    import pyaudio
ModuleNotFoundError: No module named 'pyaudio'

となったので

pip install pyaudio
Collecting pyaudio
  Downloading PyAudio-0.2.14.tar.gz (47 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: pyaudio
  Building wheel for pyaudio (pyproject.toml) ... done
  Created wheel for pyaudio: filename=pyaudio-0.2.14-cp311-cp311-macosx_15_0_arm64.whl size=25773 sha256=85950d898df05ebcce820f2f6d30b030db44951656c7decef10274215dca529b
  Stored in directory: /Users/snowpool/Library/Caches/pip/wheels/80/b1/c1/67e4ef443de2665d86031d4760508094eab5de37d5d64d9c27
Successfully built pyaudio
Installing collected packages: pyaudio
Successfully installed pyaudio-0.2.14

[notice] A new release of pip is available: 24.3.1 -> 25.1.1
[notice] To update, run: pip install --upgrade pip

で結局反応しないのでアレクサで試したら動いた…

単純に私の発音が悪いらしい

GPTによれば発音がダメとのこと

なお最初にGPTで提案されたコードは動作しないので
今回のコードを使うことにする