橋本商会: Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC# Formアプリから使う の続き。
FormアプリからOpenCVのHighGUIを別ウィンドウで開いていたが、Bitmapオブジェクトにキャストする事で.NETアプリのPictureBoxなどに読み込む事が出来る。
これにより画像処理だけ高速にC++で行い、アプリケーションのUIやネットワーク部分はC#.NETで実装するといういいとこどりが出来る。


■出来た物
FormにLoadImageボタンとPictureBoxを追加。
LoadButtonを押すとShadowCamera.dllから呼び出しているOpenCV側のCvImageが変換されてBitmapになって出てくる関数を作った。
C++/OpenCVのCvImageを.NETのBitmapに変換できた

今回作っているShadowCameraは背景を蓄積させて統計を取り、差分で人物の影だけを抜き出すライブラリなので、毎フレーム計算し続ける必要がある。runCapture()の最後でwhileループさせて実現している。
なので、1つBitmapオブジェクトを用意しておき、whileループの最後で処理結果を毎回保存しておいてそれをC#側から適当なタイミングで読みに行く実装にした。
DLLであるC++/OpenCV側のループを毎フレーム回し続けながら、それをブロックする事なくC# Formアプリ側から取得できる。


例によってOpenCV勉強用mercurialリポジトリchanges19としてcommitしてある。


なお、VisualStudioはC#はutf8なのにC++はshift_jisで保存されてしまい、utf8にするとビルドできなくなってしまうのでリポジトリビューアでは文字化けしてしまっている。悲しい。
文字化けしている部分には大したこと書いてないので無視していいし、必要ならリポジトリの左側からzipでダウンロードできる。


ちなみに今回の方法は、3年以上前の開発合宿の記事に匿名様が
コメントで教えてくれたSharperCVのCvImage型をSystem.Drawing.Bitmap型に変換する方法が書かれていてそれを元にした。
やはりできなかった事とかも書いてみるものだなと思った。もちろん助けをアテにしちゃいけないが


■ShadowCamera.dll側の変更
C++プロジェクトのShadowCameraに、System.Drawingへの参照を追加する。Bitmapクラスを使うため。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


「新しい参照の追加」ボタンを押す
C++/OpenCVのCvImageを.NETのBitmapに変換できた


.NETタブの下の方にスクロールしていって、System.Drawing を追加する。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


ShadowCamera.hにコードを書き加える

OpenCV Study: c9320e90f1be ShadowCamera/ShadowCamera.h
DLLへの参照を追加して

using namespace System::Drawing;


勝手に書き換えられたら嫌なので、privateでグローバル変数 shadow を作っておく
private:
    System::Drawing::Bitmap^ shadow;


DLL利用側のためのアクセサを用意する
public:
    System::Drawing::Bitmap^ ShadowImage(void){
        return this->shadow;
    }
これでDLL内のshadowというBitmapオブジェクトを、DLL外から読み出す事が出来るようになった。
いまいち ^ や ~ などをつけるルールを把握しきれていないけど、他のC++/CLIアプリがそうやっていたので先人の作法に倣うことにする。


あとはrunCapture()の最後のwhileループ内で、毎回作られるimgResultというIplImage(CvImage互換)オブジェクトをdynamic_castしてshadowに保存する。
this->shadow = dynamic_cast<System::Drawing::Bitmap^>
    (gcnew System::Drawing::Bitmap(w,h,w*3, 
    System::Drawing::Imaging::PixelFormat::Format24bppRgb,
    static_cast<System::IntPtr>(imgResult->imageDataOrigin)));
長いので変換部分のみ抜粋。ShadowCamera.hの85~125行目がwhileループです

dynamic_castやstatic_castの違いはC++の新しいキャストが参考になった。
オブジェクトの代入のためのcastは.NETの上で動いているからdynamic_castで、unmanaged領域と行き来するSystem::IntPtrと*char間のcastはstatic_castになるという理解でいいのかな?



■ShadowCamera.dllを使うC# Formアプリ側の変更
ボタン(buttonLoadImage)と画像を表示するためのPictureBox(pictureBoxResult)を配置。
PictureBoxはSizeModeをStretchImageにしておくと自動リサイズされて便利。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


で、ボタン押下イベントで
pictureBoxResult.Image = cam.ShadowImage();
BitmapをPictureBoxのImageプロパティに突っ込むだけで表示できる。楽ちんだ
C++/OpenCVのCvImageを.NETのBitmapに変換できた

終わり。

ボタンじゃなくてThreadなどで適当にsleepを入れながら読み込み続ければふつうに影カメラになる。