GUST NOTCH? DIARY

OpenGL(GLUT)とOpenCVとARToolkit

最近これらを使うことが多いのですが、それぞれ目的が少しずつ違うので、

というように得意分野が違うわけです。なので、必然的にこれらを組み合わせて使う必要がでてきたりするので、個人的なメモとして書いときます。環境はWindows XP, VS2008, OpenCV2.1, ARToolkit2.72.1, GLUT3.7。

この場合、GUIとカメラキャプチャはARToolkitということになるので、カメラからの取得画像の変換ができればよい。


  ARUint8 *dataPtr; // ARToolkit によるカメラ画像へのポインタ
  IplImage *image; // OpenCV の画像
  
  // カメラから画像を取得
  dataPtr = (ARUint8 *)arVideoGetImage()
  // OpenCV用の画像を作成する。ARToolkit がRGBAの4チャンネルなのでそれにあわせる
  image = cvCreateImage( cvSize(win_xsize, win_ysize), IPL_DEPTH_8U, 4 );
  memcpy( image->imageData, dataPtr, image->imageSize ); // 画像をコピー
  // OpenCVで何か処理する
  cvFlip( image, image, 1 ); // 左右反転

ARToolkitGLUTベースなので、OpenCV用にはRGBAフォーマットで画像バッファを作成してIplImage->imageDataに内容を渡してやればよい。逆もまた然り。

基本的には上記と同じなのですが、カメラキャプチャもOpenCVで行うことになる。


  frameImage = cvQueryFrame( capture ); // カメラから画像を取得
  cvCvtColor( frameImage, copyImage, CV_BGR2RGB ); // OpenCV のBGR並びをRGBに変換しながらコピー
  cvFlip( copyImage, copyImage, 0 ); // OpenGLは左下原点なので上下を入れ替える
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // バッファクリア
  glMatrixMode(GL_PROJECTION); // 投影行列を初期化
  glLoadIdentity();
  glRasterPos2i( -1, -1 ); // 二次元画像の原点を左下に設定
  // OpenCV の画像データを描画
  glDrawPixels( copyImage->width, copyImage->height, GL_RGB, GL_UNSIGNED_BYTE, copyImage->imageData );

ここでは、カメラで取得した画像をGLUTで作成したウィンドウ上に表示している。OpenCVで得られる画像は、Windows NativeのBGR並びになっているので、これをOpenGLに合わせてRGB(またはRGBA)に変換しないと色がおかしくなる。また、OpenGLは左下原点、得られた画像は左上原点になっているのでこれをあわせる必要がある。ここではOpenCV側で反転画像を作成してから描画しているが、OpenGL側で画像座標系を指定することで解決させることも可能。

GUI作成とカメラキャプチャはOpenCVで行う。OpenCVで作ったウィンドウにどうやってOpenGLの3Dモデルを表示するかがポイント。ちょっと長いですが、動くものを載せときます。


#include
#include
#include
#include
#include
#include // for glutSolidCube()
  
// OpenGLのためのフォーマット指定
static PIXELFORMATDESCRIPTOR pfd = {
  sizeof (PIXELFORMATDESCRIPTOR),
  1,
  PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
  PFD_TYPE_RGBA,
  24,
  0, 0, 0,
  0, 0, 0,
  0, 0,
  0, 0, 0, 0, 0,
  32,
  0,
  0,
  PFD_MAIN_PLANE,
  0,
  0,
  0,
  0
};
  
int main( int argc, char *argv )
{
  CvCapture *capture=NULL;
  IplImage *frameImage=NULL, *copyImage=NULL, *outputImage=NULL;
  int key;
  int count=0;
  HWND hwnd;
  HDC hdc;
  HGLRC hglrc;
  int pfdID;
  GLfloat light_diffuse
= {1.0, 0.0, 0.0, 1.0};
  GLfloat light_position[] = {3.0, 3.0, 3.0, 0.0};
  
  capture = cvCreateCameraCapture( -1 );
  
  frameImage = cvQueryFrame( capture ); // カメラ画像の取得
  cvNamedWindow( "OpenCV", CV_WINDOW_AUTOSIZE ); // ウインドウ作成
  cvResizeWindow( "OpenCV", frameImage->width, frameImage->height );
  // 画像バッファ作成
  copyImage = cvCreateImage( cvSize(frameImage->width, frameImage->height), 8, 4 ); // RGBA の4バイト
  outputImage = cvCreateImage( cvSize(frameImage->width, frameImage->height), 8, 3 ); // BGR の3バイト
  
  hwnd = (HWND)cvGetWindowHandle( "OpenCV" ); // ウインドウハンドルを取得
  hdc = GetDC(hwnd); // デバイスコンテキストを取得
  pfdID = ChoosePixelFormat(hdc, &pfd);
  SetPixelFormat(hdc, pfdID, &pfd); // デバイスコンテキストにピクセルフォーマットを設定
  hglrc = wglCreateContext(hdc); // OpenGL用のコンテキストを作成
  wglMakeCurrent(hdc, hglrc); // ここからOpwnGLコマンドが有効になる
  
  // 光源設定
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  // 陰面処理を有効に
  glEnable(GL_DEPTH_TEST);
  
  while( 1 ) {
    frameImage = cvQueryFrame( capture );
    cvCvtColor(frameImage, copyImage, CV_BGR2RGBA ); // OpenGL用に画素の並びを変換
    cvFlip( copyImage, copyImage, 0 ); // OpenGLの原点に合わせて上下反転
  
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); // バッファクリア
    glMatrixMode(GL_PROJECTION); // 投影行列を初期化
    glLoadIdentity();
  
    glRasterPos2i( -1, -1 ); // 二次元画像の原点を左下に設定
    // OpenCV の画像データを描画
    glDrawPixels( copyImage->width, copyImage->height, GL_RGBA, GL_UNSIGNED_BYTE, copyImage->imageData ); //キャプチャした画像を背景として描画
  
    glClear( GL_DEPTH_BUFFER_BIT ); // デプスバッファのみをクリア
  
    glOrtho( -4.0, 4.0, -3.0, 3.0, -5.0, 5.0 ); // とりあえず 横:縦=4:3 で正射影
  
    // 原点上で立方体を回転させる
    glRotatef( (count%360), 1.0, 0.5, 0.5 );
    glutSolidCube( 1 );
    count++;
  
    // OpenGLバッファの内容を画像として取得
    glReadPixels( 0, 0, outputImage->width, outputImage->height, GL_RGB, GL_UNSIGNED_BYTE, outputImage->imageData ); // RGBで取得
    cvCvtColor( outputImage, outputImage, CV_RGB2BGR ); // OpenCVのBGR並びに変換
    cvFlip( outputImage, outputImage, 0 ); // OpenCV に合わせて上下反転
  
    cvShowImage( "OpenCV", outputImage ); // 画像表示
  
    key = cvWaitKey(10);
    if( key == 27 ){
      break;
    }
  }
  
  wglMakeCurrent(hdc, 0);
  wglDeleteContext(hglrc);
  ReleaseDC(hwnd, hdc);
  cvReleaseImage( ©Image );
  cvDestroyWindow( "OpenCV" );
  cvReleaseCapture( &capture );
  
  return 0;
}

ここでは、OpenCVが作ったウィンドウからデバイスコンテキストを取得し、OpenGL用の描画バッファを作成しています。そして、OpenGLの画像バッファにカメラの画像を書き込み、さらにその上に3Dモデルを書き重ねています。表示上はこのままOpenGLのSwapBuffers()を呼んでもいいのですが、OpenCVでいろいろ処理できるようにするため、IplImageに書き戻してから表示させています。

  • まとめ

OpenGLでの描画を行うことがわかっているならば手続きの面からも処理速度の面からも、OpenCVをベースに表示するのはあまりよくなさそうです。カメラからの動画の上に3Dを重ねたい、という場合はしょうがないですけど。
単純にカメラの映像が欲しいだけならば、ARToolkitのカメラキャリブレーションなどはオーバースペックでしょう。OpenCVを使うのが楽チンです。
OpenGL メインであれば、GLUTベースでGUIを使い、必要に応じてカメラ画像の取得や画像処理をOpenCVにやらせる、というのが妥当な感じです。