Visual Studio (VC++) 応用編 (その2)


− 静止画像ファイルの生成と表示 −


信州大学工学部 井澤裕司

(H18.2.5)


課  題 2

Microsoft Visual Studio2005 の Visual C++ を用いて,以下の機能をもつプログラムを作成しなさい.

MFCアプリケーションのメニュー「 初期設定 」,サブメニューの「 解像度 」 を起動すると,接続したUSBカメラの画素数等の情報を設定するダイヤログが開くので,解像度を「 320×240 」,ピクセルビットを「 RGB24 」に設定する.次に,メニューの「 画像表示 」,サブメミューの「 静止画 」を指定すると,USBカメラでキャプチャしたフルカラーの静止画像が解像度「 320×240画素 」で画面上に表示される.
(以上は,応用編の課題1と同じ)

次に,メニュー「 ファイル 」,サブメニューの「 書き込み 」 を起動すると,先の 静止画 」の操作と同様,カメラの画像信号がキャプチャされ,「 "image.raw" 」という名の「 バイナリファイル 」に書き込まれる.次に,サブメニューの「 読み出し 」を起動すると,バイナリファイルに保存された静止画像が画面上に表示される.

[注意]
このアプリケーションは,Video for Windows(以下,VFWと呼ぶ)のライブラリを使用しています. USBカメラに限らず,PCIインタフェースのキャプチャーカードを用いても動作しますが, 事前に使用するUSBカメラ,もしくはキャプチャーカードのデバイスドライバをインストールし,正常に動作させる必要があります.
なお,このコンテンツで紹介するプログラムは,研究室における試行錯誤から生まれたものであり, VFWの正規の使用法に準拠している保証はありません.操作方法により,動作が不安定になる場合があることをご了解下さい.


このコンテンツの中で,Visual C++ の使用法を詳細に説明することは困難です. Visual C++については,以下のような書籍が出版されていますので,参考にして下さい.
   ・Visual C++ 2005 ビギナー編
        林 晴彦 Softbank Creative
 
WEB上には 以下のようなサイトがあります.
 
   ・[Visual C++の使い方]
          http://www.nitoyon.com/vc/
   ・[Visual C++の勉強部屋]
          http://homepage3.nifty.com/ishidate/vcpp.htmvc/
   ・[Area of VC++ Tips]
          http://rararahp.cool.ne.jp/vc/vctips/tipindex.htm
 
手順の概略を以下に示します.
なお手順の (4),(11),(12) 以外 は 資料 「VC++応用編(その1)」 と同じです.
(1) プロジェクト( image_file )の新規作成
・新規プロジェクト 「 image_file 」 をシングルダイヤログ形式( SDI )で作成する.

・手順は
入門(その1) のステップ1〜3と同じ.
(2) 初期設定系メニューの 新規作成(1)
・メニューの 「 表示 」,「 リソースビュー 」 をクリックし,「 Menu 」 の IDR_MAINFRAME を編集する.

・新しいメニューとして,例えば
初期設定 」 を作成し,その下の サブメニュー を,例えば 「 解像度 」 のように作成する.

・作成したサブメニュー「
解像度 」 をダブルクリック(左)するか,メニューの右上にあるアイコンの 「 プロパティ ウィンドウ 」 を左クリックして,メニューエディタを起動し,リストの項目 ID を例えば 「 ID_INIT 」 のように設定する. 

・リソースのサブメニュー「
解像度 」 にマウスのカーソルを合わせ,マウスを ”右” クリックして,「 イベント ハンドラの追加 」 を起動する.

・メッセージの種類を 「
COMMAND 」 ,クラスの一覧の中から 「 Cimage_fileView 」 を選択し,「 追加して編集 」 のボタンをクリックする.

イベントハンドラ ウィザード により View クラスの中に 新たなクラス 「 OnInit() 」 が自動生成され,自動的にエディタのモードに移行する.
(3) 表示系メニューの 新規作成(2)
・メニューの 「 表示 」,「 リソースビュー 」 をクリックし,「 Menu 」 の IDR_MAINFRAME を編集する.

・新しいメニューとして,例えば
表示 」 を作成し,その下の サブメニュー を,例えば 「 静止画 」 のように作成する.

・作成したサブメニュー「
静止画 」 をダブルクリック(左)するか,メニューの右上にあるアイコンの 「 プロパティ ウィンドウ 」 を左クリックして,メニューエディタを起動し,リストの項目 ID を例えば 「 ID_STILL_IMAGE 」 のように設定する. 

・リソースのサブメニュー「
静止画 」 にマウスのカーソルを合わせ,マウスを ”右” クリックして,「 イベント ハンドラの追加 」 を起動する.

・メッセージの種類を 「
COMMAND 」 ,クラスの一覧の中から 「 Cimage_fileView 」 を選択し,「 追加して編集 」 のボタンをクリックする.

イベントハンドラ ウィザード により View クラスの中に 新たなクラス 「 OnStillImage() 」 が自動生成され,自動的にエディタのモードに移行する.
(4) ファイル操作系メニューの 新規作成(3)
・メニューの 「 表示 」,「 リソースビュー 」 をクリックし,「 Menu 」 の IDR_MAINFRAME を編集する.

・新しいメニューとして,例えば
ファイル 」 を作成し,その下の サブメニュー を,例えば 「 書き込み 」 のように作成する.

・作成したサブメニュー「
書き込み 」 をダブルクリック(左)するか,メニューの右上にあるアイコンの 「 プロパティ ウィンドウ 」 を左クリックして,メニューエディタを起動し,リストの項目 ID を例えば 「 ID_FILE_WRITE 」 のように設定する. 

・リソースのサブメニュー「
書き込み 」 にマウスのカーソルを合わせ,マウスを ”右” クリックして,「 イベント ハンドラの追加 」 を起動する.

・メッセージの種類を 「
COMMAND 」 ,クラスの一覧の中から 「 Cimage_fileView 」 を選択し,「 追加して編集 」 のボタンをクリックする.

イベントハンドラ ウィザード により View クラスの中に 新たなクラス 「 OnFileWrite() 」 が自動生成され,自動的にエディタのモードに移行する.

・同様にして ファイル 」 の下にもう一つの サブメニュー 読み出し 」 を作成する.

・作成したサブメニュー「 読み出し 」 をダブルクリック(左)するか,メニューの右上にあるアイコンの 「 プロパティ ウィンドウ 」 を左クリックして,メニューエディタを起動し,リストの項目 ID を例えば 「 ID_FILE_READ 」 のように設定する. 

・リソースのサブメニュー「 読み出し 」 にマウスのカーソルを合わせ,マウスを ”右” クリックして,「 イベント ハンドラの追加 」 を起動する.

・メッセージの種類を 「 COMMAND 」 ,クラスの一覧の中から 「 Cimage_fileView 」 を選択し,「 追加して編集 」 のボタンをクリックする.

イベントハンドラ ウィザード により View クラスの中に 新たなクラス 「 OnFileRead() 」 が自動生成され,自動的にエディタのモードに移行する.

(5)  Viewクラス 「 Cimage_fileView 」 の編集 (1)
・Viewクラス 「 Cimage_fileView() 」 の最初に,Video for Windowsのライブラリを呼び出すためのヘッダーファイル「 vfw32.h 」 をインクルードし,定数を定義する「 define文 」 や変数等を追加する.

‥(略)‥
#include   "image_fileView.h" // この下に追加する

#include   "vfw.h"        // 追加したヘッダーファイル
#include   "winbase.h"     // 追加したヘッダーファイル

#define  X_SIZE     320        // 水平画素数
#define  Y_SIZE     240        // 垂直画素数
#define  COLOR     4        // 色(RGB(A) 4 Byte)
#define  FILE_NAME  "image.raw"   // 画像ファイル名

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;     // キャプチャ終了フラグ

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);
}
(6)   Viewクラス 「 Cimage_fileView() 」 の編集 (2)
・デストラクタ「 Cimage_fileView::~Cimage_fileView() 」 の内部に,後処理用(リソースの解放)のコードを追加する.

Cimage_fileView::~Cimage_fileView()
{
   capDriverDisconnect( m_hCapWnd );
   ::DestroyWindow( m_hCapWnd );
   m_lpData = NULL;
   free( m_lpImage );
   m_lpImage = NULL;
   DeleteDC( m_memDC );
   DeleteObject( m_hBitmap );
}
(7)   Viewクラス 「 Cimage_fileView() 」 の編集 (3)
・デストラクタ「 Cimage_fileView::~Cimage_fileView() 」 の後に,画像キャプチャ終了時に起動するコードを追加する.

static LRESULT FAR PASCAL CaptureImage(HWND hWnd,
     LPVIDEOHDR lpVHdr)
{
   memcpy(m_lpData, (LPSTR)lpVHdr->lpData,
          m_lpImage->biSizeImage);

   dispFlag = TRUE;
   return (LRESULT)TRUE;
}
(8)   Viewクラス 「 Cimage_fileView() 」 の編集 (4)
・「 Cimage_fileView::PreCreateWindow(CREATESTRUCT& cs) 」 の内部に,描画準備用のコードを追加する.

BOOL Cimage_fileView::PreCreateWindow(CREATESTRUCT& cs)
{
   dispFlag = FALSE;
   m_hCapWnd = capCreateCaptureWindowA("image_file",
      WS_OVERLAPPEDWINDOW,
      0, 0, X_SIZE, Y_SIZE, NULL, NULL);
   capDriverConnect(m_hCapWnd, 0);
   capSetCallbackOnFrame( m_hCapWnd, CaptureImage);
   size = capGetVideoFormatSize(m_hCapWnd);
   CreateDIBHeader(size);
   m_lpImage = GetDIBHeader();
   capGetVideoFormat(m_hCapWnd, m_lpImage, size);
// DIBsectionの確保
   m_hBitmap = CreateDIBSection( m_memDC.GetSafeHdc(),
        (BITMAPINFO*)m_lpImage,

        DIB_RGB_COLORS, (void**)&m_lpData, NULL, 0 );
   return CView::PreCreateWindow(cs);
}
(9)   Viewクラス 「 OnInit() 」 の編集
・メニューの「解像度」に対応する「 OnInit() 」 の内部に,以下に示す初期設定のコードを追加する.

void Cimage_fileView::OnInit()
{
   capDlgVideoFormat( m_hCapWnd );
   size = capGetVideoFormatSize( m_hCapWnd );
   CreateDIBHeader( size );
   m_lpImage = GetDIBHeader();
   capGetVideoFormat( m_hCapWnd, m_lpImage, size );
   m_hBitmap = CreateDIBSection( m_memDC.GetSafeHdc(),
      ( BITMAPINFO* )m_lpImage,
      DIB_RGB_COLORS, (void**)&m_lpData, NULL, 0 );
}
(10)   Viewクラス 「 OnDraw() 」 の編集
・「 OnDraw() 」 の引数のコメントを除去し,次のようにする.

    void Cimage_fileView::OnDraw( CDC* pDC )

・次に,以下に示す描画用のコードを追加する.

CDC         pM;
CBitmap          pB, *pOld;

pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( pDC );
SetBitmapBits( pB, X_SIZE * Y_SIZE * COLOR, &image);
pOld = pM.SelectObject ( &pB );
pDC->BitBlt (50, 50, X_SIZE, Y_SIZE, &pM, 0, 0, SRCCOPY);
pM.SelectObject( pOld );
(11)   Viewクラス 「 OnStillImage() 」 の編集
・自動生成された「 OnStillImage() 」 の内部に,以下に示す描画用のコードを追加する.

int          ix, iy;
BYTE        *m_lpData2;
unsigned char   r, g, b;
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
    }
}
InvalidateRect( NULL, FALSE );       // 画像の表示(OnDrawの起動)

(12)   Viewクラス 「 OnFileWrite() 」 の編集
・自動生成された「 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ラインのバッファ

pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );

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
    }
}

fp = fopen( FILE_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 );
(13)   Viewクラス 「 OnFileRead() 」 の編集
・自動生成された「 OnFileRead() 」 の内部に,以下に示すファイル読み込みのコードを追加する.

CClientDC    dc(this);
CDC        pM;
CBitmap         pB, *pOld;
FILE        *fp;            // 読み込みファイルのポインタ
int         ix, iy;
unsigned char  line[X_SIZE][3];    // 水平1ラインのバッファ

pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );

fp = fopen( FILE_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形式に合わせる
       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); // 画像の表示
pM.SelectObject( pOld );
(14)   描画用リソース 「 Bitmap 」 の生成
・メニューの 「 表示 」,「 リソースビュー 」 をクリックし,新しいリソースの 「 Bitmap 」を生成する.

・「 Bitmap 」のプロパティを開き,解像度を水平「 320 」,垂直「 240 」画素に設定し,色の設定を「 Trueカラー 」とする.
(15)   Video for Windows ライブラリ vfw32.lib 」 のリンク設定
・メニュー「 表示 」の 「 プロパティマネージャ 」,「 構成プロパティ 」 をクリックし,「 プロパティページ 」のダイヤログを開く.

・左の「 構成プロパティ 」から「 リンカ 」,「 入力 」を選択して,右側の「 追加の依存ファイル 」の欄に「 vfw32.lib 」と入力する.
(16)   コンパイル , デバッグ および 実行
入門(その2) のステップ5〜7に同じ.


ステップ1. Microsoft Visual Studio2005 を起動する.

メニューの 「 ファイル 」 から 「 新規作成 」 の 「 プロジェクト 」 を指定します.
  

ステップ2. Visual C++ のMFC アプリケーションを立ち上げる.

プロジェクトの種類から「 Visual C++ 」 の 「 MFC 」 (Microsoft Foundation Class Library) を選択し,右側のテンプレートから 「 MFCアプリケーション 」 を指定します.
下の欄のプロジェクト名として,例えば 「 image_file 」 と入力します.


ステップ3. MFC アプリケーション ウィザードに従ってスケルトン(雛形)を生成する.

MFCアプリケーション ウィザード 」 は,MFCアプリケーションの雛形を自動生成するツールです.
このウィザードに従って,スケルトンを生成します.
なお単純化するため,「
アプリケーションの種類 」 は「 シングル ドキュメント 」とし,「 高度な機能 」については,すべての チェックをはずし て下さい.

ステップ4. メニューのリソースを追加し,イベントハンドラを起動して,ソースファイルを編集する.

4-[a]
     以上の手順により, MFC アプリケーション の雛形が完成しました.


4-[b]

メニューの「 表示 」から 「 リソースビュー 」 を指定し,「 image_file 」 プロジェクトの リソース一覧を表示します.
image_file.rc 」 の「 Menu 」 をクリックし,ファイル一覧を表示します.

なお,一覧が表示されない場合は,上位のフォルダをダブルクリックすると,その下の階層が表示されます.

リソース一覧の中から 「 IDR_MAINFRAME 」 をダブルクリックして,MFC アプリケーションの画面を表示します.

4-[c]

ここへ入力 」 の欄の下端にマウスのカーソルを合わせ,左ダブルクリックして,「 初期設定 」 と入力します.
次に,「 初期設定 」 の下の 「 ここへ入力 」 の下端にマウスのカーソルを合わせ,左ダブルクリックして 「 解像度 」 と入力します.
解像度 」 の ”右側” ”にマウスのカーソルを合わせ,左ダブルクリックして,「 メニューエディタ 」 を起動します.
なお,メニュー欄の右端にあるアイコン 「
プロパティ ウィンドウ 」 を左クリックしても メニューエディタ」 が開きます.

画面左の「 メニューエディタ 」 の「 ID 」 の欄に,ユニークな名前,例えば,「 ID_INIT 」 を入力します.

4-[d]

メニューエディタ 」 を閉じた後,再びサブメニューの 「 解像度 」 にマウスのカーソルを合わせ,今度は ”右” クリックして, 「 イベントハンドラの追加 」 を起動します.

イベント ハンドラ ウィザード 」 のクラス一覧から Viewクラス ,すなわち 「 Cimage_fileView 」 を選択して,「 追加して編集 」 を起動します.
なお,「 メッセージ 」 の欄は 「 COMMAND 」 の状態で結構です.


4-[e]

イベント ハンドラ ウィザード 」 により, Viewクラス の中に,新規メニューに対応する新しいクラス 「 Onimage_file() 」 が自動的に生成されます


4-[f]

同様にして,今度は表示系のメニューを追加します.
ここへ入力 」 の欄の下端にマウスのカーソルを合わせ,左ダブルクリックして,「 画像表示 」 と入力します.
次に,「 画像表示 」 の下の 「 ここへ入力 」 の下端にマウスのカーソルを合わせ,左ダブルクリックして 「 静止画 」 と入力します. 静止画 」 の ”右側” にマウスのカーソルを合わせ,左ダブルクリックして,「 メニューエディタ 」 を起動します.
なお,メニュー欄の右端にあるアイコン 「
プロパティ ウィンドウ 」 を左クリックしても メニューエディタ」 が開きます.

画面左の「 メニューエディタ 」 の「 ID 」 の欄に,ユニークな名前,例えば,「 ID_STILL_IMAGE 」 を入力します.

4-[g]

メニューエディタ 」 を閉じた後,再びサブメニューの 「 静止画 」 にマウスのカーソルを合わせ,今度は ”右” クリックして, 「 イベントハンドラの追加 」 を起動します.

イベント ハンドラ ウィザード 」 のクラス一覧から Viewクラス ,すなわち 「 Cimage_fileView 」 を選択して,「 追加して編集 」 を起動します.
なお,「
メッセージ 」 の欄は 「 COMMAND 」 の状態で結構です.


4-[h]
イベント ハンドラ ウィザード 」 により, Viewクラス の中に,新規メニューに対応する新しいクラス 「 OnStillImage() 」 が自動的に生成されます


4-[i]

最後にファイル操作系の新規メニューを追加します.
ここへ入力 」 の欄の下端にマウスのカーソルを合わせ,左ダブルクリックして,「 ファイル 」 と入力します.
次に,「 ファイル 」 の下の 「 ここへ入力 」 の下端にマウスのカーソルを合わせ,左ダブルクリックして 「 書き込み 」 と入力します. 書き込み 」 の ”右側” ”にマウスのカーソルを合わせ,左ダブルクリックして,「 メニューエディタ 」 を起動します.
なお,メニュー欄の右端にあるアイコン 「
プロパティ ウィンドウ 」 を左クリックしても メニューエディタ」 が開きます.

画面左の「 メニューエディタ 」 の「 ID 」 の欄に,ユニークな名前,例えば,「 ID_FILE_WRITE 」 を入力します.

4-[j]

メニューエディタ 」 を閉じた後,再びサブメニューの 「 書き込み 」 にマウスのカーソルを合わせ,今度は ”右” クリックして, 「 イベントハンドラの追加 」 を起動します.

イベント ハンドラ ウィザード 」 のクラス一覧から Viewクラス ,すなわち 「 Cimage_fileView 」 を選択して,「 追加して編集 」 を起動します.
なお,「
メッセージ 」 の欄は 「 COMMAND 」 の状態で結構です.


4-[k]
イベント ハンドラ ウィザード 」 により, Viewクラス の中に,新規メニューに対応する新しいクラス 「 OnFileWrite() 」 が自動的に生成されます


4-[l]

同様にして,今度はファイル系のメニューを追加します.
ここへ入力 」 の欄の下端にマウスのカーソルを合わせ,左ダブルクリックして,「 ファイル 」 と入力します.
次に先ほど追加した「 ファイル 」 の欄の下にある 「 ここへ入力 」 の下端にマウスのカーソルを合わせ,左ダブルクリックして 「 読み出し 」 と入力します. 読み出し 」 の ”右側” ”にマウスのカーソルを合わせ,左ダブルクリックして,「 メニューエディタ 」 を起動します.
なお,メニュー欄の右端にあるアイコン 「
プロパティ ウィンドウ 」 を左クリックしても メニューエディタ」 が開きます.

画面左の「 メニューエディタ 」 の「 ID 」 の欄に,ユニークな名前,例えば,「 ID_FILE_READ 」 を入力します.

4-[m]

メニューエディタ 」 を閉じた後,再びサブメニューの 「 読み出し 」 にマウスのカーソルを合わせ,今度は ”右” クリックして, 「 イベントハンドラの追加 」 を起動します.

イベント ハンドラ ウィザード 」 のクラス一覧から Viewクラス ,すなわち 「 Cimage_fileView 」 を選択して,「 追加して編集 」 を起動します.
なお,「
メッセージ 」 の欄は 「 COMMAND 」 とします.


4-[n]
イベント ハンドラ ウィザード 」 により, Viewクラス の中に,新規メニューに対応する新しいクラス 「 OnFileRead() 」 が自動的に生成されます


ステップ5. ソースファイルを編集する.

5-[a]

Viewクラス 「 Cimage_fileView() 」 の最初に,Video for Windowsのライブラリを呼び出すためのヘッダーファイル「 vfw32.h 」 をインクルードし,定数を定義する「 define文 」 等を追加します.



‥(略)‥
#include   "image_fileView.h"

#include   "vfw.h"        // 追加したヘッダーファイル
#include   "winbase.h"     // 追加したヘッダーファイル

#define  X_SIZE     320         // 水平画素数
#define  Y_SIZE     240         // 垂直画素数
#define  COLOR     4         // 色(RGB(A) 4 Byte)
#define  FILE_NAME  "image.raw" // ファイル名(raw形式)

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;     // 表示終了フラグ

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);
}

以下にその編集画面を示します.



5-[b]

デストラクタ「 Cimage_fileView::~Cimage_fileView() 」 の内部に,後処理用(リソースの解放)のコードを追加します.



Cimage_fileView::~Cimage_fileView()
{
   capDriverDisconnect( m_hCapWnd );
   ::DestroyWindow( m_hCapWnd );
   m_lpData = NULL;
   free( m_lpImage );
   m_lpImage = NULL;
   DeleteDC( m_memDC );
   DeleteObject( m_hBitmap );
}

以下にその編集画面を示します.



5-[c]

デストラクタ「 Cimage_fileView::~Cimage_fileView() 」 の後に,画像キャプチャ終了時に起動するコードを追加します.



static LRESULT FAR PASCAL CaptureImage(HWND hWnd,LPVIDEOHDR lpVHdr)
{
   memcpy(m_lpData, (LPSTR)lpVHdr->lpData, m_lpImage->biSizeImage);
   dispFlag = TRUE;
   return (LRESULT)TRUE;
}

以下にその編集画面を示します.



5-[d]

Cimage_fileView::PreCreateWindow(CREATESTRUCT& cs) 」 の内部に,描画準備用のコードを追加します.



BOOL Cimage_fileView::PreCreateWindow(CREARESTRCT& cs)
{
   dispFlag = FALSE;
   m_hCapWnd = capCreateCaptureWindowA("ImageFile",
              WS_OVERLAPPEDWINDOW, 0, 0, X_SIZE, Y_SIZE, NULL, NULL);

   capDriverConnect(m_hCapWnd, 0);
   capSetCallbackOnFrame( m_hCapWnd, CaptureImage);
   size = capGetVideoFormatSize(m_hCapWnd);
   CreateDIBHeader(size);
   m_lpImage = GetDIBHeader();
   capGetVideoFormat(m_hCapWnd, m_lpImage, size);
// DIBsectionの確保
   m_hBitmap = CreateDIBSection( m_memDC.GetSafeHdc(),(BITMAPINFO*)m_lpImage,
                        DIB_RGB_COLORS, (void**)&m_lpData, NULL, 0 );

   return CView::PreCreateWindow(cs);
}

以下にその編集画面を示します.


5-[e]

メニューの「解像度」に対応する「 OnInit() 」 の内部に,以下に示す描画用のコードを追加します.



void Cimage_fileView::OnInit()
{
   capDlgVideoFormat(m_hCapWnd);
   size = capGetVideoFormatSize(m_hCapWnd);
   CreateDIBHeader(size);
   m_lpImage = GetDIBHeader();
   capGetVideoFormat(m_hCapWnd, m_lpImage, size);
   m_hBitmap = CreateDIBSection( m_memDC.GetSafeHdc(),
      (BITMAPINFO*)m_lpImage,
     DIB_RGB_COLORS,
       (void**)&m_lpData, NULL, 0 );
}

以下にその編集画面を示します.



5-[f]

OnDraw() 」 の内部に,以下に示す描画用のコードを追加します.



void Cimage_fileView::OnDraw ( CDC* pDC )
{
   CDC       pM;
   CBitmap        pB, *pOld;

   pB.LoadBitmap( IDB_BITMAP1 );     // ビットマップ(IDB_BITMAP1)をpB(処理用オブジェクト)に読み込む
   pM.CreateCompatibleDC( pDC );     // 互換性のある作業用デバイスコンテキストの作成
   SetBitmapBits( pB, X_SIZE * Y_SIZE * COLOR, &image);
  // pB(処理用オブジェクト) にサイズ等のパラメータをセットする
   pOld = pM.SelectObject ( &pB );
  // pB(処理用オブジェクト)をメモリ(pM)に展開し,古いビットマップをpOldに保存する
   pDC->BitBlt (50, 50, X_SIZE, Y_SIZE, &pM, 0, 0, SRCCOPY);
  // メモリ(pM)のビットマップイメージを表示用デバイスコンテキストに転送する
   pM.SelectObject( pOld );         // もとのビットマップに戻す
}

以下にその編集画面を示します.



5-[g]

自動生成された「 OnStillImage() 」 の内部に,以下に示す描画用のコードを追加します.



int        ix, iy;
BYTE      *m_lpData2;
unsigned char        r, g, b;

dispFlag = FALSE;
capGrabFrame( m_hCapWnd )   // キャプチャ開始のコマンド
;
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++; // Blue(青)
       g = *m_lpData2; m_lpData2++; // Green(緑)
       r = *m_lpData2; m_lpData2++; // Red(赤)
       image[ Y_SIZE-iy-1 ][ ix ][ 0 ] = b; // データは上下が反転している
       image[ Y_SIZE-iy-1 ][ ix ][ 1 ] = g; // このため上下を入れ替える
       image[ Y_SIZE-iy-1 ][ ix ][ 2 ] = r;
    }
}

InvalidateRect( NULL, FALSE ); // OnDrawを強制的に動作させることにより描画する

以下にその編集画面を示します.


5-[h]

自動生成された「 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ラインのバッファ

pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );

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
    }
}

fp = fopen( FILE_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 );

以下にその編集画面を示します.

5-[i]

自動生成された「 OnFileRead() 」 の内部に,以下に示す描画用のコードを追加します.



CClientDC    dc(this);
CDC        pM;
CBitmap         pB, *pOld;
FILE        *fp;                // 読み込みファイルのポインタ
int         ix, iy;
unsigned char  line[X_SIZE][3];         // 水平1ラインのバッファ

pB.LoadBitmap( IDB_BITMAP1 );
pM.CreateCompatibleDC( &dc );

fp = fopen( FILE_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);   // 画像の表示
pM.SelectObject( pOld );

以下にその編集画面を示します.


ステップ6. 描画用リソース 「BITMAP」 の生成.

6-[a]
VC++のメニュー 「 表示 」,「 リソースビュー 」 を左クリックして「 リソースの一覧 」を表示します.
リソースビュー 」 にマウスのカーソルを置き,右クリックして「 リソースの追加 」 を選択します.

6-[b]

ソースの追加 」,から「 Bitmap 」 を選択します.
6-[c]
新しい「 Bitmap 」が生成されました.
IDの名称は,「
IDB_BITMAP1 」 となり,自動的に番号が付加されます.
なお後ほど補足しますが,ユーザで指定することも可能です.
6-[d]
IDB_BITMAP1 」 を右クリックして,その「 プロパティ 」 を開きます.
6-[e]
リソースビュー 」の右の白い空欄にカーソルを置いて右クリックすると, ビットマップエディタ 」が開き,詳細な「 プロパティ 」 が表示されます.
6-[f]
プロパティ 」に新たに 位置 」の項目が追加されますので,その「 Height 」 を 「 240 」 , 「 WiDTh 」 を 「 320 」 という値に変更します.
さらに 「 表示 」の Color 」の項目が追加されますので,その内容を 「 Trueカラー 」 に修正します.

ステップ7. Video for Windows のライブラリ 「vfw32.lib」 のリンク設定.

7-[a]
メニュー「 表示 」の 「 プロパティマネージャ 」をクリックします.

7-[b]

プロパティマネージャ 」のアイコンをダブルクリックし,「 構成プロパティ を開きます.

7-[c]
画面左の「 構成プロパティ 」から「 リンカ 」,「 入力 」を選択して,右側の「 追加の依存ファイル 」の欄に「 vfw32.lib 」と入力します.

ステップ8. ソースコードのコンパイル(ビルド ・ リビルド).

VC++のメニュー 「 ビルド 」,「 ソリューションのビルド 」 をクリックして コンパイル(ビルド) します.
エラーが表示されたら,該当する箇所を修正します.

ステップ9. 作成したプログラムの実行

VC++ の メニュー「 デバッグ 」 の 「 デバッグなしで開始 」 をクリックして,作成したプログラムを実行します.


ステップ10 プログラムの実行結果(1)

メニュー「 初期設定 」 の下の サブメニュー「 解像度 」 をクリックします.

解像度設定用のダイヤログが表示されますので, 解像度 」 を「 320×240 」 ,ピクセル深度を「 RBG24 」 に設定します.

ステップ11 プログラムの実行結果(2)

メニュー「 画像表示 」 の下にあるサブメニュー「 静止画 」 をクリックすると,USBカメラで撮影した静止画像がフルカラーで表示されます.



ステップ12 プログラムの実行結果(3)

メニュー「 ファイル 」 の下にあるサブメニュー「 書き込み 」 をクリックすると,
USBカメラで撮影した静止画像がフルカラーで表示され, image.raw という名のファイルにビットマップ形式で書き込まれます.
なお,
raw という識別子は,解像度や色などのヘッダー情報がないビットマップ形式を表しており,
これらの情報を入力すれば,Photoshopなどのアプリケーションを用いて表示することができます.



ステップ13 プログラムの実行結果(4)

メニュー「 ファイル 」 の下にあるサブメニュー「 読み出し 」 をクリックすると,書き込み命令で生成された image.raw
が読み出され,フルカラーで表示されます.



まとめ
本編では,VC++を用いて,USBカメラの画像をキャプチャして画面上に表示し,ビットマップ形式の画像ファイルとして保存する方法について紹介しました.

説明に用いたプログラムでは,1枚の静止した画像が表示されますが,作成したクラスの中で for文などを用いてループを構成することにより,動画像に拡張することも可能です.
その構成法については,次の応用編(その3)で説明します.