課題3のページ
このページは課題3【1つのウィンドウに複数の画像を表示させる】をやっている人向けのページです。
課題3の作成時の注意
今回の課題はプロジェクト作成時から前回までのやり方ではできないので
このページを見ながら作成することをおすすめします。
プロジェクトの作成
1、まず、プロジェクトを作成します。
ファイル → 新規作成 → プロジェクト
※ここからが前回と違います
前回までは、Win32コンソールアプリケーションで作成したと思いますが、今回の課題では、
CLRを選択し、右側に windows フォームアプリケーションを選択し、好きなプロジェクト名を入力してOKを押してください。
2、次に出てくる画面で、前回と同じように「プリコンパイル済みヘッダ」のチェックをはずして完了を押してください。
フォームアプリケーションとは、GUIを簡単に作れるものです。
なので、画面にボタンを簡単に配置したり、画像を置いたりすることができます。
プログラムを書く
今回の課題は、画面に画像を配置したりして場所などを決めます
画像を配置すると、自動的にプログラムを書いてくれます。
プロジェクトを作成したら、Form.h[デザイン]のようなものが開いている状態になっていると思います。
そこで、画面の右端に「ツールボックス」と書かれた縦型のタブみたいなのがあると思います。
その上にマウスを当てるとぴよっとなんか出てくるので、その中から「button」と書かれたものを見つけて
form.h[デザイン]の画面にドラッグします。
※ここで、もしツールボックスの中が空だった人は、Form.h[デザイン]を開いた状態でもう一度マウスをツールボックスに当ててみてください。
すると、画面にボタンが現れると思います。
次に、同じようにツールボックスの中の「PictureBox」を画面にドラッグします。
すると、四角で囲まれた範囲が現れるので、その中で右クリックしてイメージの選択でイメージを選択します。
すると、画面に画像が表示されます。
同じように4つPictureBoxを同じようにドラッグしてイメージを選択してください。
今は、画像の位置などを決めるだけなのでなんの画像でもかまいません。
あとから、
OpenCVで画像を読み込んでその画像を表示するプログラムに変えます。
ここまで終わった時点で、プログラムを書くファイル(Form.h)には、ボタンを配置するプログラムと画像の位置や、サイズを定義して表示するプログラムが完成していると思います。
Form.h[デザイン]で画像の位置を変えたりすると自動的にプログラムが変更されます。
確認のために、Form.hを開いてみてください。
開き方がわからない人は、ボタンをForm.h[デザイン]の先ほど配置したボタンをダブルクリックしてみてください。
そうすると、上のタブにForm.hが現れてくると思いますので、そこを開いてみると
// button1
//
this->button1->Location = System::Drawing::Point(417, 471);
this->button1->Name = L"button1";
this->button1->Size = System::Drawing::Size(75, 23);
this->button1->TabIndex = 0;
this->button1->Text = L"チェンジ";
this->button1->UseVisualStyleBackColor = true;
this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click_1);
//
// pictureBox3
//
this->pictureBox3->Image = (cli::safe_cast<System::Drawing::Image^ >(resources->GetObject(L"pictureBox3.Image")));
this->pictureBox3->Location = System::Drawing::Point(70, 56);
this->pictureBox3->Name = L"pictureBox3";
this->pictureBox3->Size = System::Drawing::Size(0, 0);
this->pictureBox3->TabIndex = 2;
this->pictureBox3->TabStop = false;
//
のように表示されていると思います。
PictureBoxのところは、PictureBoxの数だけ出てきます。
ここで、一度実行してみましょう。
ここでエラーが出た場合は、エラーの行数を確認し、もしPictureBoxのところにエラーが出てきていたら
そのエラーの部分を削除してしまいましょう。
実行すると、ボタンと画像が表示されていると思います。
次に、ボタンを押したときの処理を書きます。
ボタンを押したときの処理は
#pragma
endregion
private: System::Void button1_Click_1(System::Object^ sender, System::EventArgs^ e) {
}
この中にかきます。
そして画像を表示させる処理は
this->pictureBox3->Size = System::Drawing::Size(縦, 横);
になります。
縦と横には自分の好きなサイズを入力してください。
この処理をPictureBoxの番号を変えてかいてやります。
このままだと、ボタンを押したときに全部の画像が出てきてしまうので
flagなどの変数を置いて、ボタンが押されたらflag++などして場合わけをしましょう。
そして実行してみましょう。
すると、ボタンを押す前から画像が表示されてしまっていると思います。
それは、PictureBoxのそれぞれのいろいろかいてあるところにも
this->pictureBox3->Size = System::Drawing::Size(縦, 横);
があるからです。
しかし、消してしまうとエラーが出てしいます。
そこで、始めに表示しないように
this->pictureBox3->Size = System::Drawing::Size(0, 0);
のようにサイズを0×0にします。
そうすると、プログラム的には表示しているが、実際は表示していないようになります。
これで、すべてのPictureBoxに同じようにかいて実行すると、何も表示されないで
ボタンを押すと、画像が出てくると思います。
ここまでが、ボタンを押すと画像が表示されるプログラムです。
しかし、今までは、画像は、PictureBoxに元からイメージを選択してやっていたので
ここからOpenCVを使い、画像を読み込んで処理していくところを説明します。
まず、Form.h[デザイン]のフレームの中のボタンとかなにもないとこで
ダブルクリックします。
すると、プログラムの方に
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {
}
が出てくると思います。
その中に今まで、メイン関数に書き込んでいたことを書き込みます。
そして、OpenCVを使うときに、しないといけないのが、インクルード。
今回の課題は、
"cv.h"
"highgui.h"
をインクルードします。
そして、cppファイルのほうにもライブラリファイルをインクルードしないといけません
ソースファイルの中に、プロジェクト名.cpのファイルがあると思うので開きます。
ライブラリファイルのインクルードは
#pragma
commentを使います。
普通にいつも#includeなどとかくところ(上のところに)
#pragma
comment( lib, "cv.lib" )
#pragma
comment( lib, "cxcore.lib" )
#pragma
comment( lib, "cvaux.lib" )
#pragma
comment( lib, "highgui.lib" )
と記述します。
今まで書いてあった#includeの文は消さないでください。
※pragmaとcommentは改行されて表示されているかもしれませんが、実際にプログラムに書きこむときは改行しないで1行で書いてください。
これでインクルードは完了です。
↓ここからはForm.hの
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {
}
内に記述します。
まず、流れとしては
1、cvLoadImage関数でファイル読み込み(変数名は自由につけてください)
2、回転した後の画像を格納するIplImage型を生成(cvCreateImage関数を使う)(変数名は自由につけてください)
→ここでは、結果的に読み込んだ画像を回転させて3つの画像を作るので3つ生成する。
3、画像の中心を求めて、回転の軸を決める
4、回転行列を生成する
5、変換行列を求める
6、画像を回転させる
7、画像を表示させるために、Bitmap形式にする。
8、cvReleaseImage関数を使ってデータを開放する
9、PictureBoxに変換した画像をセットする。
みたいな感じです。
課題2までの内容で、1,8は書けると思いますのでここでは省略します
もしわからないってなった場合は課題2のソースを見るなり、wikiの課題2のページを参照するようにしてください。
それでは、処理後の画像を格納するところを作っていきます。
cvCreateImage()関数を使います。
今までも使ってきたので見覚えあると思います
IplImage *destinationImage = cvCreateImage( cvGetSize( sourceImage ), IPL_DEPTH_8U, 3);
IplImage *des1 = cvCreateImage( cvGetSize( sourceImage ), IPL_DEPTH_8U, 3);
IplImage *des2 = cvCreateImage( cvGetSize( sourceImage ), IPL_DEPTH_8U, 3);
これを記述します
今回は、destinationImage、des1、des2というIplImage型を生成しました。
生成するものの名前は、自由に決めてください
名前によって各々これから作っていくプログラムのdestinationImage、des1、des2らの名前を変更しながら勧めてください。
説明すると、
第一引数のcvGetSize()では、生成する画像のサイズを指定しています。
今回は読み込んだ画像と同じサイズを生成するので()の中身はsourceImage(読み込んだ画像名)でOKです。
ここから回転の処理をしていきます。
まず、回転の中心を決めます
回転の中心を求めるには
cvPoint2D32f( 中心のx座標, 中心のy座標); //回転の中心
基本的に画像の中心なので次のように記述します。
CvPoint2D32f center = cvPoint2D32f( sourceImage->width / 2.0, sourceImage->height / 2.0); //回転の中心
次に、回転行列を生成します。
回転行列は
cvCreateMat()関数を使って
CvMat *rotationMatrix = cvCreateMat( 2, 3, CV_32FC1 );
のように記述します。
そして変換行列は
cv2DRotationMatrix( center, ANGLE, SCALE, rotationMatrix);
を使います。
centerは回転の中心
ANGLEは回転角度
SCALEは拡大縮小の倍数
rotationMatrixは出力行列
ここで、一番上で#includeを書いてるところで
回転角度と拡大縮小の倍数をあらかじめ定義しておきます。
ここでは、
#define
ANGLE 90.0 //90度回転なので
#define
SCALE 1.0 //拡大縮小はしないので1.0でokです。
と記述しておいてください。
そして画像の拡大縮小は
cvWarpAffine( 原画, 出力画像, 変換行列, 補間方法, 対応の取れない点に対して与える値 );
ここで、原画、出力画像はsourceImageなどIplImage型の変数を入れます
ここには、自分で宣言したものをいれてください、
補間方法には、
CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS
を記述してください。
意味は
CV_INTER_LINEAR → 値が決定されない画素には第4引数で指定した色を与える
CV_WARP_FILL_OUTLIERS → 出力画像の座標値を元に、対応する入力画像の座標を求める逆変換を行う
第4引数には、cvScalarAll( 0 )と記述してください
cvScalarAll( 0 )はCvScalar型の画像のすべての要素が0、すなわち黒をあらわします。
最終的に
cvWarpAffine( sourceImage, destinationImage, rotationMatrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll( 0 ) );
こうなります。
ここで、実際に4つの画像を表示させないといけないので
3つの回転済みの画像を生成することになります。
なので、cvWarpAffine()関数は全部で3回使うことになります。
流れとしては
sourceImage →90度回転→ destinationImage
destinationImage →90度回転 → des1
des1 →90度回転→ des2
という感じで変換すれば簡単にできるので
上の流れにそって引数を入れていきます
原画は変換前の画像なので左側になります
出力画像は変換後の画像なので右側になります
先ほどの1回目の変換(sourceImageを90度回転してdestinationImageに格納する)例を示すと・・・
cvWarpAffine( sourceImage, destinationImage, rotationMatrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll( 0 ) );
これは、sourceImageを90度回転してdestinationImageに格納するということになりますので
残りの2つは、同じように引数を変えてやってみてください。
次に読み込んだ画像はjpg形式だったりいろいろな形式だと思いますが
実際にFormでPictureBoxでイメージを選択した場合は自動的にBitmap形式に変換して表示しています
なので
cvLoadImage()関数で読み込んだ画像は自動で変換はしてくれないので
変換するプログラムを書きます。
IntPtr ip( new unsigned char[ sourceImage->widthStep * sourceImage->height ] );
memcpy( ip.ToPointer(), sourceImage->imageData, sourceImage->widthStep * sourceImage->height );
Bitmap^ bmp = gcnew Bitmap(sourceImage->width, sourceImage->height, sourceImage->widthStep, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip);
これで変換できます。
ここは、C++の書き方なのでよくわかりませんが
サイズなどを指定しているところに画像の名前が入ってることがわかります。
これを4つの画像すべてに適用します
よってこの3行を4つかくことになります。
それぞれ画像の名前を変えてかいてやればOKです。
そして、上の変換のプログラムでのに注目してほしいのですが
Bitmap形式のbmpに入れています、
これは、画像ごとに用意する必要がありますので
画像ごとにbmp1、bmp2、bmp3を用意しましょう。
そして
1行目の初めの
IntPtr ipも画像ごとのサイズ情報などが入っていますので3行目の最後の引数のipを画像ごとにかえてやる必要があります。
今回はip1、ip2、ip3を用意しました。
IntPtr ip( new unsigned char[ sourceImage->widthStep * sourceImage->height ] );
memcpy( ip.ToPointer(), sourceImage->imageData, sourceImage->widthStep * sourceImage->height );
Bitmap^ bmp = gcnew Bitmap(sourceImage->width, sourceImage->height, sourceImage->widthStep, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip);
IntPtr ip2( new unsigned char[ destinationImage->widthStep * destinationImage->height ] );
memcpy( ip2.ToPointer(), destinationImage->imageData, destinationImage->widthStep * destinationImage->height );
Bitmap^ bmp2 = gcnew Bitmap(destinationImage->width, destinationImage->height, destinationImage->widthStep, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip2);
IntPtr ip3( new unsigned char[ des1->widthStep * des1->height ] );
memcpy( ip3.ToPointer(), des1->imageData, des1->widthStep * des1->height );
Bitmap^ bmp3 = gcnew Bitmap(des1->width, des1->height, des1->widthStep, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip3);
IntPtr ip4( new unsigned char[ des2->widthStep * des2->height ] );
memcpy( ip4.ToPointer(), des2->imageData, des2->widthStep * des2->height );
Bitmap^ bmp4 = gcnew Bitmap(des2->width, des2->height, des2->widthStep, System::Drawing::Imaging::PixelFormat::Format24bppRgb, ip4);
これでBitmapに変換できました。
あとは、イメージの読み込みデータを解放してやります
ここでは、かきませんが
cvReleaseImage()関数を使います。(ほとんど書いてますが・・・)
生成した画像、読込んだ画像すべて解放しましょう。
あとは、PictureBoxに格納するだけです。
this->pictureBox3->Image = bmp;
このようにかきます
これは、PictureBox3にbmpを格納する、という意味です。
bmpというのは先ほどBitmapに変換したものです。
ほかの3つ画像も同じようにかいてやります。
そして実行してみましょう。
読込んだ画像がボタンをおしたら表示されるようになっているといいな。
実際、書いてる途中で頭ぐちゃぐちゃになったので
どっかでエラーが出てしまうかもしれないので、エラーがでてもう無理だ!ってなったら
聞いてください~。
長くなりましたが、これで課題3の説明です。
最終更新:2009年05月28日 14:18