やりたいこと
- Windows11 で表示した Window (Window1)のうち、(最小化ではなく)背面に隠れている画面のキャプチャを取得(画像1)
- UWSC の ChkImgX で、取得した画像1とターゲット画像(画像2)を比較し、画像2が画像1の中にあればその位置(位置1)を取得
- UWSC で、取得した位置1をもとに、Windows1 の該当する箇所をクリック
といった、自動化をやりたい。
ただ、Window1 が背面にある場合、ChkImgX で画面のキャプチャが取得できない(真っ黒画面が取得される)ことが分かった(これは、Window1側の設定によるものなのかは不明。)
そこで、Powershell Core 7.3 + C++ (14?)でやってみたときのメモです。
やったこと
- 以下を実施するdll(独自dll ) を、C++ で作成
- Win32API の PrintWindow を呼び出して、(最小化ではなく)背面に隠れている画面のキャプチャを取得(画像1)
- OpenCV 4.7 で、取得した画像1とターゲット画像(画像2)を比較し、画像2が画像1の中にあればその位置(位置1)を取得
- Powershell から、独自dll を呼び出して、位置1を取得
- (今回対象の Window が Windows Subsystem for Android のアプリだったので)Powershell から、位置1をタップするコマンド(adb.exe ***)を実行
初めてのC++ステップ
C++ について、初めて触れる → 独自dll 作成までの各ステップ
- Vissual Studio 2022 をインストールする
- マイクロソフトのLearning を参考に、独自dll とそれを呼び出すコンソールアプリ(アプリ1)を作成する
- このタイミングで、独自dll のRelease バージョンビルド後、Powershell から呼び出せることを確認する(でないと、どこが悪いかわからなくなるので)
- デバッグ用として、独自dll からの結果をアプリ1で表示する方法、またはVisual Studio 2022 のデバッガを起動して変数の値を表示する方法を確認する
- PrintWindow 関数を呼び出すときに必要なお作法(GetDC でデバイスコンテキスト取得したら、処理完了後、プログラム上で責任もって解放する(ReleaseDC)等)を調べる
- PrintWindow 関数を呼び出して、画像1が取得できることを確認する(クリップボードに保存が簡単だった)
- 画像1を、OpenCV で使用するMat 型に変換し、変換できていることを確認する(画像ファイルに保存が簡単だった)
- OpenCV の matchTemplate 関数を使用して位置1を取得し、取得できていることを確認する(OpenCV のサンプルコードが参考になった)
- 独自dll のRelease バージョンビルド後、Powershell から呼び出せることを確認する
実際のコード
ChkImgCpp.h
#pragma once #define CHKIMGCPP_API __declspec(dllexport) #include <windows.h> #include <string> struct CHKIMGCPP_API ReturnPoint { int doneMatching; // 0: not done template matching for some errors, 1: done template matching int maxValInt; // maxVal of temaplate matching scaled 0 to 100 int x; int y; }; extern "C" CHKIMGCPP_API ReturnPoint __stdcall point_template_matched_bitmap( const wchar_t* class_name, const wchar_t* window_title, const wchar_t* bitmap_file_path );
ChkImgCpp.cpp
#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier #include <windows.h> #include <dwmapi.h> #include <string> #include "ChkImgCpp.h" #include <opencv2/opencv.hpp> /** * WindowDCを解放するためのdeleter * https://setuna-kanata.hatenadiary.org/entry/20100713/1279039787 */ struct dc_deleter_release { void operator()(HDC hdc)const { ReleaseDC(hwnd, hdc); } dc_deleter_release(HWND hwnd) : hwnd(hwnd) {} HWND hwnd; }; struct dc_deleter_delete { void operator()(HDC hdc)const { DeleteDC(hdc); } dc_deleter_delete() {} }; struct hbitmap_deleter_delete { void operator()(HBITMAP hBitmap)const { DeleteObject(hBitmap); } hbitmap_deleter_delete() {} }; // https://gist.github.com/siwa32/98704 std::string convString(const std::wstring& input) { size_t i; char* buffer = new char[input.size() * MB_CUR_MAX + 1]; wcstombs_s(&i, buffer, input.size() * MB_CUR_MAX + 1, input.c_str(), _TRUNCATE); std::string result = buffer; delete[] buffer; return result; } ReturnPoint __stdcall point_template_matched_bitmap( const wchar_t* class_name, const wchar_t* window_title, const wchar_t* bitmap_file_path ) { ReturnPoint r{}; r.doneMatching = 0; // 0: not done template matching for some errors, 1: done template matching HWND hwnd = FindWindow(class_name, window_title); if (hwnd == NULL) { return r; } RECT rect{}; if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)) != S_OK) { return r; } // ReleaseDC 等を呼ばなくてすむように、std::unique_ptr を利用 // https://setuna-kanata.hatenadiary.org/entry/20100713/1279039787 std::unique_ptr<HDC__, dc_deleter_release> hdc(::GetDC(hwnd), dc_deleter_release(hwnd)); // Bitmap to cv::Mat // http://ckron.net/?p=205 BITMAPINFO bmpInfo{}; bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = rect.right - rect.left; bmpInfo.bmiHeader.biHeight = rect.bottom - rect.top; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = 32; bmpInfo.bmiHeader.biCompression = BI_RGB; LPDWORD lpPixel{}; std::unique_ptr<HBITMAP__, hbitmap_deleter_delete> hBitMap(CreateDIBSection(hdc.get(), &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0), hbitmap_deleter_delete()); if (hBitMap.get() != NULL) { // メモリDC std::unique_ptr<HDC__, dc_deleter_delete> hdcMem(CreateCompatibleDC(hdc.get()), dc_deleter_delete()); SelectObject(hdcMem.get(), hBitMap.get()); PrintWindow(hwnd, hdcMem.get(), PW_RENDERFULLCONTENT); } BITMAP bmp{}; GetObject(hBitMap.get(), sizeof(BITMAP), &bmp); /* // for debugging bitmap data // http://www13.plala.or.jp/kymats/study/MULTIMEDIA/convert_dib_ddb.html if (OpenClipboard(hwnd)) { HBITMAP hb; hb = CreateDIBitmap(hdc.get(), &bmpInfo.bmiHeader, CBM_INIT, lpPixel, &bmpInfo, DIB_RGB_COLORS); EmptyClipboard(); SetClipboardData(CF_BITMAP, hb); CloseClipboard(); } return r; */ // PrintWindow で取得した画像は32bitカラーイメージだったので決め打ち cv::Mat revMat(bmp.bmHeight, bmp.bmWidth, CV_8UC4, (void*)bmp.bmBits); cv::Mat mat{}; cv::flip(revMat, mat, 0); if (!mat.data) { return r; } // 比較ファイル cv::Mat target = cv::imread(convString(bitmap_file_path), 1); if (!target.data) { return r; } // template matching cv::Mat matBGR{}; cv::cvtColor(mat, matBGR, cv::COLOR_BGRA2BGR); int result_cols = matBGR.cols - target.cols + 1; int result_rows = matBGR.rows - target.rows + 1; cv::Mat result{}; result.create(result_rows, result_cols, CV_8UC3); cv::matchTemplate(matBGR, target, result, cv::TM_CCOEFF_NORMED); double minVal{}; double maxVal{}; cv::Point minLoc; cv::Point maxLoc; cv::Point matchLoc; minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat()); matchLoc = maxLoc; r.doneMatching = 1; // 0: not done template matching for some errors, 1: done template matching r.maxValInt = maxVal * 100; r.x = matchLoc.x + target.cols / 2; r.y = matchLoc.y + target.rows / 2; /* //for debugging matched point cv::Mat img_display{}; matBGR.copyTo(img_display); rectangle(img_display, matchLoc, cv::Point(matchLoc.x + target.cols, matchLoc.y + target.rows), cv::Scalar::all(0), 2, 8, 0); imshow("debugging matched point", img_display); cv::waitKey(0); //*/ return r; }
Powershell で呼び出す
# [ChkImgCpp]::point_template_matched_bitmap returns ReturnPoint for window (also class name) and bitmap_file_path. # Return Point has # - int doneMatching; // 0: not done template matching for some errors, 1: done template matching # - int maxValInt; // maxVal of temaplate matching scaled 0 to 10000 # - int x; // center x position of maxVal location # - int y; // center y position of maxVal location $dllFilePathEscaped = (Join-Path -Path ${PSScriptRoot} -ChildPath "ChkImgCpp.dll").Replace('\', '\\') $source = @" using System.Runtime.InteropServices; public struct ReturnPoint { public int doneMatching; // 0: not done template matching for some errors, 1: done template matching public int maxValInt; // maxVal of temaplate matching scaled 0 to 100 public int x; public int y; } public static class ChkImgCpp { [DllImport("${dllFilePathEscaped}", CharSet=CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] public static extern ReturnPoint point_template_matched_bitmap( System.String class_name, System.String window_title, System.String bitmap_file_path ); } "@ Add-Type -TypeDefinition $source; $local:class_name_of_window = "<ウィンドウの class name: UWSC で取得が簡単>" $local:title_of_window = "<ウィンドウの タイトル>" $local:bmp_relative_path_non_escaped = "<比較するbmp file (24bit) の相対パス>" $local:bmpFilePathEscaped = (Join-Path -Path ${PSScriptRoot} -ChildPath $bmp_relative_path_non_escaped).Replace('\', '\\') $returnPoint = [ChkImgCpp]::point_template_matched_bitmap($class_name_of_window, $title_of_window, $bmpFilePathEscaped) Write-Output $returnPoint
つまづいた点
- dll、lib、exe の違いって何?
- ヘッダファイル、ソースファイルって何?
- dll ってどこにある?どこにおく?
- ビルドすると何が出力される?dll だけ?lib、exe も?
- C++ のstring って何?どうやって関数に渡すの?ポインタの概念はなんとなくわかるけど・・
- GetDC のあとは必ずReleaseDC しろって、何かいい方法はないの?
- Win32API のBITMAP 型って何?save(bmp, filePath) で保存じゃないの?