このエントリはOpenNI Advent Calendar 2011 : ATNDの12月10日分です!!
Advent Calendarでの、僕の全プロジェクトはこちらです
今回はKINECTの最大の特長である、骨格の検出を使ってみます。
先日、最小限の骨格検出のコードを書いたので、今回はポーズあり/なし両対応のものを作ってみました。
ただし、今のところWPFでポーズありがうごきません^^;
Windows Formだと動くので、なんか作り方がおかしいです。。。
見た目の解説
前回同様、Imageを一つはっつけてます
<Window x:Class="SkeletonFullWPF.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 SkeletonFullWPF { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { Context context; ImageGenerator image; DepthGenerator depth; 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 = true; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; depth = context.FindExistingNode( NodeType.Depth ) as DepthGenerator; depth.AlternativeViewpointCapability.SetViewpoint( image ); // ユーザーの作成 user = context.FindExistingNode( NodeType.User ) as UserGenerator; // ユーザー認識のコールバックを登録 user.NewUser += new EventHandler<NewUserEventArgs>( user_NewUser ); //キャリブレーションにポーズが必要か確認 if ( user.SkeletonCapability.DoesNeedPoseForCalibration ) { // ポーズ検出のサポートチェック if ( !user.IsCapabilitySupported( "User::PoseDetection" ) ) { throw new Exception( "ポーズ検出をサポートしていません" ); } // ポーズ検出のコールバックを登録 user.PoseDetectionCapability.PoseDetected += new EventHandler<PoseDetectedEventArgs>( poseDetect_PoseDetected ); } // スケルトン検出機能をサポートしているか確認 if ( !user.IsCapabilitySupported( "User::Skeleton" ) ) { throw new Exception( "ユーザー検出をサポートしていません" ); } // キャリブレーションのコールバックを登録 user.SkeletonCapability.CalibrationEnd += new EventHandler<CalibrationEndEventArgs>( skelton_CalibrationEnd ); // すべてをトラッキングする user.SkeletonCapability.SetSkeletonProfile( SkeletonProfile.All ); // ジェスチャーの検出開始 context.StartGeneratingAll(); // 画像更新のためのスレッドを作成 shouldRun = true; readerThread = new Thread( new ThreadStart( ReaderThread ) ); readerThread.Start(); } catch ( Exception ex ) { MessageBox.Show( ex.Message ); } } // ユーザーの検出通知 void user_NewUser( object sender, NewUserEventArgs e ) { // キャリブレーションポーズが必要な場合は、ポーズの検出を開始する if ( user.SkeletonCapability.DoesNeedPoseForCalibration ) { user.PoseDetectionCapability.StartPoseDetection( user.SkeletonCapability.CalibrationPose, e.ID ); } // キャリブレーションポーズが不要な場合は、キャリブレーションを開始する else { user.SkeletonCapability.RequestCalibration( e.ID, true ); } } // ポーズの検出通知 void poseDetect_PoseDetected( object sender, PoseDetectedEventArgs e ) { // ポーズの検出を停止し、キャリブレーションを開始する user.PoseDetectionCapability.StopPoseDetection( e.ID ); user.SkeletonCapability.RequestCalibration( e.ID, true ); } // キャリブレーションの完了 void skelton_CalibrationEnd( object sender, CalibrationEndEventArgs e ) { // キャリブレーション成功 if ( e.Success ) { user.SkeletonCapability.StartTracking( e.ID ); } else { user.PoseDetectionCapability.StartPoseDetection( user.SkeletonCapability.CalibrationPose, e.ID ); } } private void ReaderThread() { while ( shouldRun ) { context.WaitAndUpdateAll(); this.Dispatcher.BeginInvoke( DispatcherPriority.Input, 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(); DrawingVisual drawingVisual = new DrawingVisual(); using ( DrawingContext drawingContext = drawingVisual.RenderOpen() ) { drawingContext.DrawImage( DrawPixels( imageMD ), new Rect( 0, 0, imageMD.XRes, imageMD.YRes ) ); // 骨格の描画 var users = user.GetUsers(); foreach ( var u in users ) { if ( !user.SkeletonCapability.IsTracking( u ) ) { continue; } foreach ( SkeletonJoint s in Enum.GetValues( typeof( SkeletonJoint ) ) ) { if ( !user.SkeletonCapability.IsJointAvailable( s ) ) { continue; } var joint = user.SkeletonCapability.GetSkeletonJoint( u, s ); var point = depth.ConvertRealWorldToProjective( joint.Position.Position ); drawingContext.DrawEllipse( new SolidColorBrush( Colors.Red ), new Pen( Brushes.Red, 1 ), new Point( point.X, point.Y ), 5, 5 ); } } } // 描画可能なビットマップを作る // http://stackoverflow.com/questions/831860/generate-bitmapsource-from-uielement RenderTargetBitmap bitmap = new RenderTargetBitmap( imageMD.XRes, imageMD.YRes, 96, 96, PixelFormats.Default ); bitmap.Render( drawingVisual ); image1.Source = bitmap; } private WriteableBitmap DrawPixels( ImageMetaData imageMD ) { // マネージドのバッファにする byte[] rgb = new byte[imageMD.DataSize]; Marshal.Copy( imageMD.ImageMapPtr, rgb, 0, imageMD.DataSize ); // ユーザーを検出したピクセルの色を変える SceneMetaData 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 WriteableBitmap 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 ); return bitmap; } private void Window_Closing( object sender, System.ComponentModel.CancelEventArgs e ) { shouldRun = false; } } }
初期化
骨格の追跡では、UserGeneratorを主に使用します。そのほかに画面表示用のImageGenerator、座標変換用のDepthGeneratorを使用します。
// ContextとImageGeneratorの作成 ScriptNode node; context = Context.CreateFromXmlFile( "../../SamplesConfig.xml", out node ); context.GlobalMirror = true; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; depth = context.FindExistingNode( NodeType.Depth ) as DepthGenerator; depth.AlternativeViewpointCapability.SetViewpoint( image ); // ユーザーの作成 user = context.FindExistingNode( NodeType.User ) as UserGenerator;
ユーザー検出の通知
ポーズの必要/不要にかかわらず、ユーザーの検出がトリガになりますので、そのイベントを登録します。
// ユーザー認識のコールバックを登録 user.NewUser += new EventHandler<NewUserEventArgs>( user_NewUser );
ポーズの設定
キャリブレーションにポーズが必要であれば、ポーズの検出イベントを登録します。ポーズが不要であれば、この処理も不要です
//キャリブレーションにポーズが必要か確認 if ( user.SkeletonCapability.DoesNeedPoseForCalibration ) { // ポーズ検出のサポートチェック if ( !user.IsCapabilitySupported( "User::PoseDetection" ) ) { throw new Exception( "ポーズ検出をサポートしていません" ); } // ポーズ検出のコールバックを登録 user.PoseDetectionCapability.PoseDetected += new EventHandler<PoseDetectedEventArgs>( poseDetect_PoseDetected ); }
骨格追跡のためのキャリブレーション設定
骨格を追跡するためのキャリブレーションのイベントを設定します。キャリブレーション完了時に、追跡させる処理を実装する必要があるので、キャリブレーション完了イベントを登録します。
// スケルトン検出機能をサポートしているか確認 if ( !user.IsCapabilitySupported( "User::Skeleton" ) ) { throw new Exception( "ユーザー検出をサポートしていません" ); } // キャリブレーションのコールバックを登録 user.SkeletonCapability.CalibrationEnd += new EventHandler<CalibrationEndEventArgs>( skelton_CalibrationEnd ); // すべてをトラッキングする user.SkeletonCapability.SetSkeletonProfile( SkeletonProfile.All );
ユーザーの検出イベント
ユーザーの検出イベントを受けたら、ポーズの必要状態によって、ポーズの検出または、キャリブレーションを行います
// ユーザーの検出通知 void user_NewUser( object sender, NewUserEventArgs e ) { // キャリブレーションポーズが必要な場合は、ポーズの検出を開始する if ( user.SkeletonCapability.DoesNeedPoseForCalibration ) { user.PoseDetectionCapability.StartPoseDetection( user.SkeletonCapability.CalibrationPose, e.ID ); } // キャリブレーションポーズが不要な場合は、キャリブレーションを開始する else { user.SkeletonCapability.RequestCalibration( e.ID, true ); } }
ポーズの検出イベント
ポーズが必要な場合、ユーザー検出の次はポーズの検出になります。ここでは、ポーズの検出を止めて、キャリブレーションの開始を行います。
// ポーズの検出通知 void poseDetect_PoseDetected( object sender, PoseDetectedEventArgs e ) { // ポーズの検出を停止し、キャリブレーションを開始する user.PoseDetectionCapability.StopPoseDetection( e.ID ); user.SkeletonCapability.RequestCalibration( e.ID, true ); }
キャリブレーションの完了イベント
キャリブレーションが成功したら、追跡を開始します。
// キャリブレーションの完了 void skelton_CalibrationEnd( object sender, CalibrationEndEventArgs e ) { // キャリブレーション成功 if ( e.Success ) { user.SkeletonCapability.StartTracking( e.ID ); } else { user.PoseDetectionCapability.StartPoseDetection( user.SkeletonCapability.CalibrationPose, e.ID ); } }
骨格の表示
表示方法は、前回と同じです
まとめ
まだちゃんと動かないので、もう少し調べます^^;