Kinect for Windows v1 の KinectInteraction

Kinect for Windows Developer Toolkit には、KinectInteraction という、
つまむ操作 (Grip) や押す操作 (Press) を判定するためのライブラリが含まれています。
今回は、KinectInteraction でこれらのジェスチャ認識を実装するための方法について説明します。
Kinect for Windows のバージョンは 1.8 とします。

Kinect for Windows Developer Toolkit に付属するサンプルの中にある「ControlsBasics-WPF」では、
Microsoft.Kinect.Toolkit.Controls プロジェクトの中で UI コントロールまで作り込まれていますが、
ここでは KinectInteraction を単独で利用してみます。

KinectInteraction を利用するには、次の DLL が必要になります。

  • Microsoft.Kinect.dll
  • Microsoft.Kinect.Toolkit.Interaction.dll
  • KinectInteraction180_32.dll (ネイティブ、32 ビット向け)
  • KinectInteraction180_64.dll (ネイティブ、64 ビット向け)

ちなみに、Microsoft.Kinect.Toolkit.dll は KinectInteraction とは直接には関係がないため不要です。

 

■ プロジェクトの作成と参照の追加

Visual Studio で、WPF アプリケーション プロジェクトを作成します。
既定では [プラットフォーム ターゲット] が Any CPU、[32 ビットの優先] がオンに設定されています。
この設定のままであれば、32 ビット用の DLL を利用します。

64 ビットで動作させたい場合は、
[プラットフォーム ターゲット] を x64 に設定するか、[32 ビットの優先] をオフに設定して、
64 ビット用の DLL を利用します。

.NET の DLL への参照は、[参照の追加] から 1 つずつ追加してもよいのですが、
なるべく環境変数を利用したいので、プロジェクト ファイル (.csproj) を直接編集します。
次のように、Kinect に関連する部分を ItemGroup に追加します。


<Reference Include="Microsoft.Kinect">
  <HintPath>$(KINECTSDK10_DIR)Assemblies\Microsoft.Kinect.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Kinect.Toolkit.Interaction">
  <HintPath>$(KINECT_TOOLKIT_DIR)Redist\Microsoft.Kinect.Toolkit.Interaction.dll</HintPath>
</Reference>


次に、ネイティブの DLL をビルド時にコピーさせるための設定です。
プロジェクト ファイルの下のほうに、次の ItemGroup を追加します。


<ItemGroup>
  <Content Include="$(KINECT_TOOLKIT_DIR)Redist\x86\KinectInteraction180_32.dll">
    <Link>KinectInteraction180_32.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>


 

プロジェクトのプロパティ

 

■ 実装について

今回は、取得できるプロパティの値の一覧を表示するアプリケーションを実装します。
次のコードはその一部です (全体のソースコードは KinectInteraction (GitHub) にあります)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Microsoft.Kinect;
using Microsoft.Kinect.Toolkit.Interaction;
using Reactive.Bindings;
namespace InteractionWpf
{
public class InteractionTracker
{
public ReactiveProperty<UserInfo[]> UserInfoes { get; } = new ReactiveProperty<UserInfo[]>(mode: ReactivePropertyMode.DistinctUntilChanged);
public ReadOnlyReactiveProperty<UserInfo> UserInfo { get; }
KinectSensor Sensor;
InteractionStream InteractionStream;
public InteractionTracker()
{
UserInfo = UserInfoes.Select(ToFirstUserInfo).ToReadOnlyReactiveProperty();
if (DispatcherHelper.IsInDesignMode) return;
if (!KinectSensor.KinectSensors.Any()) return;
Sensor = KinectSensor.KinectSensors[0];
Sensor.AllFramesReady += AllFramesReady;
InteractionStream = new InteractionStream(Sensor, new DummyClient());
InteractionStream.InteractionFrameReady += InteractionFrameReady;
Sensor.DepthStream.Enable();
Sensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;
Sensor.SkeletonStream.Enable();
Sensor.Start();
}
void AllFramesReady(object sender, AllFramesReadyEventArgs e)
{
using (var frame = e.OpenDepthImageFrame())
{
if (frame != null)
{
InteractionStream.ProcessDepth(frame.GetRawPixelData(), frame.Timestamp);
}
}
using (var frame = e.OpenSkeletonFrame())
{
if (frame != null)
{
var skeletons = new Skeleton[frame.SkeletonArrayLength];
frame.CopySkeletonDataTo(skeletons);
InteractionStream.ProcessSkeleton(skeletons, Sensor.AccelerometerGetCurrentReading(), frame.Timestamp);
}
}
}
void InteractionFrameReady(object sender, InteractionFrameReadyEventArgs e)
{
using (var frame = e.OpenInteractionFrame())
{
if (frame == null) return;
var userInfoes = new UserInfo[InteractionFrame.UserInfoArrayLength];
frame.CopyInteractionDataTo(userInfoes);
UserInfoes.Value = userInfoes;
}
}
UserInfo ToFirstUserInfo(UserInfo[] us) =>
UserInfo.Value == null ?
us.FirstOrDefault(u => u.SkeletonTrackingId != 0) :
us.FirstOrDefault(u => u.SkeletonTrackingId == UserInfo.Value.SkeletonTrackingId);
}
public class DummyClient : IInteractionClient
{
static readonly InteractionInfo DefaultInteractionInfo = new InteractionInfo();
public InteractionInfo GetInteractionInfoAtLocation(int skeletonTrackingId, InteractionHandType handType, double x, double y)
{
return DefaultInteractionInfo;
}
}
}

InteractionStream のコンストラクターには、IInteractionClient オブジェクトを渡す必要があります。
IInteractionClient インターフェイスの実装については公式ドキュメントも存在しないようで、詳細は不明です。
ここでは、とくに何の処理もしない DummyClient クラスを用意しています。
(サンプルの「ControlsBasics-WPF」には、 IInteractionClient インターフェイスを実装した KinectAdapter クラスが存在します。)

InteractionStream クラスは、DepthImageStream と SkeletonStream を拡張したクラスとなっており、
AllFramesReady イベント ハンドラーでそれぞれのデータを InteractionStream に渡します。
すると、InteractionFrameReady イベントで UserInfo のコレクションを取得できます。
最大で 2 人まで同時に認識できるようです。

次の図は、アプリケーションを実行した結果です。

InteractionWpf

表示されているのは、主に InteractionHandPointer クラスのプロパティの値です。
IsInteractive が true となるには、それぞれの手が有効な範囲に入る必要があります。
有効な範囲とは、左手であれば左肩周辺、右手であれば右肩周辺です。
この領域の左上が (X, Y) = (0, 0)、右下が (X, Y) = (1, 1) となります。

IsTracked は有効な範囲に入っていなくても true になることがあります。
IsInteractive は、IsTracked かつ (X ,Y) が (0, 0) から (1, 1) までの四角形の範囲に入っている状態です。

座標を取得するためのプロパティとして、X, Y, PressExtent, RawX, RawY, RawZ があります。
X, Y と RawX, RawY はそれぞれ同じ値のようです。
PressExtent ≧ 1 のとき、IsPressed が true となります。
PressExtent は、下限値が 0 で、1 より大きい値にもなります。

有効な範囲が高い位置にあるため、少し下の位置で使えるようにしたいとは思うのですが、
とくに PressExtent と RawZ は、有効な範囲にないときの値はあまり信用できません。
このためか、IsInteractive でないときも IsPressed になることがあるため注意が必要です。
したがって、ジェスチャを判定するときは先に IsInteractive が true であることを確認しましょう。

 

作成したサンプル
KinectInteraction (GitHub)

バージョン情報
.NET Framework 4.5
Kinect for Windows SDK 1.8

参照
KinectInteraction
Microsoft.Kinect.Toolkit.Interaction Namespace

Kinect for Windows SDK のセットアップ

カテゴリー: 周辺機器. タグ: . Leave a Comment »