ネスカフェ バリスタを使って、"ツイッター投稿"、"スマホでコントロール" に、続いて第3弾。
今度は、音声認識を使って、バリスタのスイッチをコントロールしてみた。
ネスカフェ バリスタを音声コントロール
ハードウェアの方は、USBマイクをつける以外は、"スマホでコントロール"の時と同じで、今回は、ソフトウェアの設定が主になる。
音声認識は、Juliusというシステムを使わせていただいた。
Juliusについてもたくさんの参考サイトが説明してくれているので、非常に助かる!
以下、その詳細。
では、まず、USBマイクを接続して、認識されていることと、優先順位が最初に来ているかを確認
$ sudo lsusb Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070] Bus 001 Device 005: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller 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. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub $ sudo cat /proc/asound/modules 0 snd_usb_audio 1 snd_bcm2835
私の場合、USBには、BUFFALOのWiFIドングルのWLI-UC-GNM2 と、USBマイクのBSHSMO5BKというのが刺さっている。
上記リストのうちC-Media というのがマイクで、ちゃんと認識されていた。
優先順位のほうは、0のほうが優先順位が高いそうなのだが、そのままだと、SND_BCM2835のほうが優先になっていた。
で、順位を変更するのだけれど、ラズパイのOSのバージョンによって少し、動きが違っていた。
NOOBS v1.4.1 をインストールしたときは、"/etc/modprobe.d/alsa-base.conf" を編集すれば上記のようなったのだけど、NOOBS v1.5.0 をインストールしたときは、そもそも、この"alsa-base.conf"というファイル自体が見つからなかった。
で、いろいろググって見ると、こちらのサイトで助けられた。
簡単にできる!音声認識と音声合成を使ってRaspberrypiと会話 - Qiita
v1.4.1のときは、だいたい、以下のサイトに習って設定すればうまくいった。
Raspberry Piで音声認識 - Qiita
上記のサイトを参考にさせてもらって、Juliusのインストールとテストまでは、問題なく進んだ。
インストールできたら、自分に必要なワードの辞書を作成して辞書ファイルに変換。
以下のサイトも大変参考になる。
ラズパイで音声認識をしてみる | うしこlog
私の場合は、以下のように辞書ファイル作成
~$ nano word.yomi ブラックにして ぶらっくにして ラテにして らてにして カプチーノにして かぷちーのにして エスプレッソにして えすぷれっそにして マグにして まぐにして コーヒーいれて こーひーいれて バリスタくん ばりすたくん おしまい おしまい $ cd ~/julius-4.3.1/gramtools/yomi2voca $ iconv -f utf8 -t eucjp ~/word.yomi | ./yomi2voca.pl > ~/julius-kits/dictation-kit-v4.3.1-linux/word.dic
辞書ファイルができたら、上記サイトを参考に設定ファイルを作成
$ nano ~/julius-kits/dictation-kit-v4.3.1-linux/word.jconf
うまく認識されていることを確認。
認識できることが確認されたら、認識した文字でバリスタのスイッチをコントロールするプログラムをPythonで作成。
その前に、先ほどの”Word.jconf”のファイルの最後に "-module" という行を追加して、Julius をモジュールモードで実行するように設定
Pythonのプログラムは、以下のサイトを参考にさせていただいた。
Raspberry Pi
こちらのサイトの2015/08/30の記事のPythonプログラムをベースに下記のように、自分用にアレンジした。
~$ nano barista01.py
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- ''' ファイル名 : barista01.py 起動方法 (1) Julius をモジュールモードで起動 julius -C julius-kits/dictation-kit-v4.3.1-linux/word.jconf Julius(フリーの高性能音声認識ソフトウェア) ・TCP/IP で認識結果を受信し認識結果を表す<RECOGOUT>...</RECOGOUT> 部分を抽出する 参考 URL http://moosoft.jp/index.php?option=com_content&view=article&id=99&Itemid=134 http://julius.osdn.jp/ http://kuhjaeger.co.jp/laboratory/raspberrypi/julius/ ''' import threading import RPi.GPIO as GPIO import socket import time import sys # モジュールトップレベル変数設定 Start SOCK = None # TCP/IP ソケット ENCODING = 'utf-8' # codec # GPIO ピン番号設定 # GPIO.setmode(GPIO.BOARD) P1 ヘッダ上のピン番号を用いる RELAY_1 = 24 # コーヒー GPIO8 = 24 ピン RELAY_2 = 11 # ブラック GPIO17 = 11 ピン RELAY_3 = 12 # マグ GPIO18 = 12 ピン RELAY_4 = 13 # エスプレッソ GPIO27 = 13 ピン RELAY_5 = 15 # カプチーノ GPIO22 = 15 ピン RELAY_6 = 16 # ラテ 2 GPIO23 = 16 ピン P0 = 18 # Aques bit0 P1 = 22 # Aques bit1 P2 = 19 # Aques bit2 HOST_NAME = 'localhost' PORT_NUM = 10500 BUF_SIZE = 4096 DELIM_STR = '.\n' START_STR = '<RECOGOUT>' END_STR = '</RECOGOUT>' FIND_STR0 = ['コーヒー' , 'いれて'] FIND_STR1 = ['ブラック', 'にして'] FIND_STR2 = ['マグ', 'にして'] FIND_STR3 = ['エスプレッソ', 'にして'] FIND_STR4 = ['カプチーノ', 'にして'] FIND_STR5 = ['ラテ', 'にして'] FIND_STR6 = ['バリスタ','くん'] FIND_STR7 = ['おし', 'まい'] # モジュールトップレベル変数設定 End # GPIO 初期化 def gpio_init(): try: GPIO.setwarnings(False) # Warning 表示停止 #GPIO.setmode(GPIO.BCM) # GPIO ピン番号を用いる GPIO.setmode(GPIO.BOARD) # P1 ヘッダ上のピン番号を用いる GPIO.setup(RELAY_1,GPIO.OUT) GPIO.setup(RELAY_2,GPIO.OUT) GPIO.setup(RELAY_3,GPIO.OUT) GPIO.setup(RELAY_4,GPIO.OUT) GPIO.setup(RELAY_5,GPIO.OUT) GPIO.setup(RELAY_6,GPIO.OUT) GPIO.setup(P0,GPIO.OUT) GPIO.setup(P1,GPIO.OUT) GPIO.setup(P2,GPIO.OUT) GPIO.setup(RELAY_1, # SW 1 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(RELAY_2, # SW 2 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(RELAY_3, # SW 3 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(RELAY_4, # SW 4 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(RELAY_5, # SW 5 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(RELAY_6, # SW 6 GPIO.OUT, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(P0, # Bit0 GPIO.OUT, pull_up_down=GPIO.PUD_UP) GPIO.setup(P1, # Bit1 GPIO.OUT, pull_up_down=GPIO.PUD_UP) GPIO.setup(P2, # Bit2 GPIO.OUT, pull_up_down=GPIO.PUD_UP) except Exception as e: print('gpio_init:{0}'.format(e)) # 受信解析 SW ON 制御クラス class tcp_recv(threading.Thread): # コンストラクタ def __init__(self): threading.Thread.__init__(self) self.daemon = True # デーモンスレッド # 受信解析 SW ON 制御 def run(self): global SOCK try: SOCK = socket.socket(socket.AF_INET, socket.SOCK_STREAM) SOCK.connect((HOST_NAME, PORT_NUM)) rv_str = '' while SOCK is not None: rv = SOCK.recv(BUF_SIZE).decode(ENCODING) rv_str += rv if DELIM_STR in rv: # '.\n' : メッセージの区切り s0 = rv_str.find(START_STR) # '<RECOGOUT>' s1 = rv_str.find(END_STR) # '</RECOGOUT>' if (s0 > -1) and (s1 > -1): rec = rv_str[s0 : s1 + len(END_STR) + 1] # 文字列抽出 print(rec) if FIND_STR0[0] in rec: # Start SW if FIND_STR0[1] in rec: isON = "1.000" in rec GPIO.output(P0, False) # コーヒー入れて GPIO.output(P1, False) # Aques GPIO.output(P2, False) time.sleep(1) GPIO.output(P0, True) GPIO.output(P1, True) GPIO.output(P2, True) time.sleep(2) GPIO.output(RELAY_1, isON) #スイッチON time.sleep(1) GPIO.output(RELAY_1,False) if FIND_STR1[0] in rec: #ブラック isON = FIND_STR1[1] in rec GPIO.output(P1, False) # Aques GPIO.output(P2, False) time.sleep(1) GPIO.output(P1, True) GPIO.output(P2, True) GPIO.output(RELAY_2, isON) # ブラックSet time.sleep(1) GPIO.output(RELAY_2,False) if FIND_STR2[0] in rec: # マグ isON = FIND_STR2[1] in rec GPIO.output(RELAY_3, isON) # マグSet GPIO.output(P0, False) # Aques GPIO.output(P2, False) time.sleep(1) GPIO.output(P0, True) GPIO.output(P2, True) GPIO.output(RELAY_3,False) if FIND_STR3[0] in rec: # エスプレッソ isON = FIND_STR3[1] in rec GPIO.output(P2, False) # Aques GPIO.output(RELAY_4, isON) # エスプレッソSet time.sleep(3) GPIO.output(P2, True) GPIO.output(RELAY_4,False) if FIND_STR4[0] in rec: # カプチーノ isON = FIND_STR4[1] in rec GPIO.output(RELAY_5, isON) # カプチーノSet GPIO.output(P0, False) # Aques GPIO.output(P1, False) # Aques time.sleep(1) GPIO.output(RELAY_5,False) GPIO.output(P0, True) # Aques GPIO.output(P1, True) # Aques if FIND_STR5[0] in rec: # ラテ isON = FIND_STR5[1] in rec GPIO.output(RELAY_6, isON) # ラテSet GPIO.output(P1, False) # Aquos time.sleep(1) GPIO.output(RELAY_6,False) GPIO.output(P1, True) # Aques if FIND_STR6[0] in rec: # バリスタくん isON = FIND_STR6[1] in rec GPIO.output(P0, False) # Aques time.sleep(1) GPIO.output(P0,True) if FIND_STR7[0] in rec: # おしまい # isON = FIND_STR7[1] in rec if FIND_STR7[1] in rec: if SOCK is not None: SOCK.shutdown(socket.SHUT_RDWR) SOCK.close() GPIO.output(RELAY_1, GPIO.LOW) # SW OFF GPIO.output(RELAY_2, GPIO.LOW) # SW OFF GPIO.output(RELAY_3, GPIO.LOW) # SW OFF GPIO.output(RELAY_4, GPIO.LOW) # SW OFF GPIO.output(RELAY_5, GPIO.LOW) # SW OFF GPIO.output(RELAY_6, GPIO.LOW) # SW OFF GPIO.output(P0, GPIO.HIGH) # Bit OFF GPIO.output(P1, GPIO.HIGH) # Bit OFF GPIO.output(P2, GPIO.HIGH) # Bit OFF GPIO.cleanup() sys.exit() # exit rv_str = '' except Exception as e: if SOCK is not None: SOCK.shutdown(socket.SHUT_RDWR) SOCK.close() SOCK = None print('run:{0}'.format(e)) # メインループ def main(): try: gpio_init() # GPIO 初期化 tcp_recv().start() # 受信解析クラス while True: time.sleep(1000) except KeyboardInterrupt: if SOCK is not None: SOCK.shutdown(socket.SHUT_RDWR) SOCK.close() GPIO.output(RELAY_1, GPIO.LOW) # SW OFF GPIO.output(RELAY_2, GPIO.LOW) # SW OFF GPIO.output(RELAY_3, GPIO.LOW) # SW OFF GPIO.output(RELAY_4, GPIO.LOW) # SW OFF GPIO.output(RELAY_5, GPIO.LOW) # SW OFF GPIO.output(RELAY_6, GPIO.LOW) # SW OFF GPIO.output(P0, GPIO.HIGH) # Bit OFF GPIO.output(P1, GPIO.HIGH) # Bit OFF GPIO.output(P2, GPIO.HIGH) # Bit OFF GPIO.cleanup() raise except Exception as e: print('main:{0}'.format(e)) # ============================================== # if __name__ == '__main__': main()
続いて、音声認識コントロールを開始するためのスクリプトを作成
$ nano barista.sh #!/bin/sh amixer sset Mic 10 julius -C /home/pi/julius-kits/dictation-kit-v4.3.1-linux/word.jconf & sleep 3 sudo python3 barista01.py & exit 0
これで、実行するときは、
$ sudo bash barista.sh
と、やれば、音声認識コントロールが開始される。
誤認識もあるけど、期待した以上の精度でスイッチがコントロールできる。 ただ、音声合成で発声しているのだけど、この音をマイクがひろってしまい、ループしてしまうことがあるので、センテンスをひろったら、しばらくマイクのレベルをゼロにするとか何かの対策が必要かも・・・。
もう少し、改良のポイントは、あるかもね・・・。