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)のモードはなく、すべての距離範囲を扱えるようになっています。
環境
筆者の環境は次の通りです。
- Windows 8.1 Pro Update1 64bit
- Visual Studio 2013 Ultimate(Express for Windows Desktopでも可)
- Kinect for Windows SDK v2.0-1409(リリース版)
- 32bitアプリケーション
- サンプルコードのリポジトリ
実行結果
Depthデータを画像化し、クリックした位置の距離(mm)を表示します。
解説
Depthデータもカラー画像と同じようにKinectSensor(クラス)を起点に、DepthFrameSource取得、そこからDepthFrameReaderを開きます。DepthFrameReaderのデータが更新されるとDepthFrameを取得でき、そこから実際のデータ(Depthデータであれば距離データ)を取得します。
解像度(幅、高さなど)はFrameDescriptionで扱います。Depthデータの取得設定に先だって、使用するバッファの初期化などをFrameDescriptionを使って作成しています。
初期化
初期化の手順は次の通りです
- Kinectを開く
- Depthデータの解像度を取得し、バッファなどを作成する
- 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#ではイベントハンドラになりますが、大きな流れは同じです。
- Depthフレームを取得する
- Depthデータを取得する
- Depthデータを表示する
- 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データの面白い使い方を考えてみてください。