python与C++进行桌面截图和模板匹配对比——以梦幻西游为例

发布时间 2024-01-08 01:50:56作者: 爆米LiuChen

项目简介

项目调用opencv配合dxgi完成对桌面和窗口的截图并进行不规则形状的模板匹配,并以梦幻西游为例,用来搜查鼠标位置(其实就是为了能写点游戏脚本玩玩)。

但最终目的其实是分别用纯C++、python搭配C++打包的dll、纯python 对比3种渠道的速度,对比结果如下

程序/数据集下载

点击进入下载地址

本文章只发布于博客园爆米算法CSDN,被抄袭后可能排版错乱或下载失效,作者:爆米LiuChen

C++部分

FindWindow.h,窗口定位头文件

#pragma once
#include <dwmapi.h>
#include <windows.h> 
#include <vector> 
#include <string>  
#include <iostream>

struct WindowData {
    HWND handle;//窗口句柄
    char title[256];//窗口标题
};

struct WindowRect
{
	int x;
	int y;
	int width;
	int height;
};

BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam);
HWND getWindowHWND(std::string titleSection);
RECT getWindowLoc(HWND hwnd);
WindowRect getWindowRect(std::string titleSection);
extern WindowRect windowRect;
extern std::vector<WindowData> windowDatas;

FindWindow.cpp,核心函数getWindowRect,输入桌面窗口标题包含的字符串,返回窗口的位置坐标

#include "FindWindow.h"

static std::vector<WindowData> windowDatas;

WindowRect windowRect{ 0,0,0,0 };

// 声明回调函数  
BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam) {
    // 通过IsWindow函数检查窗口是否有效  
    if (IsWindow(hwnd)) {
        // 通过IsWindowEnabled函数检查窗口是否启用  
        if (IsWindowEnabled(hwnd)) {
            // 通过IsWindowVisible函数检查窗口是否可见  
            if (IsWindowVisible(hwnd)) {
                // 获取窗口的文本,并打印  
                char windowText[256];
                GetWindowTextA(hwnd, windowText, sizeof(windowText));
                WindowData windowData;
                windowData.handle = hwnd;
                memcpy(windowData.title, windowText, 256);
                windowDatas.push_back(windowData);
            }
        }
    }
    // 继续枚举其他窗口  
    return TRUE;
}

//返回包含titleSection的桌面窗口句柄
HWND getWindowHWND(std::string titleSection)
{
    HWND handle = NULL;
    //每次都要清空容器
    windowDatas.clear();
    // 使用EnumWindows函数枚举所有窗口,并传递给回调函数处理  
    EnumWindows(WindowEnumerationCallback, NULL);
    //一个个找包含指定字符串的
    for (auto it = windowDatas.begin(); it != windowDatas.end(); it++)
    {
        char title[256];
        memcpy(title, it->title, 256);
        std::string windowTitle(title);
        if (windowTitle.find(titleSection) != std::string::npos)
        {
            handle = it->handle;
        }
    }
    return handle;
}


//根据窗口句柄和桌面缩放获得窗口尺寸和位置
RECT getWindowLoc(HWND hwnd)
{
    RECT frame;
    DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
    //std::cout << "窗口位置:(" << frame.left << ", " << frame.top << ")" << std::endl;
    //std::cout << "窗口大小:(" << frame.right-frame.left << ", " << frame.bottom-frame.top << ")" << std::endl;
    return frame;
}

//根据窗口名获得窗口位置尺寸
WindowRect getWindowRect(std::string titleSection)
{
    windowRect = {0,0,0,0};
    HWND hwnd = getWindowHWND(titleSection);
    if (hwnd == NULL) {
        printf("cant find window contains %s\n", titleSection.c_str());
        return windowRect;
    }
    //如果窗口小化 就将其展示 
    if (IsIconic(hwnd)) {
        ShowWindow(hwnd, SW_RESTORE);
    }
    SetForegroundWindow(hwnd); // 将窗口置顶  
    RECT rect = getWindowLoc(hwnd); // 窗口位置
    windowRect.x = rect.left;
    windowRect.y = rect.top;
    windowRect.width = rect.right - rect.left;
    windowRect.height = rect.bottom - rect.top;
    return windowRect;
}

DxgiCapture.h 截图头文件

#pragma once
#include <d3d11.h>
#include <dxgi1_2.h>
#include <stdio.h>
#include <opencv2/opencv.hpp>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#include <time.h>
#include "FindWindow.h"

struct MatInfo
{
	int windowX;//窗口在桌面的x坐标
	int windowY;//窗口在桌面的Y坐标
	int windowWidth;//窗口宽
	int windowHeight;//窗口高
	uchar* pMatUchar;
};

extern "C" __declspec(dllexport) void initDxgi();
extern "C" __declspec(dllexport) cv::Mat getDesktopMat();
extern "C" __declspec(dllexport) cv::Mat getWindowMat(std::string titleSection = "desktop");
extern "C" __declspec(dllexport) MatInfo getWindowMatInfo(const char* pTitleSection="desktop");

DxgiCapture.cpp 核心函数为getWindowMatInfo,输入窗口标题包含的字符串,返回窗口坐标和opencv的矩阵uchar*

#include "DxgiCapture.h"
static cv::Mat regionMat;
static cv::Mat regionMatClone;
static cv::Mat emptyMat;
static cv::Mat desktopMat;
static DXGI_OUTDUPL_FRAME_INFO frameInfo;
static HRESULT hr;
static D3D_DRIVER_TYPE DriverTypes[3];
static UINT NumDriverTypes;
static D3D_FEATURE_LEVEL FeatureLevels[4];
static UINT NumFeatureLevels;
static D3D_FEATURE_LEVEL FeatureLevel;
static ID3D11Device* _pDX11Dev;
static ID3D11DeviceContext* _pDX11DevCtx;
static IDXGIDevice* _pDXGIDev;
static IDXGIAdapter* _pDXGIAdapter;
static IDXGIOutput* _pDXGIOutput;
static DXGI_OUTPUT_DESC DesktopDesc;
static IDXGIOutput1* _pDXGIOutput1;
static IDXGIOutputDuplication* _pDXGIOutputDup;
static DXGI_MAPPED_RECT MappedSurface;
static IDXGIResource* desktopResource;
static ID3D11Texture2D* _pDX11Texture;
static ID3D11Texture2D* _pCopyBuffer;
static int desktopWidth, desktopHeight;
static byte* pDesktopByte;


/* 获取屏幕缩放值 */
double getZoom()
{
	// 获取窗口当前显示的监视器
	HWND hWnd = GetDesktopWindow();
	HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

	// 获取监视器逻辑宽度
	MONITORINFOEX monitorInfo;
	monitorInfo.cbSize = sizeof(monitorInfo);
	GetMonitorInfo(hMonitor, &monitorInfo);
	int cxLogical = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);

	// 获取监视器物理宽度
	DEVMODE dm;
	dm.dmSize = sizeof(dm);
	dm.dmDriverExtra = 0;
	EnumDisplaySettings(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
	int cxPhysical = dm.dmPelsWidth;

	return cxPhysical * 1.0 / cxLogical;
}

//获得桌面二进制数据指针
cv::Mat getDesktopMat()
{
	desktopResource = nullptr;

	hr = _pDXGIOutputDup->AcquireNextFrame(10, &frameInfo, &desktopResource);
	if (FAILED(hr))
	{	//没有可用的刷新帧 返回上一帧
		if (hr == DXGI_ERROR_WAIT_TIMEOUT)
		{
			if (desktopResource) {
				desktopResource->Release();
				desktopResource = nullptr;
				
			}
			hr = _pDXGIOutputDup->ReleaseFrame();
			return desktopMat;
		}
		else
		{
			return emptyMat;
		}
	}

	_pDX11Texture = nullptr;
	// query next frame staging buffer 查询下一帧暂存缓冲区
	if (desktopResource == nullptr) {
		return emptyMat;
	}
	hr = desktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&_pDX11Texture));
	desktopResource->Release();
	desktopResource = nullptr;
	if (FAILED(hr)) {
		return emptyMat;
	}

	_pCopyBuffer = nullptr;

	D3D11_TEXTURE2D_DESC desc;
	// copy old description 复制旧描述
	if (_pDX11Texture)
	{
		_pDX11Texture->GetDesc(&desc);
	}
	else if (_pCopyBuffer)
	{
		_pCopyBuffer->GetDesc(&desc);
	}
	else
	{
		return emptyMat;
	}

	// create a new staging buffer for fill frame image 为填充帧图像创建一个新的暂存缓冲区
	if (_pCopyBuffer == nullptr) {
		D3D11_TEXTURE2D_DESC CopyBufferDesc;
		CopyBufferDesc.Width = desc.Width;
		CopyBufferDesc.Height = desc.Height;
		CopyBufferDesc.MipLevels = 1;
		CopyBufferDesc.ArraySize = 1;
		CopyBufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
		CopyBufferDesc.SampleDesc.Count = 1;
		CopyBufferDesc.SampleDesc.Quality = 0;
		CopyBufferDesc.Usage = D3D11_USAGE_STAGING;
		CopyBufferDesc.BindFlags = 0;
		CopyBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
		CopyBufferDesc.MiscFlags = 0;

		hr = _pDX11Dev->CreateTexture2D(&CopyBufferDesc, nullptr, &_pCopyBuffer);
		if (FAILED(hr)) {
			return emptyMat;
		}
	}

	if (_pDX11Texture)
	{
		// copy next staging buffer to new staging buffer 将下一个暂存缓冲区复制到新的暂存缓冲区
		_pDX11DevCtx->CopyResource(_pCopyBuffer, _pDX11Texture);
	}

	IDXGISurface* CopySurface = nullptr;
	// create staging buffer for map bits 为映射位创建暂存缓冲区
	hr = _pCopyBuffer->QueryInterface(__uuidof(IDXGISurface), (void**)&CopySurface);
	if (FAILED(hr)) {
		return emptyMat;
	}
	// copy bits to user space 将位复制到用户空间
	hr = CopySurface->Map(&MappedSurface, DXGI_MAP_READ);
	CopySurface->Unmap();
	hr = CopySurface->Release();
	CopySurface = nullptr;

	if (_pDXGIOutputDup)
	{
		hr = _pDXGIOutputDup->ReleaseFrame();
	}
	cv::Mat mat  = cv::Mat(desktopHeight, desktopWidth, CV_8UC4, MappedSurface.pBits);
	cv::cvtColor(mat, desktopMat, cv::COLOR_BGRA2BGR);// 转换为3通道图片
	_pCopyBuffer->Release();
	_pDX11Texture->Release();
	_pDX11DevCtx->Release();
	return desktopMat;
}

//根据窗口标题是否包含该字符串,获得窗口截图
cv::Mat getWindowMat(std::string titleSection)
{
	getDesktopMat();
	WindowRect rect;
	if (titleSection != "desktop") {
		rect = getWindowRect(titleSection);
		regionMatClone = desktopMat(cv::Range(rect.y, rect.y + rect.height), cv::Range(rect.x, rect.x + rect.width));
		regionMat = regionMatClone.clone();
		return regionMat;
	}
	windowRect = { 0,0,desktopWidth,desktopHeight };//桌面截图 则它的窗口信息就是这样
	regionMat = desktopMat;
	return desktopMat;	
}

void initDxgi()
{
	double zoom = getZoom();
	desktopWidth = GetSystemMetrics(SM_CXSCREEN) * zoom;
	desktopHeight = GetSystemMetrics(SM_CYSCREEN) * zoom;
	_pDXGIOutputDup = nullptr;
	_pDX11Dev = nullptr;
	D3D_DRIVER_TYPE DriverTypes[] = { D3D_DRIVER_TYPE_HARDWARE,D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_UNKNOWN };
	UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
	D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0,D3D_FEATURE_LEVEL_10_1,D3D_FEATURE_LEVEL_10_0,D3D_FEATURE_LEVEL_9_1 };
	UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);

	_pDX11DevCtx = nullptr;
	// Create D3D device 创建D3D设备
	for (UINT index = 0; index < NumDriverTypes; index++)
	{
		hr = D3D11CreateDevice(nullptr,
			DriverTypes[index],
			nullptr, 0,
			NULL,
			0,
			D3D11_SDK_VERSION,
			&_pDX11Dev,
			&FeatureLevel,
			&_pDX11DevCtx);
		if (SUCCEEDED(hr)) {
			break;
		}
	}

	_pDXGIOutput = nullptr;

	_pDXGIOutput1 = nullptr;

	_pDXGIDev = nullptr;
	// Get DXGI device 获取 DXGI 设备
	hr = _pDX11Dev->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&_pDXGIDev));
	if (FAILED(hr)) {
		return;
	}
	IDXGIAdapter* _pDXGIAdapter = nullptr;
	// Get DXGI adapter 获取 DXGI 适配器
	hr = _pDXGIDev->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&_pDXGIAdapter));
	if (FAILED(hr)) {
		return;
	}
	UINT i = 0;
	// Get output 获取输出
	hr = _pDXGIAdapter->EnumOutputs(i, &_pDXGIOutput);
	if (FAILED(hr)) {
		return;
	}
	// Get output description struct 获取输出描述结构
	_pDXGIOutput->GetDesc(&DesktopDesc);
	// QI for Output1 请求接口给Output1
	hr = _pDXGIOutput->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&_pDXGIOutput1));
	if (FAILED(hr)) {
		return;
	}

	// Create desktop duplication 创建桌面副本
	hr = _pDXGIOutput1->DuplicateOutput(_pDX11Dev, &_pDXGIOutputDup);
	if (FAILED(hr)) {
		return;
	}
	getWindowMat();
	getWindowMat();
}

MatInfo getWindowMatInfo(const char* pTitleSection)
{
	std::string titleSection;
	titleSection.insert(0, pTitleSection);
	MatInfo matInfo{0,0,0,0,nullptr};
	getWindowMat(titleSection);
	if (regionMat.empty()) {
		return matInfo;
	}
	matInfo.windowHeight = regionMat.rows;
	matInfo.windowWidth = regionMat.cols;
	matInfo.windowX = windowRect.x;
	matInfo.windowY = windowRect.y;
	matInfo.pMatUchar = static_cast<uchar*>(regionMat.data);
	return matInfo;
	
}

OpenDetect.h 模板匹配头文件

#pragma once
#include "DxgiCapture.h"
#include <unordered_map>

struct MatchResult
{
	int xInWindow;//模板匹配在窗口中的x坐标
	int yInWindow;//模板匹配在窗口中的Y坐标
	int xInDesktop;//模板匹配在桌面中的X坐标
	int yInDesktop;//模板匹配在桌面中的Y坐标
	int windowX;//窗口在桌面的x坐标
	int windowY;//窗口在桌面的Y坐标
	int windowWidth;//窗口宽
	int windowHeight;//窗口高
	int templateWidth;//模板宽
	int templateHeight;//模板高
	float diff;//匹配指标 差异度 越小越好
	bool success;//匹配是否成功
};

struct TemplateAndMask
{
	cv::Mat templateMat;//模板矩阵
	cv::Mat maskMat;//掩膜矩阵
};

extern "C" __declspec(dllexport) void addTemplateMask(const char* pKey, const char* pTemplatePath, const char* pMaskPath = "");
extern "C" __declspec(dllexport) MatchResult matchInWindow(const char* pKey, const char* pTitleSection = "desktop");

OpenDetect.cpp 模板匹配,核心函数matchInWindow,输入模板名和窗口标题返回模板匹配后的位置,这里要配合掩膜才能匹配不规则模板

#include "OpenDetect.h"

static MatchResult matchResult;
static std::unordered_map<std::string, TemplateAndMask> templateMatMap;

//添加模板和掩膜到字典 入参 关键词 模板路径 掩膜路径
void addTemplateMask(const char* pKey, const char* pTemplatePath, const char* pMaskPath)
{
	std::string key, templatePath, maskPath;
	key.insert(0, pKey);
	templatePath.insert(0, pTemplatePath);
	maskPath.insert(0, pMaskPath);
	cv::Mat maskMat;
	cv::Mat templateMat = cv::imread(templatePath);
	if (maskPath != "") {
		maskMat = cv::imread(maskPath, -1);

		// 根据透明通道修改像素值 透明通道值为0 则像素值置0
		for (int y = 0; y < maskMat.rows; ++y) {
			for (int x = 0; x < maskMat.cols; ++x) {
				// 获取像素值,注意通道顺序是BGR(A)而不是RGBA
				cv::Vec4b pixel = maskMat.at<cv::Vec4b>(y, x);
				if (pixel[3] == 0) {
					maskMat.at<cv::Vec4b>(y, x) = cv::Vec4b(0, 0, 0, 0);
				}

			}
		}
		//已经根据透明通道做了置0,已经不需要透明通道 
		cv::cvtColor(maskMat, maskMat, cv::COLOR_BGRA2BGR);
		//转为灰度图
		cv::cvtColor(maskMat, maskMat, cv::COLOR_BGR2GRAY);
		//二值化
		cv::threshold(maskMat, maskMat, 1, 255, cv::THRESH_BINARY);
		//转换为3通道图片 因为每个通道有单独的遮罩
		std::vector<cv::Mat> channels = { maskMat, maskMat, maskMat };
		cv::merge(channels, maskMat);
	}
	if (maskMat.empty()) {
		printf("add %s template without mask\n", key.c_str());
	}
	else {
		printf("add %s template with mask\n", key.c_str());
	}
	TemplateAndMask tam = { templateMat,maskMat };
	templateMatMap[key] = tam;
}

//寻找窗口标题若为desktop则就是对桌面截图 根据掩膜和模板匹配结果
MatchResult matchInWindow(const char* pKey, const char* pTitleSection)
{
	std::string key, titleSection;
	key.insert(0, pKey);
	titleSection.insert(0, pTitleSection);
	TemplateAndMask tam = templateMatMap[key];
	cv::Mat templateMat = tam.templateMat;
	cv::Mat maskMat = tam.maskMat;
	cv::Mat windowMat = getWindowMat(titleSection);
	if (windowMat.empty()) {
		matchResult.success = FALSE;//没找到窗口
		return matchResult;
	}
	cv::Mat result;//模板匹配
	//cv::matchTemplate(windowMat, templateMat, result, cv::TM_SQDIFF_NORMED);//不用mask的对比
	cv::matchTemplate(windowMat, templateMat, result, cv::TM_SQDIFF_NORMED, maskMat);//mask的对比
	double minVal, maxVal;
	cv::Point minLoc, maxLoc;
	cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

	//匹配结果返回 取差异化最小值
	matchResult.xInWindow = minLoc.x;
	matchResult.yInWindow = minLoc.y;
	matchResult.diff = minVal;
	matchResult.templateWidth = templateMat.cols;
	matchResult.templateHeight = templateMat.rows;
	matchResult.xInDesktop = minLoc.x + windowRect.x;
	matchResult.yInDesktop = minLoc.y + windowRect.y;
	matchResult.windowX = windowRect.x;
	matchResult.windowY = windowRect.y;
	matchResult.windowWidth = windowRect.width;
	matchResult.windowHeight = windowRect.height;
	matchResult.success = TRUE;
	return matchResult;
}

main.cpp 调用上面的函数 进行测试

#include "OpenDetect.h"
#include <opencv2/opencv.hpp>
#include <time.h>

int main()
{
	clock_t start, finish;
	double durationS;
	cv::Mat mat;
	MatchResult result;
	initDxgi();
	printf("开始截屏测试\n");
	start = clock();
	for (int i=0;i<1000;i++)
	{
		//MatInfo matInfo = getWindowMatInfo();
		//cv::Mat mat(matInfo.windowHeight, matInfo.windowWidth, CV_8UC3, matInfo.pMatUchar);
		mat = getDesktopMat();
	}
	finish = clock();
	durationS = (double)(finish - start) / CLOCKS_PER_SEC;
	printf("1000次桌面截图并转为opencv平均%.1f毫秒/帧\n", durationS);

	printf("开始模板匹配测试\n");
	addTemplateMask("mouse1","1号鼠标模板.png","1号鼠标遮罩.png");
	addTemplateMask("mouse2", "2号鼠标模板.png", "");
	getWindowMatInfo("梦幻");
	start = clock();
	for (int i = 0; i < 10; i++) {
		result = matchInWindow("mouse1", "梦幻");
	}
	finish = clock();
	durationS = (double)(finish - start) / CLOCKS_PER_SEC;
	printf("10次桌面模板匹配平均%.0f毫秒/次\n", durationS/10*1000);
	mat = getWindowMat();
	cv::rectangle(mat, cv::Rect(result.xInDesktop, result.yInDesktop,result.templateWidth, result.templateHeight), cv::Scalar(0, 0, 255), 2);
	cv::imshow("", mat);
	cv::waitKey(0);


	return 0;
}

python部分

将上面的C++代码打包成dll,然后python调用dll与不调用作对比

import cv2
import ctypes
import numpy as np
import d3dshot
import time
print("python-----------------------------------------")
d = d3dshot.create(capture_output="numpy")
d.display = d.displays[0]
start = time.time()
for i in range(1000):
    img = d.screenshot()
end = time.time()
print("python 1000次桌面截图平均耗时%.1f毫秒/次"%(end-start))

#读取模板 遮罩
template = cv2.imdecode(np.fromfile("1号鼠标模板.png", dtype=np.uint8), -1)[:, :, :3]
mask = cv2.imdecode(np.fromfile("1号鼠标遮罩.png", dtype=np.uint8), -1)[:, :, :4]
w, h = template.shape[0],template.shape[1]
#遮罩透明点设为0权重
for i in range(mask.shape[0]):
    for j in range(mask.shape[1]):
        if mask[i,j,3]==0:
            mask[i,j,:3]=(0,0,0)
mask = mask[:,:,:3]
#遮罩二值化
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
_,mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
mask = np.stack([mask,mask,mask],axis=-1)
w, h = template.shape[0],template.shape[1]

#模板匹配
start = time.time()
for i in range(10):
    img = d.screenshot()
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    res = cv2.matchTemplate(img,template,cv2.TM_SQDIFF_NORMED,mask=mask)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
end = time.time()
print("python 10次桌面模板匹配平均耗时%.1f毫秒/次"%((end-start)/10*1000))

#可视化
top_left = min_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, (0,0,255), 2)
cv2.imshow("test", img)
cv2.waitKey(0)
raise Exception("注释掉啊混蛋")

print("注意运行下面代码需要注释掉上面的代码 因为dxgi重复初始化 python+dll-----------------------------------------")
#载入dll
openDetect = ctypes.WinDLL('OpenDetectDLL.dll')
openDetect.initDxgi()#dxgi截图需要初始化

#窗口截图矩阵信息
class MatInfo(ctypes.Structure):
    _fields_ = [("windowX",ctypes.c_int),("windowY",ctypes.c_int),("windowWidth",ctypes.c_int),("windowHeight",ctypes.c_int),("pMatByte",ctypes.POINTER(ctypes.c_uint8))]

#getWindowMatInfo函数 输入 窗口标题 返回 窗口截图矩阵信息
openDetect.getWindowMatInfo.argtypes = [ctypes.c_char_p,]
openDetect.getWindowMatInfo.restype = MatInfo


start = time.time()
for i in range(1000):
    t = time.time()
    matInfo = openDetect.getWindowMatInfo(bytes("desktop","gbk"))
    mat = np.ctypeslib.as_array(matInfo.pMatByte, shape=(matInfo.windowHeight,matInfo.windowWidth, 3))
end = time.time()
print("python+dll 1000次桌面截图平均耗时%.1f毫秒/次"%(end-start))

#addTemplateMask 增加模板遮罩对 函数输入 模板名 模板路径 遮罩路径
openDetect.addTemplateMask.argtypes = [ctypes.c_char_p,ctypes.c_char_p,ctypes.c_char_p,]
openDetect.addTemplateMask.restype = None

openDetect.addTemplateMask(bytes("mouse1","gbk"),bytes("1号鼠标模板.png","gbk"),bytes("1号鼠标遮罩.png","gbk"));
openDetect.addTemplateMask(bytes("mouse2","gbk"), bytes("2号鼠标模板.png","gbk"), bytes("","gbk"));

#模板匹配结果
class MatchResult(ctypes.Structure):
    _fields_ = [
        ("xInWindow",ctypes.c_int),("yInWindow",ctypes.c_int),
        ("xInDesktop",ctypes.c_int),("yInDesktop",ctypes.c_int),
        ("windowX",ctypes.c_int),("windowY",ctypes.c_int),
        ("windowWidth",ctypes.c_int),("windowHeight",ctypes.c_int),
        ("templateWidth",ctypes.c_int),("templateHeight",ctypes.c_int),
        ("diff",ctypes.c_float),("success",ctypes.c_bool),
        ]

#matchInWindow 模板匹配 输入 模板名 窗口包含文字 返回匹配结构体
openDetect.matchInWindow.argtypes = [ctypes.c_char_p,ctypes.c_char_p,]
openDetect.matchInWindow.restype = MatchResult

openDetect.getWindowMatInfo(bytes("梦幻","gbk"))#让梦幻西游窗口前置
start = time.time()
for i in range(10):
    result = openDetect.matchInWindow(bytes("mouse1","gbk"),bytes("desktop","gbk"))
end = time.time()
print("python+dll 10次桌面模板匹配平均耗时%.1f毫秒/次"%((end-start)/10*1000))

matInfo = openDetect.getWindowMatInfo(bytes("desktop","gbk"))
mat = np.ctypeslib.as_array(matInfo.pMatByte, shape=(matInfo.windowHeight,matInfo.windowWidth, 3))
cv2.rectangle(mat, (result.xInDesktop, result.yInDesktop), (result.xInDesktop+result.templateWidth, result.yInDesktop+result.templateHeight), (0,0, 255), 2)
cv2.imwrite("result.png",mat)
cv2.imshow("test", mat)
cv2.waitKey(0)

经测试,C++、python+dll、python都能得到下面模板匹配的截图,至于速度的对比结果请见开头

show4