2012年7月26日木曜日

OpenNIでキャプチャしてOpenCVで処理してC#で表示する

ASUSのXtionPRO LiveからRGB画像とDepth画像を取得し,
OpenCVでちょこっと処理し,結果をC#で表示するプログラム.

今回はC++で基本的な作業を行なうDLLを作成し,C#でGUI部分を作成する.

  1. 基本DLL.OpenCVやOpenNIなどの外部ライブラリを使用する.
    • プロジェクト名:XCapBase
      • ソースファイル:XtionCaptureBase.cpp
      • ヘッダファイル:XtionCaptureBase.h
  2. ラッパークラスDLLその1.C++として,1のライブラリをくるみ,入出力のみ行う.
    • プロジェクト名:XCapWrapper
      • ソースファイル:XCaptureWrapper.cpp
      • ヘッダファイル:XCaptureWrapper.h
  3. ラッパークラスDLLその2.C++/CLIとして,2のライブラリをくるむ.C#から参照可能.
    • プロジェクト名:XCWrapCLI
      • ソースファイル:XCWrapCLI.cpp
      • ヘッダファイル:XCWrapCLI.h
  4. C#のWindows Formプログラム.
    • プロジェクト名:XtionCapture
1~3はDLLとして作成する.(Win32コンソールアプリケーションを選択し,ウィザードの最後でDLLにチェックを入れる)
いろいろと無駄があるかもしれないけれど,とりあえず動くのでOK.

XCapBase : 基本処理クラス

このクラスの中であれば,OpenCVやOpenNIの関数を自由に使える.
ただし,public 関数には,独自型を使わないようにしておく.

XtionCaptureBase.h
#ifdef XCAPBASE_EXPORTS
#define XCB_DECLSPEC __declspec(dllexport) // DLLにexport
#else
#define XCB_DECLSPEC __declspec(dllimport) // DLLをimport
#endif

#include <XnCppWrapper.h>
#include <opencv2/opencv.hpp>

class XCB_DECLSPEC CXtionCaptureBase
{
public:
 CXtionCaptureBase(void);
 ~CXtionCaptureBase(void);

 void capture(int *w, int *h, char **rgb, char **dmap);
private:
 xn::Context m_context;
 xn::ImageGenerator m_image;
 xn::DepthGenerator m_depth;
 cv::Mat m_RGBmat;
 cv::Mat m_Depthmat;
};
XtionCaptureBase.cpp
#include "XtionCaptureBase.h"

#pragma comment(lib,"openNI.lib")

#ifdef _DEBUG
#pragma comment (lib, "opencv_core242d.lib") 
#pragma comment (lib, "opencv_highgui242d.lib")
#pragma comment (lib, "opencv_imgproc242d.lib")
#else
#pragma comment (lib, "opencv_core242.lib") 
#pragma comment (lib, "opencv_highgui242.lib")
#pragma comment (lib, "opencv_imgproc242.lib")
#endif

using namespace xn;
using namespace cv;

CXtionCaptureBase::CXtionCaptureBase(void)
{
 m_context.Init(); 

 XnMapOutputMode mapMode; 
 mapMode.nXRes = 640; 
 mapMode.nYRes = 480; 
 mapMode.nFPS = 30; 
 
 m_image.Create(m_context); 
 m_image.SetMapOutputMode(mapMode); 

 m_depth.Create(m_context); 
 m_depth.SetMapOutputMode(mapMode); 

 m_context.StartGeneratingAll(); 

}

CXtionCaptureBase::~CXtionCaptureBase(void)
{
}

void CXtionCaptureBase::capture(int *w, int *h, char **rgb, char **dmap)
{
 ImageMetaData imageMD; 
 DepthMetaData depthMD; 

 m_context.WaitAnyUpdateAll();//wait and error processing

 m_image.GetMetaData(imageMD);
 m_depth.GetMetaData(depthMD);

 m_depth.GetAlternativeViewPointCap().SetViewPoint(m_image);  

 //Image
 Mat imageR(480, 640, CV_8UC3, (unsigned char*)imageMD.RGB24Data());
 cvtColor(imageR, m_RGBmat, CV_RGB2BGR);

 //Depth
 Mat depth16(480, 640, CV_16UC1, (unsigned short*)depthMD.Data());
 convertScaleAbs(depth16, m_Depthmat, 0.7, -300); //16bit -> 8bit

 *w = 640;
 *h = 480;
 *rgb = (char *)m_RGBmat.data;
 *dmap = (char *)m_Depthmat.data;
}

XCapWrapper : C++のラッパークラス

プロジェクトの「参照」に,XCapBaseを追加する.

XCapWrapper.h
#ifdef XCAPWRAPPER_EXPORTS
#define XCW_DECLSPEC __declspec(dllexport) // DLLにexport
#else
#define XCW_DECLSPEC __declspec(dllimport) // DLLをimport
#endif

class XCW_DECLSPEC CXCaptureWrapper
{
public:
 CXCaptureWrapper(void);
 ~CXCaptureWrapper(void);

 void capture(int *w, int *h, char **rgb, char **dmap);
};
XCapWrapper.cpp
#include "XCaptureWrapper.h"
#include "../XCapBase/XtionCaptureBase.h"

CXtionCaptureBase *cxcb;

CXCaptureWrapper::CXCaptureWrapper(void)
{
 cxcb = new CXtionCaptureBase();
}

CXCaptureWrapper::~CXCaptureWrapper(void)
{
 delete cxcb;
}

void CXCaptureWrapper::capture(int *w, int *h, char **rgb, char **dmap)
{
 cxcb->capture(w, h, rgb, dmap);
}

XCWrapCLI : C++/CLIのラッパークラス

「構成プロパティ」の「全般」で共通言語ランタイムサポート(/clr)を選択する.
プロジェクトの共通プロパティ「参照」に「System.Drawing」と「XCapWrapper」を追加.
キャプチャした画像をBitmapにして返す.

参考サイト:C++/CLIで動かしているOpenCVのCvImageをC#.NETのBitmapオブジェクトにして読み込む

ここで取り出そうとするビットマップ画像の幅は,4の倍数じゃないとエラーが起こる.

XCWrapCLI.h
#pragma once

using namespace System::Drawing;

namespace XCWrapper {
 public ref class XCWrapCLI
 {
 public:
  XCWrapCLI(void);
  void capture(void);
  System::Drawing::Bitmap^ getBitmap(void);
  System::Drawing::Bitmap^ getDepthmap(void);
 private:
  System::Drawing::Bitmap^ bitmap;
  System::Drawing::Bitmap^ depthmap;
 };
}
XCWrapCLI.cpp
#include "XCWrapCLI.h"
#include "../XCapWrapper/XCaptureWrapper.h"

namespace XCWrapper {
 CXCaptureWrapper *xcap;
 XCWrapCLI::XCWrapCLI(void)
 {
  xcap = new CXCaptureWrapper();
 }

 void XCWrapCLI::capture()
 {
  int w, h;
  char *prgb, *pdmap;
  xcap->capture(&w, &h, &prgb, &pdmap);

  //RGB Image
  this->bitmap = dynamic_cast<System::Drawing::Bitmap^>(
   gcnew System::Drawing::Bitmap(w, h, w*3,
   System::Drawing::Imaging::PixelFormat::Format24bppRgb,
   static_cast<System::IntPtr>(prgb)));

  //DepthMap
  this->depthmap = dynamic_cast<System::Drawing::Bitmap^>(
   gcnew System::Drawing::Bitmap(w, h, w,
   System::Drawing::Imaging::PixelFormat::Format8bppIndexed,
   static_cast<System::IntPtr>(pdmap)));
  System::Drawing::Imaging::ColorPalette^ pal = this->depthmap->Palette;
  for(int i=0; i<256; i++) pal->Entries[i] = Color::FromArgb(255,i,i,i);
  this->depthmap->Palette = pal;
 }

 System::Drawing::Bitmap^ XCWrapCLI::getBitmap(void)
 {
  return this->bitmap;
 }

 System::Drawing::Bitmap^ XCWrapCLI::getDepthmap(void)
 {
  return this->depthmap;
 }
}


XtionCapture : C#のプログラム

スタートボタンを押すと,キャプチャ画像を(スレッドを使って)表示する.

mainForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Threading;
using XCWrapper;

namespace XtionCapture
{
    public partial class mainForm : Form
    {
        private Thread trd;
        private bool moveFlag;
        XCWrapCLI xcap;

        public mainForm()
        {
            InitializeComponent();
            moveFlag = false;
            xcap = new XCWrapCLI();
        }

        private void mainForm_Load(object sender, EventArgs e)
        {
            trd = new Thread(new ThreadStart(this.ThreadTask));
            trd.IsBackground = true;
            trd.Start();
        }

        private void ThreadTask()
        {
            while (true)
            {
                if (moveFlag)
                {
                    xcap.capture();
                    pictureBox1.Image = xcap.getBitmap();
                    pictureBox2.Image = xcap.getDepthmap();
                }

                Thread.Sleep(30);
            }
        }

        private void StartButton_Click(object sender, EventArgs e)
        {
            moveFlag = true;

        }

        private void StopButton_Click(object sender, EventArgs e)
        {
            moveFlag = false;
        }
    }
}