C++/CLI フォームアプリとOpenCVの連携(cv::Mat) | りさーちゃーのたまご

りさーちゃーのたまご

研究者になり損ねた社会人のつれづれ日記です。へっぽこソフトしかかけないけど、社会人研究職を目指します。
適当なソフト、語学(英語、中国語)、マラソン、旅行、ときどき小言。

テーマ:
【背景】
先日OpenGLの画像をフォームに貼り付けました。
C++/CLI フォームアプリとOpenGLの連携(フォームへのレンダリングとGUI操作)
それはよいのですが、更に以下のようなことをしたくなった。

1. OpenGL画像をOpenCV画像(cv::Mat)に変換
2. 画像処理
3. 結果画像(cv::Mat)をフォームのピクチャボックスに表示

2は知ったこっちゃ無いですね。cv::Matさえ扱えればよいのです。
1と3だけメモります。

【1. OpenGL画像をOpenCV画像(cv::Mat)に変換】
超簡単です。
// この前の段階でOpenGLレンダリングコンテキストをカレントにしておく
cv::Mat cvImg = cv::Mat::zeros(winHeight, winWidth, CV_8UC3);
glReadPixels(0, 0, winWidth, winHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, cvImg.data);
cv::flip(cvImg , cvImg , 0);

【3. 結果画像(cv::Mat)をフォームのピクチャボックスに表示】
結構はまりました。コードは簡単ですが。
めぐりめぐって結局次のサイトを組み合わせて実現しました。
画像処理ソリューション-【OpenCV】C++/CLIによるサンプルプログラム
画像処理ソリューション-【C++/CLI】Graphicsオブジェクトの作成
Code Project-How to Display Image In Picturebox in VC++ from Iplimage and Mat

2番目のサイトによると、
1. Imageオブジェクトから作成(速いからオススメ!)
2. CreateGraphicsメソッドを使う方法(遅いし再描画必要からオススメしない!)
とあるので、両方試してみた。

どちらにしろ流れはこんな感じ。
 ・ピクチャボックスに対するGraphics オブジェクト作成する。
 ・Bitmat オブジェクトのバッファにOpenCV のバッファを渡す。
 ・Graphics オブジェクトでBitmat のバッファをピクチャボックスに描画する。

以下サンプルです。

1. Imageオブジェクトから作成
 ・10回計測: 平均 = 5838[us], 標準偏差 = 102[us]
// どこかから cv::Mat cvImg がやってきたとする。
Bitmap^ bmp;
IntPtr ip(cvImg.ptr()); // OpenCVの画像バッファをintポインタに変換

if
((pictureBox1->Image == nullptr)
  || (pictureBox1->Width != 
cvImg.cols) 
  || (pictureBox1->Height != 
cvImg.rows)){
    //ピクチャボックスをビットマップ画像サイズに合わせる 
    pictureBox1->Width = 
cvImg.cols; 
    pictureBox1->Height = 
cvImg.rows; 
    //PictureBoxと同じ大きさのBitmapクラスを作成する。 
    Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height); 
    //空のBitmapをPictureBoxのImageに指定する。 
    pictureBox1->Image = bmpPicBox; 
}
Graphics^g = Graphics::FromImage(pictureBox1->Image); 
  // グラフィックオブジェクト作成

bmp = gcnew Bitmap(
cvImg.cols, cvImg.rows, cvImg.step,
  System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip); 

// bmp->Save("test.jpg"); // これはデバッグ用。バッファがコピーされているか確認。
g->DrawImage(bmp, 0, 0
cvImg.cols, cvImg.rows); 
// this->pictureBox3->Image = bmp;
pictureBox1->Refresh();    // これは必要

delete g;
delete bmp;

2. CreateGraphicsメソッドを使う方法
 ・10回計測: 平均 = 3563[us], 標準偏差 = 239[us] ← 勝利
// どこかから cv::Mat cvImg がやってきたとする。
Bitmap^ bmp;
IntPtr ip(cvImg.ptr()); // OpenCVの画像バッファをintポインタに変換

Graphics^g = pictureBox1->CreateGraphics();  // グラフィックオブジェクト作成 

bmp = gcnew Bitmap(
cvImg.cols, cvImg.rows, cvImg.step, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip);

// bmp->Save("test.jpg"); // これはデバッグ用。バッファがコピーされているか確認。
g->DrawImage(bmp, 0, 0
cvImg.cols, cvImg.rows);  
// pictureBox3->Refresh(); // これは除外。動画だと画面がちらつく。

delete g;
delete bmp;

え?2の方が速いですよ。再描画いらないし。
しかも1は再描画がいらないと書いてあるけど、Refresh()関数を呼ばないとうまく描画されません。多分説明が反対なのかもしれませんね。