画像処理というとOpenCVが有名で、C++やPythonで使用されるのが一般的です。
一昔前だと、MFCでOpenCVを使用する場合にはパソコンにOpenCVをインストールした上でプロジェクトの設定でライブラリとインクルードの追加を行う必要があって初学者の方たちには少し敷居が高かったように思います。
最近のVisual Studioには他のプログラミング言語と同様に標準でパッケージマネージャ(NuGet)が備わっているため、現在ではMFCプロジェクトにOpeneCVの導入を行うのはとても簡単になっています。
この記事では、MFCでOpenCVを使用して画像処理を行う方法について解説します。
なお、本記事ではVisual Studio 2022を使用して解説を行いますが他のバージョンでもNuGetが使用できるバージョンであればほぼ同様です。
MFCでOpenCVを使用するための準備
プロジェクトの作成
初めにMFCプロジェクトを作成します。
本記事では画像を表示できれば良いので簡単にダイアログベースで作成していきます。
ついでにダイアログ上の不要なコントロールやソース上の不要なコメントも全て削除しておきます。
OpenCVの導入
冒頭にも書きましたが以前はOpenCVの導入を行う際にはパソコンにOpenCVをインストールしてプロジェクトの設定を変更して、といくつか手順が必要でしたが最近のVisual StudioであればNuGetで簡単に導入を行うことができます。(最新版を使用したい場合にはその限りではありません)
ソリューションエクスプローラの対象プロジェクト上で右クリックをし、「NuGetパッケージの管理(N)」を選択します。
パッケージ管理を開いたら、「参照タブ」を選択し検索ボックスに「OpenCV」と入力します。
入力後に検索一覧に「OpenCV」に関連したパッケージが表示されるので、任意の項目(ここでは「opencv4.2」)を選択し、「インストールボタン」を選択することでプロジェクトにOpenCVを導入します。
インストールに成功したか確認するには、「インストール済みタブ」を選択し、パッケージ一覧の中にインストールしたパッケージが含まれていれば成功です。
プロジェクトの設定変更
MFCでOpenCVを使用する場合、プロジェクトのプロパティ→「詳細」→「MFCの使用」を「スタティックライブラリでMFCを使用する」に変更する必要があります。
「共有DLLでMFCを使用する」にするとアプリケーション終了時にメモリリークが発生してしまうため、変更しておく必要があります。
OpenCVを使用して画像の読み込み、表示
OpenCVデータをメンバに追加
OpenCVで画像を読み込む箇所と読み込んだデータを表示する箇所を分離するため、読み込んだデータを保持するための変数をメンバに追加します。
初めに、OpenCVの型や関数を使えるようにするためにincludeの追加を行います。
#include <opencv2/opencv.hpp>
using namespace cv;
続いて、ダイアログクラスのメンバ変数としてMat型の変数を追加します。(ここではmatという変数名で定義しています)
class CMfcOpenCVDlg : public CDialogEx
{
~~中略~~
protected:
Mat mat;
};
以降で、追加したmatに対して画像の読み込みを行い、描画処理にてmatの表示を行います。
OpenCVを使用して画像の読み込み
今回は簡単化のため初期化処理内で固定の画像を読み込むようにします。
ダイアログクラスのOnInitDialog内でimread関数で画像を読み込みます。
BOOL CMfcOpenCVDlg::OnInitDialog()
{
~~中略~~
mat = ::imread("画像のパス");
}
画像の読み込みに成功した場合matに画像のデータが格納されます。
画像の読み込みに失敗したか判定を行う場合にはempty関数でデータが空か判定を行います。
// 画像の読み込みに失敗した場合
if (mat.empty())
{
// 失敗した場合の処理
}
MFCでOpenCVのMatデータを表示
続いて読み込んだデータをMFCのダイアログ上に表示します。
Matデータを描画するためのRender関数を作成し、ダイアログクラスのOnPaint関数から呼び出すようにします。
void CMfcOpenCVDlg::Render(CDC* pDC, const CRect& rc, Mat* src)
{
// 入力データをクローン
Mat img = src->clone();
// 画像サイズを4の倍数に変更
Size size(img.cols & ~0x03, img.rows & ~0x03);
::resize(img, img, size);
// 上下反転
::flip(img, img, 0);
struct MYBITMAPINFO
{
BITMAPINFOHEADER bmiHeader = {};
RGBQUAD bmiColors[256] = { 0 };
MYBITMAPINFO()
{
for (int i = 0; i < 256; i++)
{
bmiColors[i].rgbRed = bmiColors[i].rgbGreen = bmiColors[i].rgbBlue = (BYTE)i;
bmiColors[i].rgbReserved = 0;
}
}
};
MYBITMAPINFO info = {};
LPBITMAPINFO pInfo = reinterpret_cast<LPBITMAPINFO>(&info);
pInfo->bmiHeader.biBitCount = (img.channels() == 1 ? 8 : 24);
pInfo->bmiHeader.biWidth = size.width;
pInfo->bmiHeader.biHeight = size.height;
pInfo->bmiHeader.biPlanes = 1;
pInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pInfo->bmiHeader.biCompression = BI_RGB;
// 描画
::StretchDIBits(pDC->GetSafeHdc(),
0, 0, size.width, size.height,
0, 0, size.width, size.height,
img.data, pInfo, DIB_RGB_COLORS, SRCCOPY);
}
void CMfcOpenCVDlg::OnPaint()
{
~~中略~~
// 生成後
if (this->GetSafeHwnd())
{
// デバイスコンテキスト
CDC* pDC = this->GetDC();
// 矩形
CRect rc;
// 矩形を取得
this->GetClientRect(rc);
// 描画
this->Render(pDC, rc, &this->mat);
// デバイスコンテキスト解放
this->ReleaseDC(pDC);
}
}
OnPaint関数ではデバイスコンテキストと矩形を取得してそのままRender関数に渡します。
Render関数では、「画像サイズが4の倍数以外の場合表示がずれてしまうため4の倍数にリサイズ」→「データをそのまま描画すると上下反転されてしまうのでflip関数で上下反転」→「描画」と処理を行っていきます。
描画前のMYBITMAPINFOの処理は描画する画像データの情報を格納しており、8bit画像(グレースケール画像)の場合にはbmiColorsが設定されていないと正常に表示されないためBITMAPINFOHEADERを拡張しています。
任意の画像を指定したうえで実行をすると、以下のようにダイアログに画像が表示されます。
なお、上記の処理では画像を実際のサイズで表示しているためダイアログサイズより大きい画像を指定した場合にはダイアログサイズの領域までしか見えない状態になります。
OpenCVを使用して簡単な画像処理(グレイスケール化、二値化)
ここまででMFCでOpenCVを使用する方法を解説しましたが、ここからは簡単な画像処理ととして「グレイスケール化」「二値化」を行う方法について紹介します。
「グレイスケール化」「二値化」は多くの画像処理の前処理として用いられるので、簡単ではありますが覚えておいたほうが良いです。
OpenCVを使用してグレイスケール化
OpenCVでグレイスケール化を行うには「cvtColor」関数を使用します。
第一引数に「入力画像」、第二引数に「出力画像」、第三引数に「COLOR_BGR2GRAY」を指定します。(ここでは入力画像と出力画像を同じ変数にしています)
::cvtColor(mat, mat, COLOR_BGR2GRAY);
ダイアログクラスのOnInitDialog関数での画像入力処理の直後に上記処理を実行した場合、以下のように入力画像がグレイスケール化された画像が表示されるようになります。
OpenCVを使用して二値化
OpenCVで二値化を行うには「threshold」関数を使用します。
第一引数に「入力画像」、第二引数に「出力画像」、第三引数に「0」、第四引数に「255」、第五引数に「THRES_OTSU」を指定します。(ここでは入力画像と出力画像を同じ変数にしています)
::threshold(mat, mat, 0, 255, THRESH_OTSU);
ダイアログクラスのOnInitDialog関数でのグレイスケール化の直後に上記処理を実行した場合、以下のようにグレイスケール画像が二値化された画像が表示されるようになります。
二値化については少し注意が必要で、8bit画像(グレイスケール画像)を入力としないと例外が発生します。
終わりに
MFCでOpenCVを使用して画像処理を行う方法について解説しました。
画像処理を行う方法はいくつかありますが、便利な関数を用意されていて簡単なOpenCVで行うのが良いですね。(描画処理は少し面倒くさいけど)
この記事について誤っている点・不明な点などありましたらコメントまでお願いします。
コメント