ブログ@kaorun55

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

Kinect for Windows SDK v1.7 の KinectFusionを使ってみる(C#)

KinectFusionを使ってみます。相当難しいのを覚悟していたのですが、ふたを開けてみると表示自体はあっけなくできて、むしろKinectInteractionのほうがライブラリ的には扱いが難しいくらいです(とはいえ、使いこなせるかどうかは、また別問題ですがw)。

理解のためにサンプルをばらして、必要な部分だけ切り出してみました。全体のコードはこちらにあります。

必要なもの

KinectFusionを扱うためには次のライブラリが必要です。

次の2つのライブラリをプロジェクトに追加し、コンテンツとして実行ファイルの場所にコピーさせます。

  • C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.7.0\Redist\amd64\KinectFusion170_64.dll
  • C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.7.0\Redist\x86\KinectFusion170_32.dll

次のライブラリを参照設定に追加します。

Microsoft.Kinect.Toolkit.Fusion.dll はDeveloper ToolkitのKinetFusion関連のサンプルまたは、Microsoft.Kinect.Toolkit.Fusion をダウンロードするとプロジェクトが取得できますので、それをビルドします。プロジェクトごとソリューションに追加するでもよいでしょう。

XAML

KinectFusion Basicsサンプルでは最終的なデータがビットマップで得られるので、今回もそれにならってImageに表示するようにします。

<Window x:Class="_02_KinectFutionBasicCS.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow" SizeToContent="WidthAndHeight">

<Grid>

<Image x:Name="ImageKinectFusion" Width="640" Height="480"/>

</Grid>

</Window>

 

コードビハインド

Kinectの初期化

今回のKinectFusionではDepthデータのみ利用するので、Depthデータのみ取得します(PCLを見てても色をつけられるので、未確認ですがそういった手段はあるのではないかと推測しています)。

private void InitializeKinect()

{

// Kinectの初期化(Depthだけ使う)

kinect = KinectSensor.KinectSensors[0];

kinect.DepthStream.Range = DepthRange.Near;

kinect.DepthStream.Enable( depthFormat );

kinect.DepthFrameReady += kinect_DepthFrameReady;

kinect.Start();

}

KinectFusionの初期化(InitializeKinectFusion)

KinectFusionには、最低でも次のデータが必要です。

  • PointCloudを構築するReconstructionのvolume
  • Depthデータから変換するFusionFloatImageFrame
  • PointCloudのバッファであるFusionPointCloudImageFrame
  • PointCloudをBitmap化したFusionColorImageFrame

それぞれのインスタンスを生成し、一度リセットします。

volumeのインスタンス生成には Reconstruction.FusionCreateReconstruction() を使います。4つの引数を与えるようになっており、それぞれは次のようになっています。

  • ReconstructionParameters

    • 構築するときのVoxelについてのパラメータ。サンプルの既定値を使用しています。

  • ReconstructionProcessor

    • AMPを選択するとGPU,CPUを選択するとCPUになります。CPUを選択した場合リアルタイムにの処理は難しくなります。

  • int

    • ReconstructionProcessorにAMPを選択した場合に設定します。AMPで使用するプロセッサを0から始まるインデックスで設定します。自動で選択させる場合には-1を指定します。

  • Matrix4

    • カメラ座標系への変換行列です。

// KinecFusionの初期化

var volParam = new ReconstructionParameters( VoxelsPerMeter, VoxelResolutionX, VoxelResolutionY, VoxelResolutionZ );

volume = Reconstruction.FusionCreateReconstruction( volParam, ReconstructionProcessor.Amp, -1, Matrix4.Identity );

変換バッファを作成し、リセットします。

// 変換バッファの作成

depthFloatBuffer = new FusionFloatImageFrame( DepthWidth, DepthHeight );

pointCloudBuffer = new FusionPointCloudImageFrame( DepthWidth, DepthHeight );

shadedSurfaceColorFrame = new FusionColorImageFrame( DepthWidth, DepthHeight );

// リセット

volume.ResetReconstruction( Matrix4.Identity );

Depthデータの取得と引き渡し

KinectFusionでは拡張された距離データを使用するので、DepthImagePixelデータを取得します。

// DepthImagePixelを使って処理する

private bool processingFrame = false;

void kinect_DepthFrameReady( object sender, DepthImageFrameReadyEventArgs e )

{

using ( DepthImageFrame depthFrame = e.OpenDepthImageFrame() ) {

if ( depthFrame != null && !processingFrame ) {

var depthPixels = new DepthImagePixel[depthFrame.PixelDataLength];

depthFrame.CopyDepthImagePixelDataTo( depthPixels );

Dispatcher.BeginInvoke(

DispatcherPriority.Background,

(Action<DepthImagePixel[]>)(d => ProcessDepthData( d )),

depthPixels );

processingFrame = true;

}

}

}

KinectFusionの処理(ProcessDepthData)

始めにDepthImagePixel から DepthFloatFrame へ変換します。変換には FusionDepthProcessor.DepthToDepthFloatFrame() を使用します。このときDepthの範囲を指定できます。また最後の引数(bool)をtrueにすると鏡のDepthイメージ、falseにすると実際のDepthイメージになります。

private int trackingErrorCount;

// DepthImagePixel から DepthFloatFrame に変換する

FusionDepthProcessor.DepthToDepthFloatFrame(

depthPixels,

DepthWidth,

DepthHeight,

depthFloatBuffer,

FusionDepthProcessor.DefaultMinimumDepth,

FusionDepthProcessor.DefaultMaximumDepth,

false );

DepthFloatFrame を処理します。ここで処理が成功すればそれまでのデータをPointCloudとして取得することができます。KinectFusionはDepthのデータを連続的に取得して精度を上げているため、Kinectまたは対象物が大きく移動すると構築できなくなります。そこで一定回数以上エラーになった場合には構築をリセットします。

private int trackingErrorCount;

// フレームを処理する

bool trackingSucceeded = volume.ProcessFrame(

depthFloatBuffer,

FusionDepthProcessor.DefaultAlignIterationCount,

FusionDepthProcessor.DefaultIntegrationWeight,

volume.GetCurrentWorldToCameraTransform() );

if ( !trackingSucceeded ) {

// 一定数エラーになったらリセット

// Kinectまたは対象を素早く動かしすぎ などの場合

trackingErrorCount++;

if ( trackingErrorCount >= 100 ) {

Trace.WriteLine( @"tracking error." );

trackingErrorCount = 0;

volume.ResetReconstruction( Matrix4.Identity );

}

return;

}

PointCloudを取得し、2次元の画像データに変換します。

private int trackingErrorCount;

// PointCloudを取得する

volume.CalculatePointCloud(

pointCloudBuffer,

volume.GetCurrentWorldToCameraTransform() );

// PointCloudを2次元のデータに描画する

FusionDepthProcessor.ShadePointCloud(

pointCloudBuffer,

volume.GetCurrentWorldToCameraTransform(),

shadedSurfaceColorFrame,

null );

画像データをbitmapに書きだします。

private int trackingErrorCount;

// 2次元のデータをBitmapに書きだす

var colorPixels = new int[depthPixels.Length];

shadedSurfaceColorFrame.CopyPixelDataTo( colorPixels );

ImageKinectFusion.Source = BitmapSource.Create( DepthWidth, DepthHeight, 96, 96,

PixelFormats.Bgr32, null, colorPixels, DepthWidth * 4 );

最後に

KinectとLeapの組み合わせの利用例なんかの動画も上がってましたが、KinectFusionでとった3DデータをLeapでグリグリするとか面白そうですね:-)