自分の手で「スマートホーム」。 パート3. Googleを使用した合成と音声認識

前回の記事では、1秒に1回、スナップショットとしてWebカメラから画像を取得できました。 音声認識と音声合成という約束を果たす時が来ました。



小さな余談



この記事から始めて、「スマートホーム」のすべてのサブシステムを調整するソフトウェアについて説明します。 この記事で説明したコードから既に十分に進んでいることに注意する必要があると考えます。trac- linkを使用して、より新しくより機能的なバージョンを見つけることができます。 配布はGNU GPLv3の下でライセンスされています 。 誰かが開発に参加したい場合-どういたしまして;)



いくつかの情報





音声認識


最初の記事ですでに書いたように、音声合成と音声認識にGoogleサービスを使用します。 多くの人がAn​​droid OSを実行しているモバイルデバイスで音声検索に出くわしたと思います。 追加機能として、この同じ音声検索がGoogle Chromeブラウザーに追加されました。 同社はこのサービスの公式APIをまだ発表していませんが、Chromeのオープンソースのおかげで、職人は何がどこに送られ、何がどのように返されるのかを見つけました。 次のようになります。



  1. サウンドサンプリング周波数が16000 Hz、モノラルのwavファイルを記述します
  2. 結果のファイルをflac形式で再コーディングします
  3. ファイルをアドレスhttps://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&lang=en-RUに送信します。これは、ChromeクライアントとしてGoogleに見える
  4. JSON形式で応答を取得します




答えは次のようなものです。



{"status":0,"id":"84e03bf4efe17fa7856333560d6faba4-1","hypotheses":[{"utterance":" ","confidence":0.85437811}]}







最後の2つのフィールド- 発話自信のみの回答に興味があります。 1つ目は、認識される必要な単語/フレーズ、2つ目は認識精度です。 信頼度0.5より大きい場合、認識が信頼できると仮定できます。



音声合成


音声合成もGoogleサービスを介して実行されますが、私が知る限り、公式のAPIは発表されていません。 テキストから音声フレーズを取得するには、完全に複雑なアクションの組み合わせを作成する必要があります。



  1. 次の形式のリクエストを送信します:http://translate.google.com/translate_tts?tl=en&q=text。ヘッダーにGoogle Chromeとして表示されます
  2. MP3エンコードで応答ストリームを取得する




ご覧のとおり、ここのすべてはまったく複雑ではありません。 次に、この情報をプログラムで実装します。



いくつかのコード



すでに書いたように、 perlで特別に作成されたデーモンは、「スマートホーム」の集中管理に従事します。 あなたの謙虚なサーバントは単なるシステム管理者であるため、キックしないコードの品質を事前にお願いします:)

したがって、このソフトウェアが実行する必要のあるタスクの範囲を決定します。



  1. 音声ファイル認識リクエストを受け入れる
  2. デバイスの状態を確認し、コマンドを与えます
  3. コマンドシーケンスが検出された場合に何らかのアクションを実行する
  4. センサーとカメラからのデータに所定の方法で応答する
  5. 統計、記録、ログを保存する
  6. ステータスの表示、カメラ、コマンドの送信などに便利なWebベースのインターフェイスを備えています。




おそらく私は何かを忘れたか見逃したかもしれませんが、私にはそう思われますが、これらはSmart Homeソフトウェアの主なタスクです。 それでは、これらすべての実装を始めましょう。



perl TCP / IPデーモンを作成するには、 Net :: Server :: Forkモジュールを使用します。 perlはすでにおなじみのものだと思います。
 #!/usr/bin/perl -w package iON; use strict; use utf8; use base qw(Net::Server::Fork); sub process_request { my $self = shift; while (<STDIN>) { if (/text (\d+)/) { toText($1); next; } if (/quit/i) { print "+OK - Bye-bye ;)\n\n"; last; } print "-ERR - Command not found\n"; logSystem(" : $_", 0); } } iON->run(port => 16000, background => undef, log_level => 4, host => 'localhost'); 1;
      
      



ここに書かれていることを簡単に調べてください。 Net :: Server :: Forkモジュールに基づいてiONという名前のモジュールを宣言し、localhostのポート16000でサーバーを最大レベルのログ詳細で「デーモン」モードなしで実行します。 次に、 process_request()関数をオーバーロードします。 彼女は、クライアントから受信したデータを処理する責任があります。 私たちの場合、サーバーがtext numberという形式のテキストを見ると、クライアントが私たちに送った数字の形式のパラメーターでtoText関数が実行されます。 チームを終了すると、すべてが明確になったと思います。



toText()関数何をますか? はい、実際には、音声認識!
 sub toText { my $num = shift; print "+OK - Trying recognize text\n"; my $curl = WWW::Curl::Easy->new; $curl->setopt(CURLOPT_HEADER,1); $curl->setopt(CURLOPT_POST,1); #$curl->setopt(CURLOPT_VERBOSE, 1); my @myheaders=(); $myheaders[0] = "Content-Type: audio/x-flac; rate=16000"; $curl->setopt(CURLOPT_HTTPHEADER, \@myheaders); $curl->setopt(CURLOPT_URL, 'https://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&lang=ru-RU'); my $curlf = WWW::Curl::Form->new; $curlf->formaddfile("data/input-$num.flac", 'myfile', "audio/x-flac"); $curl->setopt(CURLOPT_HTTPPOST, $curlf); my $response_body; $curl->setopt(CURLOPT_WRITEDATA,\$response_body); # Starts the actual request my $retcode = $curl->perform; # Looking at the results... if ($retcode == 0) { $response_body =~ /\n\r\n(.*)/g; my $json = $1; my $json_xs = JSON::XS->new(); $json_xs->utf8(1); my @hypo = $json_xs->decode($json)->{'hypotheses'}; my $dost = $hypo[0][0]{'confidence'}; my $text = $hypo[0][0]{'utterance'}; $dost = 0.0 if !defined $dost; $text = "" if !defined $text; print "+OK - Text is: \"$text\", confidence is: $dost\n"; if($dost > 0.5) { checkcmd($text); } { print "+ERR - Confidence is lower, then 0.5\n"; #sayText("  !"); } } else { # Error code, type of error, error message print("+ERR - $retcode ".$curl->strerror($retcode)." ".$curl->errbuf); } system("rm data/input-$num.flac"); }
      
      



詳細は説明しません。これらは、テキストを認識するために必要なアクションです。 Googleには、 input-number.flacという名前のデータサブディレクトリからファイルが供給されます 。 少し後で、そこでどのように形成されるか。 後-答えが読み取られ、その信頼性が0.5を超える場合、認識されたテキストはパラメーターとしてcheckcmd()関数に渡されます。 すべてが終了すると、サウンドファイルが削除されます。 curlプログラムをインストールし、スクリプトの先頭にさらにモジュールを追加する必要があることに注意してください。
 use WWW::Curl::Easy; use WWW::Curl::Form; use JSON::XS;
      
      



次に、音声合成について説明します。 これは、 sayText()と呼ばれる関数によってパラメーターとして処理されます。この関数は、発声する必要がある実際のテキストを受け取ります。 ただし、最初に、不足しているモジュールとグローバル変数をいくつか追加します。
 require Encode; use URI::Escape; use LWP::UserAgent; our $mp3_data;
      
      



次に、コード自体:
 sub sayText { my $text = shift; print "+OK - Speaking \"$text\"\n"; my $url = "http://translate.google.com/translate_tts?tl=ru&q=".uri_escape_utf8($text); my $ua = LWP::UserAgent->new( agent => "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2"); $ua->get($url, ':content_cb' => \&callback); open (MP3, "|padsp splay -M") or die "[err] Can't save: $!\n"; print MP3 $mp3_data; close(MP3); $mp3_data = undef; print "+OK - Done!\n"; return; } sub callback { my ($data, $response, $protocol) = @_; $mp3_data .= $data; # }
      
      



ご覧のように、ストリーム形式のサーバー応答は、 コールバック()関数によって処理され、 $ mp3_data変数にデータが追加されます。 データはパイプを介してsplayプログラムに転送されます。splayプログラムは、OSSをエミュレートするpadspプログラムを介して起動されます(UbuntuではOSSが切断されました)。 -Mスイッチを使用すると、プログラムは標準入力からデータを再生します。



ここで、 データディレクトリのflacにある不思議なファイルの場所について話しましょう。 ここではすべてが簡単です-別のスクリプトがこれを行います:
 #!/usr/bin/perl use strict; use IO::Socket; while (1) { my $rnd = int(rand(1000)); `rec -q -c 1 -r 16000 ./data/input-$rnd.wav trim 0 4`; `flac -f -s ./data/input-$rnd.wav -o ./data/input-$rnd.flac`; `rm ./data/input-$rnd.wav`; my $sock = new IO::Socket::INET( PeerAddr => "localhost", PeerPort => 16000, Proto => 'tcp') || next; print $sock "text ".$rnd; undef $rnd; }
      
      



ご覧のとおり、スクリプトから呼び出されるいくつかのプログラムが記録とフォーマット変換を実行します。



  1. rec( soxプログラム配布キットから)
  2. フラック




recコマンドは、名前に乱数が含まれる4秒の短いレコードを作成します。これはflacでピンチされます。 この後、メインデーモンへの接続が行われ、 テキストコマンドにthat_same_randomnom_numberが渡されます 。 なぜ4秒の短いメモを書いているのですか? 問題は、コンピューターがどのように音声を記録するかです。 ここでは2つのソリューションが可能です。



  1. 永久録音
  2. 特定のボリュームを超えたときにファイルを記録する




2番目のオプションは、マイクの不具合など、さまざまな理由で私には向いていませんでした。)最初のオプションを永続録音でさらに詳しく分析します。 記録を多数の小さな断片に分割し、それらを常に認識のためにGoogleサーバーに送信します。 私のチームはこれまでに最大3〜4秒で到着することがわかりました。 1秒の間隔でスクリプトのコピーを複数(5つと想定)実行すると、連続した音声認識が得られます。 この機能をメインプログラムに追加します。
 for(1..5) { system("perl mic.pl &>/dev/null"); sleep 1; }
      
      



複合体全体の動作を確認するには、 checkcmd()関数を実装するだけです。 また、誤検知を排除するためのターゲットアドレス指定も必要です。
 sub checkcmd { my $text = shift; if($text =~ //) { sayText("  - $text"); # if $text eq "  "; } }
      
      



今、すべてを1つの山に入れます。 srv.plmic.plという 2つのスクリプトと、サウンドファイルを保存するためのdataサブディレクトリを取得しました



srv.pl



 #!/usr/bin/perl -w package iON; use strict; use utf8; use WWW::Curl::Easy; use WWW::Curl::Form; use JSON::XS; use URI::Escape; use LWP::UserAgent; require Encode; use base qw(Net::Server::Fork); ##  ################################ $|=1; our $parent = $$; our $mp3_data; ################################ for(1..5) { system("perl mic.pl &>/dev/null"); sleep 1; } ##    ############################### iON->run(port => 16000, background => undef, log_level => 4, host => 'localhost'); ################################ ################################ sub DESTROY { if($$ == $parent) { system("killall perl"); system("rm data/*.flac && rm data/*.wav"); } } ##    ################################ sub process_request { my $self = shift; while (<STDIN>) { if (/text (\d+)/) { toText($1); next; } if (/quit/i) { print "+OK - Bye-bye ;)\n\n"; last; } print "-ERR - Command not found\n"; } } ############################### ############################### sub toText { my $num = shift; print "+OK - Trying recognize text\n"; my $curl = WWW::Curl::Easy->new; $curl->setopt(CURLOPT_HEADER,1); $curl->setopt(CURLOPT_POST,1); #$curl->setopt(CURLOPT_VERBOSE, 1); my @myheaders=(); $myheaders[0] = "Content-Type: audio/x-flac; rate=16000"; $curl->setopt(CURLOPT_HTTPHEADER, \@myheaders); $curl->setopt(CURLOPT_URL, 'https://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&lang=ru-RU'); my $curlf = WWW::Curl::Form->new; $curlf->formaddfile("data/input-$num.flac", 'myfile', "audio/x-flac"); $curl->setopt(CURLOPT_HTTPPOST, $curlf); my $response_body; $curl->setopt(CURLOPT_WRITEDATA,\$response_body); # Starts the actual request my $retcode = $curl->perform; # Looking at the results... if ($retcode == 0) { $response_body =~ /\n\r\n(.*)/g; my $json = $1; my $json_xs = JSON::XS->new(); $json_xs->utf8(1); my @hypo = $json_xs->decode($json)->{'hypotheses'}; my $dost = $hypo[0][0]{'confidence'}; my $text = $hypo[0][0]{'utterance'}; $dost = 0.0 if !defined $dost; $text = "" if !defined $text; print "+OK - Text is: \"$text\", confidence is: $dost\n"; if($dost > 0.5) { checkcmd($text); } { print "+ERR - Confidence is lower, then 0.5\n"; } } else { # Error code, type of error, error message print("+ERR - $retcode ".$curl->strerror($retcode)." ".$curl->errbuf); } system("rm data/input-$num.flac"); } ############################### ##    ############################### sub checkcmd { my $text = shift; chomp $text; $text =~ s/ $//g; print "+OK - Got command \"$text\" (Length: ".length($text).")\n"; if($text =~ //) { sayText("  - $text"); } return; } ##  ############################### sub sayText { my $text = shift; print "+OK - Speaking \"$text\"\n"; my $url = "http://translate.google.com/translate_tts?tl=ru&q=".uri_escape_utf8($text); my $ua = LWP::UserAgent->new( agent => "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2"); $ua->get($url, ':content_cb' => \&callback); open (MP3, "|padsp splay -M") or die "[err] Can't save: $!\n"; print MP3 $mp3_data; close(MP3); $mp3_data = undef; print "+OK - Done!\n"; return; } sub callback { my ($data, $response, $protocol) = @_; $mp3_data .= $data; # } ######################################## ######################################## 1;
      
      





mic.pl



 #!/usr/bin/perl use strict; use IO::Socket; while (1) { my $rnd = int(rand(1000)); `rec -q -c 1 -r 16000 ./data/input-$rnd.wav trim 0 3`; `flac -f -s ./data/input-$rnd.wav -o ./data/input-$rnd.flac`; `rm ./data/input-$rnd.wav`; my $sock = new IO::Socket::INET( PeerAddr => "localhost", PeerPort => 16000, Proto => 'tcp') || next; print $sock "text ".$rnd; undef $rnd; }
      
      





どうした



スクリプトを実行する権利を付与します。



chmod 755 srv.pl mic.pl







srv.plスクリプトを実行し、すべてのプロセスの開始を待ちます。たとえば、「System! 1、2、3!」 数秒後に「あなたのチーム-1、2、3」と聞きました。 私たちのチームはいくつかのサウンドファイルに分類され、それに応じて数回実行されることに注意してください。 これを回避するには、最後のコマンドにチェックを入力する必要があります。 次のパートでこの機能を追加します。



合計



この記事では、「スマートホーム」システムを管理するためのソフトウェアのベースを実装しました。 音声を認識して合成する以外にはほとんど何もできませんが、これは一時的なものです;)



次の記事では、おいしいものとカメラの表示を使用して、これにすべてWebインターフェイスを固定する方法を説明します。



UPD: パート4






All Articles