このエントリはOpenNI Advent Calendar 2011 : ATNDの12月9日分です!!
Advent Calendarでの、僕の全プロジェクトはこちらです
今回もKINECTの特長の一つである、ユーザーの検出を使ってみます。
ユーザーをこんな感じに色付けします。
見た目の解説
いつも通り、Imageを一つはっつけてます
<Window x:Class="UserWPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="521" Width="661" Closing="Window_Closing"> <Grid> <Image Height="480" HorizontalAlignment="Left" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="640 " /> </Grid> </Window>
中身の解説
using System; using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using OpenNI; namespace UserWPF { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { Context context; ImageGenerator image; UserGenerator user; private Thread readerThread; private bool shouldRun; public MainWindow() { InitializeComponent(); try { // ContextとImageGeneratorの作成 ScriptNode node; context = Context.CreateFromXmlFile( "../../SamplesConfig.xml", out node ); context.GlobalMirror = false; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; // ユーザーの作成 user = new UserGenerator(context); context.StartGeneratingAll(); // 画像更新のためのスレッドを作成 shouldRun = true; readerThread = new Thread( new ThreadStart( ReaderThread ) ); readerThread.Start(); } catch ( Exception ex ) { MessageBox.Show( ex.Message ); } } private void ReaderThread() { while ( shouldRun ) { context.WaitAndUpdateAll(); this.Dispatcher.BeginInvoke( DispatcherPriority.Background, new Action( Draw ) ); } } // ユーザーにつける色 static Color[] userColor= new Color[] { Color.FromRgb( 0, 0, 0 ), // ユーザーなし Color.FromRgb( 1, 0, 0 ), Color.FromRgb( 0, 1, 0 ), Color.FromRgb( 0, 0, 1 ), Color.FromRgb( 1, 1, 0 ), Color.FromRgb( 1, 0, 1 ), Color.FromRgb( 0, 1, 1 ), }; private void Draw() { // マネージドのバッファにする ImageMetaData imageMD = image.GetMetaData(); byte[] rgb = new byte[imageMD.DataSize]; Marshal.Copy( imageMD.ImageMapPtr, rgb, 0, imageMD.DataSize ); // ユーザーを検出したピクセルの色を変える var users = user.GetUserPixels(0); for (int i = 0; i < imageMD.XRes * imageMD.YRes; i++) { if ( users[i] != 0 ) { int rgbIndex = i * imageMD.BytesPerPixel; rgb[rgbIndex] = (byte)(rgb[rgbIndex] * userColor[users[i]].R); rgb[rgbIndex + 1] = (byte)(rgb[rgbIndex + 1] * userColor[users[i]].G); rgb[rgbIndex + 2] = (byte)(rgb[rgbIndex + 2] * userColor[users[i]].B); } } // バイト列をビットマップに展開 // 描画可能なビットマップを作る // http://msdn.microsoft.com/ja-jp/magazine/cc534995.aspx var bitmap = new WriteableBitmap(imageMD.XRes, imageMD.YRes, 96, 96, PixelFormats.Rgb24, null); bitmap.WritePixels(new Int32Rect(0, 0, imageMD.XRes, imageMD.YRes), rgb, imageMD.XRes * imageMD.BytesPerPixel, 0 ); image1.Source = bitmap; } private void Window_Closing( object sender, System.ComponentModel.CancelEventArgs e ) { shouldRun = false; } } }
初期化
今回はImageとUserを使います。Userの追跡にはUserGeneratorを使用します。Context.FindExistingNodeでユーザーのインスタンスが取得できなかったので、直接UserGeneratorを生成しています。このときは、Userデータの取得を開始するために、Context.StartGeneratingAllを呼び出します。
// ContextとImageGeneratorの作成 ScriptNode node; context = Context.CreateFromXmlFile( "../../SamplesConfig.xml", out node ); context.GlobalMirror = false; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; // ユーザーの作成 user = new UserGenerator(context); context.StartGeneratingAll();
ユーザー検出の表示
RGBカメラから取得されたピクセルごとに処理するため、一度バイト列に変換します。ImageMetaDataのバイト列はアンマネージドのポインタなので、マネージドのバイト列に変換します。
// マネージドのバッファにする ImageMetaData imageMD = image.GetMetaData(); byte[] rgb = new byte[imageMD.DataSize]; Marshal.Copy( imageMD.ImageMapPtr, rgb, 0, imageMD.DataSize );
検出したユーザーを色付けします。ユーザーの検出はピクセルごとに0(検出しない),1(ユーザー1),2(ユーザー2)...というように番号で取得できるので、色の配列を作成し、インデックスで色付けします
// ユーザーにつける色 static Color[] userColor= new Color[] { Color.FromRgb( 0, 0, 0 ), // ユーザーなし Color.FromRgb( 1, 0, 0 ), Color.FromRgb( 0, 1, 0 ), Color.FromRgb( 0, 0, 1 ), Color.FromRgb( 1, 1, 0 ), Color.FromRgb( 1, 0, 1 ), Color.FromRgb( 0, 1, 1 ), }; ... // ユーザーを検出したピクセルの色を変える var users = user.GetUserPixels(0); for (int i = 0; i < imageMD.XRes * imageMD.YRes; i++) { if ( users[i] != 0 ) { int rgbIndex = i * imageMD.BytesPerPixel; rgb[rgbIndex] = (byte)(rgb[rgbIndex] * userColor[users[i]].R); rgb[rgbIndex + 1] = (byte)(rgb[rgbIndex + 1] * userColor[users[i]].G); rgb[rgbIndex + 2] = (byte)(rgb[rgbIndex + 2] * userColor[users[i]].B); } }
変換したデータを、BitmapSourceとしてImageに設定します。今回はピクセルデータを書き込むので、WriteableBitmapを使っています。
// バイト列をビットマップに展開 // 描画可能なビットマップを作る // http://msdn.microsoft.com/ja-jp/magazine/cc534995.aspx var bitmap = new WriteableBitmap(imageMD.XRes, imageMD.YRes, 96, 96, PixelFormats.Rgb24, null); bitmap.WritePixels(new Int32Rect(0, 0, imageMD.XRes, imageMD.YRes), rgb, imageMD.XRes * imageMD.BytesPerPixel, 0 ); image1.Source = bitmap;
まとめ
OpenNIでユーザーデータを扱う方法でした。
WPFでバイト列をもっとスマートに扱う方法があれば教えてください。