OpenCVで複数のテンプレートマッチングする方法

こんにちは。
くろんです!

今回はOpenCVを使って画像中から複数のテンプレートマッチングを行います。

今回はサンプル画像はこちら

かえるのうたの歌詞がのった画像を用意しました!!

こちらのサンプルから「ワ」を切り出した下のようなマッチング用画像を作りました、
サンプル画像から「ワ」をすべて抽出したいと思います。


#include <vector>
#include "opencv2/opencv.hpp"

int main()
{
	cv::Mat src = cv::imread("カエルの歌.png");
	cv::Mat key = cv::imread("カエルの歌KEY.png");

	cv::Mat res;
	cv::matchTemplate(src, key, res, cv::TM_CCOEFF_NORMED);

	std::vector<cv::Point> locs;

	for (int y = 0; y < res.rows; y++) {
		for (int x = 0; x < res.cols; x++) {
			uchar* uctemp = &amp;res.data[y * res.cols * 4 + x * 4];
			float* ftemp = (float*)(uctemp);
			if (*ftemp > 0.95) {
				bool isExist = false;
				for (auto ml : locs) {
					// 今までに見つかった場所からある程度離れないと検出扱いにしない.
					int d = pow(y - ml.y, 2) + pow(x - ml.x, 2);
					if (d < 10) {
						isExist = true;
					}
				}

				if (!isExist) {
					locs.push_back(cv::Point(x, y));
				}
			}
		}
	}

	for(auto l : locs) {
		cv::rectangle(src, cv::Rect(l.x, l.y, key.cols, key.rows),
										cv::Scalar(0, 0, 255));
	}

	cv::imshow("result", src);

	cv::waitKey();

	return 0;
}

結果はこんな感じになります。

しっかり「ワ」の場所だけ抽出できているかと思います。

以上

では!

OpenCVでスクリーンショット保存

こんにちは!
くろんです。

今回はOpenCVを使って画面のスクリーンショット画像を保存してみたいと思います。

まぁ、そこまで大変でもないので適当にコードとか載せていきます。

やること

1. WINAPIにてスクリーン情報取得
2. スクリーン情報より画面のピクセル情報を取る
3. OpenCVの画像フォーマットにピクセル情報を入れ込む

準備

- OpenCV準備(https://opencv.org/releases/からDL)
- 開発環境準備 -> VisualStudio2019使用
- 新規プロジェクト作成

OpenCVは今回、4.2.0を使用しました。

Windowsなら ↑ の赤枠からexe拾ってきて

展開フォルダ内のbuildから

bin\*.dll
include
x64\(VCバージョン)\*.lib

を自分のプロジェクトフォルダに持ってくると楽ですよ!

OpenCVを新規プロジェクトで使えるようにするためにはリンカーの設定を行います。

1. 新規プロジェクトを開き、「プロジェクト」タブ→「プロパティ」選択
2. 構成を「Release」に設定(Debugは同じく設定)
3. 「リンカー」→「全般」より「追加のライブラリディレクトリ」を選択
4. x64\(VCバージョン)\*.libより持ってきた*.libファイルがあるフォルダを選択
※opencv_world420.libとopencv_world420d.libというファイル名にdがついているものとそうでいないものがあると思うがこれらはRelease用かDebug用かで変わっている。dがついているものがDebug用

5. 「リンカー」→「入力」より「追加の依存ファイル」選択
6. 4.にて指定したフォルダに入っている*.libファイルをすべて依存ファイルとして指定
ex. Release用として設定していたら「 opencv_world420.lib」を追加

ついでにプロパティページにて「C/C++」内の「追加のインクルードディレクトリ」に includeフォルダを指定しておくと後で楽になります。

最後に動的ライブラリ(*.dll)を実行ファイルと同じ場所に置きます。
ビルドすると*.exeができるかと思うのでbin\*.dllをコピーします。

準備はこれでOKなはずです。

まとめ

- リンカーを設定
  - 追加のライブラリディレクトリ
  - 追加の依存ファイル
- 動的ライブラリ(*.dll)を実行ファイル(*.exe)と同じ場所に置く

コード書く

↓ 全体

HWND hwnd = GetDesktopWindow();
RECT rect;

GetWindowRect(hwnd, &rect);
unsigned int width, height;
width = rect.right;
height = rect.bottom;

BITMAPINFO bmpInfo;

bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = width;
bmpInfo.bmiHeader.biHeight = height;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;

LPDWORD lpPixel;
HBITMAP hBitmap;
HDC hMemDC;
HDC hdc = GetDC(hwnd);
hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);

hMemDC = CreateCompatibleDC(hdc);
SelectObject(hMemDC, hBitmap);

BitBlt(hMemDC, 0, 0, width, height, hdc, 0, 0, SRCCOPY);

cv::Mat bMat = cv::Mat(height, width, CV_8UC3);
bMat.data = (uchar*)lpPixel;

cv::Mat flipImage;
cv::flip(bMat, flipImage, 0);

cv::imwrite("test.jpg", flipImage);

まず

HWND hwnd = GetDesktopWindow();
RECT rect;

GetWindowRect(hwnd, &rect);
unsigned int width, height;
width = rect.right;
height = rect.bottom;

ここらへんでスクリーンの幅・高さとディスプレイのハンドルを取得します。

次に

BITMAPINFO bmpInfo;

bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = width;
bmpInfo.bmiHeader.biHeight = height;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;

ここでBitmap形式で取得するときに必要な情報を設定します。(今回はOpenCVのMatフォーマットになるのでBitmap関係ないですがスクリーンの画素情報を取得するための儀式として書きます)

そして

LPDWORD lpPixel;
HBITMAP hBitmap;
HDC hMemDC;
HDC hdc = GetDC(hwnd);
hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);

hMemDC = CreateCompatibleDC(hdc);
SelectObject(hMemDC, hBitmap);

Bitmap形式で画素を取得する準備を行います。

Bitmap形式でスクリーンの画素情報を取得するときにはBitBltを使用します。

BitBlt(hMemDC, 0, 0, width, height, hdc, 0, 0, SRCCOPY);

最後にOpenCVのMatフォーマットに入れ込みます

cv::Mat bMat = cv::Mat(height, width, CV_8UC3);
bMat.data = (uchar*)lpPixel;

cv::Mat flipImage;
cv::flip(bMat, flipImage, 0);

cv::imwrite("test.jpg", flipImage);

biBitCountで24(8bit3チャンネル)を指定していたのでMatフォーマットは CV_8UC3タイプを選択。

bMat.data はMatファイルの画素情報に当たる部分でそこにBitmap形式でスクリーン画素情報が入っている lpPixelの情報を入れています。

cv::flipで画像を上下反転しているのですがこれはデータの並びが逆なのでそのままだと反転して画像ができてしまうためです。

cv::imwriteは画像の保存をしてくれるOpenCVの関数です。

こんな感じにするとスクリーンショットをOpenCVのMatフォーマットにできます!!

※あっ、この方法だとディスプレイ設定で拡大表示とかして100%以外だとスクリーンの一部しか取得できない状態になるので気を付けてください。

↑ を回避するためにはもう少しごにょごにょやってあげる必要があります。