めあとるーむ記録帳

なんか書く

C#(.NET)のSystem.Speech.Recognitionライブラリを使う

1ヶ月ぐらいで辞めた仕事(の出向先)で音声認識をゴニョゴニョしてたけどそのときにSystem.Speech.Recognitionライブラリを使った

このライブラリ何がアレって基本的な使い方の記事も少なく細かいところまでアレコレしようとするとだいたい公式ドキュメント(の英語版か機械翻訳版)しかないという状態だった

なんでまあ覚えてる範囲と出来た範囲でメモを残しておく

いやほんと公式ドキュメントも情報量十分かって言うとどうだろうってなっちゃうやつだったからな……イングリッシュ読めるようになりたい

具体的なコードとかはGithubに上げる。完成したら解説記事を書く(現在未完)

github.com


公式

System.Speech.Recognition 名前空間

基本はこの名前空間のクラスを使う。以下主に使うクラスなどなど

  • System.Speech.SpeechRecognitionEngine : 音声認識のエンジン部分。ここに音声認識時のイベントとか設定とかある。
  • System.Speech.SpeechRecognizer : 上とほぼ同じだがこっちはWindowsDesktop向け。上はプロセス上で動くやつ(違いはよくわからない。さっき見てて気づいた)
  • System.Speech.Grammar : 認識時の主軸となる文法データを取り扱う。同名前空間にGrammarBuilderとかもある
  • System.Speech.DictationGrammar : ディクテーション(発する言葉全てを文字起こしする)の場合に使う文法データ
  • System.Speech.Choices : 文法データのもとになる登録語をまとめるクラス
  • 各種 EventArgs : 認識したときのイベントで渡されるやつ

だいたいこのあたり。文法データを外部データにしたい場合は System.Speech.Recognition.SrgsGrammar 名前空間の SrgsGrammarCompile とかでバイナリデータを出してあげたり、Srgs文法ファイルを読ませたりできる

文法ファイルとはなんぞや、ってなる(なった)ので先に説明しておくと、このライブラリは基本的に音声コマンドを実行するためのライブラリなので、そのコマンドをまとめたのが文法というものである

詳細は後述するが、この文法に則って認識するため、自由な認識をさせることはできない。もっとも DictationGrammar であれば認識できる(が、こちらだと単語の登録はできない)


RecognitioEngine / Recognizer が音声認識機能を提供してくれるクラスになる

違いはドキュメントを見た感じだとRecognitionEngineのほうが高機能。言語・地域を設定したり、AudioStreamで音データを受け取ったり、読み込み途中で文法データを再読込させたりもできる

ただ認識自体は同じようで、イベントとか見た感じその辺に違いがあるようには見えなかった(認識以外だと環境に関係するイベントの違いがあるっぽい?)

ここではRecognitionEngineをベースに解説する。具体的なコードはそのうちGithubに上げるか公式ドキュメントで何となく理解できると思う

SpeechRecognitionEngine クラス (System.Speech.Recognition)

まずインスタンスを作ったら

  1. 文法ファイルを読み込む (LoadGrammar メソッドなど)
  2. Eventの受け取りを追加 (SpeechRecognized イベントなど)
  3. 音声のソースを設定 (SetInputToDefaultAudio メソッドなど)
  4. 音声認識の開始開始する (Recognize メソッドなど)

の順で動かす。厳密に言うと順番は関係ないかもしれないが、公式ドキュメントはどのクラス・メソッドでもだいたいこの順にやっているのでこれに従ったほうがいいと思う


文法ファイルと文法ファイルの読み込みについて

特定のコマンドや単語に反応させる、という機能を作るときにその指定をする機能

具体的にコマンドの例を挙げると「リビングの電気をつけて」とか「キッチンの電気を消して」とか

これらを登録することで認識したときに「リビングの電気をつけて」という認識をしたよー、とイベントが返してくれるわけである(ただしディクテーションは除く)

そして当然だが、これらのコマンドを全部登録するのは骨が折れる。「○○(場所)の電気を××(つけて/消して)」と全パターン登録するのは(不可能じゃないけど)面倒だよね、ってわけでちゃんとどうにかする方法がある

1つはSRGSドキュメントを使って記述し、それを読み込ませる

Speech Recognition Grammar Specification Version 1.0

音声コマンドにおけるXML記述の規格らしく、すでに他環境用に書いてる人とかはこれをそのまま XMLReader で読んで System.Speech.Recognition.SrgsGrammar クラスに渡してそのまま Grammar クラスに渡せばもう辞書登録は完了する

もう1つがGrammarBuilderクラスを用いてその場で組み立てる。

どちらの方法でもワイルドカード、単語リストなどを組み合わせることでコマンドのパターンを網羅する仕組みになっている

以下GrammarBuilderでもっと具体的に解説する。

まず単語のリストはChoicesクラスで用意する。例えばさっきの電気のオンオフであれば

Choices choices1 = new Choices(new string[]{"リビング","キッチン","玄関"});
Choices choices2 = new Choices(new string[]{"つけて","消して"});

と登録し、GrammarBuilder で

GrammarBuilder gb = new GrammarBuilder();
gb.Append(choices1);
gb.Append("の電気を");
gb.Append(choices2);

とすると、{choices1}の電気を{choices2} というパターンに対応できる

当然文法は順序によって内容が異なるのでそこは注意が必要。Appendメソッドで後ろに追加していくのがよく使うパターンだと思う

また、同時にワイルドカードを差し込んだり( AppendWildcard )、一部ディクテーション ( AppendDictation )を挟んだりもできる

この2つはどう違うかというとワイルドカードは内容を認識しないけどディクテーションは認識しようとすることだと思う。試したらまた書くね(少なくともワイルドカードは ... となるだけで内容までは出ない)

GrammarBuilderで文法ファイルを形成したらあとはGrammarクラスに渡してあげて、GrammarクラスをLoadGrammarメソッドで読み込んであげるといい

ちなみに非同期でやってくれるというLoadGrammarAsyncメソッドもあるが、Async/Awaitは使えないので自分で別スレッド作ってあげるかTask.Factory.StartNewあたりで渡してあげないといけないっぽい。終わったらLoadGrammarComplitedイベントが発火する


音声認識は認識した段階で各種イベントとして発火するようになっている

反応するイベントは以下の通り

  • SpeechRecognized
  • SpeechHypothesized
  • RecognizeComplited
  • SpeechDetected
  • SpeechRecognitionRejected

これがまた結構めんどくさく、

イベント 内容
SpeechRecognized Grammarに登録した内容を認識した。一致率は高い
SpeechHypothesized Grammarに登録した内容を認識した。一致率は中程度
RecognizeComplited 音声認識終了時にGrammarに登録した内容を認識した
SpeechDetected 何かを認識した(何を認識したかはわからない)
SpeechRecognitionRejected 認識したものがGrammarに登録したものに一致しなかった

Rejectedの一致しなかった、というのがよくわからないと思うが、ドキュメントを見ても実際に動かしてみても本当によくわからなかった。謎のイベントだった

また、SpeechDetectedもよくわからない(反応するだけで内容は不明、EventArgsも特別それっぽい情報がない)ものだった

とりあえず、認識だけで言うとSpeechRecognizedで十分である。SpeechHypothesizedはあまりにも大雑把で認識しすぎる感じである

認識次第終了、というかたちを取るのであればRecognizeComplitedを使うほうがよさそうである

認識結果は各イベントから渡されるEventArgsの中に入っている(Detected除く)。これを取ってくれば認識結果を得られるというわけになる


精度自体はあんまり高くない(コマンドの種類によっては許容内。ディクテーションは正直イマイチ)。音声認識でメモを取るとかなら Google の Cloud Speech API とか Microsoft の Bing Speech API とか使ったほうがいい

今回は音声認識を起動したら人同士の会話を聞き続けてその中でキーワードが出てきたらそれを列挙するというものだったので、最大1分(WebSocketなら10分?APIによる)しか繋げないAPIを使うことは無理だった

ひとまずここまで。ソースが上がったらGithubに上げて解説記事のような物を書くかもしれない


以下愚痴に近い私見

前職でも前前職でも「マイクを起動しっぱなしで人同士の会話を常に録音したい」というのが言われたが正直なところそんな機能がちゃんと提供できるのはあと5年ぐらいかかると思う。というか機械に向かって話しかけるのと人同士の会話を解析するっていうのは別のことで前者は割と形になってきてるけど後者はまだまだだと思うし、一緒にするのも止めてもらいたい

だいたい人同士の会話というのは人の想像以上に文章として破綻しているケースが多いので、それなりに時間を要するのではないかというのが私見

あとめっちゃどうでもいいけど文末の句点の有無が統一されてなさすぎて気持ち悪かった(全部取った