【RaspberryPi】PythonでSnowboyを利用して「GPIOを使用」と「設定した音声再生」を行う

前回はSnowboyを使って中二魂全開DXでない日輪刀を作りましたが、そのあたりの処理に関してはあまり触れていませんでした。 なので、久しぶりにpythonのコードについて記述していこうと思います。

記述は本家のSnowboyのサイトを参考にしています。

【参考】 snowboy.kitt.ai

【関連】 uepon.hatenadiary.com

uepon.hatenadiary.com

事前準備

ではまずはGitHubからCloneしたコードをパーツとして使用します。全く0から作成するよりもベースを作るのが簡単です。 今回はmy_snowboyというディレクトリを作成してコードを作ってみることにします。認識モデルなどに関しては先ほどのディレクトリに シンボリックリンクを作成してアクセスします。 以下の処理を行えば、基本的にはsnowboy処理と同じ処理のひな型が完成します。

ソースコードの雛型の準備

$ mkdir my_snowboy
$ cd my_snowboy/
$ ln -s ~/snowboy/resources resources
$ cp ~/snowboy/examples/Python3/snowboydecoder.py .
$ ln -s ~/snowboy/swig/Python3/snowboydetect.py snowboydetect.py
$ ln -s ~/snowboy/swig/Python3/_snowboydetect.so _snowboydetect.so
$ cp ~/snowboy/examples/Python3/demo.py .

ここまでできたら、実行実験を行います。これが動けは無事にソースコードの雛型の準備ができました。

$ python3 demo.py resources/models/snowboy.umdl

Snowboy認識部分だけの抽出したソースプログラム

先ほどのひな型ができたら、Snowboyの認識部分のみを抽出したコードを作ってみます。 内容としては、

  • Ctrl+Cでのinterruptのコールバックの処理
  • Hotword検知モデルの引数読み込み
  • Hotword処理の設定と実行

を設定していきます。

import snowboydecoder
import sys
import signal

interrupted = False

def signal_handler(signal, frame):
    global interrupted
    interrupted = True

def interrupt_callback():
    global interrupted
    return interrupted

if len(sys.argv) == 1:
    print("Error: need to specify model name")
    print("Usage: python demo.py your.model")
    sys.exit(-1)

model = sys.argv[1]

signal.signal(signal.SIGINT, signal_handler)

detector = snowboydecoder.HotwordDetector(model, sensitivity=0.5)
print('Listening... Press Ctrl+C to exit')

detector.start(detected_callback=snowboydecoder.play_audio_file,
               interrupt_check=interrupt_callback,
               sleep_time=0.03)

detector.terminate()

こちらのコードを改良していきます。

SnowboyからLEDを光らせてみるソースプログラム

まずはLチカを行うソースプログラム

先ほどのサンプルコードではSnowboyのコールバック設定で音声出力をコールバックに設定していました。 今回はLEDを点灯する処理をコールバックとして設定することで実現を行っていきます。

まずは、LEDを点灯する処理を単体で作成してみます。

RPi.GPIOを使用してRaspberryPiのGPIOを操作して、LEDを点滅するものになります。 単体での実行(__main__の処理)ではGPIOの17ピンを使用してLEDを点滅させています。(0.3秒点灯、0.7秒消灯)

https://snowboy.kitt.ai/docspartials/docs/_images/led-wiring.png

import RPi.GPIO as GPIO
import time

class Light(object):
    def __init__(self, port):
        self.port = port
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.port, GPIO.OUT)
        self.on_state = GPIO.HIGH
        self.off_state = not self.on_state

    def set_on(self):
        GPIO.output(self.port, self.on_state)

    def set_off(self):
        GPIO.output(self.port, self.off_state)

    def is_on(self):
        return GPIO.input(self.port) == self.on_state

    def is_off(self):
        return GPIO.input(self.port) == self.off_state

    def toggle(self):
        if self.is_on():
            self.set_off()
        else:
            self.set_on()

    def blink(self, t=0.3):
        self.set_off()
        self.set_on()
        time.sleep(t)
        self.set_off()

if __name__ == "__main__":
    light = Light(17)
    while True:
        light.blink()
        time.sleep(0.7)

実行は以下のように行います。特にsudoを付加しなくても実行できました。

$ python3 light.py

LEDが点滅すれば準備はOKです。

動画


RaspberryPiで実行したPythonでLチカしてみた

SnowboyからLEDを点滅させる

先ほどの認識部分の最小コードを変更して以下のようにします。 変更部分は3行程度です。

一番のポイントはdetector.start()の引数であるdetected_callbackの値をLED点灯にしている点です。

my_blink_demo.py

import snowboydecoder
import sys
import signal
from light import Light # 追加部分

interrupted = False

def signal_handler(signal, frame):
    global interrupted
    interrupted = True

def interrupt_callback():
    global interrupted
    return interrupted

if len(sys.argv) == 1:
    print("Error: need to specify model name")
    print("Usage: python demo.py your.model")
    sys.exit(-1)

model = sys.argv[1]

signal.signal(signal.SIGINT, signal_handler)

detector = snowboydecoder.HotwordDetector(model, sensitivity=0.5)
print('Listening... Press Ctrl+C to exit')

# 変更点 ここから
led = Light(17)
detector.start(detected_callback=led.blink,
               interrupt_check=interrupt_callback,
               sleep_time=0.03)
# 変更点 ここまで

detector.terminate()

先ほど作成したlight.pyをimportしてHotword検知した際にLEDの点灯を呼び出すようにしています。とても簡単です。 あとは同様に、認識モデルを引数として与えます。下で指定しているのはオリジナルの全集中のHotwordになります。

$ python3 myblinkdemo.py resources/models/zensyuucyuu.pmdl

動画


Hotword検知でLEDを点灯してみる

Lチカの代わりに音声を鳴らしてみる

先ほどはGPIOを使用しましたが、今回は認識時の音声を変更してみます。音声の出力に関しては、すでにSnowboyのサンプルコード(snowboydecoder.py)に含まれていますが、音声の指定がちょっと面倒なので少し変更して、ソースコードに含めます。基本はpyaudioの機能を使用して音声の再生をしているので、そこまで複雑ではありません。 今回使用した音声はRaspberryPiインストール時にすでに入っているalsaのサンプルファイルである"/usr/share/sounds/alsa/Front_Center.wav"を使っています。カレントディレクトリに音声がある場合には、ファイル名の指定だけで大丈夫です。

my_sound_demo.py

import snowboydecoder
import sys
import signal
#---追加開始
import pyaudio
import wave
import time
#---追加終了

#---追加開始
SOUNDFILE = "/usr/share/sounds/alsa/Front_Center.wav" # サウンドファイルの指定

def play_audio_file(fname=SOUNDFILE):
    sound_wav = wave.open(fname, 'rb')
    sound_data = sound_wav.readframes(sound_wav.getnframes())
    audio = pyaudio.PyAudio()
    stream_out = audio.open(
        format=audio.get_format_from_width(sound_wav.getsampwidth()),
        channels=sound_wav.getnchannels(),
        rate=sound_wav.getframerate(), input=False, output=True)
    stream_out.start_stream()
    stream_out.write(sound_data)
    time.sleep(0.2)
    stream_out.stop_stream()
    stream_out.close()
    audio.terminate()
#---追加終了

interrupted = False

def signal_handler(signal, frame):
    global interrupted
    interrupted = True

def interrupt_callback():
    global interrupted
    return interrupted

if len(sys.argv) == 1:
    print("Error: need to specify model name")
    print("Usage: python demo.py your.model")
    sys.exit(-1)

model = sys.argv[1]

signal.signal(signal.SIGINT, signal_handler)

detector = snowboydecoder.HotwordDetector(model, sensitivity=0.5)
print('Listening... Press Ctrl+C to exit')

detector.start(detected_callback=play_audio_file,
               interrupt_check=interrupt_callback,
               sleep_time=0.03)

detector.terminate()

実行するには先ほどの例と同じように引数に認識モデルを指定する必要があります。

$ python3 my_sounddemo.py resources/models/snowboy.umdl 


Snowboyで音声検知 任意の音声を再生するテスト

おわりに

折角RaspberryPiを使用してのホットワード検知を行っているので、GPIOなんかも使っていかないと面白くないかなと思って試してみました。 自分も久しぶりにpythonのコードを触ったので、たまに詰まるところもありましたが何とかなりました。

Snowboyを使用すれば、小型の音声リモコンの実現なんかも可能ですしね。

【参考】 snowboy.kitt.ai

【関連】 uepon.hatenadiary.com

uepon.hatenadiary.com

/* -----codeの行番号----- */