BCCSkeltonでもやったTextToSpeech、DirectShowなどはどんな形でC#で使われているのだろう?」という疑問から昨年末から続いてきた【WPF】と【SAPI】シリーズですが、今回で一応お開きということになります。おかげさまで(知らずに使っていた)WinFormsとは異なるUIプラットフォームのWPF(従って、XamlやMSBuild、UIElementやContent等周辺知識も含めて)や現在のC#における音声合成・認識について「一舐め」させていただいた気分です。

 

さて音声認識については、BCCSkeltonでプログラミングをしていた時からSAPI 5で音声合成と一緒に提供されていたことは知っていました。しかしこの機能を必要としなかったので、好奇心からこのサンプルをコンパイルしましたが、認識精度が低いので興味を失っていました。(マイクが遠かったり、その精度も影響していたかも?)

 

今日日(キョウビ)の音声認識はもっとスマートですし、精度も高いみたいですね。以下のプログラムはWebで拾ったサンプルを合成し、WPFでウィンドウプログラム(注)に改造したものです。マイクが近いか、遠いかでも認識精度が大きく違うみたいです。

注:今回はまだ使ったことのなかったDockPanelを使ってみました。これを使うと上下左右に振り分けが可能になります。ただし、「下(Bottom)」を選択すると、更に左右に指定することはできないようです。

 

先ず、インストールされている音声認識エンジンを確認し、どれを使うか聞いてきます。(選択しないとずーーーーーっとこのループを繰り返します。プログラムをキャンセルで終了してもよいです。)

その後はマイクに向かって(近づけた方がよく認識されます)話しかけてください。文法に沿って文章を推測し、その後確定(認識)します。いっぱい表示される推定文がうざい、と感じたら「推定不要」ボタンを押してください。認識された分だけだとプロセスが分からない、という場合は「推定必要」ボタンを押しましょう。

 

【SpeechRecognitionWin.cs】

///////////////////////////////////////////////
//SpeechRecognitionEngineのテスト-Windows版
///////////////////////////////////////////////

using System;
using System.Windows;
using System.Windows.Controls;        //コントロールを利用する為
//using System.Windows.Media.Imaging;    //BitmapFrameを使用する為(解説:今回はプログラムアイコンを使用)
using System.Speech.Recognition;    //音声認識エンジン-System.Speech.dll

namespace SpeechRecognitionWin
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            MainWindow mwnd = new MainWindow();
            Application ap = new Application();    //解説:WPFでは明示的にインスタンスを作る必要がある
            ap.Run(mwnd);
        }
    }

    public partial class MainWindow : Window
    {
        //MainWindowクラスの変数
        SpeechRecognitionEngine recEng;    //音声認識エンジン
        TextBox TBox;                    //テキストボックス
        ListBox LBox;                    //リストボックス
        Button extBtn, hypoBtn;            //終了、推定表示可否ボタン
        bool AddHypo = true;            //推定処理の表示フラグ

        //コンストラクター
        public MainWindow()
        {
            //this.Icon = BitmapFrame.Create(new Uri("Icon.ico", UriKind.Relative));    //同じフォールダーのIcon.icoを読んで使用する(解説:今回はプログラムアイコンを使います。)
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;
            this.Background = SystemColors.WindowBrush;
            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            this.Width = 525;
            this.Height = 350;
            this.ResizeMode = ResizeMode.CanResizeWithGrip;
            this.ShowActivated = true;                            //アクティブ状態で初期表示するかどうか-規定値 true
            this.ShowInTaskbar = true;                            //タスクバ-にボタン表示するか否か
            this.Title = "Speech Recognition";
            this.Loaded += Window_Loaded;
        }
        
        //WM_CREATE時処理
        private void Window_Loaded(object sender, EventArgs e)
        {
            //コントロールの設定
            InitControls();
/*解説: 音声認識を行うには、

 (1)音声認識エンジンのインスタンスを作成する

 (2)ディクテーション用辞書を作成する

 (3)

*/

           //解説:インストールされていないと致命的エラーになりますが、Windowsのロケールのエンジンはあると思います。
           //インストールされている音声認識エンジンでインスタンスを生成

            recEng = new SpeechRecognitionEngine(SelectEngine());
            //DictationGrammar (ディクティション用の文法)の追加
            recEng.LoadGrammar(new DictationGrammar());
            try
            {
                //音声認識エンジンへの入力設定(解説:マイクなどの入力デバイスがないと致命的エラーとなります。)
                recEng.SetInputToDefaultAudioDevice();
                //非同期音声認識を継続、複数音声認識を実行する

//解説:非同期音声認識操作複数音声認識(一回だけで終了しない)を参照してください。

                recEng.RecognizeAsync(RecognizeMode.Multiple);
            }
            catch
            {
                MessageBox.Show("音声標準出力がありません", "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            //音声認識イベントハンドラー
            recEng.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(recEng_SpeechRecognized);
            //recEng.SpeechRecognized += recEng_SpeechRecognized;    //解説:表示方法が異なるだけで↑と同じ。
            //音声推定イベントハンドラー
            recEng.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(recEng_SpeechHypothesized);
            //recEng.SpeechHypothesized += recEng_SpeechHypothesized;    //解説:表示方法が異なるだけで↑と同じ。
        }

        //インストールされた音声認識エンジンを表示し、選択させる
        public RecognizerInfo SelectEngine()
        {
            //取り敢えず、インストールされた全音声認識エンジンをリストボックスに表示
            foreach(RecognizerInfo ri in SpeechRecognitionEngine.InstalledRecognizers())
            {
                LBox.Items.Add(ri.Culture.Name);
            }
            //音声認識エンジンの選択
            RecognizerInfo info = null;
            while(info == null)    //選択しない限り終了しない
            {
                //インストールされた音声認識エンジンを順に表示
                foreach(RecognizerInfo ri in SpeechRecognitionEngine.InstalledRecognizers())
                {
                    MessageBoxResult result = MessageBox.Show(
                                    "以下のエンジンが見つかりました。\r\n\r\n" + 
                                    ri.Name + " : " + ri.Culture.Name + 
                                    "\r\n\r\nこれを選択しますか?(キャンセル-終了)", "音声認識エンジン", 
                                    MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
                    if(result == MessageBoxResult.Yes)    //選択
                    {
                        info = ri;
                        MessageBox.Show(ri.Culture.Name + " で音声認識を開始します。", "通知", MessageBoxButton.OK, MessageBoxImage.Information);
                        break;
                    }
                    else if(result == MessageBoxResult.Cancel)    //キャンセルは終了
                    {
                        Close();
                    }
                }
            }
            return info;
        }

        //コントロールの設定
        public void InitControls()
        {
            //グリッド(Grid)を作成
            Grid grid = new Grid();
            //縦二列を作成・定義
            ColumnDefinition colL = new ColumnDefinition();
            ColumnDefinition colR = new ColumnDefinition();
            colL.Width = new GridLength(this.Width / 2, GridUnitType.Pixel);
            //colL.Width = GridLength.Auto;
            colR.Width = GridLength.Auto;
            grid.ColumnDefinitions.Add(colL);
            grid.ColumnDefinitions.Add(colR);
            //テキストボックスを作成
            TBox = new TextBox();
            //位置、フォントサイズとその他設定
            TBox.Margin = new Thickness(5, 5, 5, 5);
            TBox.FontSize = 12;
            TBox.HorizontalAlignment = HorizontalAlignment.Left;
            TBox.TextWrapping = TextWrapping.NoWrap;
            TBox.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
            TBox.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
            //グリッド左列にをテキストボックスを登録
            Grid.SetColumn(TBox, 0);        //Gridクラスの静的メソッドである点に注意
            grid.Children.Add(TBox);
            //ドックパネル(DockPanel)を作成

            //解説:今回はまだ使ったことのなかったDockPanelにしてみました。
            DockPanel dPanel = new DockPanel();
            //ラベルを生成
            Label lbl = new Label();
            lbl.Content = "このPCにインストールされている音声認識エンジン";
            lbl.HorizontalAlignment = HorizontalAlignment.Center;
            DockPanel.SetDock(lbl, Dock.Top);    //DockPanelクラスの静的メソッドである点に注意
            dPanel.Children.Add(lbl);
            //リストボックスを生成(解説:これも使ったことが無かったので無理矢理使ってみました。)
            LBox = new ListBox();
            LBox.SelectionMode = SelectionMode.Single;
            LBox.Margin = new Thickness(10, 10, 10, 10);
            LBox.HorizontalAlignment = HorizontalAlignment.Stretch;    //解説:伸びることは無いですね。
            LBox.VerticalAlignment = VerticalAlignment.Stretch;
            DockPanel.SetDock (LBox, Dock.Top);    //DockPanelクラスの静的メソッドである点に注意
            dPanel.Children.Add(LBox);
            //終了ボタンを生成(解説:「縦の底」を選ぶと下から挿入します。)
            extBtn = new Button();
            extBtn.Width = 60;
            extBtn.Height = 24;
            extBtn.Margin = new Thickness(10, 10, 10, 10);    //構造体 Thickness(左, 上, 右, 下)
            extBtn.HorizontalAlignment = HorizontalAlignment.Stretch;    //解説:結局中央(Center)になります。
            extBtn.VerticalAlignment = VerticalAlignment.Bottom;    //解説:「縦の底」
            extBtn.Content = "終了";
            extBtn.Click += extBtn_Click;
            DockPanel.SetDock (extBtn, Dock.Bottom);    //DockPanelクラスの静的メソッドである点に注意
            dPanel.Children.Add(extBtn);
            //推定ボタンを生成
            hypoBtn = new Button();
            hypoBtn.Width = 60;
            hypoBtn.Height = 24;
            hypoBtn.Margin = new Thickness(10, 10, 10, 10);    //構造体 Thickness(左, 上, 右, 下)
            hypoBtn.HorizontalAlignment = HorizontalAlignment.Stretch;    //解説:結局中央(Center)になります。
            hypoBtn.VerticalAlignment = VerticalAlignment.Bottom;
            hypoBtn.Content = "推定不要";    //解説:初期値(AddHypoが真の場合)
            hypoBtn.Click += hypoBtn_Click;
            DockPanel.SetDock (hypoBtn, Dock.Bottom);    //DockPanelクラスの静的メソッドである点に注意
            dPanel.Children.Add(hypoBtn);
            //グリッド右列にドックパネルを登録
            Grid.SetColumn(dPanel, 1);            //Gridクラスの静的メソッドである点に注意
            grid.Children.Add(dPanel);
            //MainWindowにgridを追加
            this.Content = grid;
        }

        //終了処理
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            //終了時に音声認識エンジンを開放する。

            //解説:一応はガーベージコレクターが解放するようですが、「注意」に明示的開放が必要とあります?
            recEng.Dispose();
        }

        //認識時の処理
        void recEng_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            this.TBox.Text = "認識:" + e.Result.Text + "(" + e.Result.Confidence + ")\r\n" + this.TBox.Text;
        }

        //推定時の処理
        void recEng_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            if(AddHypo)    //解説:AddHypoが真の際に推定値を表示する
                this.TBox.Text = "推定:" + e.Result.Text + "("+ e.Result.Confidence + ")\r\n" + this.TBox.Text;
        }

        //推定ボタンクリック時の処理
        void hypoBtn_Click(object sender, RoutedEventArgs e)
        {
            AddHypo = !AddHypo;    //解説:所謂トグル処理
            if(AddHypo)
                hypoBtn.Content = "推定不要";
            else
                hypoBtn.Content = "推定必要";
        }

        //終了ボタンクリック時の処理
        void extBtn_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}

 

今回もWPFでウィンドウプログラムを書きましたが、次は【WPF】シリーズの総括をしようと思っています。