ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

OpenNI でユーザーデータを扱う( C# + WPF ) #openni_ac

このエントリは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でバイト列をもっとスマートに扱う方法があれば教えてください。