今回は、以前のエントリでも挑戦してみたいと思っていた、音声のホットワードを検知するSnowboy
をインストールして、トリガー検知を行ってみたいと思います。
使用するSnowboy
は以下のような説明になっていました。
Snowboy is an highly customizable hotword detection engine that is embedded real-time and is always listening (even when off-line) compatible with Raspberry Pi, (Ubuntu) Linux, and Mac OS X.
日本語訳すると…
Snowboyは高度にカスタマイズ可能なホットワード検出エンジンであり、リアルタイムに組み込まれ、Raspberry Pi、(Ubuntu)Linux、およびMac OS Xと互換性があります(オフラインの場合でも)。
ホットワードとは、Google Homeでいえば「OK、Google」、Alexaでいえば「Alexa」というアレです。Snowboy
を使用するとこのホットワードを任意のものに変換することでできるようにできます。あとオフラインでの認識ができるので、ネットワークを使わない分、高速に動作させることもできます。しかもRaspberryPiでも動作できるのでライトウエイトな実装でもあります。
以下のエントリーを参考にさせていただきました。リリースの時期が結構古いので動作環境も変わっているので、ちょっと注意が必要かもしれません。
今回の作業手順
手順としては以下のようになります。
- マイク接続とAlsaの設定
- snowboyのインストール
- ホットワードのカスタマイズ
使用したRaspberryPiのバージョン
まずは今回の動作環境の確認をしておきます。
RaspberryPiのハードウエアはRaspberryPi 3Bを使用します。 そして、OSのバージョンは次のようになります。(Raspbian GNU/Linux 10 (buster) 10.6)
$ cat /proc/device-tree/model Raspberry Pi 3 Model B Rev 1.2 $ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster $ cat /etc/debian_version 10.6
この環境で進めていきます。
マイク接続とAlsaの設定
音声認識によるホットワード検知となるので、マイクは必須となります。今回は以下のマイクをRaspberryPiに接続しました。
MM-MCUSB16
この製品はすでにディスコンになっているので、今だと以下が近い感じでしょうか。ミュートボタンが追加されているのでこちらのほうが便利かも?
接続すると自動的に認識されると思います。lsusb
コマンドで表示すると…Bus 001 Device 004: ID 0d8c:0134 C-Media Electronics, Inc.
と表示されているのがわかります。
$ lsusb Bus 001 Device 004: ID 0d8c:0134 C-Media Electronics, Inc. Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
ちなみに音声の確認(スピーカー出力)はピンジャックのイヤホンを使用しています。(後でこれがトラブルのもとになります。)
あとはソフトウエア(ALSA
)の設定となります。
サウンドデバイスの確認と設定変更
サウンドカードの状態を確認するにはaplay
とarecord
コマンドを使用します。以下のように実行すると現在の状態をリスト化して表示してくれます。
実行すると以下のように表示されます。
$ aplay -l **** ハードウェアデバイス PLAYBACK のリスト **** カード 0: Headphones [bcm2835 Headphones], デバイス 0: bcm2835 Headphones [bcm2835 Headphones] サブデバイス: 8/8 サブデバイス #0: subdevice #0 サブデバイス #1: subdevice #1 サブデバイス #2: subdevice #2 サブデバイス #3: subdevice #3 サブデバイス #4: subdevice #4 サブデバイス #5: subdevice #5 サブデバイス #6: subdevice #6 サブデバイス #7: subdevice #7 $ arecord -l **** ハードウェアデバイス CAPTURE のリスト **** カード 1: Device [USB PnP Audio Device], デバイス 0: USB Audio [USB Audio] サブデバイス: 1/1 サブデバイス #0: subdevice #0
今回のRaspberryPiはヘッドレスにしているので、HDMIデバイスは再生デバイスとしては表示されていません。設定時にはどちらにするか必ず確認するようにしてください。カード
情報のどこかにHDMIと入っているものが該当します。
今回は再生デバイスはカード0デバイス0を、録音デバイスはカード1デバイス0のものを使用していきます。こちらを使用するように~/.asoundrc
を編集していきます。vi
やnano
などでファイルを下記のように生成します。設定での注意としてはslave.pcm
の値をカード0デバイス0の場合にはhw:0,0
というように変更していきます。
~/.asoundrc
pcm.!default { type asym playback.pcm { type plug slave.pcm "hw:0,0" } capture.pcm { type plug slave.pcm "hw:1,0" } }
これでALSAでのサウンドデバイスの設定変更は完了です。 設定がうまく行っているかを確認する場合には以下のコマンドを実行してみて、録音、再生ができていれば大丈夫です。
# マイクからの音声の録音 $ rec test.wav # スピーカーから録音した音声ファイルの再生 $ play test.wav
再生しても、録音した内容が聞こえないのであれば、もう一度設定ファイルの中身を確認してみてください。
Snowboyのインストール
Snowboy
をGitHub
からgit clone
でソースを取得してインストールを行います。
git
を使わない場合には以下を使用しても大丈夫です。
https://s3-us-west-2.amazonaws.com/snowboy/snowboy-releases/rpi-arm-raspbian-8.0-1.3.0.tar.bz2
$ git clone https://github.com/Kitt-AI/snowboy.git Cloning into 'snowboy'... remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (3/3), done. remote: Total 2148 (delta 0), reused 1 (delta 0), pack-reused 2145 Receiving objects: 100% (2148/2148), 55.22 MiB | 2.27 MiB/s, done. Resolving deltas: 100% (1072/1072), done. Checking out files: 100% (313/313), done.
GitHub
でgit clone
を行うとカレントディレクトリにsnowboy
というディレクトリが作成されていると思います。ディレクトリ構成は以下のようになっていると思います。
$ cd snowboy/ $ ls LICENSE README.md README_commercial.md examples lib resources setup.py tsconfig.json MANIFEST.in README_ZH_CN.md binding.gyp include package.json scripts swig
ソースが取得出来たら、依存するパッケージをインストールしていきます。
念のためpython3でpyaudio
を使用できるように追加してみました。さすがにそろそろpython2系の使用はやめたい感じです。
$ sudo apt update
$ sudo apt install swig3.0 python-pyaudio python3-pyaudio sox
$ pip install pyaudio
$ pip3 install pyaudio
$ sudo apt install libatlas-base-dev
Snowboy
で使用するSharedObject
(soファイル)を生成するため、GitHub
で取得したファイルからmake
を行います。
以下のように行えばmake
ができるはずなのですが、ビルドエラーが発生します。
$ cd ~/snowboy/swig/Python3
$ make
エラーは次の様になっています。swig
という名前のコマンドがないためです。apt
でswigをインストールすると、コマンド名がswig3.0
となっているのが原因のようです。
ちなみにswig
をソースコードからインストールすると、実行ファイル名が単なるswig
となるので、エラーは発生しません。
/bin/sh: 1: swig: not found expr: syntax error: unexpected argument `30010' swig -I../../ -c++ -python -o snowboy-detect-swig.cc snowboy-detect-swig.i make: swig: コマンドが見つかりませんでした make: *** [Makefile:67: snowboy-detect-swig.cc] エラー 127
仕方ないのでmakefile
のswigコマンド
の指定部分を修正することで対応していきます。
$ vi Makefile
修正前Makefile
# Example Makefile that converts snowboy c++ library (snowboy-detect.a) to # python3 library (_snowboydetect.so, snowboydetect.py), using swig. # Please use swig-3.0.10 or up. SWIG := swig (以下略)
修正後Makefile
# Example Makefile that converts snowboy c++ library (snowboy-detect.a) to # python3 library (_snowboydetect.so, snowboydetect.py), using swig. # Please use swig-3.0.10 or up. SWIG := swig3.0 (以下略)
インストールが終わったら、以下のように実行します。(今回はpython3系で実行しています)実行時の引数として音声認識モデルをソースコードに同梱されていたsnowboy.umdl
を使用しています。これはスノーボーイという呼びかけ(Hot Word)を検知します。実行後にHot Wordを検知状態になり、検知すると音が鳴るというサンプルになります。
$ cd ~/snowboy/examples/Python3
$ python3 demo.py resources/models/snowboy.umdl
これで実行できると思いきや…以下のようなエラーが
$ python3 demo.py ./resources/models/snowboy.umdl Traceback (most recent call last): File "demo.py", line 1, in <module> import snowboydecoder File "/home/pi/snowboy/examples/Python3/snowboydecoder.py", line 5, in <module> from . import snowboydetect ImportError: attempted relative import with no known parent package
snowboydecoder
のインポート(5行目)がおかしいというエラーメッセージになります。では該当するsnowboydecoder.py
の中をみると以下のようになっているので
5行目のfrom . import snowboydetect
をimport snowboydetect
に変更してみます。
$ vi snowboydecoder.py
(変更前)snowboydecoder.py冒頭
#!/usr/bin/env python import collections import pyaudio from . import snowboydetect import time import wave import os import logging from ctypes import * from contextlib import contextmanager (略)
(変更後)snowboydecoder.py冒頭
#!/usr/bin/env python import collections import pyaudio import snowboydetect import time import wave import os import logging from ctypes import * from contextlib import contextmanager (略)
実行するとかなり大量のWarningが表示されるのですが、一応実行できます。表示されるのはALSAのバージョンによるものだと思います。
実行ログ
$ python3 demo.py resources/models/snowboy.umdl Listening... Press Ctrl+C to exit Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.front.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM front ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround51.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround21 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround51.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround21 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround40.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround40 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround51.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround41 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround51.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround50 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround51.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround51 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.surround71.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround71 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_headpho.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock INFO:snowboy:Keyword 1 detected at time: 2020-10-20 23:33:33 Expression 'paInvalidSampleRate' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2048 Expression 'PaAlsaStreamComponent_InitialConfigure( &self->playback, outParams, self->primeBuffers, hwParamsPlayback, &realSr )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2722 Expression 'PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerBuffer, &inputLatency, &outputLatency, &hostBufferSizeMode )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2843 Traceback (most recent call last): File "demo.py", line 33, in <module> sleep_time=0.03) File "/home/pi/snowboy/examples/Python3/snowboydecoder.py", line 221, in start callback() File "/home/pi/snowboy/examples/Python3/snowboydecoder.py", line 71, in play_audio_file rate=ding_wav.getframerate(), input=False, output=True) File "/usr/lib/python3/dist-packages/pyaudio.py", line 750, in open stream = Stream(self, *args, **kwargs) File "/usr/lib/python3/dist-packages/pyaudio.py", line 441, in __init__ self._stream = pa.open(**arguments) OSError: [Errno -9997] Invalid sample rate
ただ、呼びかけてもエラーが発生します。エラー内容は
Traceback (most recent call last): File "demo.py", line 33, in <module> sleep_time=0.03) File "/home/pi/snowboy/examples/Python3/snowboydecoder.py", line 221, in start callback() File "/home/pi/snowboy/examples/Python3/snowboydecoder.py", line 71, in play_audio_file rate=ding_wav.getframerate(), input=False, output=True) File "/usr/lib/python3/dist-packages/pyaudio.py", line 750, in open stream = Stream(self, *args, **kwargs) File "/usr/lib/python3/dist-packages/pyaudio.py", line 441, in __init__ self._stream = pa.open(**arguments) OSError: [Errno -9997] Invalid sample rate
Hot Wordを検知したときに鳴らす音の出力時にエラーが出ているようです。そこで先ほど設定した~/.asound
を変更してみました。変更したのは出力(再生)のサンプリングレートを固定したslave.rate 16000
という部分になります。
修正後の~/.asound
pcm.!default { type asym playback.pcm { type plug slave.pcm "hw:0,0" slave.rate 16000 } capture.pcm { type plug slave.pcm "hw:1,0" } }
設定変更後に実行を行うと問題なくHot Wordを検知し、認識音が鳴るになりました。
$ cd ~/snowboy/examples/Python3 $ python3 demo.py ./resources/models/snowboy.umdl Listening... Press Ctrl+C to exit Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 (中略) INFO:snowboy:Keyword 1 detected at time: 2020-10-24 22:47:29 INFO:snowboy:Keyword 1 detected at time: 2020-10-24 22:47:32
エラーは解消しませんが、無事に動作するようになりました。
ちなみに
違う再生デバイス(USBスピーカー)で確認してみました。
スイッチサイエンスさんで購入可能なUSB接続ミニステレオスピーカーを再生デバイスとしてしようしてみました。これコンパクトでいいんですよね。かなりおすすめです。音量の操作が手元でできないのでその部分だけが少しネックですが。
実際に動作させると先ほどのような設定変更を行わずとも、Snowboy
は動作できました。アナログピン接続のイヤフォンなどでは制限がでてくるのかもしれません。
おわりに
一応Snowboy
が動作するようになりました。これからオリジナルHotwordを検知させていこうと思うのですが、
割と量が多くなったので、【後編】で続けていこうと思います。
たまに集中力が切れてしまって後編つづく詐欺をしてしまうこともあるのですがそこはご容赦ください。