ラズパイを音声でシャットダウン

ラズパイを音声でシャットダウン

車にラズパイを持ち込んで使いたいけど
シャットダウンするときに困りそうなので
音声でシャットダウンできるようにする

bluetooth ボタンも考えたけど
ハズレをひくと買い直しになるし紛失を考えると
音声でシャットダウンし
シガーソケットから給電すればエンジンをかければ電源がはいる

音声認識エンジンJulius をまずはいれる

Raspberry Pi×JuliusとPythonでスマートスピーカー風にカメラを操作

Raspberry PiとJuliusで特定の単語を認識させる

を参考に

mkdir julius
cd julius/
wget https://github.com/julius-speech/julius/archive/v4.4.2.1.tar.gz
tar xvzf v4.4.2.1.tar.gz 
cd julius-4.4.2.1/

でGit で取得し展開したファイルに移動

sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev

で必要なライブラリのインストール

Raspberry pi3B+でjuliusを動かせるようになるまでの覚書き(2019.3.10現在)

にあるように
RaspberryPi3b+の最新カーネルでは
snd-pcm-ossモジュールが含まれていないので

sudo apt-get install osspd-alsa
sudo apt-get  install libasound2-dev

でサウンドドライバをインストール

./configure --with-mictype=alsa
make
sudo make install

でコンパイルしてインストール

次に音声認識パッケージの
ディクテーションキットの取得

cd ../
mkdir julius-kit
cd julius-kit/
wget https://osdn.net/dl/julius/dictation-kit-v4.4.zip
unzip dictation-kit-v4.4.zip 

で取得

さらに必要なライブラリのインストール

sudo apt-get install alsa-utils sox libsox-fmt-all

次にマイクの設定

 arecord -l

の結果が

**** ハードウェアデバイス CAPTURE のリスト ****
カード 2: Device [USB PnP Sound Device], デバイス 0: USB Audio [USB Audio]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

これを元に
ALSADEV設定で使用デバイスを指定

vim ~/.profile 

で設定ファイルを開き

export ALSADEV=hw:2

を最終行へ追記

hw;にはカード番号を記述

source ~/.profile 

で設定反映

あとは起動して実験

cd  ~/julius/julius-kit/dictation-kit-v4.4
julius -C main.jconf -C am-gmm.jconf -demo

起動はするけど精度が低すぎて使い物にならない

このため辞書ファイルを作成する

日本語入力ができるように mozc をインストール

sudo apt-get install fcitx-mozc

次に辞書ファイルの作成

cd ~/julius/
mkdir dict
cd dict/
sudo vim hello.yomi

でファイルを作成

おはよう おはよう
こんにちわ こんにちわ

として保存

左に認識させる言葉
右にはよみかたをひらがなで記述して
.yomi という拡張子で保存

区切りのスペースを半角にしないとバグるので注意

これを元に音素ファイルを作成する
このときに文字コードをUTF8 から EUC-JP に変換するので

iconv -f utf8 -t eucjp hello.yomi | ../julius-4.4.2.1/gramtools/yomi2voca/yomi2voca.pl | iconv -f eucjp -t utf8 > hello.phone

これでファイルが作成される
拡張子は
.phone となる

内容は

おはよう	o h a y o u
こんにちわ	k o N n i ch i w a

となる

次に構文ファイルの作成
これで
認識する文章の構成を定義している

sudo vim hello.grammar

でファイルを作成

S : NS_B HELLO NS_E
HELLO OHAYOU
HELLO KONNICHIWA

として保存

S : NS_B HELLO NS_E

NS_Bが文章の開始
NS_Eが文章の終了
という意味

HELLO OHAYOU
HELLO KONNICHIWA
の部分は
.phone ファイルの読みを大文字にしたもの

あとは語彙ファイルの作成
これはJulius に認識させたい言葉を定義するもので
拡張子は
.voca になる

sudo cp hello.phone hello.voca

でファイルをコピーし編集

% OHAYOU
おはよう o h a y o u
% KONNICHIWA
こんにちは k o N n i ch i w a
% NS_B
[s] silB
% NS_E
[/s] silE

というようにする

次に辞書ファイルへ変換

cd ~/julius/julius-4.4.2.1/gramtools/mkdfa/
mkdfa.pl ~/julius/dict/hello

を実行したがエラー

/home/pi/julius/dict/hello.grammar has 3 rules
/home/pi/julius/dict/hello.voca    has 4 categories and 4 words
---
Now parsing grammar file
Error:       parse error
Error: cannot open "/home/pi/julius/dict/hello.dfa.tmp"
---
no .dfa or .dict file generated

となる

このため
julius 辞書 自作
で検索

ラズパイ4日目①:Juliusで独自辞書を作成する

をみたところ

grammar ファイルの区切りで 
:
で区切っていないので修正

cd julius/dict/
sudo vim hello.grammar

でファイル編集

S : NS_B HELLO NS_E
HELLO : OHAYOU
HELLO : KONNICHIWA

として保存

mkdfa.pl ~/julius/dict/hello

を実行すると
.dfa
.term
.dict
ファイルが生成される

これで独自辞書ができたので
音声認識をするため

julius -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf -nostrip -gram ~/julius/dict/hello -input mic

を実行

これで辞書ファイルへ登録した
おはよう
こんにちわ
だけは認識するようになるが
それ以外は表示されない

次に辞書の追加

Raspberry Pi×JuliusとPythonでスマートスピーカー風にカメラを操作

を参考に
.yomi ファイルを編集

おはよう おはよう
こんにちわ こんにちわ
電源オフ でんげんおふ

として保存

iconv -f utf8 -t eucjp hello.yomi | ../julius-4.4.2.1/gramtools/yomi2voca/yomi2voca.pl | iconv -f eucjp -t utf8 > hello.phone

を実行

sudo vim hello.grammar

でファイルを編集

S : NS_B HELLO NS_E
HELLO : OHAYOU
HELLO : KONNICHIWA
HELLO : DENGENNOHU

として保存

sudo vim hello.voca 

でファイルを編集

% OHAYOU
おはよう o h a y o u
% KONNICHIWA
こんにちは k o N n i ch i w a
% DENGENNOHU
電源オフ d e N g e N o f u
% NS_B
[s] silB
% NS_E
[/s] silE

として保存

mkdfa.pl ~/julius/dict/hello

で辞書ファイル作成

julius -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf -nostrip -gram ~/julius/dict/hello -input mic


電源オフ
と認識されるのがわかる

次にモジュールモードでJulius の起動

julius -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf  -nostrip -gram ~/julius/dict/hello -input mic -module

これで Julius がサーバとなり
python プログラムとの通信待ちになる

次に

sudo vim speech.py

でファイルを区制

ラズパイと音声認識でLチカ

も参考に

いくつかコードも調べてみた

# -*- coding: utf-8 -*-

はpython3 なら記載不要

なおインストールしたままの状態だと

python --version

で調べると
Python 2.7.16

Python で文頭に記載する文字コードの「アレ」の名称(なんちゃら UTF-8 みたいなやつ)

を参考に

ファイルの内容は

# -*- coding: utf-8 -*-
import socket

host = 'localhost'   # Raspberry PiのIPアドレス
port = 10500         # juliusの待ち受けポート

# パソコンからTCP/IPで、自分PCのjuliusサーバに接続
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

res = ''
while True:
 # 音声認識の区切りである「改行+.」がくるまで待つ
 while (res.find('\n.') == -1):
  # Juliusから取得した値を格納していく
  res += sock.recv(1024)

 # 音声XMLデータから、<WORD>を抽出して音声テキスト文に連結する
 word =''
 for line in res.split('\n'):
  # Juliusから取得した値から認識文字列の行を探す
  index = line.find('WORD=')

  # 認識文字列があったら...
  if index != -1:
   # 認識文字列部分だけを抜き取る
   line = line[index + 6 : line.find('"', index + 6)]
   # 文字列の開始記号以外を格納していく
   if line != '[s]':
    word = word + line

  # 「電源オフ」という文字列を認識したら...
  if word == '電源オフ':
   print("電源オフ")
  res =''

この後に

sudo python speech.py

とすると
マイクで
電源オフ
と話しかけると
電源オフ
と表示される

これで音声の認識はできたので
次に
python で linux コマンドの実行

これは subprocess モジュールを使うことでできる

Python: subprocessでOSコマンドを実行する

Pythonでシェルコマンドを実行する

Pythonからシェルコマンドを実行!subprocessでサブプロセスを実行する方法まとめ

を参考に

使い方は
subprocess.run([“実行したいコマンド”,”オプションなど”,…])
でOK

今回はshutdown コマンドを使うので

Raspberry Piの電源をブラウザからOFF

を参考に

# -*- coding: utf-8 -*-
import socket
import subprocess
cmd = "sudo shutdown -h now"

host = 'localhost'   # Raspberry PiのIPアドレス
port = 10500         # juliusの待ち受けポート

# パソコンからTCP/IPで、自分PCのjuliusサーバに接続
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

res = ''
while True:
 # 音声認識の区切りである「改行+.」がくるまで待つ
 while (res.find('\n.') == -1):
  # Juliusから取得した値を格納していく
  res += sock.recv(1024)

 # 音声XMLデータから、<WORD>を抽出して音声テキスト文に連結する
 word =''
 for line in res.split('\n'):
  # Juliusから取得した値から認識文字列の行を探す
  index = line.find('WORD=')

  # 認識文字列があったら...
  if index != -1:
   # 認識文字列部分だけを抜き取る
   line = line[index + 6 : line.find('"', index + 6)]
   # 文字列の開始記号以外を格納していく
   if line != '[s]':
    word = word + line

  # 「電源オフ」という文字列を認識したら...
  if word == '電源オフ':
    print("電源オフ")
    subprocess.call(cmd, shell=True)
  res =''

として保存

再度

sudo python speech.py 

を実行し
マイクに電源オフと話すと
電源オフと表示された後にラズパイの電源が落ちる

python おさらい その6

python おさらい その6

標準入力+1で出力

print(int(input())+1)

次にリストの中に重複した値があるか true false で判定

list =["HND","NRT","KIX","NGO","NGO"]
flag =len(list) != len(set(list))
if flag:
    print("true")
else:
    print("False")

Pythonでリスト(配列)に重複した要素があるか判定

を参考に

set() にリストを渡すと重複する値は無視されて
位置地な値のみになるので
これを len() で比較すれば重複した要素があるか判定できる

最後の判定は true で出力なので
そのまま処理結果を出すと True になる

python の set() について調べたいなら
Pythonのsetの基本的な使い方 : set()

も参考に

リストとの違いは要素に順番を持たず
重複した値は取り除かれるということ

次にリストの中から同じ要素のカウント


array = ["HND", "NRT", "KIX", "NGO", "NGO", "NGO", "NGO", "NGO"]
count = {}

for pattern in array:
    if pattern in count:
        count[pattern] += 1
    else:
        count[pattern] = 1

for (key, value) in count.items():
    if value != 1:
        print(value)

全体の要素数をカウントするなら len() を使う
各要素ごとの出現回数をカウントするなら count() を使う

今回は

count ={}

としておくことで key value 形式で格納できるようにしている

詳しくは
PythonのCounterでリストの各要素の出現個数をカウント

を参考に

items() はキーと値のペアを両方取り出す

for (key, value) in count.items():

なら
count の中のキーと値を取り出しながらループとなる

この使い方については
Pythonで辞書をforループするkeysメソッド・valuesメソッド・itemsメソッドの使い方

を参考に

次にリストのソートと表示

list =[1, 3, 5, 6, 3, 2, 5, 23, 2]
list.sort()

for item in list:
    print(item)

昇順でリストをソートするなら
sort() で行う
なお
sort(reverse=True)
とすると降順ソートになる

リスト関連は
Pythonのリストのソートまとめ|sort(), sorted(), reverse()
を参考に

python おさらい その5

python おさらい その5

文字列の長さを表示するには len() を使う

print(len(input()))

で標準入力で入力した文字数を表示できる

次に文字列のインデックスの表示

input_line = input()
print(input_line[0])

というように 0 からインデックスが振られるので
最初の入力文字を表示するなら0を指定する

次にリストのループ表示

list =["Nara", "Shiga", "Hokkaido", "Chiba"]
for item in list:
    print(item)

今回は標準入力ではなく
リストの中身の表示

for の構文がわかっていれば
あとはリストの中身を表示するだけでOK

解説としては
【Python入門】for文を使った繰り返し文の書き方

がわかりやすいかも

次に標準入力を1文字ずつ表示
とはいっても入力自体を1文字ずつなので
別に分割するとか考えなくてOK

string=input()

for item in string:
    print(item)

最初の string の中身がリストから標準入力になっただけ

次に2つの入力値があり
最初に文字列
次に文字を入力し
文字の出現回数を計算する


string = input()
char = input()
count = 0

for item in string:
    if item == char:
        count +=1
print(count)

最初に2つの input で文字列と文字を取得

あとは回数を格納する変数を用意して
for で文字列をループしながら単語に分解

その中で文字と一致したら
カウントを増やして最後に print() で出力

次に
標準入力の
文字列の n 文字目と n + 1 文字目
という場合

count = int(input())-1
string = input()

if count +1 < len(string):
    print(string[count]+" "+string[count+1])

インデックスはゼロから数えるので
入力の時点で -1 したほうが計算が楽

文字列の長さを超えることがあるので
if で判定しておく
文字列連結は + でできる
これは java とかもおなじ
php だと . で連結なのでわかりにくいかも

s が t の中で何回出現するかカウントして出力
の場合

pattern = input()
string = input()
result = 0

for i in range(len(string) - len(pattern) + 1):
    portion = string[i:i + len(pattern)]

    if portion == pattern:
        result += 1

print(result)

次に標準入力+1で出力する場合

num= int(input())+1
print(num)

int() で整数にして+1しておけばOK

次に
n m
というように空白区切りで入力して
n 個の整数を改行区切りで標準

num = int(input())

for i in range(num):
    print(input().split()[1])

最初の num で入力回数を取得
次に for で回数分ループ
split() で分けて インデックス1の部分なら
2番めに入力したものがでるのでこれでOK

次に標準入力したものをソートして出力

input()
string = input().split()
nums=[]

for num in string:
    nums.append(int(num))
nums.sort()

for i in nums:
    print(i)

となる

格納リストを用意し
ループで入力したものを代入
代入するときに整数に変換

ループ後に sort() でソート
あとはこれをループさせて
1つずつ表示

次に入力されるn個の組を整数の値で昇順にソートして
文字列を表示

num = int(input())
inputs = {}

for i in range(num):
    tmp = input().split()
    inputs[int(tmp[1])] = tmp[0]

inputs = sorted(inputs.items())

for i in inputs:
    print(i[1])

最初に入力数を受け取る

次に 配列格納変数inputs を用意
for で range の範囲を入力値にして
tmp に格納しながら標準入力したものをループでまわす

inputs[int(tmp[1])] = tmp[0]

の部分で key value 形式で格納

inputs = sorted(inputs.items())

の部分で
sorted() で昇順にソート

sort() と sorted() の違いについては
Pythonでリストをソートするsortとsortedの違い

を参考に

基本的に文字列をソートするなら sorted() を使う

items() はすべてのキーと値の組み合わせを取得

なお値だけなら values()
キーだけならkeys() を使う

これらの辞書関連メソッドについては
辞書に含まれるすべてのキーと値を取得する

を参考に

今回は inputs に key -value 形式で格納
あとは

for i in inputs:
    print(i[1])

で 値のみループで表示すればOK

python おさらい その4

python おさらい その4

文字列の分割

input_line = input()
for string in input_line.split():
    print(string)

というように
一度 input() で入力し
split() で分解した数だけループ処理

あとは print で表示

次に整数の足し算

a,b = map(int,input().split())
print(a+b)

で2つの数値を合計して出力

for で書くのなら


strings = input()
result =0
for num in strings.split():
    result +=int(num)
print(result)

というかんじ

次に文字列の長さ
長さは len() で取得できるので


input_line = input()
print(len(input_line))

とすればOK

次に配列のインデックス表示

input_line = input()
print(input_line[0])

というように変数にインデックスが割り当てられているので
0からカウントしていく

今回なら最初の文字列が0なので
最初の文字列を表示

次に大文字への変換
これは upper() を使う

メソッドについては
Python文字列操作マスター

を参考に

print(input().upper())

次に指定範囲の数値を表示

これは range() を使う
第2引数に+1しないとその1つ前の数値で処理が終了してしまう

input_line = list(map(int,input().split()))

for input in range(input_line[0], input_line[1]+1):
    print(input)

でOK
リストに格納したほうが int でキャストしなくていいので楽

書き方については
Python3の標準入力やり方まとめ

を参考に

次に指定した文字列だけ大文字変換する

nums= list(map(int,input().split()))
string = input()


print(string[0:nums[0] - 1], end='')
print(string[nums[0] - 1:nums[1]].upper(), end='')
print(string[nums[1]:])

print() で end=” を指定すると改行なしとなるので
処理したものを改行なしで表示できる

python おさらい その3

python おさらいその3

文字列の分割

input_line = input().split()
print(input_line[0])
print(input_line[1])

とすれば
標準入力を分割し、表示できる

std_in = input()

for string in std_in.split():
    print(string)

でも答えは同じ

次に標準入力で2つの値をいれて合計を出す

a,b= map(int,input().split())
print(a+b)

でも

std_in = input()
result = 0

for num in std_in.split():
    result += int(num)

print(result)

でも答えは同じ

次に文字列の長さを取得
これは len() で簡単にできる

input_line = input()
print(len(input_line))

もっと簡潔に書くなら

print(len(input()))

文字列の長さ(文字数)を取得する

を参考に

次に入力した1番めの文字列を取得

input_line = input()
print(input_line[0])

というように
格納した変数のインデックスで取得表示できる

文字列の指定したインデックスの文字(要素)を取得する

を参考に

次にスペース区切りの入力をすべて表示

nums = input().split()

for i in range(int(nums[0]), int(nums[1]) + 1):
    print(i)

というように range() に範囲指定して行う

split() を使うことで分割され
これはインデックスとして格納される

range() は
第1引数で開始位置
第2引数で範囲
を指定する

Pythonのrange()関数を使ったリスト作成や繰り返し処理の書き方

を参考に

python おさらい その2

python おさらい その2

最初に入力回数をいれて
次に文字列をいれ、それを出力していく

input_line = int(input())
for i in range(input_line):
    string = input()
    print(string)

標準入力で空白区切り、もしくはスペース区切りの場合は

数値の場合なら
Python3 標準入力から複数の値を受け取りたい時

のように

map(int,input(),split())

を使う

文字列の場合は

input().split()

でOK

スペース区切りの文字列を分割して
2行にするのなら

std_in = input()

for string in std_in.split():
    print(string)

というかんじ

次にスペース区切りで2つの整数を入力し
それを足すというもの

a,b =map(int,input().split())
print(a+b)

でOK

2つの整数をそれぞれ足して
最後に合計を出す
もし同じ2つの値のときには掛け算する

time = int(input())
result = 0

for i in range(time):
    std_in = input()
    array = std_in.split()

    if array[0] == array[1]:
        result += int(array[0]) * int(array[1])
    else:
        result += int(array[0]) + int(array[1])

print(result)

python おさらい

python おさらい

python で標準入力するときに
数値入力するのなら
input() を使う

この場合、数値になるので
入力値 +1とするなら

input_line =int(input())
print(input_line +1 )

とすればOK

標準入力関連については
Python3の標準入力やり方まとめ

を参考に

また、最初に入力回数、次に改行で入力値
というものを出力するのなら

input_line = int(input())
for i in range(input_line):
    print(input())

次に入力値がなく
1, 3, 5, 6, 3, 2, 5, 23, 2
のリストの合計値を出力する場合

mylist =[1, 3, 5, 6, 3, 2, 5, 23, 2]
print(sum(mylist))

となる

合計をだすには
sum() を使う

python でのリスト作成には
【Python入門】listの使い方とメソッドまとめ

リストの足し算については
Pythonでリストの要素を足し算する方法を現役エンジニアが解説【初心者向け】

を参考に

次に標準入力で
入力値が5位上なら high
以下なら low とするなら

if int(input()) >= 5:
    print("high")
else:
    print("low")

となる
if の中に入力を判定としていれたほうがコードが短くできる

次に5位上の数だけリストから足し算する場合

mylist =[4, 0, 5, -1, 3, 10, 6, -8]
result = 0

for i in mylist:
    if i >= 5:
        result += i
print(result)

というように格納変数を用意し
for でループしながら
条件に当てはまるものだけ足していく

次に標準入力から5位上のものを足していくというもの

result = 0
num = int(input())

for i in range(num):
    tmp = int(input())

    if tmp >= 5:
        result += tmp

print(result)

result は判定した数値を足すための格納用変数

最初の num で入力回数を把握

これを for でループの回数にセット
tmp で入力した数値を代入し
それを if で判定し足していく

python の if その2

python の if その2

python の if の構文は

if 条件式 :

	処理する内容

となる

else をつけるときの構文は
[/python]
if 条件式:
true のときの処理
else:
false のときの処理
[/python]
となる

python は ruby みたいに改行で区切りなので
ほかの言語からくると最初は間違えやすいかもしれない

else if をつかうときには

if 条件式:
	if の判定が true のときの処理
elif if には当てはならないときの次の条件:
	else if が true のときの処理
else:
	どれにもあてはまらないときの処理

となる

python では if が他の言語みたいに
{} でスコープで囲わない

どちらかというと PHP に近い感覚

ちょっと変わった書き方がドットインストールにあったので
メモ

print "OK" if score > 60 else "NG"

というように
print で表示する時に条件式をつけて
結果により表示を変更することが可能

この場合、else や if の条件式に : はいらないみたい

python で if 文

python で if 文

score = 70
として

socre が 60 より大きければ
メッセージを表示するようにする

構文は

if 条件式 :
	行う処理

となる

python にしては珍しく : などをつかっている
あと、処理を書くときに字下げをしている必要がある

とはいっても、普通に : の後 Enter を押したら字下げされた

字下げされているなら、複数処理を書くこともできる

今回は

#coding: UTF-8
# if example

score = 70
if score > 69:
        print "ok"

とした

#coding:UTF-8

は日本語取扱いのために必要

if で使える比較演算子は
基本的には java とかPHPとほぼ同じ

ただし 論理演算子がちょっと違う
&& ではなく and
|| ではなく or
! ではなく not
と普通に単語でつかうことになる

詳しくは python 論理演算子で検索

論理演算子

が参考になる

ちなみに、書き方には

score = 70
if score > 69 and score <80:
        print "ok"

score = 70
if 64 < score <80:
        print "ok"

というように書くこともできる