正規表現で音声認識(C#)
サンプルexe
http://rtilabs.net/files/2011_11_20/SpeechRecognitionRegexp.exe
サンプルソース
http://rtilabs.net/files/2011_11_20/SpeechRecognitionRegexp.zip
開発環境: VS2010 / C# 4 / win7 64bit ultimate
音声認識では簡単なルールか SAPI xml形式で記述することで、複雑なパターンを記述できますが、やっぱり正規表現でルール書きたくね?ってことで正規表現でルールをかける音声認識エンジンを作ってみましょう。
こんなXMLより、
<?xml version="1.0" encoding="UTF-8"?> <GRAMMAR> <RULE name="S" toplevel="ACTIVE"> <L> <P>こんにちは</P> </L> <L> <P>レモン</P> <P>ばなな</P> </L> <L> <P>ください</P> </L> </RULE>
っていうXMLより、こっちのほうが解りやすくね?って話しです。
こんにちは(レモン|ばなな)ください
もっと、複雑なルールになると XML でますます書きづらくなります。
SAPI って 二重ネストを書きづらい。
こんな長い正規表現をSAPI XML で技術するのは結構大変です。
こんにちは(レモン|ばなな(みるく|ジュース)|なのは?)ください
一応、MS-Researchの人の Creating Speech Recognition Grammars from Regular Expressions for Alphanumeric Concepts ってのがあるんですが、まー動くものが欲しいよねってことで作ってみました。
http://research.microsoft.com/apps/pubs/default.aspx?id=75237
ソースは長いんですけど、キモなのは、AddRegexp メソッドです。
これまた長いんですが、正規表現をパースしながら、それをSAPIのルールに置き換えています。
using System; using System.Collections.Generic; using System.Linq; using System.Text; //using System.Speech.Recognition; これはwindows7(vistaも?)から邪悪な仕様になったので使ってはならぬ using SpeechLib; //音声認識をCOMで制御する. (Microsoft Speech Library Object) namespace SpeechRecognitionRegexp { class SpeechRecognizer { //音声認識オブジェクト private SpeechLib.SpInProcRecoContext RecognizerRule = null; //言語モデル private SpeechLib.ISpeechRecoGrammar RecognizerGrammarRule = null; //言語モデルのルールのトップレベルオブジェクト. private SpeechLib.ISpeechGrammarRule RecognizerGrammarRuleGrammarRule = null; //中継するデリゲート public event _ISpeechRecoContextEvents_StartStreamEventHandler StartStream; //ストリームが開始された時 public event _ISpeechRecoContextEvents_HypothesisEventHandler Hypothesis; //認識途中でなんか拾った時 public event _ISpeechRecoContextEvents_RecognitionEventHandler Recognition; //認識完了時 public event _ISpeechRecoContextEvents_FalseRecognitionEventHandler FalseRecognition; //認識失敗時 public event _ISpeechRecoContextEvents_EndStreamEventHandler EndStream; //ストリーム終了時 public SpeechRecognizer() { //ルール認識 音声認識オブジェクトの生成 this.RecognizerRule = new SpeechLib.SpInProcRecoContext(); //マイクから拾ってね。 this.RecognizerRule.Recognizer.AudioInput = this.CreateMicrofon(); //イベント設定(中継) this.RecognizerRule.Hypothesis += delegate(int streamNumber, object streamPosition, SpeechLib.ISpeechRecoResult result) { this.Hypothesis(streamNumber, streamPosition, result); }; this.RecognizerRule.Recognition += delegate(int streamNumber, object streamPosition, SpeechLib.SpeechRecognitionType srt, SpeechLib.ISpeechRecoResult isrr) { this.Recognition(streamNumber, streamPosition, srt, isrr); }; this.RecognizerRule.StartStream += delegate(int streamNumber, object streamPosition) { this.StartStream(streamNumber, streamPosition); }; this.RecognizerRule.FalseRecognition += delegate(int streamNumber, object streamPosition, SpeechLib.ISpeechRecoResult isrr) { this.FalseRecognition(streamNumber, streamPosition, isrr); }; this.RecognizerRule.EndStream += delegate(int streamNumber, object streamPosition, bool streamReleased) { this.EndStream(streamNumber, streamPosition, streamReleased); }; //言語モデルの作成 this.RecognizerGrammarRule = this.RecognizerRule.CreateGrammar(0); } //マイクから読み取るため、マイク用のデバイスを指定する. // C++ だと SpCreateDefaultObjectFromCategoryId ヘルパーがあるんだけど、C#だとないんだなこれが。 private SpeechLib.SpObjectToken CreateMicrofon() { var ObjectTokenCat = new SpeechLib.SpObjectTokenCategory(); ObjectTokenCat.SetId(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\AudioInput"); var token = new SpeechLib.SpObjectToken(); token.SetId(ObjectTokenCat.Default); return token; } //音声認識でルールを追加する public void SetRegexp(string str) { //現在のルールをすべて消す. this.RecognizerGrammarRule.Reset(0); //言語モデルのルールのトップレベルを作成する. this.RecognizerGrammarRuleGrammarRule = this.RecognizerGrammarRule.Rules.Add("TopLevelRule", SpeechRuleAttributes.SRATopLevel | SpeechRuleAttributes.SRADynamic); // this.RecognizerGrammarRuleGrammarRule.Clear(); //正規表現でルールを追加する. this.AddRegexp(str , this.RecognizerGrammarRule , this.RecognizerGrammarRuleGrammarRule); //ルールを反映させる。 this.RecognizerGrammarRule.Rules.Commit(); //音声認識開始。(トップレベルのオブジェクトの名前で SpeechRuleState.SGDSActive を指定する.) this.RecognizerGrammarRule.CmdSetRuleState("TopLevelRule", SpeechRuleState.SGDSActive); } //音声認識でルールを追加するの実装部(ムダに長い) private void AddRegexp (string str, ISpeechRecoGrammar grammer, ISpeechGrammarRule topRule) { string matchString; int i; if ((topRule.Attributes & SpeechRuleAttributes.SRATopLevel) == SpeechRuleAttributes.SRATopLevel) { //一番最初だけ正規表現の構文変換をかける. // .+ --> (:?.*) // (まる|さんかく)? --> (まる|さんかく|) 正しい正規表現としてはエラーだが、このエンジンの場合容認する. // なのは? --> なの(は|) string optstr = ""; for(i = 0 ; i < str.Length ;++i) { if (i+1 < str.Length && str[i] == '.' && str[i + 1] == '+') { // .+ --> (:?.*) optstr += "(?:.+)"; i += 1; } else if (i + 1 < str.Length && str[i + 1] == '?') { if (str[i] == ')') {// (まる|さんかく)? --> (まる|さんかく|) optstr += "|)"; } else {// なのは? --> なの(は|) optstr += "(?:" + str[i] + "|)"; } i += 1; } else if (str[i] == '*' || str[i] == '+' || str[i] == '.' || str[i] == '[' || str[i] == ']') { throw new Exception("現在は、メタ文字 " + str[i] + " は利用できません。利用可能なメタ文字 () | .+ ?"); } else { optstr += str[i]; } } str = optstr; } //正規表現をパースしながら回す. int splitPos = 0; ISpeechGrammarRule currentRule = topRule; for (i = 0; i < str.Length; ++i) { if (str[i] == '(') { //閉じ括弧まで飛ばす. ) int nest = 1; int n = i + 1; for( ; n < str.Length; ++n ) { if (str[n] == '(') { ++nest ; } else if (str[n] == ')') { --nest ; if (nest <= 0) { break; } } } //ネスとする前の部分 matchString = str.Substring(splitPos, i - splitPos); //キャプチャー? if (str[i + 1] == '?' && str[i + 2] == ':') { i += 2; } ISpeechGrammarRule nestRule = grammer.Rules.Add( currentRule.Name + "_" + i, SpeechRuleAttributes.SRADynamic); ISpeechGrammarRuleState nestRuleState = currentRule.AddState(); //ネストする前の部分を挿入. currentRule.InitialState.AddWordTransition(nestRuleState, matchString); //かっこの後にも構文が連続する場合、そのツリーを作成する. if (n+1 < str.Length && (str[n + 1] != '|')) { ISpeechGrammarRule afterRule = grammer.Rules.Add( currentRule.Name + "_af" + +i, SpeechRuleAttributes.SRADynamic); ISpeechGrammarRuleState afterRuleState = currentRule.AddState(); nestRuleState.AddRuleTransition(afterRuleState, nestRule); afterRuleState.AddRuleTransition(null, afterRule); currentRule = afterRule; } else { //かっこで構文がとまる場合はそこで終端 nestRuleState.AddRuleTransition(null, nestRule); } //ネストしているルールを再帰して実行. matchString = str.Substring(i + 1, n - i - 1); this.AddRegexp(matchString, grammer, nestRule); i = n ; splitPos = n + 1; //+1は最後の ) を飛ばす. iは forの ++i で i == splitPos となる。(わかりにくい) } else if (str[i] == '|') { matchString = str.Substring(splitPos, i - splitPos); if (matchString.Length >= 1) { currentRule.InitialState.AddWordTransition(null, matchString); } splitPos = i + 1; currentRule = topRule; } else if (str[i] == '.' && str[i+1] == '+') { currentRule.InitialState.AddSpecialTransition(null, SpeechSpecialTransitionType.SSTTDictation); i += 1; splitPos = i + 1; } } //最後の残り matchString = str.Substring(splitPos, i - splitPos); if (str.Length >= 1 && str[i - 1] != ')') { currentRule.InitialState.AddWordTransition(null, matchString); } } } }
現在、 () によるキャプチャを実装していません。
今後は、キャプチャの実装したりしてもっと使いやすくする予定です。
それと、使えるメタ文字に文字に制限があります。
現在使えるのは、 () (:?) +? ? | です。
{} [] * . + とかは、使えません。