2012年9月13日木曜日

KINECTサンプル(C#) HoGを使った人認識

今回はちょっと難しい(自分でも仕組みを全部理解できていません)サンプルです。
内容はSUBARUのEyeSightみたいに人物(EyeSightでは人物以外の物体も認識していますが)を認識するプログラムです。
もともとはFaceBookで知り合いが人認識できないかなという話題から始まって、たしかOpenCVにサンプルがあったことを思い出したので、それを参考に作ってみました。(最終的に参考にしたのはOpenCVSharpのサンプルプログラムですけど)

ではサンプルです。

まず、参照設定ですが、以前のOpenCVサンプルで追加した参照以外に今回は「OpenCvsharp.CPlusPlus」を追加してください。あと「OpenCvSharpExtern.dll」をプロジェクトにコピーして、コピーしたファイルプロパティ内の「出力ディレクトリにコピー」を「常にコピーする」に変更してください。


MainWindow.xaml
<Window x:Class="Kinect_HoG.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="519" Width="662">
    <Grid>
        <Image Height="480"  Name="image1" Stretch="Uniform"  Width="640" />
        <Canvas Height="480" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="640" />
    </Grid>
</Window>

イメージコントロールとキャンバスコントロールを重ねて配置しています。


MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using System.Drawing;
using Microsoft.Kinect;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using OpenCvSharp.CPlusPlus;
using CPP = OpenCvSharp.CPlusPlus;

namespace Kinect_HoG
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor kinect;
        private byte[] colorPixel;
        private IplImage CvImg;
        private WriteableBitmap srcImg;
        private Int32Rect bitmapRect;

        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTがみつかりません。");
                Close();
            }

            kinect = KinectSensor.KinectSensors[0];

            kinect.ColorStream.Enable();
            kinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(kinect_ColorFrameReady);

            kinect.Start();

            //カラー画像用
            srcImg=new WriteableBitmap(kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight,96,96,PixelFormats.Rgb24,null);
            bitmapRect =new Int32Rect(0,0,kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight);

        }

        void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame != null)
                {
                    colorPixel = new byte[colorFrame.PixelDataLength];
                    colorFrame.CopyPixelDataTo(colorPixel);

                    //カラーイメージ表示
                    srcImg.WritePixels(bitmapRect, CvtBGR32toRGB24(colorPixel),srcImg.BackBufferStride, 0);
                    image1.Source = srcImg;

                    //10フレームごとにHoG処理
                    if (colorFrame.FrameNumber % 10 == 0)
                    {
                        Hog(srcImg);
                    }
                }
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="srcImg"></param>
        private void Hog(WriteableBitmap srcImg)
        {
            //画像をMat配列に読み込み
            CPP.Mat CvImg = new CPP.Mat(srcImg.ToIplImage());
          
            //HoG検出器の準備
            CPP.HOGDescriptor hog = new CPP.HOGDescriptor();
            //人物検出用分類器の準備
            hog.SetSVMDetector(CPP.HOGDescriptor.GetDefaultPeopleDetector());
            //人物検出の実行(パラメーターは元のサンプルのまま)
            CvRect[] found = hog.DetectMultiScale(CvImg, 0, new CvSize(8, 8), new CvSize(24, 16), 1.05, 2);

            //人物を認識したときはキャンバスに矩形を表示する
            //キャンバスのクリア
            canvas1.Children.Clear();

            //検出時のみ矩形を描画
            if (found.Length !=0)
            {
                foreach (CvRect rect in found)
                {
                    canvas1.Children.Add(new System.Windows.Shapes.Rectangle()
                    {
                        Margin = new Thickness(rect.X + (int)Math.Round(rect.Width * 0.1), rect.Y + (int)Math.Round(rect.Height * 0.1), 0, 0),
                        Stroke = new SolidColorBrush(Colors.Red),
                        StrokeThickness = 3,
                        Width = (int)Math.Round(rect.Width * 0.8),
                        Height = (int)Math.Round(rect.Height * 0.8)
                    });
                }
            }
        }

        /// <summary>
        /// BGR32からRGB24に変換
        /// </summary>
        /// <param name="pixels"></param>
        /// <returns></returns>
        private byte[] CvtBGR32toRGB24(byte[] pixels)
        {
            var bytes = new byte[pixels.Length / 4 * 3];
            for (int i = 0; i < pixels.Length / 4; i++)
            {
                bytes[i * 3] = pixels[i * 4+2];
                bytes[i * 3 + 1] = pixels[i * 4+1];
                bytes[i * 3 + 2] = pixels[i * 4];
            }

            return bytes;
        }
    }
}
カラーイメージを表示している所はRGBサンプルとほぼ同じです。
Hog関数でカラーイメージをOpenCVを利用して処理しています。
カラーイメージ内で人を認識できた結果は矩形座標となるので、それを元にキャンバスコントロールに矩形を描画しています。
Hog関数自体はWritableBitmap形式のデータを与えれば動作するのでちょっと変更すればKINECT以外のデータ(静止画とかWEBカメラの映像イメージ)も処理できます。

人認識のアルゴリズムについては理解できていないところが多いですが、赤字の部分ですべての処理ができるようです。認識精度などはパラメーターを変更すればあげられるかもしれません。

HoGを使った処理ですがやはり相当重いようです。使っているPCはCorei7-2600Kですが、動作中はすべてのコアの使用率が軒並み50%以上になりました。
30FPSで処理するとまともに画像が表示されません。
C++でプログラムを記述するほうがいいのかもしれませんね。(HoG処理自体C++のインターフェイス向けなので)



2012年9月9日日曜日

KINECTサンプル(C#) OpenCV for SDK1.5

今日のサンプルはOpenCVを使っての初歩的な画像処理を行います。
KINECTそのものはあくまでもセンサーなので、受け取ったデータはPCで処理をしなければいけません。その中でメインはやはり画像処理になってくるのでしょうが、Windows自体は画像処理は得意な方ではありません。そこで外部ライブラリを利用することになります。よく使われているのがOpenCVというライブラリになります。
OpenCVはもともとC++およびC言語向けとしてプログラミングインターフェイスを公開しているので.NetFrameworkからの利用は難しくなります。そこでOpenCVSharpというラッパーライブラリを利用することにします。

まずはOpenCVのインストールです。

現在OpenCVの最新バージョンは2.4.2(2012/09/09現在)となりますが、少し前のバージョン2.3.1を使います。
http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.3.1/より「OpenCV-2.3.1-win-superpack.exe」をダウンロードしてください。
ダウンロードしたファイルを解凍すると、opencvフォルダができますので、好きな場所にコピーします。今回はC:\にコピーしました。
次にシステム環境変数のPathを設定をします。ここでは使っているOSの種類やOpenCVの種類によって指定するPathが変わるのですが、今回は32bit向けの設定にします。(OSが64ビットでも動きます。)
「コンピュータ」のプロパティを開き、「システムの詳細設定」>「詳細設定」タブ>「環境変数」ボタンを押します。システム環境変数の「Path」の選び編集ボタンを押します。変数値の一番最後に「c:\opencv\build\x86\vc10\bin;c:\opencv\build\common\tbb\ia32\vc10」と2つのパスを追加します。かならず現在の変数値の最後に追加するようにしてください。誤ってほかの箇所を変更してしまうと動かなくなるアプリケーションが出るかもしれません。また追加する際にパスの区切りである「;」(セミコロン)を忘れずにつけましょう。
以上でOpenCVの設定は終わりです。

続いてOpenCVSharpの設定です。
http://code.google.com/p/opencvsharp/の左メニューの「Downloads」から「OpenCvSharp-2.3.1-x86-20120519.zip」をダウンロードして適当な場所に解凍します。
このままでもいいのですが、解凍したフォルダ内のDLLと「XmlDoc-Japanese」内のファイルを同一フォルダにまとめておくと、プログラム入力中に日本語でツールチップが表示されるので便利です。

ではサンプルプログラムです。
サンプルではKINECTのカラーイメージとOpenCVでCannyを用いた2値化画像を同時に表示しています。

MainWindow.xaml
------------------------------------------------------------------------------

<Window x:Class="KinectOpenCV.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignWidth="1308" SizeToContent="WidthAndHeight" d:DesignHeight="519">
    <Grid>
        <Image Height="480" HorizontalAlignment="Left"  Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="640" />
        <Image Height="480" HorizontalAlignment="Left" Margin="646,0,0,0" Name="image2" Stretch="Fill" VerticalAlignment="Top" Width="640" />
    </Grid>
</Window>
------------------------------------------------------------------------------
イメージコントロールを2つ横に並べて配置しています。



参照設定がいくつか必要になります。
「参照設定」>「参照の追加」の「.NET」タブから「Microsoft.Kinect」と「System.Drawing」を追加してください。次に参照タブから先ほどOpenCVSharpを保存したフォルダを選択して、「OpenCvSharp.dll」と「OpenCvSharp.Extensions.dll」を選択してください。


MainWindow.xaml.cs
------------------------------------------------------------------------------

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using System.Drawing;

using Microsoft.Kinect;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace KinectOpenCV
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor kinect;
        private WriteableBitmap srcImg;
        private WriteableBitmap distImg;
        private Int32Rect bitmapRect;
        private IplImage CvImg;
        private IplImage CvGrayImg;
        private IplImage CannyImg;

        /// <summary>
        ///
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTがみつかりません。");
                Close();
            }

            kinect = KinectSensor.KinectSensors[0];

            kinect.ColorStream.Enable();

            kinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(kinect_ColorFrameReady);

            kinect.Start();

            //OpenCV用
            srcImg=new WriteableBitmap(kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight,96,96,PixelFormats.Bgra32,null);
            //OpenCV処理後用
            distImg = new WriteableBitmap(kinect.ColorStream.FrameWidth, kinect.ColorStream.FrameHeight, 96, 96, PixelFormats.Bgr32, null);
            //画像のサイズ(640*480)
            bitmapRect=new Int32Rect(0,0,kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight);

            //OpenCV用画像
            CvImg = Cv.CreateImage(new CvSize(640, 480), BitDepth.U8, 3);
            //OpenCV用グレースケール画像
            CvGrayImg = new IplImage(CvImg.Size, BitDepth.U8, 1);
            //OpenCV用Canny処理画像
            CannyImg = new IplImage(CvImg.Size, BitDepth.U8, 1);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame != null)
                {
                    //KINECTからのカラーイメージデータ
                    byte[] colorPixel = new byte[colorFrame.PixelDataLength];
                    colorFrame.CopyPixelDataTo(colorPixel);

                    //OpenCV向けWritablebitmapに書込み
                    srcImg.WritePixels(bitmapRect, colorPixel, colorFrame.Width * colorFrame.BytesPerPixel, 0);
                    //通常画像むけWritablebitmapに書込み
                    distImg.WritePixels(bitmapRect, colorPixel, colorFrame.Width * colorFrame.BytesPerPixel, 0);

                    //WritablebitmapをIplImageに変換
                    CvImg = srcImg.ToIplImage();
                 
                    //グレースケール処理
                    Cv.CvtColor(CvImg, CvGrayImg, ColorConversion.BgrToGray);
                    //Canny処理(2値化)
                    Cv.Canny(CvGrayImg, CannyImg, 50, 200);

                    //通常画像を表示
                    image1.Source = distImg;
                    //Canny処理後の画像を変換して表示
                    image2.Source = CannyImg.ToWriteableBitmap();

                }
            }                
        }
    }
}
------------------------------------------------------------------------------


以前のRGBカメラサンプルではイメージコントロールにBitmapSourceを使っていましたが、今回はWritableBitmapを使っています。
OpenCVでは画像処理形式としてIplImageというデータ形式を使っています。IplImageではBGR32データは受け取れないのでRGB24形式に変換するプログラムを書くか、BGRA32形式のデータを使うかしなければいけません。
今回はBGRA32形式のWritabebitmapを変換してOpenCVに渡すようにしています。



2012年9月8日土曜日

KINECTサンプル(C#) Speech2 for SDK1.5

前回の音声認識サンプルではプログラム内で認識したい言葉を記述していましたが、SpeechPlatform自体はSRGS(Speech Recognition Grammar Specification)形式でのGrammar設定もできるようになっています。SRGSはxmlですので決められたタグを用いて、柔軟に認識させたい言葉、文章を記述することができます。

まずはGrammarファイルです。今回は2つ用意しました。

greeting.xml
------------------------------------------------------------------------------

<?xml version="1.0" encoding="utf-8" ?>
<grammar xml:lang="ja-JP" version="1.0"
xmlns="http://www.w3.org/2001/06/grammar"
tag-format="semantics/1.0" root="greeting">

  <rule id="greeting">
    <one-of>
      <item>おはよう</item>
      <item>こんにちは</item>
      <item>こんばんは</item>
    </one-of>
  </rule>

</grammar>
------------------------------------------------------------------------------
前半はほぼ固定された内容になります。rootエレメントは自由に記述できます。
ruleタグのidエレメントも自由に記述することができます。
itemタグで認識させたい内容を記述するのですが、one-ofタグがない場合には連続した言葉として登録されます。one-ofタグでくくることによりitemタグ毎の言葉として登録することができるようになります。

次は文章を表すSRGSの記述方法です。

main.xml
------------------------------------------------------------------------------
<?xml version="1.0" encoding="utf-8" ?>
<grammar xml:lang="ja-JP" version="1.0" 
xmlns="http://www.w3.org/2001/06/grammar"
tag-format="semantics/1.0" root="main">

  <rule id="main">
    <item>
      <ruleref uri="#fruits" />
      を
      <ruleref uri="#verb" />
    </item>
  </rule>

  <rule id="fruits" scope="public">
      <one-of>
        <item>みかん</item>
        <item>りんご</item>
        <item>すいか</item>
        <item>なし</item>
        <item>ぶどう</item>
      </one-of>
  </rule>

  <rule id="verb" scope="public">
      <one-of>
        <item>とる</item>
        <item>かう</item>
        <item>むく</item>
        <item>たべる</item>
      </one-of>
  </rule>

</grammar>
------------------------------------------------------------------------------
rulerefタグを使うことで別に記述したruleタグの内容を当てはめることができるようになります。
このファイルの場合mainのところで「fruitsの内のどれか1つ」 + 「を」 + 「verb」の内のどれか一つという文章を定義しています。
フルーツ5種類×動詞4種類=20種類の文章を定義していることになります。
rulerefタグで参照されるruleタグではscopeエレメントにpublicを指定してください。

以上のファイルをUTF-8形式で保存してください。

プロジェクト>追加>既存の項目で2つのファイルを追加してください。

画面設定です。
MainWindows.xaml
------------------------------------------------------------------------------
<Window x:Class="KinectSpeech2forSDK15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" Closed="Window_Closed">
    <Grid>
        <TextBox Height="50" HorizontalAlignment="Left" Margin="12,90,0,0" Name="textBox1" VerticalAlignment="Top" Width="479" TextAlignment="Center" FontSize="36" Text="" />
    </Grid>
</Window>
------------------------------------------------------------------------------
テキストボックスを1つ配置しています。

メインプログラムです。
MainWindows.xaml.cs
------------------------------------------------------------------------------
using System;
using System.Windows;

using Microsoft.Kinect;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;
using System.IO;

namespace KinectSpeech2forSDK15
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor kinect;
        private KinectAudioSource audio;
        private SpeechRecognitionEngine sre;

        //日本語音声認識エンジンの名前
        const string sreName = "SR_MS_ja-JP_Kinect_11.0";

        /// <summary>
        /// 
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTがみつかりません。");
                Close();
            }

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //KINECTの初期化
            kinect = KinectSensor.KinectSensors[0];

            //音声認識エンジンの初期化
            sre = new SpeechRecognitionEngine(sreName);

            //Grammarファイルを読み込む(SRGS形式)
            var greet = new Grammar("../../greeting.xml");
            var ma = new Grammar("../../main.xml");

            //認識エンジンにグラマーを登録
            sre.LoadGrammar(greet);
            sre.LoadGrammar(ma);

            //イベントの登録
            sre.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(sre_SpeechRecognized);
            sre.SpeechRecognitionRejected += new EventHandler<SpeechRecognitionRejectedEventArgs>(sre_SpeechRecognitionRejected);

            //KINECTのスタート
            kinect.Start();

            //KINECTのマイク入力
            audio = kinect.AudioSource;
            audio.AutomaticGainControlEnabled = false;
            audio.EchoCancellationMode = EchoCancellationMode.None;

            //マイク入力開始
            Stream s = audio.Start();

            //オーディオフォーマットの設定
            var speechAudioFormat = new SpeechAudioFormatInfo(EncodingFormat.Pcm,
                                                                16000,
                                                                16,
                                                                1,
                                                                32000,
                                                                2,
                                                                null);

            //認識エンジンに入力ストリームとフォーマットを指定
            sre.SetInputToAudioStream(s, speechAudioFormat);

            //認識開始(非同期、複数認識を有効にする。) 
            //※認識が可能になるのが数秒かかる
            sre.RecognizeAsync(RecognizeMode.Multiple);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sre_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            textBox1.Text = "認識できません。"; 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度が0.7以上のときに認識したことにする 
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる) 
            if (e.Result.Confidence >= 0.7)
            {
                //認識した単語の表示
                textBox1.Text = e.Result.Text;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closed(object sender, EventArgs e)
        {
            if (kinect != null)
            {
                if (kinect.IsRunning == true)
                {
                    kinect.AudioSource.Stop();
                    kinect.Stop();
                    sre.RecognizeAsyncStop();
                }
            }
        }

    }
}
------------------------------------------------------------------------------
プログラムの基本的な部分は以前のサンプルとほぼ一緒です。
Grammarを指定する部分でSRGS形式で記述したxmlファイルを読み込んでいます。

KINECTにおける音声認識の目的はPCを音声でコントロールすることがメインのようです。
ただ、SpeechPlatformは現在LyncServerにも含まれていますので、コミュニケーションアイテムとしての可能性もあるのではないかと思います。

参考としてKINECTを使ったこんなプロジェクトもあるようです。



2012年9月4日火曜日

KINECTサンプル(VB) Speech for SDK1.5

音声認識サンプルのVB版になります。
基本的にC#版と同じですが、チルト制御の代わりに音声合成ができるようにしてみました。

音声合成のために「http://www.microsoft.com/en-us/download/details.aspx?id=27224」から「MSSpeech_TTS_ja-JP_Haruka.msi」をダウンロードしてインストールしてください。

続いてサンプルプログラムです。


MainWindow.xaml
------------------------------------------------------------------------------
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox Height="66" HorizontalAlignment="Left" Margin="12,94,0,0" Name="textBox1" VerticalAlignment="Top" Width="479" FontSize="40" Text="" TextAlignment="Center" />
        <TextBox Height="24" HorizontalAlignment="Left" Margin="148,193,0,0" Name="textBox2" VerticalAlignment="Top" Width="195" TextAlignment="Center" />      
    </Grid>
</Window>


------------------------------------------------------------------------------
C#版と同じ内容です。テキストボックスを2つ配置しています。



MainWindow.xaml.vb
------------------------------------------------------------------------------


Imports Microsoft.Kinect
Imports Microsoft.Speech.AudioFormat
Imports Microsoft.Speech.Recognition
Imports Microsoft.Speech.Synthesis
Imports System.IO
Imports System.Media


Class MainWindow
    Inherits Window

    Private kinect As KinectSensor
    Private audio As KinectAudioSource
    Private sre As SpeechRecognitionEngine
    Private voice As SpeechSynthesizer
    Private player As SoundPlayer

    Const sreName As String = "SR_MS_ja-JP_Kinect_11.0"

    ''' <summary>
    '''
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。

        If KinectSensor.KinectSensors.Count = 0 Then
            MessageBox.Show("KINECTが見つかりません。")
            Exit Sub
        End If
    End Sub

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        ''KINECTの初期化
        kinect = KinectSensor.KinectSensors(0)

        ''認識エンジンの初期化
        sre = New SpeechRecognitionEngine(sreName)

        ''音声合成エンジンの初期化
        voice = New SpeechSynthesizer

        ''音声再生用
        player = New SoundPlayer

        ''認識させたい単語
        Dim word As Choices = New Choices

        With word
            .Add("おはよう")
            .Add("こんにちは")
            .Add("こんばんは")
            .Add("キネクト")
            .Add("終わり")
            .Add("STOP")
            .Add("START")
        End With

        ''グラマービルダーの設定
        Dim gb As GrammarBuilder = New GrammarBuilder
        ''言語を認識エンジンに合わせる
        gb.Culture = sre.RecognizerInfo.Culture

        ''単語を登録
        gb.Append(word)

        ''グラマーの準備
        Dim g As Grammar = New Grammar(gb)

        ''認識エンジンにグラマーを登録
        sre.LoadGrammar(g)

        ''イベントの登録
        ''音声認識用
        AddHandler sre.SpeechRecognized, AddressOf sre_SpeechRecognized
        AddHandler sre.SpeechRecognitionRejected, AddressOf sre_SpeechRecognitionRejected

        ''音声合成用
        AddHandler voice.SpeakCompleted, AddressOf voice_SpeakCompleted

        ''KINECTの開始
        kinect.Start()

        ''KINECTのマイク設定
        audio = kinect.AudioSource
        audio.AutomaticGainControlEnabled = False
        audio.EchoCancellationMode = EchoCancellationMode.None

        ''音声入力の開始
        Dim s As Stream = audio.Start()
        ''入力フォーマットの設定
        Dim speechAudioFormat As SpeechAudioFormatInfo = New SpeechAudioFormatInfo(EncodingFormat.Pcm,
                                                                                 16000,
                                                                                 16,
                                                                                 1,
                                                                                 32000,
                                                                                 2,
                                                                                 Nothing)

        ''認識エンジンに入力ストリームとフォーマットを指定
        sre.SetInputToAudioStream(s, speechAudioFormat)

        ''音声認識開始(非同期、複数認識で)
        sre.RecognizeAsync(RecognizeMode.Multiple)

    End Sub

    Sub sre_SpeechRecognitionRejected(ByVal sender As Object, ByVal e As SpeechRecognitionRejectedEventArgs)

        textBox1.Text = "認識できません。"

    End Sub

    Sub sre_SpeechRecognized(ByVal sender As Object, ByVal e As SpeechRecognizedEventArgs)

        ''認識信頼度が0.7以上の時認識したと判断する
        If e.Result.Confidence >= 0.7 Then

            ''音声合成の準備
            player.Stream = New MemoryStream()
            voice.SetOutputToWaveStream(player.Stream)

            ''音声の合成(内部でWAVデータとしている)
            voice.SpeakAsync(e.Result.Text & " を認識しました")

            ''結果の表示
            textBox1.Text = e.Result.Text
            textBox2.Text = e.Result.Confidence.ToString()

        End If
    End Sub

    Sub voice_SpeakCompleted(ByVal sender As Object, ByVal e As SpeakCompletedEventArgs)

        ''WAVデータを最初から再生
        player.Stream.Position = 0
        player.Play()

    End Sub

    Private Sub Window_Closed(sender As System.Object, e As System.EventArgs) Handles MyBase.Closed

        If kinect Is Nothing = False Then
            If kinect.IsRunning = True Then
                kinect.Stop()
                sre.RecognizeAsyncStop()
                kinect.Dispose()
            End If
        End If
    End Sub
End Class
------------------------------------------------------------------------------

赤字の部分は音声合成を利用するための記述になります。
音声認識の部分はC#版と同じです。コメントを読めば何をしているのか理解できるかと思います。

認識できた言葉に応じて自動応答ができるようにもなりますね。

2012年9月2日日曜日

KINECTサンプル(C#) Speech for SDK1.5

今回のサンプルは音声認識です。
KINECTにはアレイマイクが内蔵されています。
このマイク入力を利用して音声でPCなどをコントロールすることができます。
ただ、一部誤解があるのですが、KINECT自体に音声を認識する機能は持っていません。あくまでもマイクの機能があるだけです。音声認識自体はPC上の音声認識エンジンで行われます。

最初に音声認識のための参照設定が必要です。

参照の追加で「C:\Program Files\Microsoft SDKs\Speech\v11.0\Assembly\Microsoft.Speech.dll」を直接選択してください。

あと忘れずに日本語認識エンジンもインストールする必要があります。
DeveloperToolKitのKinect Speech Language PacksからWEBサイトに移動して「KinectSpeechLanguagePack_ja-JP.exe」をダウンロードしてインストールしてください。

次に画面のサンプルです。


MainWindow.xaml
------------------------------------------------------------------------------

<Window x:Class="KinectSpeechforSDK15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" Closed="Window_Closed">
    <Grid>
        <TextBox Height="66" HorizontalAlignment="Left" Margin="12,94,0,0" Name="textBox1" VerticalAlignment="Top" Width="479" FontSize="40" Text="" TextAlignment="Center" />
        <TextBox Height="24" HorizontalAlignment="Left" Margin="148,193,0,0" Name="textBox2" VerticalAlignment="Top" Width="195" TextAlignment="Center" />
    </Grid>
</Window>
------------------------------------------------------------------------------
今回はテキストボックスを2つ配置しています。上のテキストボックスには認識した言葉を表示します。下のテキストボックスには認識した時の信頼度を表示しています。

次はプログラム本体です。

MainWindow.xaml.cs
------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.IO;

using Microsoft.Kinect;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;


namespace KinectSpeechforSDK15
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        
        private KinectSensor kinect;
        private SpeechRecognitionEngine sre;
        
        //日本語音声認識エンジンの名前
        const string sreName="SR_MS_ja-JP_Kinect_11.0";
        
        //英語音声認識エンジンの名前
        //const string sreName = "SR_MS_en-US_Kinect_11.0";

        /// <summary>
        /// 
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTが見つかりません.");
                Close();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //KINECTの初期化
            kinect = KinectSensor.KinectSensors[0];

            //音声認識エンジンの初期化
            sre = new SpeechRecognitionEngine(sreName);

            //認識したい単語の設定
            var word = new Choices();

            //好きな単語を登録する。一部の漢字も使えるし、なぜか英語も使える
            word.Add("おはよう");
            word.Add("こんにちは");
            word.Add("こんばんは");
            word.Add("キネクト");
            word.Add("終わり");
            word.Add("STOP");
            word.Add("START");
            word.Add("上");
            word.Add("下");
            word.Add("さいしょ");

            //グラマービルダーの設定
            var gb = new GrammarBuilder();

            //グラマービルダーの言語設定を認識エンジンに合わせる
            gb.Culture = sre.RecognizerInfo.Culture;

            //グラマービルダーに単語を登録
            gb.Append(word);

            //グラマービルダーをもとにグラマーを設定
            var g = new Grammar(gb);

            //認識エンジンにグラマーを登録
            sre.LoadGrammar(g);

            //イベントの登録
            sre.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(sre_SpeechRecognized);
            sre.SpeechRecognitionRejected += new EventHandler<SpeechRecognitionRejectedEventArgs>(sre_SpeechRecognitionRejected);

            //KINECTのスタート
            kinect.Start();

            //KINECTのマイク入力
            KinectAudioSource audio = kinect.AudioSource;
            audio.AutomaticGainControlEnabled = false;
            audio.EchoCancellationMode = EchoCancellationMode.None;

            //マイク入力開始
            Stream s = audio.Start();

                //オーディオフォーマットの設定
                var speechAudioFormat = new SpeechAudioFormatInfo(EncodingFormat.Pcm,
                                                                    16000,
                                                                    16,
                                                                    1,
                                                                    32000,
                                                                    2,
                                                                    null);

                //認識エンジンに入力ストリームとフォーマットを指定
                sre.SetInputToAudioStream(s, speechAudioFormat);

                //認識開始(非同期、複数認識を有効にする。) 
                //※認識が可能になるのが数秒かかる
                sre.RecognizeAsync(RecognizeMode.Multiple);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sre_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            textBox1.Text = "認識できません。";
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度が0.7以上のときに認識したことにする 
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる) 
            if (e.Result.Confidence >= 0.7)
            {
                //認識した単語の表示
                textBox1.Text = e.Result.Text;
                //認識信頼度の表示
                textBox2.Text = e.Result.Confidence.ToString();

                //KINECTの角度を変更する。(範囲は -27°~ 27°)
                switch (e.Result.Text)
                {
                    case "上":
                        if (kinect.ElevationAngle + 5 < 27)
                        {
                            kinect.ElevationAngle += 5;
                        }

                        break;

                    case "下":
                        if (kinect.ElevationAngle - 5 > -27)
                        {
                            kinect.ElevationAngle -= 5;
                        }

                        break;

                    case "さいしょ":
                        kinect.ElevationAngle = 0;

                        break;
                }
            }
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            if (kinect != null)
            {
                if (kinect.IsRunning==true)
                {
                    kinect.AudioSource.Stop();
                    kinect.Stop();
                    sre.RecognizeAsyncStop();
                    kinect.Dispose();
                }
            }
        }
    }
}
------------------------------------------------------------------------------


最初に青字の部分が追加になっています。
オーディオ入出力を扱うのでSysytem.IOが必要になります。
後の2行は音声認識のために追加が必要です。

おおまかな処理の流れは
「認識エンジンの選択」>「認識エンジンにグラマー(単語など)を登録」>「認識エンジンに音声入力を指定」>「認識の開始」となります。

認識エンジンの選択では日本語以外の認識エンジンも選択できます。
KINECT用には現在12種類のエンジンがあるのですが、それ以外にも「http://www.microsoft.com/en-us/download/details.aspx?id=27224」でまだ多くの認識エンジンが公開されています。KINECT用にない言語を認識させたい場合にはインストールして利用することができます。(ファイル名に「SR」と入っているのが認識エンジンです。「TTS」と入っているのは音声合成エンジンなので間違えないようにしてください。)

今回のサンプルでは認識させたい言葉を直接プログラム内で記述しています。それ以外にXMLファイルに記述する方法などもあります。

おまけで「上」、「下」、「さいしょ」の言葉でKINECTの首ふりをするようにしました。
ただし連続で行うと例外が発生することもあるので少し間を開けて実行してください。(SDK1.5では連続でチルトモーターを動かせないようになっている気もしますが。)