株式会社インデペンデンスシステムズ横浜

システム開発エンジニアの西田五郎が運営しております。Raspberry Pi や Arduino その他新規開発案件のご依頼をお待ちしております。

OpenCV

OpenCVをWin32ベースで利用する(その1)ダイアログベースアプリ

投稿日:2014年11月1日 更新日:

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でプロジェクトを生成します。(以下参照)

0002

0004

これをそのままビルドして実行すると以下のような画面が表示されます。
0005

これは、メインウインドウとメニューを持ったアプリケーションのテンプレートですが、これをもっと簡単なダイアログベースのテンプレートに変更します。まず、メインウインドウとなるダイアログをリソースに定義します。以下のようなダイアログを定義しました。ピクチャーコントロールとボタンのダイアログです。

0001

但し、ここで問題があります。Visual Studio Express 2013 for Windows Desktopではリソースエディターが利用出来ないです。そのため以下のようにテキストベースで編集するしかないです。Visual Studioの上位のバージョンがあれば、そちらを利用すればいいです。(※あとはフリーのリソースエディタのResEditを使う方法があります。ページ最後の方に補足で書きました。)ここでは、Visual Studioの上位バージョンで作成したファイルをベースにテキストベースで編集します。編集方法はリソースファイルを右クリックからコードの表示で開きます。
WS000001

(※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関数用

文字コードを変更しておきます。プロジェクトを選択して、右クリックでプロパティページを開きます。文字コードをマルチバイトにしておきます。マルチバイトの方がとりあえずは扱いやすいです。
0001

次に、メインのソースファイルを以下のように変更します。

// 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版で英語ですが最新の情報もあるようです。また価格的にはいいかもしれないです。)


AdSense

AdSense

-OpenCV

執筆者:

関連記事

OpenCVで輪郭抽出から隣接領域の切り出し(その3)凸包の取得

OpenCVで画像内の輪郭抽出からその輪郭の隣接領域(四角形)を求めてその領域を切り出すという処理を作ってみました。前回までのプログラムとほぼ同じですが、前回までは輪郭抽出 → 直線近似 → 隣接領域 …

OpenCVをWin32ベースで利用する(その4)カメラ入力からの動画処理

OpenCVをWin32ベースで利用するの4回目です。前回はカメラ入力からの画像処理(静止画)を扱いましたが、今回は動画を処理します。動画と言っても特に変わることはなくキャプチャしたフレームごとに処理 …

NuGetでOpenCVを導入する

Visual Studio Express 2013 for Windows Desktopの開発環境でNuGetを使ってOpenCVを導入してみました。その方法についてです。 まずは、OpenCV( …

Raspberry Pi 4にOpenCVをインストールしてPythonで動作確認

Raspberry Pi 4にOpenCVをインストールして動作確認をしました。Pythonでのプログラミングを想定して動作確認はPythonで行いました。

OpenCVで背景差分とテスト的な動体検知

OpenCVで背景差分を試してみました。あとテスト的な動体検知も試してみました。背景差分とはあらかじめ取得した画像を背景画像として、観測時点の画像とその背景画像との差分を取ることによりその差分を前景領 …