ブログ@kaorun55

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

Kinect for Windows SDK v2.0 でDepth(距離)データを取得する

Kinect for Windows SDK v2.0入門 目次

続いてDepth(距離)データを取得して表示する方法について解説します。Kinect for Windows v2のDepthデータは512x424の解像度になります。従来では640x480の解像度がありましたが、実際には320x240を引き延ばしていたので、解像度としては向上しています。

距離の範囲について、0.5m~4.5mとのことですが、距離データ自体は8mまで取得できます。4.5mはスケルトンの認識範囲になります。

v1にあった、デフォルト(Default)、ニア(Near)、拡張(Extend)のモードはなく、すべての距離範囲を扱えるようになっています。

環境

筆者の環境は次の通りです。

実行結果

Depthデータを画像化し、クリックした位置の距離(mm)を表示します。

スクリーンショット 2014-07-22 11.01.55

解説

Depthデータもカラー画像と同じようにKinectSensor(クラス)を起点に、DepthFrameSource取得、そこからDepthFrameReaderを開きます。DepthFrameReaderのデータが更新されるとDepthFrameを取得でき、そこから実際のデータ(Depthデータであれば距離データ)を取得します。

解像度(幅、高さなど)はFrameDescriptionで扱います。Depthデータの取得設定に先だって、使用するバッファの初期化などをFrameDescriptionを使って作成しています。

初期化

初期化の手順は次の通りです

  1. Kinectを開く
  2. Depthデータの解像度を取得し、バッファなどを作成する
  3. Depthリーダーを開く

まずKinectを開きます。

続いてDepthFrameSourceからFrameDescriptionを取得します。カラーと違いフォーマットなどは1つだけなので、規定のDescriptionを使ってビットマップやバッファの作成を行います。

最後にDepthリーダーを作成し、データの読み込み準備を行います。データの読み込みは、C++ではポーリング、C#ではイベントハンドラの登録を行います。

C++

void initialize()

{

// デフォルトのKinectを取得する

ERROR_CHECK( ::GetDefaultKinectSensor( &kinect ) );

// Kinectを開く

ERROR_CHECK( kinect->Open() );

BOOLEAN isOpen = false;

ERROR_CHECK( kinect->get_IsOpen( &isOpen ) );

if ( !isOpen ){

throw std::runtime_error("Kinectが開けません");

}

// Depthリーダーを取得する

ComPtr<IDepthFrameSource> depthFrameSource;

ERROR_CHECK( kinect->get_DepthFrameSource( &depthFrameSource ) );

ERROR_CHECK( depthFrameSource->OpenReader( &depthFrameReader ) );

// Depth画像のサイズを取得する

ComPtr<IFrameDescription> depthFrameDescription;

ERROR_CHECK( depthFrameSource->get_FrameDescription( &depthFrameDescription ) );

ERROR_CHECK( depthFrameDescription->get_Width( &depthWidth ) );

ERROR_CHECK( depthFrameDescription->get_Height( &depthHeight ) );

depthPointX = depthWidth / 2;

depthPointY = depthHeight / 2;

// Depthの最大値、最小値を取得する

ERROR_CHECK( depthFrameSource->get_DepthMinReliableDistance( &minDepthReliableDistance ) );

ERROR_CHECK( depthFrameSource->get_DepthMaxReliableDistance( &maxDepthReliableDistance ) );

std::cout << "Depthデータの幅 : " << depthWidth << std::endl;

std::cout << "Depthデータの高さ : " << depthHeight << std::endl;

std::cout << "Depth最小値 : " << minDepthReliableDistance << std::endl;

std::cout << "Depth最大値 : " << maxDepthReliableDistance << std::endl;

// バッファーを作成する

depthBuffer.resize( depthWidth * depthHeight );

// マウスクリックのイベントを登録する

cv::namedWindow( DepthWindowName );

cv::setMouseCallback( DepthWindowName, &KinectApp::mouseCallback, this );

}

C#(デスクトップ)

private void Window_Loaded( object sender, RoutedEventArgs e )

{

try {

kinect = KinectSensor.GetDefault();

if ( kinect == null ) {

throw new Exception("Kinectを開けません");

}

kinect.Open();

// 表示のためのデータを作成

depthFrameDesc = kinect.DepthFrameSource.FrameDescription;

// 表示のためのビットマップに必要なものを作成

depthImage = new WriteableBitmap( depthFrameDesc.Width, depthFrameDesc.Height,

96, 96, PixelFormats.Gray16, null );

depthBuffer = new ushort[depthFrameDesc.LengthInPixels];

depthRect = new Int32Rect( 0, 0, depthFrameDesc.Width, depthFrameDesc.Height );

depthStride = (int)(depthFrameDesc.Width * depthFrameDesc.BytesPerPixel);

ImageDepth.Source = depthImage;

// 初期の位置表示座標

depthPoint = new Point( depthFrameDesc.Width / 2, depthFrameDesc.Height / 2 );

// Depthリーダーを開く

depthFrameReader = kinect.DepthFrameSource.OpenReader();

depthFrameReader.FrameArrived += depthFrameReader_FrameArrived;

}

catch ( Exception ex ) {

MessageBox.Show( ex.Message );

Close();

}

}

C#(Windows ストアアプリ)

protected override void OnNavigatedTo( NavigationEventArgs e )

{

base.OnNavigatedTo( e );

try {

kinect = KinectSensor.GetDefault();

if ( kinect == null ) {

throw new Exception( "Kinectを開けません" );

}

kinect.Open();

// 表示のためのデータを作成

depthFrameDesc = kinect.DepthFrameSource.FrameDescription;

// ビットマップ

depthBitmap = new WriteableBitmap( depthFrameDesc.Width, depthFrameDesc.Height );

ImageDepth.Source = depthBitmap;

// Depthデータ用のバッファ

depthBuffer = new ushort[depthFrameDesc.LengthInPixels];

// DepthデータをBGRA(カラー)データにするためのバッファ

depthBitmapBuffer = new byte[depthFrameDesc.LengthInPixels * 4];

// 距離を表示する位置

depthPoint = new Point( depthFrameDesc.Width / 2, depthFrameDesc.Height / 2 );

// カラーリーダーを開く

depthFrameReader = kinect.DepthFrameSource.OpenReader();

depthFrameReader.FrameArrived += depthFrameReader_FrameArrived;

}

catch ( Exception ex ) {

MessageDialog dlg = new MessageDialog(ex.Message);

dlg.ShowAsync();

}

}

データの取得

続いてデータを取得し表示します。C++ではポーリング、C#ではイベントハンドラになりますが、大きな流れは同じです。

  1. Depthフレームを取得する
  2. Depthデータを取得する
  3. Depthデータを表示する
  4. Depthデータを使ってポイントされた位置を表示する

Depthフレーム、データを取得する

イベントが発生またはフレームが取得できたら、フレームからDepthデータをコピーします。Dpethデータは1ピクセルあたり16bitのデータが512x424個ならんでいます。配列の要素にアクセスすることで、任意の座標のDepthデータ(距離)を取得できます。

C++

void updateDepthFrame()

{

// Depthフレームを取得する

ComPtr<IDepthFrame> depthFrame;

auto ret = depthFrameReader->AcquireLatestFrame( &depthFrame );

if ( ret != S_OK ){

return;

}

// データを取得する

ERROR_CHECK( depthFrame->CopyFrameDataToArray( depthBuffer.size(), &depthBuffer[0] ) );

// 自動解放を使わない場合には、フレームを解放する

// depthFrame->Release();

}

C#(デスクトップ)

private void UpdateDepthFrame( DepthFrameArrivedEventArgs e )

{

using ( var depthFrame = e.FrameReference.AcquireFrame() ) {

if ( depthFrame == null ) {

return;

}

// Depthデータを取得する

depthFrame.CopyFrameDataToArray( depthBuffer );

}

}

C#(Windows ストアアプリ)

private void UpdateDepthFrame( DepthFrameArrivedEventArgs args )

{

// Depthフレームを取得する

using ( var depthFrame = args.FrameReference.AcquireFrame() ) {

if ( depthFrame == null ) {

return;

}

// Depthデータを取得する

depthFrame.CopyFrameDataToArray( depthBuffer );

}

}

Depthデータを表示する

Depthデータには0(mm)~8000(mm)の値が入っています。C++ではこれを8bitのグレーデータ(0~255の値)、C#(デスクトップ)では16bitのグレーデータ(0~65535の値)、C#(Windows ストアアプリ)では32bitのBGRAデータ(それぞれのピクセルはグレーデータ)に変換して表示しています。変換自体にあまり意味はありませんが、可視化のための処理となります。

C++

void drawDepthFrame()

{

// Depthデータを表示する

cv::Mat depthImage( depthHeight, depthWidth, CV_8UC1 );

// Depthデータを0-255のグレーデータにする

for ( int i = 0; i < depthImage.total(); ++i ){

depthImage.data[i] = ~((depthBuffer[i] * 255) / 8000);

}

...

}

C#(デスクトップ)

private void DrawDepthFrame()

{

// 距離情報の表示を更新する

UpdateDepthValue();

// 0-8000のデータを0-65535のデータに変換する(見やすく)

for ( int i = 0; i < depthBuffer.Length; i++ ) {

depthBuffer[i] = (ushort)(depthBuffer[i] * 65535 / 8000);

}

depthImage.WritePixels( depthRect, depthBuffer, depthStride, 0 );

}

C#(Windows ストアアプリ)

private void DrawDepthFrame()

{

// ポイントされた場所の距離を表示する

UpdateDepthValue();

// DepthデータをBGRAデータに変換する

for ( int i = 0; i < depthBuffer.Length; i++ ) {

// 0-8000を0-255に変換する

byte value = (byte)(depthBuffer[i] * 255 / 8000);

int colorindex = i * 4;

depthBitmapBuffer[colorindex + 0] = value;

depthBitmapBuffer[colorindex + 1] = value;

depthBitmapBuffer[colorindex + 2] = value;

depthBitmapBuffer[colorindex + 3] = 255;

}

// ビットマップにする

var stream = depthBitmap.PixelBuffer.AsStream();

stream.Write( depthBitmapBuffer, 0, depthBitmapBuffer.Length );

depthBitmap.Invalidate();

}

Depthデータを使ってポイントされた位置を表示する

最後にポイントした位置の距離を表示します。

それぞれの環境でマウスクリックの位置を取得し、そのX,Y座標をDepthデータ配列のインデックスに変換します。インデックスの値がその座標の距離データになるので、それを表示しています。

C++

クリックした座標を取得する

void mouseCallback( int event, int x, int y, int flags )

{

if ( event == CV_EVENT_LBUTTONDOWN ) {

depthPointX = x;

depthPointY = y;

}

}

クリックした座標の距離を表示する

void drawDepthFrame()

{

...

// Depthデータのインデックスを取得して、その場所の距離を表示する

int index = (depthPointY * depthWidth) + depthPointX;

std::stringstream ss;

ss << depthBuffer[index] << "mm";

cv::circle( depthImage, cv::Point( depthPointX, depthPointY ), 10, cv::Scalar( 0, 0, 255 ), 2 );

cv::putText( depthImage, ss.str(), cv::Point( depthPointX, depthPointY ), 0, 1, cv::Scalar( 0, 255, 255 ) );

cv::imshow( DepthWindowName, depthImage );

}

C#(デスクトップ)

クリックした座標を取得する

private void Window_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )

{

depthPoint = e.GetPosition( this );

}

クリックした座標の距離を表示する

private void UpdateDepthValue()

{

CanvasPoint.Children.Clear();

// クリックしたポイントを表示する

var ellipse = new Ellipse()

{

Width = R,

Height = R,

StrokeThickness = R / 4,

Stroke = Brushes.Red,

};

Canvas.SetLeft( ellipse, depthPoint.X - (R / 2) );

Canvas.SetTop( ellipse, depthPoint.Y - (R / 2) );

CanvasPoint.Children.Add( ellipse );

// クリックしたポイントのインデックスを計算する

int depthindex =(int)((depthPoint.Y * depthFrameDesc.Width) + depthPoint.X);

// クリックしたポイントの距離を表示する

var text = new TextBlock()

{

Text = string.Format( "{0}mm", depthBuffer[depthindex] ),

FontSize = 20,

Foreground = Brushes.Green,

};

Canvas.SetLeft( text, depthPoint.X );

Canvas.SetTop( text, depthPoint.Y - R );

CanvasPoint.Children.Add( text );

}

C#(Windows ストアアプリ)

クリックした座標を取得する

private void Page_PointerPressed( object sender, PointerRoutedEventArgs e )

{

depthPoint = e.GetCurrentPoint( CanvasPoint ).Position;

}

クリックした座標の距離を表示する

private void UpdateDepthValue()

{

CanvasPoint.Children.Clear();

// クリックしたポイントを表示する

var ellipse = new Ellipse()

{

Width = R,

Height = R,

StrokeThickness = R / 4,

Stroke = new SolidColorBrush( Colors.Red ),

};

Canvas.SetLeft( ellipse, depthPoint.X - (R / 2) );

Canvas.SetTop( ellipse, depthPoint.Y - (R / 2) );

CanvasPoint.Children.Add( ellipse );

// クリックしたポイントのインデックスを計算する

int depthindex =(int)((depthPoint.Y * depthFrameDesc.Width) + depthPoint.X);

// クリックしたポイントの距離を表示する

var text = new TextBlock()

{

Text = string.Format( "{0}mm", depthBuffer[depthindex] ),

FontSize = 20,

Foreground = new SolidColorBrush( Colors.Green ),

};

Canvas.SetLeft( text, depthPoint.X );

Canvas.SetTop( text, depthPoint.Y - R );

CanvasPoint.Children.Add( text );

}

まとめ

今回はDepthデータの取得と表示、距離データの取得方法について解説しました。Depthデータは距離を測るだけではなく、データ全体を使って距離データで画像処理を行う、後述するCameraPoint(3D位置)に変換して利用するといった、利用方法があります。DepthデータはDepthセンサーのキモとなるデータですので、ぜひDepthデータの面白い使い方を考えてみてください。

Kinect for Windows SDK v2.0入門 目次