ラベル C# の投稿を表示しています。 すべての投稿を表示
ラベル C# の投稿を表示しています。 すべての投稿を表示

2014年7月23日水曜日

KINECT V2  音声シャッター(SDK2.0β,C#)

FaceBookのグループでスナップショットを撮るときクリックするのが大変という話があったので、音声認識でスナップショットをとれるようにしてみました。
SDK2.0βのColorBasics-WPFサンプルに音声認識部分を追加しています。

今回もKinectAudioStream.csをプロジェクトに追加しています。

変更はnamespaceとMainWindow.xaml.csだけです。

MainWindow.xaml.cs

//------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.ColorBasics.speech
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Kinect;
    using Microsoft.Speech.AudioFormat;
    using Microsoft.Speech.Recognition;

    /// <summary>
    /// Interaction logic for MainWindow
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        /// <summary>
        /// Size of the RGB pixel in the bitmap
        /// </summary>
        private readonly uint bytesPerPixel = 0;

        /// <summary>
        /// Active Kinect sensor
        /// </summary>
        private KinectSensor kinectSensor = null;

        /// <summary>
        /// Reader for color frames
        /// </summary>
        private ColorFrameReader colorFrameReader = null;

        /// <summary>
        /// Bitmap to display
        /// </summary>
        private WriteableBitmap colorBitmap = null;

        /// <summary>
        /// Intermediate storage for receiving frame data from the sensor
        /// </summary>
        private byte[] colorPixels = null;

        /// <summary>
        /// Current status text to display
        /// </summary>
        private string statusText = null;

        /// <summary>
        /// 音声ストリーム
        /// </summary>
        KinectAudioStream convertStream = null;

        /// <summary>
        /// 音声認識エンジン
        /// </summary>
        SpeechRecognitionEngine engine=null;

        /// <summary>
        /// 日本語音声認識エンジンのID
        /// </summary>
        const string engin_id = "SR_MS_ja-JP_Kinect_11.0";


        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            // get the kinectSensor object
            this.kinectSensor = KinectSensor.GetDefault();

            // open the reader for the color frames
            this.colorFrameReader = this.kinectSensor.ColorFrameSource.OpenReader();

            // wire handler for frame arrival
            this.colorFrameReader.FrameArrived += this.Reader_ColorFrameArrived;

            // create the colorFrameDescription from the ColorFrameSource using Bgra format
            FrameDescription colorFrameDescription = this.kinectSensor.ColorFrameSource.CreateFrameDescription(ColorImageFormat.Bgra);

            // rgba is 4 bytes per pixel
            this.bytesPerPixel = colorFrameDescription.BytesPerPixel;

            // allocate space to put the pixels to be rendered
            this.colorPixels = new byte[colorFrameDescription.Width * colorFrameDescription.Height * this.bytesPerPixel];

            // create the bitmap to display
            this.colorBitmap = new WriteableBitmap(colorFrameDescription.Width, colorFrameDescription.Height, 96.0, 96.0, PixelFormats.Bgr32, null);

            // set IsAvailableChanged event notifier
            this.kinectSensor.IsAvailableChanged += this.Sensor_IsAvailableChanged;

            // open the sensor
            this.kinectSensor.Open();

            // set the status text
            this.StatusText = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.NoSensorStatusText;

            // use the window object as the view model in this simple example
            this.DataContext = this;

            // initialize the components (controls) of the window
            this.InitializeComponent();


            ////////////////////////////////////////////////////////////
            //音声認識関連
            ////////////////////////////////////////////////////////////

            //音声入力設定
            IReadOnlyList<AudioBeam> audioBeamList = kinectSensor.AudioSource.AudioBeams;
            Stream audioStream = audioBeamList[0].OpenInputStream();

            //audioStreamのビット変換
            convertStream = new KinectAudioStream(audioStream);

            //音声認識エンジン設定
            engine = new SpeechRecognitionEngine(engin_id);

            //認識するワード
            var word = new Choices();

            word.Add("チーズ");
            word.Add("キャプチャ");
            word.Add("シャッター");
           
            //グラマービルダー
            var gb = new GrammarBuilder();

            //言語設定
            gb.Culture = engine.RecognizerInfo.Culture;

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

            //グラマー作成
            var g = new Grammar(gb);

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

            //イベントの登録
            engine.SpeechRecognized += engine_SpeechRecognized;
            engine.SpeechRecognitionRejected += engine_SpeechRecognitionRejected;

            //オーディオストリームの変換をアクティブにする
            convertStream.SpeechActive = true;

            //認識エンジンに入力設定
            engine.SetInputToAudioStream(convertStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            //非同期で連続認識の開始
            engine.RecognizeAsync(RecognizeMode.Multiple);

        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void engine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void engine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度の閾値
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる)
            const double ConfidenceThreshold = 0.6;

            if (e.Result.Confidence >= ConfidenceThreshold)
            {
                //認識した単語を表示
                if (this.colorBitmap != null)
                {
                    // create a png bitmap encoder which knows how to save a .png file
                    BitmapEncoder encoder = new PngBitmapEncoder();

                    // create frame from the writable bitmap and add to encoder
                    encoder.Frames.Add(BitmapFrame.Create(this.colorBitmap));

                    string time = System.DateTime.Now.ToString("hh'-'mm'-'ss", CultureInfo.CurrentUICulture.DateTimeFormat);

                    string myPhotos = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

                    string path = Path.Combine(myPhotos, "KinectScreenshot-Color-" + time + ".png");

                    // write the new file to disk
                    try
                    {
                        // FileStream is IDisposable
                        using (FileStream fs = new FileStream(path, FileMode.Create))
                        {
                            encoder.Save(fs);
                        }

                        this.StatusText = string.Format(Properties.Resources.SavedScreenshotStatusTextFormat, path);
                    }
                    catch (IOException)
                    {
                        this.StatusText = string.Format(Properties.Resources.FailedScreenshotStatusTextFormat, path);
                    }
                }

            }
        }

        /// <summary>
        /// INotifyPropertyChangedPropertyChanged event to allow window controls to bind to changeable data
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the bitmap to display
        /// </summary>
        public ImageSource ImageSource
        {
            get
            {
                return this.colorBitmap;
            }
        }

        /// <summary>
        /// Gets or sets the current status text to display
        /// </summary>
        public string StatusText
        {
            get
            {
                return this.statusText;
            }

            set
            {
                if (this.statusText != value)
                {
                    this.statusText = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText"));
                    }
                }
            }
        }

        /// <summary>
        /// Execute shutdown tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (this.colorFrameReader != null)
            {
                // ColorFrameReder is IDisposable
                this.colorFrameReader.Dispose();
                this.colorFrameReader = null;
            }

            if (this.kinectSensor != null)
            {
                this.kinectSensor.Close();
                this.kinectSensor = null;
            }
        }

        /// <summary>
        /// Handles the user clicking on the screenshot button
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void ScreenshotButton_Click(object sender, RoutedEventArgs e)
        {
            if (this.colorBitmap != null)
            {
                // create a png bitmap encoder which knows how to save a .png file
                BitmapEncoder encoder = new PngBitmapEncoder();

                // create frame from the writable bitmap and add to encoder
                encoder.Frames.Add(BitmapFrame.Create(this.colorBitmap));

                string time = System.DateTime.Now.ToString("hh'-'mm'-'ss", CultureInfo.CurrentUICulture.DateTimeFormat);

                string myPhotos = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

                string path = Path.Combine(myPhotos, "KinectScreenshot-Color-" + time + ".png");

                // write the new file to disk
                try
                {
                    // FileStream is IDisposable
                    using (FileStream fs = new FileStream(path, FileMode.Create))
                    {
                        encoder.Save(fs);
                    }

                    this.StatusText = string.Format(Properties.Resources.SavedScreenshotStatusTextFormat, path);
                }
                catch (IOException)
                {
                    this.StatusText = string.Format(Properties.Resources.FailedScreenshotStatusTextFormat, path);
                }
            }
        }

        /// <summary>
        /// Handles the color frame data arriving from the sensor
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Reader_ColorFrameArrived(object sender, ColorFrameArrivedEventArgs e)
        {
            bool colorFrameProcessed = false;

            // ColorFrame is IDisposable
            using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
            {
                if (colorFrame != null)
                {
                    FrameDescription colorFrameDescription = colorFrame.FrameDescription;

                    // verify data and write the new color frame data to the display bitmap
                    if ((colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (colorFrameDescription.Height == this.colorBitmap.PixelHeight))
                    {
                        if (colorFrame.RawColorImageFormat == ColorImageFormat.Bgra)
                        {
                            colorFrame.CopyRawFrameDataToArray(this.colorPixels);
                        }
                        else
                        {
                            colorFrame.CopyConvertedFrameDataToArray(this.colorPixels, ColorImageFormat.Bgra);
                        }

                        colorFrameProcessed = true;
                    }
                }
            }

            // we got a frame, render
            if (colorFrameProcessed)
            {
                this.RenderColorPixels();
            }
        }

        /// <summary>
        /// Renders color pixels into the writeableBitmap.
        /// </summary>
        private void RenderColorPixels()
        {
            this.colorBitmap.WritePixels(
                new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
                this.colorPixels,
                this.colorBitmap.PixelWidth * (int)this.bytesPerPixel,
                0);
        }

        /// <summary>
        /// Handles the event which the sensor becomes unavailable (E.g. paused, closed, unplugged).
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Sensor_IsAvailableChanged(object sender, IsAvailableChangedEventArgs e)
        {
            // on failure, set the status text
            this.StatusText = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.SensorNotAvailableStatusText;
        }
    }
}


説明:
 前回の音声認識サンプルとほぼ同じです。スナップショットを撮るルーチンもClickイベント部分と同じです。
 慣れてくれば簡単にイベント部分を音声認識イベントに変更できそうです。

2014年7月20日日曜日

KINECT V2で音声認識(SDK2.0β C#)

7月15日からKINECT for Windows V2が発売されました。SDK2.0もパブリックベータ版が同時に公開されています。

今回はハードが大幅に変更になっているのでSDKも色々変わっています。

ColorやDepthの解説はぼちぼちブログやIT系メディアで紹介されていますが、音声認識関連はなかなか出てこないです。

取り急ぎSDKのSpeechサンプルを参考にして簡単なプログラムを作ってみました。

開発環境は

Windows8.1Pro 64bit
Visual Studio Pro 2012

です。

その他 KINECT SDK2.0 及び SpeechPlatform SDK 11.0,LanguagePack_jaJPもインストールしてください。

サンプルプログラムはC#+WPFで作成しています。

最初にプロジェクトを新しく作成してから、SDK2.0βのSpeech Basics-WPFプロジェクトよりKinectAudioStream.csファイルをインポートして、namespaceを変更してください。

MainWindow.xaml
<Window x:Class="speech_jp.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" Closing="Window_Closing">
    <Grid>
        <TextBox x:Name="tb1" HorizontalAlignment="Left" Height="30" Margin="10,82,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="497" FontSize="20"/>
        <TextBox x:Name="tb2" HorizontalAlignment="Left" Height="30" Margin="10,153,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="497" FontSize="20"/>
        <Label Content="認識した単語" HorizontalAlignment="Left" Margin="10,52,0,0" VerticalAlignment="Top" Width="107"/>
        <Label Content="信頼度" HorizontalAlignment="Left" Margin="10,123,0,0" VerticalAlignment="Top" Width="74"/>

    </Grid>
</Window>

説明:
 テキストボックスを2つ配置しています。


MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Kinect;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;

namespace speech_jp
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        //KINECT
        KinectSensor kinect=null;

        //AudioStream
        KinectAudioStream convertStream=null;

        //音声認識エンジン
        SpeechRecognitionEngine engine;

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

        //英語音声認識エンジンのID
        //const string engin_id = "SR_MS_en-US_Kinect_11.0";

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

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //KINECTの準備
            try
            {
                kinect = KinectSensor.GetDefault();
                if (kinect == null)
                {
                    throw new Exception("KINECTを開けません。");
                }

                //KINECTの使用開始
                kinect.Open();

                //音声入力設定
                IReadOnlyList<AudioBeam> audioBeamList = kinect.AudioSource.AudioBeams;
                Stream audioStream = audioBeamList[0].OpenInputStream();

                //audioStreamのビット変換
                convertStream = new KinectAudioStream(audioStream);


            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                Close();
            }

            //音声認識エンジン設定
            engine = new SpeechRecognitionEngine(engin_id);

            //認識するワード
            var word = new Choices();

            word.Add("ハンバーグ");
            word.Add("ラーメン");
            word.Add("焼肉");
            word.Add("すし");
            word.Add("スパゲッティ");
            word.Add("卵焼き");
            word.Add("ぎゅう丼");
            word.Add("うどん");
            word.Add("からあげ");

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

            //言語設定
            gb.Culture = engine.RecognizerInfo.Culture;

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

            //グラマー作成
            var g = new Grammar(gb);

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

            //イベントの登録
            engine.SpeechRecognized += engine_SpeechRecognized;
            engine.SpeechRecognitionRejected += engine_SpeechRecognitionRejected;

            //オーディオストリームの変換をアクティブにする
            convertStream.SpeechActive = true;

            //認識エンジンに入力設定
            engine.SetInputToAudioStream(convertStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            //非同期で連続認識の開始
            engine.RecognizeAsync(RecognizeMode.Multiple);
           
        }

        void engine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            tb1.Text = "認識できません。";
            tb2.Text = "";
        }

        void engine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度の閾値
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる)
            const double ConfidenceThreshold = 0.6;

            if (e.Result.Confidence >= ConfidenceThreshold)
            {
                //認識した単語を表示
                tb1.Text = e.Result.Text;
                tb2.Text = e.Result.Confidence.ToString();
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //終了処理
            if (kinect != null)
            {
                kinect.Close();
                kinect = null;
            }
        }
    }
}



説明:
 大きく変わったのはマイクで録音した時のオーディオフォーマットが変更になっているところです。V1の時はPCM 16bitだったのがV2ではPCM 32bit floatになっています。音声認識エンジンへの入力フォーマットはPCM 16bitの必要があるので32bitfloat->16bitへの変換が必要になります。
 今回はSDKのサンプルプログラムから変換用クラスをそのままインポートして利用しています。
 また、音声ストリームの指定方法が少し変更になっていますね。AudioBeam配列から選択するように変わりました。
 音声認識部分についてはSpeechPlatformのバージョンは以前と変わっていないので特に変更はありません。


入力音声を配列で取得できるということは複数人の音声を識別できそうです。
現在はまだそこまで調べていないので何とも言えないのですが。できるならイントロクイズとかに使えるかもしれないですね。

2013年12月27日金曜日

Windowsで音声認識(Choices+GrammarBuilder)

先日の音声合成に続いて、Windows標準の音声認識機能を使ってみます。

標準の音声認識機能は学習方式と辞書方式どちらにも対応しているようなのですが、今回は辞書方式で認識させます。

音声合成の場合と同じく特に用意するものはありません。

さっそくサンプルプログラムです。コンソールアプリケーションです。

using System;
using System.Speech.Recognition;

namespace SpeechRecognized_Sample
{
    class Program
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {

            //認識エンジン(既定のロケールを使用)
            using (SpeechRecognitionEngine sre = new SpeechRecognitionEngine())
            {

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

                //語彙登録
                Choices words = new Choices(new string[] { "晴れ", "曇り", "雨", "雪" });

                //GrammarBuilderインスタンス
                GrammarBuilder gb = new GrammarBuilder("今日の天気は");

                //GrammarBuilderインスタンスに語彙を追加
                gb.Append(words);

                //Grammarインスタンスを作成
                Grammar g = new Grammar(gb);

                //Grammarインスタンスをロード
                sre.LoadGrammar(g);

                Console.Write("認識開始" + "\n");

                //入力ソース(既定のマイク)
                sre.SetInputToDefaultAudioDevice();

                //非同期で認識開始
                sre.RecognizeAsync(RecognizeMode.Multiple);

                while (true)
                {
                    Console.ReadLine();
                }
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            Console.WriteLine("認識しました:" + e.Result.Text + " 信頼度:" + e.Result.Confidence);
        }
    }
}


説明:
「プロジェクト」>「参照の追加」>「アセンブリ」>「フレームワーク」内の「System.Speech」のチェックを忘れないでください。
今回辞書への登録方法としてChoicesクラスとGrammarBuilderクラスを使っています。
Choicesクラスに認識させたい単語を文字列配列として与えます。
その後Choicesオブジェクトを引数としてGrammarBuilderクラスに与えます。
次にGrammarクラスにGrammarBuilderオブジェクトを与えます。
最後に認識エンジンにGrammarオブジェクトをロードして辞書に登録します。

サンプルではGrammarBuilderインスタンスを作成するときに文字列を与えています。
このようにすることで「今日の天気は」+「Choicesクラスに登録した単語」の組み合わせの文章を登録できます。

音声合成の場合より少し複雑ですが、手順がわかればそれほど難しくはないと思います。

次回はSRGSを使って認識させるサンプルの予定です。


2013年12月25日水曜日

Windowsで音声合成

先日大阪でDev(ice)LOVE デバイス祭りという勉強会に参加しました。
「KINECTを使った音声対話」という内容で話をさせていただいたのですが、その時のスライドに対応したサンプルプログラム+αを何回かに分けて書いていきたいと思います。

ちなみにスライドはこちらにアップロードしています。

まず最初に音声合成のサンプルプログラムです。
勉強会の時はSpeechPlatform11をインストールした状態での説明でしたが、Windows標準の状態でもできますので特に何も用意する必要はありません。(もちろん開発環境は必要です^^;)

サンプルプログラムの動作確認は Windows8 Pro+VS C# 2012 Express for Desktopですが、
Windows7やC#2010でも動くと思います。.NET Framework3.5以上なら大丈夫でしょう。もしかするとXPでも動くかもしれないです。

さっそくプログラムです。コンソールアプリとなります。

using System;
using System.Speech.Synthesis;

namespace Speak_Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            //音声合成エンジン
            SpeechSynthesizer ss = new SpeechSynthesizer();
           
            //出力を既定のスピーカーに設定
            ss.SetOutputToDefaultAudioDevice();

            //音声出力
            ss.Speak("こんにちは,今日はようこそいらっしゃいました。");

            //キー入力待ち
            Console.ReadKey();
           
        }
    }
}

あらかじめ「プロジェクト」>「参照の追加」>「アセンブリ」>「フレームワーク」内の「System.Speech」にチェックを入れてください。

2行目の「System.Speech.Synthesis」でWindows標準の音声合成機能を利用できるようにしています。

しゃべらせるだけならこれだけです。ひらがな、カタカナ、漢字も使えます。英語も標準で使えます。
多国語対応ですが、別に言語パックなどをインストールしなければいけないでしょう。

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月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では連続でチルトモーターを動かせないようになっている気もしますが。)




2012年8月30日木曜日

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

今回は骨格追跡のサンプルになります。
骨格追跡は座標系がRGBカメラやDepthカメラと全然違うので(厳密に言うとDepthもですけど)色々やらなければいけないことが増えています。

サンプルを実行すると、認識した人物の各関節部分に円を表示します。
認識できている部位は緑色で、推定している部位は赤色で表示します。
それから頭部の座標をSkeletonFrameの場合と、Depthデータとマッピングした時の場合を表示しています。

MainWindow.xaml
------------------------------------------------------------------------------
<Window x:Class="KinectSkeletonforSDK15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="519" Width="1175">
    <Grid Width="1141">
        <Canvas Height="480" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="640" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="743,46,0,0" Name="skelx" VerticalAlignment="Top" Width="120" FontSize="20" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="869,46,0,0" Name="skely" VerticalAlignment="Top" Width="120" FontSize="20" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="995,46,0,0" Name="skelz" VerticalAlignment="Top" Width="120" FontSize="20" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="743,95,0,0" Name="depthx" VerticalAlignment="Top" Width="120" FontSize="20" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="869,95,0,0" Name="depthy" VerticalAlignment="Top" Width="120" FontSize="20" />
        <TextBox Height="40" HorizontalAlignment="Left" Margin="995,95,0,0" Name="depthz" VerticalAlignment="Top" Width="120" FontSize="20" />
        <Label Content="Skeleton" Height="40" HorizontalAlignment="Left" Margin="646,46,0,0" Name="label1" VerticalAlignment="Top" FontSize="20" />
        <Label Content="Depth" FontSize="20" Height="40" HorizontalAlignment="Left" Margin="671,95,0,0" Name="label2" VerticalAlignment="Top" />
        <Label Content="Z" FontSize="20" Height="40" HorizontalAlignment="Left" Margin="995,0,0,0" Name="label3" VerticalAlignment="Top" Width="120" HorizontalContentAlignment="Center" />
        <Label Content="X" FontSize="20" Height="40" HorizontalAlignment="Left" Margin="743,0,0,0" Name="label4" VerticalAlignment="Top" Width="120" HorizontalContentAlignment="Center" />
        <Label Content="Y" FontSize="20" Height="40" HorizontalAlignment="Left" Margin="869,0,0,0" Name="label5" VerticalAlignment="Top" Width="120" HorizontalContentAlignment="Center" />
    </Grid>
</Window>
------------------------------------------------------------------------------
今回はCanvasコントロールと座標データを表示するためのテキストボックスを配置しています。



MainWindow.xaml.cs
------------------------------------------------------------------------------
using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using Microsoft.Kinect;

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

        /// <summary>
        ///
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
     
            //接続の確認
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTがみつかりません。");
                Close();
            }

            kinect = KinectSensor.KinectSensors[0];

            //Depthカメラを有効にする
            kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
            //骨格追跡を有効にする
            kinect.SkeletonStream.Enable();

            //イベントの登録
            kinect.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(kinect_AllFramesReady);
         
            //KINECTのスタート
            kinect.Start();

        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinect_AllFramesReady(object sender, AllFramesReadyEventArgs e)
        {
         
            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {

                if (skeletonFrame != null)
                {
                    //キャンバスのクリア
                    canvas1.Children.Clear();

                    //Skeletonデータの取得
                    Skeleton[] skeletonData = new Skeleton[skeletonFrame.SkeletonArrayLength];
                    skeletonFrame.CopySkeletonDataTo(skeletonData);

                    //プレイヤーごとの描画
                    foreach (var skeleton in skeletonData)
                    {
                        if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                        {
                            //骨格を描画
                            foreach (Joint joint in skeleton.Joints)
                            {
                                if (joint.TrackingState != JointTrackingState.NotTracked)
                                {
                                    //骨格の座標(頭)
                                    if (joint.JointType == JointType.Head)
                                    {
                                        skelx.Text = joint.Position.X.ToString();
                                        skely.Text = joint.Position.Y.ToString();
                                        skelz.Text = joint.Position.Z.ToString();
                                    }

                                    //座標の変換(DepthFrame 640*480)
                                    DepthImagePoint point = depthFrame.MapFromSkeletonPoint(joint.Position);

                                    //depth座標に変換後の座標(頭)
                                    if (joint.JointType == JointType.Head)
                                    {
                                        depthx.Text = point.X.ToString();
                                        depthy.Text = point.Y.ToString();
                                        depthz.Text = point.Depth.ToString();
                                    }

                                    //円の表示
                                    if (joint.TrackingState == JointTrackingState.Tracked)
                                    {
                                        canvas1.Children.Add(new Ellipse()
                                        {
                                            Margin = new Thickness(point.X, point.Y, 0, 0),
                                            Fill = new SolidColorBrush(Colors.Green),
                                            Width = 10,
                                            Height = 10
                                        });
                                    }

                                    else
                                    {
                                        canvas1.Children.Add(new Ellipse()
                                        {
                                            Margin = new Thickness(point.X, point.Y, 0, 0),
                                            Fill = new SolidColorBrush(Colors.Red),
                                            Width = 10,
                                            Height = 10
                                        });
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
------------------------------------------------------------------------------
フレームの更新イベントが変わっています。複数タイプのフレームを同期しながら処理する場合に「AllFramesReady」を利用します。今回はDepthフレームとSkeletonフレームを使っています。
骨格追跡を行うにはDepthフレーム、Skeletonフレームはどちらも必要です。

Skeletonフレームには認識できたプレイヤーの人数分のデータが構造体として格納されています。
Skeleton構造体には各関節(Joint)の座標データやトラッキングの状態などが格納されています。

プログラムでは1人ずつのSkeleton構造体を取得してから、Joint構造体の値(座標)を取得します。
Joint構造体の座標は3次元座標(KINECTを中心にした右手系座標)なので、平面に表示するために変換が必要になります。
今回はDepthフレームに合わせて2次元座標(Depthフレームのデータは左上を原点としています)に変換しています。(MapFromSkeletonPointメソッドを使っています。)
座標の違いはテキストボックスの値を確認してください。ちなみにKINECTまでの距離(Z座標)の単位はSkeletonはメートル(m)、Depthはミリメートル(mm)です。

変換した座標を元にCanvasコントロールに円を追加して各関節部分を表示しています。

各フレームの重ね合わせの方法がいくつかあるのでまたの機会にまとめてみたいと思います。