OpenCVをWin32ベースのアプリケーションで利用してみます。今回はまず、Win32ベースとはどういう作り方かというところからベースになるダイアログベースのアプリを作成します。また、それをベースにしてOpenCVのアプリケーションを作成するとどういったメリットが考えられるかというところまでを書きます。
まずは、今現在でMicrosoftが提供する開発環境(Visual Studio)でWindows上で動作するアプリケーション全般を開発する場合にベースになるAPI(フレームワーク、SDK等の表現も出来るかと思います)は以下があると思います。
1. Win32API(MFC、COMを含む)
2. .NET Framework
3. WinRT、WinJS
上から登場した順です。これらをベースに、Windowsフォームアプリ(デスクトップアプリ)、Webアプリ、Windowsアプリ(Windows Phoneアプリ)を開発することが可能です。それぞれ混在させて開発することも可能です。アプリの形態に対して、ベースとするには向き、不向きがあります。当然、新しい形態は新しいAPIをベースにする方が合理的です。
今回はこの中でWin32APIを使います。Win32APIとは、32ビットプロセッサで動作するWindows95以降やWindowsNT、Windows2000で利用出来るAPIのことです。
今回、OpenCVのアプリケーションにわざわざ古くさいともいえるこのWin32APIベースを選んだ理由は、C言語、C++ベースでプログラミングが出来るという点です。OpenCVはマルチプラットフォームでいろいろな言語から利用出来ますが、はじめはサンプルもいろいろと公開されているC言語、C++で試すことも多いと思います。その場合、OpenCV自体にも簡単なGUIはありますが簡単過ぎて物足りないと思います。それを補いサンプル等もそのまま記述出来ると思ったのでWin32APIをベースに考えました。但し、このベースを元に本格的なアプリケーションを作成するのは無理があります。本格的なアプリケーションを作成するのであれば、.NET Framework以降を利用する方が妥当です。
それでは、OpenCVの前にWin32APIでのダイアログベースアプリを作成してみます。開発環境は、Visual Studio Express 2013 for Windows Desktopです。
(※2014.11.16追記)
以下で画面上の入出力用のコントロールを追加したバージョンを作成しました。
OpenCVでWin32ベースで利用する(その5)GUIコントロールの追加
(※2015.04.24追記)
以下で、Visual Studio Community 2013のVisual C++で同様のプログラムを作成しました。
Visual Studio Community 2013のVisual C++でOpenCVを使う
今回のソースは以下です。とりあえず動かす、ソースを見たい場合は以下からダウンロード出来ます。
(※必要な場合は用途に限らずご利用頂いて問題ありませんが、一切無保証です。弊社は一切の責任を負いません。)
今回のソースファイル一式
まず、Visual Studio Express 2013 for Windows Desktopから新しいプロジェクトで、Visual C++ → Win32 → Win32プロジェクトを選択します。そして、適当なプロジェクト名を入力します。OKボタンで次に進みます。ここで、OKでプロジェクトを生成します。(以下参照)
これをそのままビルドして実行すると以下のような画面が表示されます。
これは、メインウインドウとメニューを持ったアプリケーションのテンプレートですが、これをもっと簡単なダイアログベースのテンプレートに変更します。まず、メインウインドウとなるダイアログをリソースに定義します。以下のようなダイアログを定義しました。ピクチャーコントロールとボタンのダイアログです。
但し、ここで問題があります。Visual Studio Express 2013 for Windows Desktopではリソースエディターが利用出来ないです。そのため以下のようにテキストベースで編集するしかないです。Visual Studioの上位のバージョンがあれば、そちらを利用すればいいです。(※あとはフリーのリソースエディタのResEditを使う方法があります。ページ最後の方に補足で書きました。)ここでは、Visual Studioの上位バージョンで作成したファイルをベースにテキストベースで編集します。編集方法はリソースファイルを右クリックからコードの表示で開きます。
(※2015.04.24追記 ここから)
以下のリンク先ページでは、Visual Studio Community 2013を使いました。Visual Studio Community 2013ではダイアログエディタも使えます。
Visual Studio Community 2013のVisual C++でOpenCVを使う
(※2015.04.24追記 ここまで)
ここに、以下のようにダイアログの定義を追加します。これは、Visual Studioの上位バージョンで作成したものです。ここではこういうものだということでいいです。
///////////////////////////////////////////////////////////////////////////// // // ダイアログ // IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "バージョン情報 Win32Project1" FONT 9, "MS UI Gothic" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 LTEXT "Win32Project1, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX LTEXT "Copyright (C) 2014",IDC_STATIC,42,26,114,8 DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP END //以下が追加したダイアログ IDD_MAIN_DIALOG DIALOGEX 0, 0, 720, 400 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Win32 Dialog OpenCV" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "読み込み", ID_READ, 620, 24, 80, 20 DEFPUSHBUTTON "保存", ID_SAVE, 620, 47, 80, 20 DEFPUSHBUTTON "処理1", ID_EXEC1, 620, 93, 80, 20 DEFPUSHBUTTON "処理2", ID_EXEC2, 620, 116, 80, 20 DEFPUSHBUTTON "処理3", ID_EXEC3, 620, 140, 80, 20 DEFPUSHBUTTON "処理4", ID_EXEC4, 620, 165, 80, 20 DEFPUSHBUTTON "処理5", ID_EXEC5, 620, 189, 80, 20 PUSHBUTTON "終了", ID_CLOSE, 620, 235, 80, 20 CONTROL "", IDC_PICTURE, "Static", SS_BLACKFRAME, 1, 1, 600, 380 END
ヘッダファイルを編集します。Resource.hに以下を追加します。
//追加した定義 #define ID_READ 3 #define ID_EXEC1 4 #define ID_EXEC2 5 #define ID_EXEC3 6 #define ID_EXEC4 7 #define ID_EXEC5 8 #define ID_SAVE 9 #define ID_CLOSE 10 #define IDC_PICTURE 1003 #define IDD_MAIN_DIALOG 129 //ここまで
stdafx.h に以下を追加します。
// TODO: プログラムに必要な追加ヘッダーをここで参照してください #include <Commdlg.h> //GetOpenFileName関数用 #pragma comment(lib, "Comdlg32.lib") //GetOpenFileName関数用
文字コードを変更しておきます。プロジェクトを選択して、右クリックでプロパティページを開きます。文字コードをマルチバイトにしておきます。マルチバイトの方がとりあえずは扱いやすいです。
次に、メインのソースファイルを以下のように変更します。
// Win32Project1.cpp : アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include "Win32Project1.h" //ファイル名関連 TCHAR szOpenFileName[MAX_PATH]; TCHAR szSaveFileName[MAX_PATH]; int openFileNameBox(HWND hWnd); //GetOpenFileName関数用 int saveFileNameBox(HWND hWnd); //GetSaveFileName関数用 /*---------------------------------------------- ダイアログプロシージャ -----------------------------------------------*/ LRESULT CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { int ret; int id = LOWORD(wParam); switch (message) { case WM_INITDIALOG: // ダイアログの初期化処理 return TRUE; case WM_COMMAND: switch (id) { case ID_READ: ret = openFileNameBox(hDlg); break; case ID_SAVE: ret = saveFileNameBox(hDlg); break; case IDC_MYICON: // ×ボタンの処理 EndDialog(hDlg, IDOK); return TRUE; case ID_CLOSE: // 閉じるボタンの処理 EndDialog(hDlg, IDCANCEL); return TRUE; case ID_EXEC1: MessageBox(hDlg, "処理1", "テスト", MB_OK); break; case ID_EXEC2: MessageBox(hDlg, "処理2", "テスト", MB_OK); break; case ID_EXEC3: MessageBox(hDlg, "処理3", "テスト", MB_OK); break; case ID_EXEC4: MessageBox(hDlg, "処理4", "テスト", MB_OK); break; case ID_EXEC5: MessageBox(hDlg, "処理5", "テスト", MB_OK); break; default: break; } break; } return FALSE; } /*----------------------------------- オープンファイル名を取得する -------------------------------------*/ int openFileNameBox(HWND hWnd) { int i; OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); // 構造体の初期化 ofn.lStructSize = sizeof(OPENFILENAME); //構造体のサイズ ofn.hwndOwner = hWnd; //親ウィンドウのハンドル ofn.lpstrFilter = TEXT("All(*.*)\0*.*\0JPEGファイル(*.jpg)\0*.jpg\0PNGファイル(*.png)\0*.png\0\0"); //フィルター ofn.lpstrFile = szOpenFileName; // フルパスファイル名 ofn.nMaxFile = sizeof(szOpenFileName); //フルパスファイルのサイズ ofn.lpstrFileTitle = szOpenFileName; //ファイル名 ofn.nMaxFileTitle = sizeof(szOpenFileName); //ファイル名のサイズ //既存ファイル名のみ入力許可 | 読み取りチェックボックスを表示しない ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = TEXT("*"); //デフォルトの拡張子 ofn.lpstrTitle = TEXT("画像ファイルの読込み"); //タイトルバーに表示される文字列 i = GetOpenFileName(&ofn); if (i == 0) { //OKボタンが押されていない時 return 1; } MessageBox(hWnd, szOpenFileName, TEXT("debug"), MB_OK); return 0; } /*----------------------------------- 保存ファイル名を取得する -------------------------------------*/ int saveFileNameBox(HWND hWnd) { int i; OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); // 構造体の初期化 ofn.lStructSize = sizeof(OPENFILENAME); //構造体のサイズ ofn.hwndOwner = hWnd; //親ウィンドウのハンドル ofn.lpstrFilter = TEXT("All(*.*)\0*.*\0JPEGファイル(*.jpg)\0*.jpg\0PNGファイル(*.png)\0*.png\0\0"); //フィルター ofn.lpstrFile = szSaveFileName; // フルパスファイル名 ofn.nMaxFile = sizeof(szSaveFileName); //フルパスファイルのサイズ ofn.lpstrFileTitle = szSaveFileName; //ファイル名 ofn.nMaxFileTitle = sizeof(szSaveFileName); //ファイル名のサイズ ofn.Flags = OFN_OVERWRITEPROMPT; // 上書き確認ダイアログの表示 ofn.lpstrDefExt = TEXT("*"); //デフォルトの拡張子 ofn.lpstrTitle = TEXT("画像ファイルの保存"); //タイトルバーに表示される文字列 i = GetSaveFileName(&ofn); //ファイル名を付けて保存ダイアログボックス if (i == 0) { //OKボタンが押されていない時 return 1; } MessageBox(hWnd, szSaveFileName, TEXT("debug"), MB_OK); return 0; } /*----------------------------------------------------- メイン関数 -------------------------------------------------------*/ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN_DIALOG), NULL, (DLGPROC)DlgProc); return 0; }
簡単に構成を書きますと、WinMainがC言語標準のmain関数の役割になっていて、ここでダイアログを生成します。あとは、DlgProcでイベントドリブン、つまり、ボタンが押された等の処理で分岐していきます。その他、ファイル名選択用の関数を追加しています。今回はOpenCVを利用することが目的なので、ここではWin32APIの詳細には触れないです。
この状態でビルドを行います。問題がなければ実行します。正常に動作すればダイアログが表示されるはずです。各ボタンで動作確認が出来ます。
この時点ではもちろんOpenCVは何も組み込まれていません。OpenCVの組み込みから利用方法までは次回に続きます。
今回のソースは以下です。(上のソースと同じです。)
今回のソースファイル一式
以下は、RedEditについての補足です。
Visual Studio Express 2013 for Windows Desktopではリソースエディタが使えないです。そのため代わりにResEditを使う方法があります。ただし、実際にやってみましたが万能ではないです。保存したファイルを再度読み込むと読めなかったということがありました。あと、公式のダウンロード先からダウンロードしたファイルは壊れているのかうまく解凍出来ませんでした。以下からダウンロード出来ます。補助的に使うことは出来ると思います。
窓の杜のResEditのページ
詳細は以下等を参照して下さい。
リソースエディタResEditの使い方 – インコのWindowsSDK
次回は今回のソースにOpenCVを組み込んで使ってみます。
関連書籍
(※APIで学ぶWindows徹底理解は中古でしか手に入らないようですが私も持っています。)
(※OpenCV 2 プログラミングブックは私も持っています。)
(※ここからはKindle版で英語ですが最新の情報もあるようです。また価格的にはいいかもしれないです。)