Visual Studio (VC++) 応用編 (その3)
−動画像ファイルの生成と表示−
信州大学工学部 井澤裕司
(H18.2.15)
はじめに
前章の応用編「 その2 」 では基本となる 「 静止画像ファイルの生成方法 」 について解説しました.
一方,この研究会の課題は,画像信号をリアルタイムに解析して 「 物体の動き 」 を計測することにあります.
このため,扱う画像は必然的に「 動画像 」 となります.
ここでは,応用編「 その2 」 の 「 静止画像ファイル 」 を 「 動画像ファイル 」 に拡張する方法を説明します.
応用編「 その2 」 のサブメニュー 「 静止画像 」 では,ソースコードを簡略化するため 「 InvalidateRect() 」 を用い,
「 OnDraw() 」を強制的に呼び出す方法を用いました.
しかし,この「 OnDraw() 」では,その中でループを構成しても,最終的な結果しか表示されません.
このため,「 画像ファイルの書き込み 」 と 「 読み出し 」 では 「 動画像への拡張性 」を考慮し,
「 クライアントのDC(デバイスコンテキスト) 」 をその中で定義して,描画する方法を用いました.
応用編「 その3 」 のプログラミングの構成は 応用編「 その2 」 とほとんど変わりません.
本編ではそれらの 「 変更点 」 を中心に解説します.
主な 「 変更点 」 は,以下の2つです.
(1) 画像ファイルの書き込み ( OnFileWrite() )の ループ化
(2) 画像ファイルの読み込み ( OnFileRead() )の ループ化
また,取り込むフレーム数や表示間隔のパラメータを入力するため,
(3) パラメータ設定用のダイヤログ ( CParamDlg() )
を追加します.
最初に,応用編「 その2 」 で作成したプロジェクト 「 image_file 」 を 「 フォルダごとコピー 」 し,
最も上のフォルダ名を,例えば 「 image_file_mov 」 のように 「 リネーム 」 して明確に区別して下さい.
本編では,リネームした 「 image_file_mov 」 について,修正を加えます.
ステップ1.
パラメータ設定用ダイヤログの生成
1-[a]
メニューの「 表示 」から 「 リソースビュー 」 を指定し,「 image_file 」 プロジェクトの リソース一覧を表示します.
次に,「 image_file.rc 」 の「 Dialog 」 をクリックし,「 新規ダイヤログ 」 を生成します.
次に,このダイヤログを,リソースエディタを用いて編集します.
メニューの 「 表示 」 から 「 ツールボックス 」を選択します.
「 ツールボックス 」 から2つの ”Edit Control” を選択し,下の図のようにダイヤログを設計します.
なお,ダイヤログ上部のキャプションは,デフォルト値で,「 Dialog 」 となっています.
これを変更するため,ダイヤログにマウスのカーソルを置き,右クリックにより,「 プロパティ 」を選択します.
画面の左側に,ダイヤログの「 プロパティ 」 の詳細がリスト形式で表示されます.
プロパティの「 Caption 」 の欄を「 ファイル関連パラメータ 」に修正します.
また,新たに追加したダイヤログを識別するため,「 ID 」の欄は, 「 IDD_PARAM_DLG 」 と入力して下さい.
1-[b]
デザインした 「 ダイヤログ 」 を,実際のプログラムに相当する 「 クラス 」 に変換します.
マウスのカーソルを 「 ダイヤログ 」 本体に合わせ,右クリックにより,「 クラスの追加 」 を選択します.
「 MFC クラス ウィザード 」 が起動しますので, 「 クラス名 」 として「 CParamDlg 」 を入力し,「 完了 」 をクリックします.
次に,下の表のように2つの 「 Edit Control 」 に対応する 「 メンバ変数 」 を定義します.
それぞれの 「 Edit Control 」 にマウスのカーソルを合わせ,右クリックにより,「 変数の追加 」 を選択します.
「 メンバ変数の追加ウィザード 」 が起動しますので,「 カテゴリ 」 に 「 Value 」 を選択し,
「 変数の種類 」 と 「 変数名 」 をそれぞれ入力します.
入力を終えたら 「 完了 」 をクリックして,「 ウィザード 」 を終了します.
ID 変数の内容 メンバ変数 変数の種類 IDC_EDIT1 最大フレーム数 m_max_frame int IDC_EDIT2 表示フレーム間隔 m_frame_interval int 以上で,「 ダイヤログ 」の設定は完了です.
ステップ2.
メニューの追加 (ファイル関連)
2-[a]
次は,ステップ1で設計したダイヤログを呼び出す 「 メニュー 」 を追加します.
応用編「 その2 」 で作成したメニューの 「 初期設定 」 にサブメニューを追加します.
次に,「 初期設定 」 の 「 解像度 」 の下にある「 ここに入力 」 の下端にマウスのカーソルを合わせ,左ダブルクリックして 「 ファイルパラメータ 」 と入力します.
「 ファイルパラメータ 」 の ”右側” にマウスのカーソルを合わせ,左ダブルクリックして,「 メニューエディタ 」 を起動します.
なお,メニュー欄の右端にあるアイコン 「 プロパティ ウィンドウ 」 を左クリックしても 「 メニューエディタ」 が開きます.
画面左の「 メニューエディタ 」 の「 ID 」 の欄に,「 ID_FILE_PARAM 」 と入力します.
2-[b]
「 メニューエディタ 」 を閉じた後,再びサブメニューの 「 ファイルパラメータ 」 にマウスのカーソルを合わせ,今度は ”右” クリックして, 「 イベントハンドラの追加 」 を起動します.
イベント ハンドラ ウィザード 」 のクラス一覧から Viewクラス ,すなわち 「 Cimage_fileView 」 を選択して,「 追加して編集 」 を起動します.
なお,「 メッセージ 」 の欄は 「 COMMAND 」 とします.
「 イベント ハンドラ ウィザード 」 により, Viewクラス の中に,新規メニューに対応する新しいクラス 「 OnFileParam() 」 が自動的に生成されます .
これで,メニューの設定は完了です.
ステップ3.
ソースファイルの編集
リソースの設定作業が終わりましたので,「
ソースファイルの編集
」 を行います.
3-[a]
Viewクラス 「 Cimage_fileView() 」 の最初に,ダイヤログを呼び出すためのヘッダーファイル「 ParamDlg.h 」 をインクルードし,
ファイル操作数を行うための 「 変数 」 を追加します.
「 赤い部分 」 が追加するコードです.
‥(略)‥
#include "image_fileView.h"
#include "vfw.h" // 追加したヘッダーファイル
#include "winbase.h" // 追加したヘッダーファイル
#include "ParamDlg.h" // 追加したダイヤログのヘッダーファイル
#define X_SIZE 320 // 水平画素数
#define Y_SIZE 240 // 垂直画素数
#define COLOR 4 // 色(RGB(A) 4 Byte)
static unsigned char image[Y_SIZE][X_SIZE][COLOR];
CDC m_memDC; // メモリDC
HBITMAP m_hBitmap = NULL; // DIB Section
BITMAPINFOHEADER* m_lpImage = NULL;
static BYTE* m_lpData = NULL; // RGBデータ
HWND m_hCapWnd = NULL;
int size; // データサイズ
BOOL dispFlag = FALSE; // 表示終了フラグ
int max_frame = 100; // 最大表示フレーム数 (デフォルト値:100)
char file_head[30] = "ucam_file\\cam"; // 画像ファイルフォルダ+ファイル先頭部 (+数字.raw)
int frame_interval = 80; // 読み出し時フレーム表示間隔 (デフォルト値: 80ms)
BITMAPINFOHEADER* GetDIBHeader(){ // DIB(Device Independent BMP)のヘッダ
return m_lpImage;
}
BOOL CreateDIBHeader(int size){ // DIB(Device Independent BMP)のヘッダ作成
m_lpImage = (BITMAPINFOHEADER*)malloc(size);
return (m_lpImage != NULL);
}
以下にその編集画面を示します.
3-[b]
自動生成された「 OnFileParam() 」 の内部に,以下に示す描画用のコードを追加します.
「 赤い部分 」 が追加するコードです.
CParamDlg dlg;
dlg.m_max_frame = max_frame; // 表示する全フレーム数(デフォルト値)
dlg.m_frame_interval = frame_interval; // 読み出し時の表示フレーム間隔 (ms)(デフォルト値)
if (dlg.DoModal() == IDOK) { // ダイヤログを表示し,OKボタンが押されるまで待つ
max_frame = dlg.m_max_frame; // 表示する全フレーム数
frame_interval = dlg.m_frame_interval; // 読み出し時の表示フレーム間隔 (ms)
}
以下にその編集画面を示します.
3-[c]
応用編「 その2 」 で作成したソースコード「 OnFileWrite() 」 を以下のように 「 追加・編集 」します.
「 赤い部分 」 が追加するコードです.
CClientDC dc(this);
CDC pM;
CBitmap pB, *pOld;
int ix, iy;
BYTE *m_lpData2;
unsigned char r, g, b;
FILE *fp; // 書き込みファイルのポインタ
unsigned char line[X_SIZE][3]; // 水平1ラインのバッファ
int times; // 連続フレーム表示用の変数
char f_name[40]; // ファイル名を入れる変数
pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );
for ( times = 0 ; times < max_frame ; times++){ // フレームの連続書き込み
dispFlag = FALSE;
capGrabFrame( m_hCapWnd ); // 1フレーム(1枚)を取得するコマンド
while( dispFlag == FALSE) { } // フレームの読み込みが終了するまで待つ
m_lpData2 = (BYTE*)m_lpData; // 画像メモリのポインタ
for( iy = 0; iy < Y_SIZE; iy++){
for( ix = 0; ix < X_SIZE; ix++){
b = *m_lpData2; m_lpData2++;
g = *m_lpData2; m_lpData2++;
r = *m_lpData2; m_lpData2++;
image[Y_SIZE-iy-1][ix][0] = b; // Blue データは上下反転している
image[Y_SIZE-iy-1][ix][1] = g; // Green このため上下を入れ替える
image[Y_SIZE-iy-1][ix][2] = r; // Red
}
}
sprintf( f_name, "%s%d.raw", file_head, times); // ファイル名の生成
fp = fopen( f_name, "wb"); // 書き込み,バイナリモード
if( fp == NULL) { // ファイルオープンに失敗したとき
AfxMessageBox( (CString) "File Open Error"); // エラー表示
return;
}
for( iy = 0; iy < Y_SIZE; iy++){
for( ix = 0; ix < X_SIZE; ix++){
line[ix][0] = image[iy][ix][2]; // Red raw形式に合わせる
line[ix][1] = image[iy][ix][1]; // Green
line[ix][2] = image[iy][ix][0]; // Blue
}
fwrite ( line, X_SIZE, 3, fp ); // ラインバッファの書き込み
}
fclose ( fp ); // ファイルのクローズ
SetBitmapBits( pB, X_SIZE * Y_SIZE * COLOR, &image);
pOld = pM.SelectObject ( &pB );
dc.BitBlt (50, 50, X_SIZE, Y_SIZE, &pM, 0, 0, SRCCOPY); // 画像の表示
}
pM.SelectObject( pOld );
以下にその編集画面を示します.
3-[d]
応用編「 その2 」 で作成したソースコード「 OnFileRead(() 」 を以下のように 「 追加・編集 」します.
「 赤い部分 」 が追加するコードです.
CClientDC dc(this);
CDC pM;
CBitmap pB, *pOld;
FILE *fp; // 読み込みファイルのポインタ
int ix, iy;
unsigned char line[X_SIZE][3]; // 水平1ラインのバッファ
int times; // 連続フレーム表示用の変数
char f_name[40]; // ファイル名を入れる変数
pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );
for ( times = 0 ; times < max_frame ; times++){ // フレームの連続読み出し
sprintf( f_name, "%s%d.raw", file_head, times); // ファイル名の生成
fp = fopen( f_name, "rb"); // 読み込み,バイナリモード
if( fp == NULL) {
AfxMessageBox( (CString) "File Open Error"); // エラー表示
return;
}
for( iy = 0; iy < Y_SIZE; iy++){
fread (line, X_SIZE, 3, fp); // ラインバッファへの読み込み
for( ix = 0; ix < X_SIZE; ix++){
image[iy][ix][0] = line[ix][2]; // Blue raw ⇒ Bitmap形式に合わせる
image[iy][ix][1] = line[ix][1]; // Green
image[iy][ix][2] = line[ix][0]; // Red
}
}
fclose (fp); // ファイルのクローズ
SetBitmapBits( pB, X_SIZE * Y_SIZE * COLOR, &image);
pOld = pM.SelectObject ( &pB );
dc.BitBlt (50, 50, X_SIZE, Y_SIZE, &pM, 0, 0, SRCCOPY); // 画像の表示
Sleep(frame_interval); // 読み込みフレームの表示間隔調整
}
pM.SelectObject( pOld );
以下にその編集画面を示します.
ステップ4 画像ファイル保存用フォルダの生成
このプロジェクトが入っている「 image_file 」 フォルダの中に 「 ucam_file 」 というフォルダを新たに作成します.
画像ファイルはこの中に,「 cam番号.raw 」 のような形式で連続的に書き込まれます.
ステップ5 プログラムの実行結果(1)
メニュー「 初期設定 」 の下のサブメニュー 「 ファイルパラメータ 」 をクリックします.
以下のような解像度設定用のダイヤログが表示されます.
パラメータの 「 デフォルト値 」 は図のように設定されているはずです.
ステップ6 プログラムの実行結果(2)
メニュー「 ファイル 」 の下にあるサブメニュー「 書き込み 」 をクリックすると,USBカメラで撮影した静止画像がフルカラーで表示され,
下の図に示すように,「 image_file 」 フォルダの中にあらかじめ作成した 「 ucam_file 」 というフォルダの中に,連続的に書き込まれます.
なおファイル名は,「 cam0.raw 」,「 cam1.raw 」 のように,ファイルのHEAD部に数字が付加された形になります.
ステップ7 プログラムの実行結果(3)
メニュー「 ファイル 」 の下にあるサブメニュー「 読み出し 」 をクリックすると,「
書き込み命令で生成された「 ファイル群 」 が 「 インターバル間隔 」で読み出され,連続的なフルカラーの動画像として表示されます.
本編では,VC++を用いて,USBカメラの画像をキャプチャして画面上に表示し,
ビットマップ形式の画像ファイルとして保存する方法について紹介しました.
物体の動きを検出・計測するシステムの開発では,計測結果の再現性が極めて重要です.
このため,システムの性能を評価する段階では,実際のUSBカメラの信号を使用しないで,
本章で説明した手法を用いて生成した動画像ファイルを用いる手法が有効です.
次章では,テンプレートマッチングにより,画像のズレを検出するプログラムを作成します.