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




2012年8月31日金曜日

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

KINECTサンプルプログラム 骨格追跡 のVB版です。

C#版と内容はほとんど同じです。

違うところは関節部分を表す円のサイズを距離によって変化するようにしてみました。


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="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>
------------------------------------------------------------------------------
C#版と一緒です。
キャンバスとテキストボックスを配置しています。

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

Imports Microsoft.Kinect

Class MainWindow
    Inherits Window

    Private kinect As KinectSensor
    Private skeletonData As Skeleton()


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

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

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

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

        kinect = KinectSensor.KinectSensors(0)

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

        ''イベントの登録
        AddHandler kinect.AllFramesReady, AddressOf kinect_AllFramesReady

        ''KINECTのスタート
        kinect.Start()

    End Sub

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub kinect_AllFramesReady(ByVal sender As Object, ByVal e As AllFramesReadyEventArgs)

        ''フレームデータの取得
        Using skelFrame As SkeletonFrame = e.OpenSkeletonFrame()
            Using depthFrame As DepthImageFrame = e.OpenDepthImageFrame()

                If skelFrame Is Nothing = False Then
                    ''キャンバスのクリア
                    canvas1.Children.Clear()

                    ''Skeletonデータの取得
                    skeletonData = New Skeleton(skelFrame.SkeletonArrayLength - 1) {}
                    skelFrame.CopySkeletonDataTo(skeletonData)

                    ''プレイヤーごとの描画
                    For Each skel In skeletonData
                        If skel.TrackingState = SkeletonTrackingState.Tracked Then
                            ''骨格を描画
                            For Each jt As Joint In skel.Joints
                                If jt.TrackingState <> JointTrackingState.NotTracked Then
                                    ''骨格の座標(頭)
                                    If jt.JointType = JointType.Head Then
                                        skelx.Text = jt.Position.X.ToString()
                                        skely.Text = jt.Position.Y.ToString()
                                        skelz.Text = jt.Position.Z.ToString()
                                    End If

                                    ''座標の変換(DepthFrame 640*480)
                                    Dim pt As DepthImagePoint = depthFrame.MapFromSkeletonPoint(jt.Position)

                                    ''depth座標に変換後の座標(頭)
                                    If jt.JointType = JointType.Head Then
                                        depthx.Text = pt.X.ToString()
                                        depthy.Text = pt.Y.ToString()
                                        depthz.Text = pt.Depth.ToString()
                                    End If

                                 
                                    ''円の表示
                                    If jt.TrackingState = JointTrackingState.Tracked Then
                                        Dim el As New Ellipse
                                        canvas1.Children.Add(el)
                                        With el
                                            .Margin = New Thickness(pt.X, pt.Y, 0, 0)
                                            .Fill = New SolidColorBrush(Colors.Green)
                                            .Width = CInt((4000 - pt.Depth) / 100) + 10
                                            .Height = CInt((4000 - pt.Depth) / 100) + 10
                                        End With
                                    Else
                                        Dim el As New Ellipse
                                        canvas1.Children.Add(el)
                                        With el
                                            .Margin = New Thickness(pt.X, pt.Y, 0, 0)
                                            .Fill = New SolidColorBrush(Colors.Red)
                                            .Width = CInt((4000 - pt.Depth) / 100) + 10
                                            .Height = CInt((4000 - pt.Depth) / 100) + 10
                                        End With
                                    End If
                                End If
                            Next
                        End If
                    Next


                End If

            End Using
        End Using

------------------------------------------------------------------------------
プログラムの内容はC#版を参照してください。
違うところは円を表示するところでサイズを距離に応じて変えている部分になります。
「CInt((4000 - pt.Depth) / 100)」式のうち4000はデフォルトモードでの認識最大距離4mをミリメートルとしているためです。ちょっとだけ3Dっぽく見えるはずです。


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コントロールに円を追加して各関節部分を表示しています。

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


2012年8月29日水曜日

KINECTサンプル(VB) Depthカメラ for SDK1.5

DepthサンプルのVBです。

基本的にC#版と同じです。

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="520" Width="700">
    <Grid>
        <Image Name="DepthImg" Stretch="Uniform" Height="480" Width="640" />
    </Grid>
</Window>
------------------------------------------------------------------------------

イメージコントロールを1つ配置しています。

MainWindow.xaml.vb
------------------------------------------------------------------------------
Imports Microsoft.Kinect

Class MainWindow

    Inherits Window

    Private kinect As KinectSensor
    Private colorPixel As Byte()
    Private depthPixel As Short()

    ''' <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 = KinectSensor.KinectSensors(0)

        ''Depthカメラの設定
        kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30)

        ''データ用配列の準備
        colorPixel = New Byte(kinect.DepthStream.FramePixelDataLength * 4 - 1) {}
        depthPixel = New Short(kinect.DepthStream.FramePixelDataLength - 1) {}

        ''イベントの登録
        AddHandler kinect.DepthFrameReady, AddressOf kinect_depthFrameReady

        ''KINECTのスタート
        kinect.Start()

    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub kinect_depthFrameReady(ByVal sender As Object, ByVal e As DepthImageFrameReadyEventArgs)

        Using depthFrame As DepthImageFrame = e.OpenDepthImageFrame
            If depthFrame Is Nothing = False Then
                ''Depthカメラのデータを取得
                depthFrame.CopyPixelDataTo(depthPixel)

                ''距離データから画像を生成
                Dim i As Integer
                Dim j As Integer = 0
                Dim depth As Short
                Dim dist As Byte
                For i = 0 To depthPixel.Length - 1

                    ''距離データの取り出し
                    depth = CShort(depthPixel(i) >> DepthImageFrame.PlayerIndexBitmaskWidth)

                    ''画素データに変換 SDKのサンプルを参照
                    dist = CByte((depth + 1) And Byte.MaxValue)

                    ''距離データを100分率で表現
                    ''dist = CByte(255 - (255 * depth / 4095))

                    ''//距離データ 13ビットを4ビットシフトしてから8ビット分だけ取り出す場合
                    ''dist = CByte((depth + 1) >> 4 And Byte.MaxValue)

                    ''RGBイメージに書込み
                    colorPixel(j) = dist
                    colorPixel(j + 1) = dist
                    colorPixel(j + 2) = dist
                    j = j + 4

                Next

                DepthImg.Source = BitmapSource.Create(depthFrame.Width, depthFrame.Height, 96, 96, PixelFormats.Bgr32, Nothing, colorPixel, depthFrame.Width * 4)

            End If
        End Using
    End Sub
End Class
------------------------------------------------------------------------------

C#版とは違いはありません。
Depthイメージをそのまま表示することはほとんどないでしょうが、データ構造を理解するのには知っておく必要があるのではないかと思います。

次は骨格認識の予定です。

2012年8月28日火曜日

KINECTサンプル(C#) Depthカメラ for SDK1.5

今回はDepthカメラのデータを表示してみます。

RGBカメラのデータは色情報ですので、ほぼそのまま画像データとして利用できますが、Depthカメラのデータは距離データ+αなのでそれを画像データに変換することが必要になります。

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

<Window x:Class="KinectDepthforSDK15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="520" Width="700">
    <Grid>
        <Image Name="Depth" Stretch="Uniform" Height="480" Width="640" />
    </Grid>
</Window>
------------------------------------------------------------------------------

RGBカメラの時と同様にイメージコントロールを一つ配置しています。


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

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

using Microsoft.Kinect;

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

        public MainWindow()
        {
            InitializeComponent();

            //接続の確認
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTが見つかりません。");
                Close();
            }

            kinect = KinectSensor.KinectSensors[0];

            //Depthカメラの設定
            kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);

            //イベントの登録
            kinect.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(kinect_DepthFrameReady);

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

        }

        void kinect_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {
                if (depthFrame != null)
                {
                    //Depthカメラの深度データ
                    short[] depthPixel = new short[depthFrame.PixelDataLength];
                    depthFrame.CopyPixelDataTo(depthPixel);

                    //表示用画像データ配列(画像データは4チャンネル必要なので要素数は4倍になる)
                    byte[] colorPixel = new byte[depthFrame.Width*depthFrame.Height * 4];

                    //距離データから画像を生成するループ
                    int j = 0;

                    for (int i = 0; i < depthPixel.Length; i++)
                    {
                        //距離データの取り出し
                        short depth = (short)( depthPixel[i] >> DepthImageFrame.PlayerIndexBitmaskWidth);

                        //画素データに変換(距離データのうち下位1バイトで表現している)
                        //SDKのDepthサンプルを参考
                        byte dist = (byte)((depth + 1) & byte.MaxValue);    //byte.MaxValue=255

                        //距離データ(0-4095)を100分率で表現するなら次の式で
                        //byte dist = (byte)(255 - (255 * depth / 4095));

                        //距離データ 13ビットを4ビットシフトしてから8ビット分だけ取り出す場合
                        //byte dist = (byte)(depth + 1 >> 4 & byte.MaxValue);

                        //RGBイメージ配列に書込み
                        colorPixel[j] = dist;
                        colorPixel[j + 1] = dist;
                        colorPixel[j + 2] = dist;
                        j += 4;
                    }

                    //イメージコントロールに設定
                    Depth.Source = BitmapSource.Create(depthFrame.Width, depthFrame.Height, 96, 96, PixelFormats.Bgr32, null, colorPixel, depthFrame.Width * 4);

                }
            }
        }

    }
}


------------------------------------------------------------------------------

プログラムの前半はRGBカメラの時とほとんど変わりません。

Depthカメラからのデータは1ピクセルあたり2バイト(16ビット)になりますのでshort型配列に取り込みます。

表示用のイメージデータはBGR32タイプになるので画像サイズ(640*480)の4倍必要になります。

距離データは16ビット中上位13ビットで標準モードの場合0-4095(mm)の距離を表します。残り3ビットは認識しているプレイヤーのIDが入るのですが、今回は骨格認識を有効にしていないので利用できません。

 「depthPixel[i] >> DepthImageFrame.PlayerIndexBitmaskWidth」で3ビット右にシフトして距離データを取り出しています。

距離データは0-4095の値になるのでこれを0-255(画素データは8ビット)に収まるようにすれば距離に応じたイメージデータを得ることができます。
SDKのサンプルでは取り出した距離データに1を足して(計測できない距離データが-1になるのでそれを補正しているのだと思います。)から下位8ビットデータを取り出して画素データとしています。
ほかにもいろいろ表現の仕方はあると思います。

得られた画像を見てみると人物などに影ができていますが、これはDepthカメラの仕様(赤外線を利用している)でどうしてもできてしまうようです。