【OpenCV】IplImageから.NETのBitmapへ変換

OpenCVのプログラムを作っていると、OpenCV用のGUI(highgui)はかなり物足りなく、ウィンドウや表示まわりは.NETに任せたいので、OpenCVのIplImageから.NET(C++/CLI)のBitmapへ変換すると、簡単にウィンドウプログラムを作成する事が可能となります。

 

変換部分のサンプルプログラムは以下の通りです。

// IplImage の確保
IplImage *src_img = cvLoadImage("c:\\Sample.bmp");
//  IplImageからBitmapの確保
Bitmap^ bmp = gcnew Bitmap(src_img->width, src_img->height, src_img->widthStep,
      System::Drawing::Imaging::PixelFormat::Format24bppRgb, (IntPtr)src_img->imageData);

ただし、PixelFormatはIplImageで確保した画像データのdepthおよびnChannelsに合わせて設定して下さい。

 

PixelFormat depth nChannels
Format8bppIndexed 8 1
Format24bppRgb 8 3

 

他の組合せも考えられますが、.NETやOpenCVの関数が対応していないものが多いので、実質的に、この2つの組合せになると思います。
モノクロ8Bit画像(Format8bppIndexed)の場合は、別途、カラーパレットを設定する必要があります。
カラーパレットの設定については、8Bitモノクロのカラーパレット設定方法のページを参照願います。

 

ここで作成したBitmapをPictureBoxに表示する場合、

 

pictureBox1->Image = bmp;

 

とすると

 

‘System.AccessViolationException’ のハンドルされていない例外が System.Drawing.dll で発生しました。
追加情報: 保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。

 

というエラーメッセージが出てしまうので、DrawImageを使って

 

g->DrawImage(bmp, 0, 0, src_img->width, src_img->height);

 

というようにして、ピクチャボックスへ描画します。

 

ちなみに今回はIplImageからBitmapクラスへ変換する方法を紹介しましたが、BitmapクラスからIplImageへ変換する場合はBitmapクラスのポインタがガーベージコレクションにより移動してしまう可能性があるので、BitmapのポインタをLock~Unlockで取得し、IplImageのimageDataへコピーすればよいと思います。

 

また、OpenCV2.0からは画像データにC++用のcv::Matがありますが、cv::Matでは画像データのメモリの幅が4の倍数に調整されていないので、画像の幅が4の倍数でない場合はcv::Matと.NETのBitmapとではメモリのサイズが異なるため、そのままcv::MatのポインタをBitmapへ渡す事ができません。
そのため、cv::MatからBitmapへ変換する場合はcv::MatそのものをIplImageを確保してからcv:Matへ変換するか、画像の1行ごとにデータをコピーするなどの工夫が必要となります。

 

 

OpenCVへ戻る

 

【C++/CLI】画像の拡大縮小表示(簡易版)

C++/CLIで簡単にできる画像の拡大縮小表示を紹介します。

 

今回、作成したプログラムはこんな感じ↓です。

 

 

このサンプルプログラムはこちら
SimpleZoomImage.zip
(VisualStudio2005 Express Edtionで作成)

 

作成手順は本当に簡単。
下図のようにフォームの上にMenuStripPanel、Panelの上にPictureBoxと配置していきます。

 

 

Panelをクリックすると、右上に三角マークが表示されるので、それをクリックし、
親コンテナにドッキングするをクリックします。

 

 

次にパネルとピクチャボックスのプロパティを以下のように設定します。

 

panel1->AutoScroll = true;
pictureBox1->SizeMode = System::Windows::Forms::PictureBoxSizeMode::Zoom;

 

■AutoScroll

trueに設定するとパネル上に配置されたコントロールコンテンツ(ピクチャボックスなど)がパネルの大きさより大きい場合は自動的にスクロールバーを表示し、コントロールをスクロールしてくれます。

■SizeMode

ピクチャボックスに設定されたイメージをどのように表示するかを設定します。

 

Normal 画像をピクチャボックスの左上に合わせて表示します。
StretchImage 画像をピクチャボックスの幅、高さに合わせて表示します。
画像をピクチャボックスの幅、高さに合わせて表示します。
AutoSize ピクチャボックスのサイズを画像サイズに合わせて変更します。
CenterImage ピクチャボックスの中心と画像の中心を合わせるようにして表示します。画像サイズがピクチャボックスより大きい場合は、画像がはみ出して表示されます。
Zoom 画像全体をピクチャボックス全体に表示するように画像表示サイズを変更し、ピクチャボックスの中心に表示します。
画像の縦横比はくずれません。

 

画像の読み込みは、こんな感じ↓です。

 

private: System::Void mnuFileOpen_Click(System::Object^  sender, System::EventArgs^  e) {
	//ファイルを開くダイアログの作成
	OpenFileDialog^ dlg = gcnew OpenFileDialog;
	//ファイルフィルタ
	dlg->Filter = "画像ファイル(*.bmp,*.jpg,*.png,*.tif,*.ico)|*.bmp;*.jpg;*.png;*.tif;*.ico";
	//ダイアログの表示
	dlg->ShowDialog();
	//ビットマップファイルから、Bitmapを作成
	Bitmap^ bmp = gcnew Bitmap(dlg->FileName);
	//ピクチャボックスをビットマップ画像サイズに合わせる
	pictureBox1->Width = bmp->Width;
	pictureBox1->Height = bmp->Height;
	//ピクチャボックスのImageへ
	pictureBox1->Image = bmp;
}

あとは、ピクチャボックスの大きさを拡大縮小したい倍率に合わせて変更すれば、勝手に画像は拡大縮小し、スクロールバーは自動的に調整してくれます。

private: System::Void mnuZoomEnlargement_Click(System::Object^  sender, System::EventArgs^  e) {
	//拡大(2倍)
	pictureBox1->Width *= 2;
	pictureBox1->Height *= 2;
}
private: System::Void mnuZoomReduce_Click(System::Object^  sender, System::EventArgs^  e) {
	//縮小(1/2倍)
	pictureBox1->Width /= 2;
	pictureBox1->Height /= 2;
}

と、これだけ!
本当に簡単にすごいことができちゃいます。

ただし、この方法では、大きい画像やモノクロの画像の上に線などの描画ができません。
その場合はDrawImageメソッドをお使い下さい。

【C++/CLI】8Bitモノクロのカラーパレット設定方法

モノクロ8Bitの画像を新規に作成する場合の、モノクロのカラーパレットは、以下のように作成します。

 

//8BitのBitmap作成
Bitmap^ bmp = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height, Imaging::PixelFormat::Format8bppIndexed);

Imaging::ColorPalette^ pal = bmp->Palette;

for (i = 0; i < 256; i++){ pal->Entries[i] = Color::FromArgb(255, i, i, i);
}

bmp->Palette = pal;

 

この処理を使ったサンプルプログラムは、こちらです。
cppcli_colorpalette(Visual C++ 2005 Express Editionで作成 )

 

プログラムのイメージはこんな感じです。

 

【C++/CLI】System::String^からchar*へ変換

アンマネージのC言語で作られたライブラリなどへ.NETのプログラム(マネージ)から文字列を渡す場合、System::String^からchar*へ変換する必要があります。

 

この場合、
System::Runtime::InteropServices::Marshal::StringToHGlobalAnsiメソッド
を使って変換します。
char*ポインタを使い終わったら
System::Runtime::InteropServices::Marshal::FreeHGlobalメソッド
でメモリを解放します。

 

以下、.NETのファイルを開くダイアログボックスを使ってファイル名を取得し、OpenCVの画像読込関数(cvLoadImage)へ渡す例を示します。

 

//ファイルを開くダイアログの作成
OpenFileDialog^ dlg = gcnew OpenFileDialog;
//ファイルフィルタ
dlg->Filter = "画像ファイル(*.bmp,*.jpg,*.png,*.tif,jp2)|*.bmp;*.jpg;*.png;*.tif;*.jp2";
//ダイアログの表示
dlg->ShowDialog();

//System::String^型のファイル名
System::String^ strFilename = dlg->FileName;
//System::String^からchar*へ変換
char* pStr = (char*)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(strFilename).ToPointer();
//アンマネージ関数へchar*を渡す
IplImage *src_img = cvLoadImage(pStr, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
//メモリの解放
System::Runtime::InteropServices::Marshal::FreeHGlobal((IntPtr)pStr);

 

【C++/CLI】新しくウィンドウを開く/閉じる

例えば、Form1からForm2というフォームを表示する場合は、

 

モーダル表示

表示したウィンドウしか操作できないウィンドウを開く

#include "Form2.h"

(中略)

Form2^ frm = gcnew Form2();
// モーダル表示
frm->ShowDialog(this);

 

モーダレス表示

表示したウィンドウと表示元のウィンドウの両方が操作できる

#include "Form2.h"

(中略)

Form2^ frm = gcnew Form2();
// モーダレス表示
frm->Show(this);

 

フォームを閉じる場合

frm->Close();

のようにします。

 

ShowDialogおよびShowの引数で渡しているthisは無くてもよいのですが、このthisを渡す事で、
Form2からthis->Ownerを参照する事で、親のフォーム(どのフォームから呼ばれたのか?)が分かるので、thisを渡した方が何かと便利です。

 

また、

frm->Hide();

のようにすると、フォームそのものは破棄されずウィンドウを非表示にする事ができます。
そのため、再びウィンドウを表示したい場合は、再度

frm->Show();

を呼び出すと、Hide()する直前の状態のフォームが表示されます。

 

Visual Studioのツールボックスのアイコン表示がおかしくなった場合

Visual Studioを使っていると、なぜか途中でツールバーのアイコンがこんな感じ↓

 

 

でおかしくなる場合があります。
でも、ちゃんと復帰方法もあります。

 

復帰方法

ツールボックスのウィンドウ上で右ボタン

「ツールボックスのリセット」

復帰完了!

 

 

Visual Studioへ戻る

 

【C++/CLI】マウスイベント処理

マウスイベント(マウスをクリック、ダブルクリックなど)の処理を追加するには、イベント処理を行うオブジェクト(ピクチャボックスやボタンなど)を選択した状態でイベントのプロパティウィンドウを表示し、イベント処理の文字の部分をダブルクリックします。

 

 

すると、イベントハンドラが自動的に作成されます。
下記の例はMouseClickを追加した場合

 

this->pictureBox1->MouseClick += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::pictureBox1_MouseClick);

:
:

private: System::Void pictureBox1_MouseClick(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {

        //この位置にイベント処理を追加する。

}

 

このイベントハンドラ内にイベント処理を追加していきます。

 

ここで注意しないといけないのが、pictureBox1_MouseClickという名前になっていますが、必ずしもpicutureBox1からのイベントとは限りません。
どのオブジェクトかは、最初の引数の System::Object^ sender を参照します。
sender のプロパティはそのまま参照できないので、下記のようにキャストします。

 

PictureBox^ pic = static_cast<PictureBox^>(sender);

 

また、マウスのどのボタンがクリックされ、どの位置でクリックされたか?などは2番目の引数の
System::Windows::Forms::MouseEventArgs^ e を参照します。

 

ボタンの判別例は以下の通りです。

 

if (e->Button == System::Windows::Forms::MouseButtons::Left){
      //左ボタンクリック
      MessageBox::Show("左ボタンがクリックされました。");
}else if (e->Button == System::Windows::Forms::MouseButtons::Right){
      //右ボタンクリック
      MessageBox::Show("右ボタンがクリックされました。");
}else if (e->Button == System::Windows::Forms::MouseButtons::Middle){
      //中ボタンクリック
      MessageBox::Show("中ボタンがクリックされました。");
}

 

クリックされた位置のX座標は e->X、Y座標は e->Y で取得できます。

 

また、マウスをクリックした時にShiftキー、Ctrlキー、Altキー、が押されているかどうかを判別するにはControl::ModifierKeysプロパティを参照すれば良いのですが、厳密にはマウスがクリックされたときのキーを取得するのはなく、Control::ModifierKeysプロパティが呼ばれた時のキーを取得するので、ご注意下さい。

 

//Shift,Ctrl,Altキーが押されているか?
Keys key = Control::ModifierKeys;

if ((key & Keys::Control) == Keys::Control){
      //Controlボタンクリック
      MessageBox::Show("Controlボタンがクリックされました。");
}else if ((key & Keys::Shift) == Keys::Shift){
      //Shiftボタンクリック
      MessageBox::Show("Shiftボタンがクリックされました。");
}else if ((key & Keys::Alt) == Keys::Alt){
      //Altボタンクリック
      MessageBox::Show("Altボタンがクリックされました。");
}

マウスイベントには以下のようなものがあります。

 

イベント 意味
Click クリックされたとき
DoubleClick ダブルクリックされたとき
MouseCaptureChanged マウスのキャプチャがなくなるとき(意味不明???)
MouseClick マウスでクリックされたとき
MouseDoubleClick マウスでダブルクリックされたとき
MouseDown マウスボタンが押されたとき
MouseEnter マウスポインタがコントロールの外側から内側に入ったとき
MouseHover マウスポインタがコントロール上に留まっているとき
MouseLeave マウスポインタがコントロールの内側から外側に出たとき
MouseMove マウスポインタがコントロール上を移動したとき
MouseUp マウスボタンが離されたとき
MouseWheel コントロールにフォーカスがあり、マウスホイールが動いたとき

 

ClickイベントとMouseClickイベント、DoubleClickイベントとMouseDoubleClickイベントとは、ほぼ同じですが、2番目の引数が EventArgs^  e か MouseEventArgs^  e の違いがあります。
マウスの情報をより詳しく取得したい場合は Mouse××イベントをお使い下さい。

また、イベントの発生順番は

 

  1. MouseDown
  2. Click
  3. MouseClick
  4. MouseUp
  5. MouseDown
  6. DoubleClick
  7. MouseDoubleClick
  8. MouseUp

 

となります。