Kinect for Windows SDK v2.0入門 目次
赤外線画像を取得して表示する方法について解説します。Kinect for Windows v1でも赤外線画像の取得はできましたが、PrimeSense系センサーの仕様で、カラー画像と赤外線画像はどちらかしか使えませんでした。v2ではカラー画像と赤外線画像は同時に取得できるようになり、有効に活用できそうです。
赤外線画像の使いどころとして、最初に思いつくのが暗所での利用でしょう。暗いところでも画像が取得できるようになるので、赤外線画像を利用した画像処理が可能になるかと思います。
それを発展させた例として昨年のKinect for Windows Contestで技術賞を受賞した「非接触バイタル・センシング」が挙げられます。Kinect v2でも実現可能なこの技術をいち早く実装しており、これに赤外線画像を利用しています(下記画像はKinect for Windows Contestのサイトより引用)。
ここでは基本となる赤外線画像データの取得と表示について解説します。
なお、現在のSDKでは赤外線画像のInfraredFrameと長時間露光した赤外線画像のLongExposureInfraredFrameの2種類がありますが、違いが内容に見受けられます。試したい場合にはこのサンプルをInfraredXxxxxからLongExposureInfraredXxxxxに変えてみてください。
環境
筆者の環境は次の通りです。
- Windows 8.1 Pro Update1 64bit
- Visual Studio 2013 Ultimate(Express for Windows Desktopでも可)
- Kinect for Windows SDK v2.0-1409(リリース版)
- 32bitアプリケーション
- サンプルコードのリポジトリ
実行結果
赤外線画像を表示します。
解説
赤外線画像はほぼカラー画像と同じ方法で取得します。InfraredFrameSource、InfraredFrameReader、InfraredFrameを使用します。
赤外線画像の解像度(幅、高さなど)はFrameDescriptionで取得できます。解像度は512x424のDepthデータと同じサイズ。データフォーマットはushort(16bit)の配列です。
初期化
初期化の手順は次の通りです
- Kinectを開く
- 赤外線画像リーダーを開く
まずKinectを開きます。続いてInfraredFrameSourceを使ってFrameDescriptionを取得します。最後に赤外線画像リーダーを作成し、データの読み込み準備を行います。データの読み込みは、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が開けません");
}
// 赤外線画像リーダーを取得する
ComPtr<IInfraredFrameSource> infraredFrameSource;
ERROR_CHECK( kinect->get_InfraredFrameSource( &infraredFrameSource ) );
ERROR_CHECK( infraredFrameSource->OpenReader( &infraredFrameReader ) );
// 赤外線画像のサイズを取得する
ComPtr<IFrameDescription> infraredFrameDescription;
ERROR_CHECK( infraredFrameSource->get_FrameDescription( &infraredFrameDescription ) );
ERROR_CHECK( infraredFrameDescription->get_Width( &infraredWidth ) );
ERROR_CHECK( infraredFrameDescription->get_Height( &infraredHeight ) );
// バッファーを作成する
infraredBuffer.resize( infraredWidth * infraredHeight );
}
C#(デスクトップ)
private void Window_Loaded( object sender, RoutedEventArgs e ){
try {
// Kinectを開く
kinect = KinectSensor.GetDefault();
if ( kinect == null ) {
throw new Exception("Kinectを開けません");
}
kinect.Open();
// 赤外線画像の情報を取得する
infraredFrameDesc = kinect.InfraredFrameSource.FrameDescription;
// 赤外線リーダーを開く
infraredFrameReader = kinect.InfraredFrameSource.OpenReader();
infraredFrameReader.FrameArrived += infraredFrameReader_FrameArrived;
}
catch ( Exception ex ) {
MessageBox.Show( ex.Message );
Close();
}
}
C#(Windows ストアアプリ)
protected override void OnNavigatedTo( NavigationEventArgs e ){
base.OnNavigatedTo( e );
try {
// Kinectを開く
kinect = KinectSensor.GetDefault();
if ( kinect == null ) {
throw new Exception( "Kinectを開けません" );
}
kinect.Open();
// 赤外線画像の情報を取得する
infraredFrameDesc = kinect.InfraredFrameSource.FrameDescription;
// 画像化のためのバッファを作成する
infraredBitmapBuffer = new byte[infraredFrameDesc.LengthInPixels * 4];
infraredBitmap = new WriteableBitmap( infraredFrameDesc.Width, infraredFrameDesc.Height );
ImageInfrared.Source = infraredBitmap;
infraredBuffer = new ushort[infraredFrameDesc.LengthInPixels];
// 赤外線画像リーダーを開く
infraredFrameReader = kinect.InfraredFrameSource.OpenReader();
infraredFrameReader.FrameArrived += infraredFrameReader_FrameArrived;
}
catch ( Exception ex ) {
MessageDialog dlg = new MessageDialog( ex.Message );
dlg.ShowAsync();
}
}
データの取得
続いてデータを取得し表示します。C++ではポーリング、C#ではイベントハンドラになりますが、大きな流れは同じです。
- 赤外線画像フレームを取得する
- 赤外線画像データを取得する
- 赤外線画像データを表示する
イベントが発生またはフレームが取得できたら、フレームから赤外線画像データをコピーします。赤外線画像データは1ピクセルあたり16bitのデータが512×424個ならんでいます。C++およびC#(デスクトップ)ではこれをそのまま画像化して表示します。C#(Windows ストアアプリ)ではBGRAデータに変換して表示しています。
C++
void updateInfrared(){
// フレームを取得する
ComPtr<IInfraredFrame> infraredFrame;
auto ret = infraredFrameReader->AcquireLatestFrame( &infraredFrame );
if ( ret == S_OK ){
// BGRAの形式でデータを取得する
ERROR_CHECK( infraredFrame->CopyFrameDataToArray( infraredBuffer.size(), &infraredBuffer[0] ) );
// フレームを解放する
// infraredFrame->Release();
}
}
void draw()
{
// カラーデータを表示する
cv::Mat colorImage( infraredHeight, infraredWidth, CV_16UC1, &infraredBuffer[0] );
cv::imshow( "Infrared Image", colorImage );
}
C#(デスクトップ)
void infraredFrameReader_FrameArrived( object sender, InfraredFrameArrivedEventArgs e ){
// カラーフレームを取得する
using ( var colorFrame = e.FrameReference.AcquireFrame() ) {
if ( colorFrame == null ) {
return;
}
// 赤外線画像データを取得する
var infraredBuffer = new ushort[infraredFrameDesc.Width * infraredFrameDesc.Height];
colorFrame.CopyFrameDataToArray( infraredBuffer );
// ビットマップにする
ImageColor.Source = BitmapSource.Create( infraredFrameDesc.Width, infraredFrameDesc.Height, 96, 96,
PixelFormats.Gray16, null, infraredBuffer, infraredFrameDesc.Width * (int)infraredFrameDesc.BytesPerPixel );
}
}
C#(Windows ストアアプリ)
void infraredFrameReader_FrameArrived( InfraredFrameReader sender, InfraredFrameArrivedEventArgs args ){
UpdateInfraredFrame( args );
DrawInfraredFrame();
}
private void UpdateInfraredFrame( InfraredFrameArrivedEventArgs args )
{
// 赤外線画像フレームを取得する
using ( var infraredFrame = args.FrameReference.AcquireFrame() ) {
if ( infraredFrame == null ) {
return;
}
// 赤外線画像データを取得する
infraredFrame.CopyFrameDataToArray( infraredBuffer );
}
}
private void DrawInfraredFrame()
{
// 赤外線画像データをBGRAデータに変換する
for ( int i = 0; i < infraredBuffer.Length; i++ ) {
// 0-65535を0-255に変換する
byte value = (byte)(infraredBuffer[i] * 255 / 65535);
int colorindex = i * 4;
infraredBitmapBuffer[colorindex + 0] = value;
infraredBitmapBuffer[colorindex + 1] = value;
infraredBitmapBuffer[colorindex + 2] = value;
infraredBitmapBuffer[colorindex + 3] = 255;
}
// ビットマップにする
var stream = infraredBitmap.PixelBuffer.AsStream();
stream.Write( infraredBitmapBuffer, 0, infraredBitmapBuffer.Length );
infraredBitmap.Invalidate();
}
まとめ
赤外線画像自体は簡単に取得できます。これを使って何をするかというところがアイデアになります。赤外線画像に対する画像処理は、観測範囲ではあまり見かけないので、試してみるとよいかと思います。