当前位置: 首页 > news >正文

【python】使用opencv的模板匹配进行自动化点击

【python】使用opencv的模板匹配进行自动化点击

  • 1 前言
  • 2 理论
    • 2.1 OpenCV-Python简介
    • 2.2 模板匹配
  • 3 举例
    • 3.1 安装 opencv-python
    • 3.2 一个简单的模板匹配例子
    • 3.3 实战:匹配图片 并点击
    • 3.4 补充:模版匹配各个算法测试
        • 需求
        • cv2.TM_SQDIFF 有概率失败
        • cv2.TM_SQDIFF_NORMED
        • cv2.TM_CCORR 失败
        • cv2.TM_CCORR_NORMED 不完全失败
        • cv2.TM_CCOEFF 失败
        • cv2.TM_CCOEFF_NORMED
        • 完整测试代码
        • 总结

1 前言

之前做软件自动化点击,uiautomation并不能适用所有软件,比如:打不了游戏、ui很乱的软件定位不到
这时候就想到了用 opencv的模板匹配 来匹配图片,进行自动点击

之前的文章:【python】UI自动化-uiautomation-CSDN博客

OpenCV-Python入门:b站有很多,我看的heima的

上手门槛:有基础,会opencv的模板匹配功能

2 理论

2.1 OpenCV-Python简介

OpenCV:简介 - OpenCV 计算机视觉库

OpenCV(开源计算机视觉库:https://opencv.ac.cn)是一个包含数百种计算机视觉算法的开源库

OpenCV 具有模块化结构,这意味着该软件包包含多个共享或静态库。以下模块可用:

  • 核心功能 (core) - 一个紧凑的模块,定义了基本数据结构,包括稠密多维数组 Mat 以及所有其他模块使用的基本函数。
  • 图像处理 (imgproc) - 一个图像处理模块,包括线性与非线性图像滤波、几何图像变换(调整大小、仿射和透视扭曲、基于通用表的重映射)、色彩空间转换、直方图等。
  • 图像文件读写 (imgcodecs) - 包含用于读写各种格式图像文件的函数。
  • 视频 I/O (videoio) - 一个易于使用的视频捕获和视频编解码器接口。
  • 高级 GUI (highgui) - 一个易于使用的简单 UI 功能接口。
  • 视频分析 (video) - 一个视频分析模块,包括运动估计、背景减除和目标跟踪算法。
  • 相机校准与三维重建 (calib3d) - 基本的多视图几何算法、单目和立体相机校准、物体姿态估计、立体匹配算法以及三维重建元素。
  • 二维特征框架 (features2d) - 显著特征检测器、描述符和描述符匹配器。
  • 目标检测 (objdetect) - 检测预定义类别中的物体和实例(例如,人脸、眼睛、马克杯、人物、汽车等)。
  • 深度神经网络模块 (dnn) - 深度神经网络模块。
  • 机器学习 (ml) - 机器学习模块包含一组用于数据统计分类、回归和聚类的类和函数。
  • 计算摄影 (photo) - 高级照片处理技术,如去噪、图像修复。
  • 图像拼接 (stitching) - 用于图像拼接和全景图创建的函数。
  • … 其他辅助模块,例如 FLANN 和 Google 测试包装器、Python 绑定等。

2.2 模板匹配

  • 理论

    • 所谓的模板匹配,就是在 给定的图片中 查找 和模板最相似的区域
    • 该算法的 输入包括 模板和图片
    • 整个任务的思路就是按照 滑窗 的思路不断的移动模板图片,计算其与图像中对应区域的匹配度,最终将匹配度最高的区域选择为最终的结果。
  • 优点:

    • 实现简单,易于理解
    • 计算效率较高
  • 缺点:

    • 对尺度变化敏感
    • 只能匹配单一模板
res = cv.matchTemplate(img,template,method) # 模板匹配
# cv2.minMaxLoc(res) 获取匹配结果,返回元组 如:(0.0, 9845936.0, (1045, 459), (1100, 796))
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

参数:

  • img: 要进行模板匹配的图像
  • Template :模板
  • method:实现模板匹配的算法,主要有:
# 前2个算法值越小 匹配度越高,后面4个算法值越大 匹配度越高
cv2.TM_SQDIFF			# 平方差匹配,值越小 匹配度越高(0 表示完全匹配)
cv2.TM_SQDIFF_NORMED	# 标准平方差匹配,值越小 匹配度越高(0 表示完全匹配)
cv2.TM_CCORR			# 相关匹配,值越大 匹配度越高(1 表示完全匹配)
cv2.TM_CCORR_NORMED		# 标准相关匹配,值越大 匹配度越高(1 表示完全匹配)
cv2.TM_CCOEFF			# 相关系数匹配,值越大 匹配度越高(1 表示完全匹配)
cv2.TM_CCOEFF_NORMED	# 标准相关系数匹配,值越大 匹配度越高(1 表示完全匹配)
# 这2个算法,用 钉钉截图没匹配不上,其他4个可以【可能是钉钉或者我的的问题】
cv2.TM_CCORR 不行
cv2.TM_CCOEFF 不行

AI辅助生成

算法名称匹配度判断规则参数参考优点缺点
平方差匹配
TM_SQDIFF
值越小匹配度越高
(0 表示完全匹配)
匹配度:min_val;
坐标:min_loc
计算快对亮度变化敏感
标准平方差匹配
TM_SQDIFF_NORMED
值越小匹配度越高
(0 表示完全匹配)
匹配度:min_val;
坐标:min_loc
抗亮度变化对旋转、缩放变化敏感
相关匹配
TM_CCORR
值越大匹配度越高
(1 表示完全匹配)
匹配度:max_val;
坐标:max_loc
能突出相似区域受背景亮度干扰大
标准相关匹配
TM_CCORR_NORMED
值越大匹配度越高
(1 表示完全匹配)
匹配度:max_val;
坐标:max_loc
抗亮度干扰,相似度得分直观对目标遮挡敏感
相关系数匹配
TM_CCOEFF
值越大匹配度越高
(1 表示完全匹配)
匹配度:max_val;
坐标:max_loc
抗亮度偏移(消除均值影响)计算速度相对较慢
标准相关系数匹配
TM_CCOEFF_NORMED
值越大匹配度越高
(1 表示完全匹配)
匹配度:max_val;
坐标:max_loc
抗亮度、对比度变化能力强,匹配精度高计算速度相对其他基础算法较慢

3 举例

3.1 安装 opencv-python

安装 opencv-python、其他可能用的上的包

pip install opencv-python matplotlib pillow
  • matplotlib 图像显示、绘制图表
  • Pillow (PIL) 用于图像格式转换、基础处理

3.2 一个简单的模板匹配例子

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt# 1 图像和模板读取
img = cv.imread('./image/wulin2.jpeg')
template = cv.imread('./image/wulin.jpeg')
h,w,l = template.shape
# 2 模板匹配
# 2.1 模板匹配
res = cv.matchTemplate(img, template, cv.TM_CCORR)
# 2.2 返回图像中最匹配的位置,确定左上角的坐标,并将匹配位置绘制在图像上
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
# 使用平方差时最小值为最佳匹配位置
# top_left = min_loc
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv.rectangle(img, top_left, bottom_right, (0,255,0), 2)
# 3 图像显示
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'), plt.xticks([]), plt.yticks([])
plt.show()

3.3 实战:匹配图片 并点击

功能:1. 实时截屏,查找模板图片(图片自行截图准备)2. 如果指定动作(点击、移动...),则执行动作3. 绑定窗口,只在指定窗口才执行截图
import pyautogui
import win32gui
import win32api
import win32con
import pywintypes
import pythoncom
from win32com import client
import numpy
import cv2
import time
from matplotlib import pyplot as pltdef switch_window(_hwnd):"""切换到指定窗口(一次性尝试), 切换成功返回True"""# while True:print(f"--32 switch_window 正在尝试切换到应用窗口 {_hwnd}...")time.sleep(1)try:# 初始化 COM 组件pythoncom.CoInitialize()# 发送 Alt 键获取权限shell = client.Dispatch("WScript.Shell")shell.SendKeys('%')  # 等价于按下并释放 Alt 键# 切换窗口win32gui.SetForegroundWindow(_hwnd)except pywintypes.error as e:print(f"窗口切换失败:{e.strerror}(错误码:{e.winerror})")pyautogui.hotkey('alt', 'tab')except Exception as e:print(f"其他错误:{str(e)}")pyautogui.hotkey('alt', 'tab')finally:# 释放 COM 资源(单线程可省略,多线程必需)print("释放 COM 资源")pythoncom.CoUninitialize()if win32gui.GetForegroundWindow() == _hwnd:  # 判断当前窗口是否是应用窗口print(f"--54 切换窗口成功, ID: {_hwnd}")return Trueelse:print(f"--60 窗口切换失败, 当前不是指定窗口")pyautogui.hotkey('alt', 'tab')return Falsedef click_left(x, y):"""在指定的屏幕坐标 (x, y) 处模拟鼠标左键点击。"""# 将鼠标移动到指定位置win32api.SetCursorPos((x, y))time.sleep(0.05) # 短暂延迟,确保鼠标移动完成# 按下鼠标左键win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)time.sleep(0.1) # 短暂延迟,模拟按住效果(可选)# 释放鼠标左键win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)print(f"鼠标点击执行于坐标: ({x}, {y})")class ImgRecognition:"""原始 图像识别 类功能:实时截屏,查找模板图片,如果指定动作(点击、移动...),则执行动作"""def __init__(self):self.win_hwnd = 0   # 窗口句柄self.all_task_exit_flag = None  # 所有任务退出标志def click_image_v(self, _template_img, _switch_window=False, find_count=50, sleep_time=0.2, threshold=0.99,action='pass', click_time=0, customize_xy=(0, 0), region=None, algorithm='TM_SQDIFF_NORMED'):"""查找图片, 返回字典 版本Args:_template_img: 模板图片(要找的图片)_switch_window: 是否要 切换到窗口find_count: 查找图片 超时次数sleep_time: 下一次查找图片的等待时间threshold: 精确值action: 动作click_time: 点击 延时customize_xy: 点击 xy偏移的位置 (0, 0)region: 截图范围坐标algorithm: 指定匹配算法:return: 返回 result 字典"""# object() 表示创建一个空对象result = {"左上角坐标": (0, 0), "中心点坐标": (0, 0), "匹配结果": object(),"最佳匹配值": object(), "图片名": _template_img , "匹配阈值": '',"匹配阈值-数字": 1, '识别耗费时间':''}temp_time_out_count = 0  # 匹配超时记录次数(初始值1)while True:if self.all_task_exit_flag:print(f"click_image: self.all_task_exit_flag=True, {_template_img}取消查找")breakelif temp_time_out_count >= find_count:# print(f"查找图片 超时次数:{temp_time_out_count}次 ,{_template_img}取消查找")breaktemp_time_out_count += 1if _switch_window:  # 要 切换到窗口if switch_window(self.win_hwnd):  # 切换到窗口passelse:time.sleep(1) # 窗口切换失败,等待1秒continueif self.win_hwnd == win32gui.GetForegroundWindow():    # 如果当前窗口是应用窗口,则进行后续操作start_time = time.time()  # 记录开始时间# 1. 截屏src_img = pyautogui.screenshot(region=region)# 2. 转换为OpenCV格式(BGR)src_img = numpy.array(src_img)src_img = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)# 3. 加载模板图片(确保与截图格式一致)读取为BGR格式;借助 numpy.fromfile 和 cv2.imdecode 来实现中文路径的读取_template = cv2.imdecode(numpy.fromfile(file=_template_img, dtype=numpy.uint8), cv2.IMREAD_COLOR)h, w = _template.shape[:2]  # 只获取高度和宽度,忽略通道数# 4. 模板匹配# method = cv2.TM_SQDIFF        # 指定 平方差匹配        匹配算法,值越小 匹配度越高(0 表示完全匹配)method = cv2.TM_SQDIFF_NORMED  # 指定 标准平方差匹配   匹配算法,值越小 匹配度越高(0 表示完全匹配)# method = cv2.TM_CCORR         # 指定 相关匹配         匹配算法,值越 大 匹配度越高(1 表示完全匹配)# method = cv2.TM_CCORR_NORMED  # 指定 标准相关匹配    匹配算法,值越 大 匹配度越高(1 表示完全匹配)# method = cv2.TM_CCOEFF        # 指定 相关系数匹配      匹配算法,值越 大 匹配度越高(1 表示完全匹配)# method = cv2.TM_CCOEFF_NORMED # 指定 标准相关系数匹配 匹配算法,值越 大 匹配度越高(1 表示完全匹配)# 匹配算法if algorithm == 'TM_SQDIFF_NORMED':method = cv2.TM_SQDIFF_NORMEDelif algorithm == 'TM_CCORR_NORMED':method = cv2.TM_CCORR_NORMEDelif algorithm == 'TM_CCOEFF_NORMED':method = cv2.TM_CCOEFF_NORMEDres = cv2.matchTemplate(src_img, _template, method)  # 模板匹配# print(cv2.minMaxLoc(res))min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 获取匹配结果if method == cv2.TM_SQDIFF or method == cv2.TM_SQDIFF_NORMED:result['左上角坐标'] = min_loc  # 平方差算法/标准平方差匹配算法:最小值 是最佳匹配best_val = min_val# print(f'匹配结果: mni_val:{min_val}, max_val:{max_val}, min_loc:{min_loc}, max_loc:{max_loc}')# print(f"采用 最小匹配算法, 匹配阈值: {best_val}, 左上角位置: {best_xy}")else:result['左上角坐标'] = max_loc  # 其他算法:最大值 是最佳匹配best_val = max_val# print(f'匹配结果: mni_val:{min_val}, max_val:{max_val}, min_loc:{min_loc}, max_loc:{max_loc}')# print(f"采用 最大匹配算法, 匹配阈值: {best_val}, 左上角位置: {best_xy}")# 5. 阈值判断if method == cv2.TM_SQDIFF or method == cv2.TM_SQDIFF_NORMED:  # 平方差算法/标准平方差匹配算法:最小值 是最佳匹配if best_val > (1 - threshold):result['匹配阈值'] = f"最小匹配算法 匹配失败!最佳值 {best_val:.2f} > 阈值 {(1 - threshold):.2f}"result['匹配阈值-数字'] = float(f'{best_val:.2f}')result['左上角坐标'] = (0, 0)# print(result['匹配阈值'])else:result['匹配阈值'] = f"最小匹配算法 匹配成功!最佳值 {best_val:.2f} < 阈值 {(1 - threshold):.2f}"result['匹配阈值-数字'] = float(f'{best_val:.2f}')else:  # 其他算法:最大值 是最佳匹配if best_val < threshold:# raise 是 Python 中的一个关键字,用于手动触发(抛出)一个异常。当你调用 raise 时,程序会立即停止当前流程,并跳转到最近的异常处理代码(try...except 块)。result['匹配阈值'] = f"最大匹配算法 匹配失败!最佳值 {best_val:.2f} < 阈值 {threshold:.2f}"result['匹配阈值-数字'] = float(f'{best_val:.2f}')result['左上角坐标'] = (0, 0)# raise ValueError(_str)else:result['匹配阈值'] = f"最大匹配算法 匹配成功!最佳值 {best_val:.2f} > 阈值 {threshold:.2f}"result['匹配阈值-数字'] = float(f'{best_val:.2f}')"""测试: 将匹配范围 显示出来"""show = 11if show == 1:# 6. 绘制匹配框h, w, l = _template.shape  # 获取模板图像的高度、宽度和通道数top_left = result['左上角坐标']  # 获取 左上角匹配位置bottom_right = (top_left[0] + w, top_left[1] + h)  # 计算矩形的右下角坐标cv2.rectangle(src_img, top_left, bottom_right, (0, 255, 0), 2)  # 在原始图像上绘制矩形框标记匹配区域# 7. 显示结果plt.rcParams["font.family"] = ["SimHei"]plt.imshow(src_img[:, :, ::-1])  # BGR→RGBplt.title('匹配结果'), plt.xticks([]), plt.yticks([])plt.show()end_time = time.time()  # 记录结束时间elapsed_time = end_time - start_time  # 计算耗费时间# print(f"--111--图片 {_template_img} 识别耗费时间: {elapsed_time:.2f} 秒")result['识别耗费时间'] = f'{elapsed_time:.2f}'if result['左上角坐标'] == (0, 0):# print(f"-- 349 未找到图像 {_template_img}")time.sleep(sleep_time)continueelse:# 计算中心点坐标:左上角坐标 + 模板宽高的一半center_x = result['左上角坐标'][0] + w // 2center_y = result['左上角坐标'][1] + h // 2result['中心点坐标'] = (center_x, center_y)print(f"-- 438 {_template_img} 模板宽 {w},模板高 {h},识别后的中心点坐标 {result['中心点坐标']}")# 执行动作if action == 'click_left':    # 单击左键time.sleep(click_time)# click_left(result['左上角坐标'][0], result['左上角坐标'][1])click_left(result['中心点坐标'][0], result['中心点坐标'][1])elif action == 'pass':passelif action == 'customize':    # 偏移到 指定位置,然后 单击左键time.sleep(click_time)click_left(result['左上角坐标'][0] + customize_xy[0], result['左上角坐标'][1] + customize_xy[1])elif action == 'move':    # 移动鼠标time.sleep(click_time)pyautogui.moveTo(result['左上角坐标'][0], result['左上角坐标'][1])elif action == 'move_customize':    # 移动鼠标,并 偏移到 指定位置time.sleep(click_time)pyautogui.moveTo(result['左上角坐标'][0] + customize_xy[0], result['左上角坐标'][1] + customize_xy[1])breakelse:if self.all_task_exit_flag:print(f"-- 127 -- 当前不是应用窗口,检测到退出标志,执行退出")breakprint(f"-- 127 -- 当前不是应用窗口,等待1秒")time.sleep(1)if result['左上角坐标'] == (0, 0):result['匹配结果'] = Falseelse:result['匹配结果'] = Truereturn resultif __name__ == '__main__':# 示例# 实例化a = ImgRecognition()# 更新 窗口句柄a.win_hwnd = win32gui.FindWindow(None, '此电脑')   # 获取窗口句柄# 图片识别b = a.click_image_v(r"D:\Users\Desktop\测试图片.png",action='click_left')print(b)
# 测试输出-- 127 -- 当前不是应用窗口,等待1秒
-- 127 -- 当前不是应用窗口,等待1秒
-- 127 -- 当前不是应用窗口,等待1秒
-- 438 D:\Users\Desktop\测试图片.png 模板宽 27,模板高 31,识别后的中心点坐标 (446, 1051)
鼠标点击执行于坐标: (446, 1051)
{'左上角坐标': (433, 1036), '中心点坐标': (446, 1051), '匹配结果': True, '最佳匹配值': <object object at 0x0000021470E67770>, '图片名': 'D:\\Users\\Desktop\\测试图片.png', '匹配阈值': '最小匹配算法 匹配成功!最佳值 0.00 < 阈值 0.01', '匹配阈值-数字': 0.0, '识别耗费时间': '0.30'}进程已结束,退出代码为 0

3.4 补充:模版匹配各个算法测试

需求
匹配对象:Wrod上的 绿色数字100【左上角位置: (1045, 459)】
截图工具:钉钉手动截取了 Wrod上的 绿色数字100
测试方式:能匹配到的情况是:把word缩放 调到 90%手动让它不能匹配到的情况是:把word缩放 调到 100%

在这里插入图片描述

输出:

cv2.TM_SQDIFF 有概率失败

基本成功,有概率失败

# 故意让它不匹配,把文字放大
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(499494.0, 98454944.0, (1045, 458), (1100, 796))
匹配结果: mni_val:499494.0, max_val:98454944.0, min_loc:(1045, 458), max_loc:(1100, 796)
该算法 采用最小值匹配, 匹配阈值: 499494.0, 左上角位置: (1045, 458)
未找到图像 100.png , 匹配失败!最佳值 499494.00 > 反向阈值 0.010000000000000009# 正常匹配
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(0.0, 98454944.0, (1045, 459), (1100, 796))
匹配结果: mni_val:0.0, max_val:98454944.0, min_loc:(1045, 459), max_loc:(1100, 796)
该算法 采用最小值匹配, 匹配阈值: 0.0, 左上角位置: (1045, 459)
--111--图片 100.png 识别耗费时间: 0.27-- 138 -- _xy == (1045, 459) , 找到图片 100.png
cv2.TM_SQDIFF_NORMED
# 故意让它不匹配,把文字放大
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(0.01854194886982441, 1.0, (1046, 447), (0, 0))
匹配结果: mni_val:0.01854194886982441, max_val:1.0, min_loc:(1046, 447), max_loc:(0, 0)
该算法 采用最小值匹配, 匹配阈值: 0.01854194886982441, 左上角位置: (1046, 447)
未找到图像 100.png , 匹配失败!最佳值 0.02 > 反向阈值 0.010000000000000009# 正常匹配
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(0.004150602500885725, 1.0, (1045, 458), (0, 0))
匹配结果: mni_val:0.004150602500885725, max_val:1.0, min_loc:(1045, 458), max_loc:(0, 0)
该算法 采用最小值匹配, 匹配阈值: 0.004150602500885725, 左上角位置: (1045, 458)
--111--图片 100.png 识别耗费时间: 0.27-- 138 -- _xy == (1045, 458) , 找到图片 100.png
cv2.TM_CCORR 失败
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(11454237.0, 124933968.0, (1131, 796), (1559, 257))
匹配结果: mni_val:11454237.0, max_val:124933968.0, min_loc:(1131, 796), max_loc:(1559, 257)
该算法 采用最大值匹配, 匹配阈值: 124933968.0, 左上角位置: (1559, 257)
未找到图像 100.png , 匹配失败!最佳值 124933968.00 > 反向阈值 0.010000000000000009
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(11454236.0, 124933968.0, (1100, 796), (1559, 257))
匹配结果: mni_val:11454236.0, max_val:124933968.0, min_loc:(1100, 796), max_loc:(1559, 257)
该算法 采用最大值匹配, 匹配阈值: 124933968.0, 左上角位置: (1559, 257)
未找到图像 100.png , 匹配失败!最佳值 124933968.00 > 反向阈值 0.010000000000000009
cv2.TM_CCORR_NORMED 不完全失败
其他算法,缩放 调到 100%不能识别,但是这个可以匹配到,但是坐标略有偏差稍微放大(比如100%)、缩小一点(80%),都可以正常匹配到
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(0.5932061672210693, 0.9907437562942505, (1033, 793), (1046, 447))
匹配结果: mni_val:0.5932061672210693, max_val:0.9907437562942505, min_loc:(1033, 793), max_loc:(1046, 447)
该算法 采用最大值匹配, 匹配阈值: 0.9907437562942505, 左上角位置: (1046, 447)
--111--图片 100.png 识别耗费时间: 0.28-- 138 -- _xy == (1046, 447) , 找到图片 100.png
cv2.TM_CCOEFF 失败
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(-2322919.25, 2460627.75, (113, 114), (1045, 428))
匹配结果: mni_val:-2322919.25, max_val:2460627.75, min_loc:(113, 114), max_loc:(1045, 428)
该算法 采用最大值匹配, 匹配阈值: 2460627.75, 左上角位置: (1045, 428)
未找到图像 100.png , 匹配失败!最佳值 2460627.75 > 反向阈值 0.010000000000000009
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(-2322919.25, 2460627.75, (113, 114), (1045, 428))
匹配结果: mni_val:-2322919.25, max_val:2460627.75, min_loc:(113, 114), max_loc:(1045, 428)
该算法 采用最大值匹配, 匹配阈值: 2460627.75, 左上角位置: (1045, 428)
未找到图像 100.png , 匹配失败!最佳值 2460627.75 > 反向阈值 0.010000000000000009
cv2.TM_CCOEFF_NORMED
# 故意让它不匹配,把文字放大
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(-0.5692965984344482, 0.9013311266899109, (212, 415), (1045, 427))
匹配结果: mni_val:-0.5692965984344482, max_val:0.9013311266899109, min_loc:(212, 415), max_loc:(1045, 427)
该算法 采用最大值匹配, 匹配阈值: 0.9013311266899109, 左上角位置: (1045, 427)
未找到图像 100.png , 匹配失败!最佳值 0.90 < 阈值 0.99
# 匹配情况
-- 127 -- 当前的窗口是:135052, 窗口名是:文档1 - Word
(-0.5692965984344482, 0.9999955296516418, (212, 415), (1045, 428))
匹配结果: mni_val:-0.5692965984344482, max_val:0.9999955296516418, min_loc:(212, 415), max_loc:(1045, 428)
该算法 采用最大值匹配, 匹配阈值: 0.9999955296516418, 左上角位置: (1045, 428)
--111--图片 100.png 识别耗费时间: 0.27-- 138 -- _xy == (1045, 428) , 找到图片 100.png
完整测试代码
import pyautogui
import numpy as np
import cv2
from matplotlib import pyplot as pltimport timeimport win32gui
import win32api
import win32con# 模拟鼠标左键单击
def click_left(x, y):"""在指定的屏幕坐标 (x, y) 处模拟鼠标左键点击。"""# 将鼠标移动到指定位置win32api.SetCursorPos((x, y))time.sleep(0.05) # 短暂延迟,确保鼠标移动完成# 按下鼠标左键win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)time.sleep(0.1) # 短暂延迟,模拟按住效果(可选)# 释放鼠标左键win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)print(f"鼠标点击执行于坐标: ({x}, {y})")# 图片识别
def image_recognition(_hwnd, _template, threshold=0.92):# 1. 截屏src_img = pyautogui.screenshot(region=(0, 0, 1920, 1080))  # 指定截图区域# 2. 转换为OpenCV格式(BGR)src_img = np.array(src_img)src_img = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)# 3. 加载模板图片(确保与截图格式一致)读取为BGR格式;借助 np.fromfile 和 cv2.imdecode 来实现中文路径的读取_template = cv2.imdecode(np.fromfile(file=_template, dtype=np.uint8), cv2.IMREAD_COLOR)# 4. 模板匹配# method = cv2.TM_SQDIFF   # 指定匹配算法method = cv2.TM_SQDIFF_NORMED   # 指定匹配算法# method = cv2.TM_CCORR   # 指定匹配算法# method = cv2.TM_CCORR_NORMED   # 指定匹配算法# method = cv2.TM_CCOEFF   # 指定匹配算法# method = cv2.TM_CCOEFF_NORMED   # 指定匹配算法res = cv2.matchTemplate(src_img, _template, method) # 模板匹配print(cv2.minMaxLoc(res))min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 获取匹配结果# 对于TM_SQDIFF算法,最佳匹配位置为 min_loc# 对于其他算法,最佳匹配位置为 max_locif method == cv2.TM_SQDIFF or method == cv2.TM_SQDIFF_NORMED:best_xy = min_loc  # 平方差算法:最小值 是最佳匹配best_val = min_valprint(f'匹配结果: mni_val:{min_val}, max_val:{max_val}, min_loc:{min_loc}, max_loc:{max_loc}')print(f"该算法 采用最小值匹配, 匹配阈值: {best_val}, 左上角位置: {best_xy}")else:best_xy = max_loc  # 其他算法:最大值 是最佳匹配best_val = max_valprint(f'匹配结果: mni_val:{min_val}, max_val:{max_val}, min_loc:{min_loc}, max_loc:{max_loc}')print(f"该算法 采用最大值匹配, 匹配阈值: {best_val}, 左上角位置: {best_xy}")# 5. 阈值判断if method in [cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR_NORMED]:if best_val < threshold:# raise 是 Python 中的一个关键字,用于手动触发(抛出)一个异常。当你调用 raise 时,程序会立即停止当前流程,并跳转到最近的异常处理代码(try...except 块)。raise ValueError(f"匹配失败!最佳值 {best_val:.2f} < 阈值 {threshold}")else:  # 平方差算法值越小越好,可根据场景调整阈值逻辑(如 best_val > 阈值 则失败)if best_val > (1 - threshold):  # 示例:反向阈值,需根据实际场景微调raise ValueError(f"匹配失败!最佳值 {best_val:.2f} > 反向阈值 {1 - threshold}")"""测试: 将匹配范围 显示出来"""show = 11if show == 1:# 6. 绘制匹配框h, w, l = _template.shape   # 获取模板图像的高度、宽度和通道数top_left = best_xy         # 获取 左上角匹配位置bottom_right = (top_left[0] + w, top_left[1] + h)   # 计算矩形的右下角坐标cv2.rectangle(src_img, top_left, bottom_right, (0, 255, 0), 2)  # 在原始图像上绘制矩形框标记匹配区域# 7. 显示结果plt.rcParams["font.family"] = ["SimHei"]plt.imshow(src_img[:, :, ::-1])  # BGR→RGBplt.title('匹配结果'), plt.xticks([]), plt.yticks([])plt.show()return best_xy  # 返回匹配位置(左上角)def click_image(_template_img, find_count=60, sleep_time=0.2, threshold=0.99, action='pass', click_time=0, customize_xy=(0, 0)):"""查找图片Args:_template_img: 模板图片(要找的图片)find_count: 查找图片 超时次数sleep_time: 下一次查找图片的等待时间threshold: 精确值action: 动作click_time: 点击 延时customize_xy: 点击 xy偏移的位置 (0, 0):return: 找到就返回True,没找到返回False"""temp_time_out_count = 1  # 匹配超时次数_xy = None  # 图片坐标while temp_time_out_count <= find_count:temp_time_out_count += 1print(f"-- 127 -- 当前的窗口是:{win32gui.GetForegroundWindow()}, 窗口名是:{win32gui.GetWindowText(win32gui.GetForegroundWindow())}")if game_hwnd == win32gui.GetForegroundWindow():     # 如果当前窗口是游戏窗口,则进行后续操作try:start_time = time.time()  # 记录开始时间_xy = image_recognition(game_hwnd, _template_img, threshold=threshold)   # 识别图片end_time = time.time()  # 记录结束时间elapsed_time = end_time - start_time  # 计算耗费时间print(f"--111--图片 {_template_img} 识别耗费时间: {elapsed_time:.2f} 秒")# 执行动作if action == 'left_click':time.sleep(click_time)click_left(_xy[0], _xy[1])elif action == 'pass':passelif action == 'customize':time.sleep(click_time)click_left(_xy[0] + customize_xy[0], _xy[1] + customize_xy[1])breakexcept ValueError as e:      # 如果抛出异常,则说明未找到图片print(f"未找到图像 {_template_img} , {e}")time.sleep(sleep_time)else:print(f"-- 127 -- 当前不是游戏窗口,等待1秒")time.sleep(1)if _xy is None:print(f"-- 138 -- _xy == None , 未找到图片 {_template_img}")return Falseelse:print(f"-- 138 -- _xy == {_xy} , 找到图片 {_template_img}")return True# 查找窗口句柄
game_hwnd = win32gui.FindWindow(None, '文档1 - Word')
# game_hwnd = 199016
print(f"-- 95 -- 窗口句柄: {game_hwnd}")click_image('100.png')
总结
# 这3个成功了
cv2.TM_SQDIFF			# 平方差匹配,值越小 匹配度越高(0 表示完全匹配)
cv2.TM_SQDIFF_NORMED	# 标准平方差匹配,值越小 匹配度越高(0 表示完全匹配)cv2.TM_CCOEFF_NORMED	# 标准相关系数匹配,值越大 匹配度越高(1 表示完全匹配)
http://www.dtcms.com/a/601915.html

相关文章:

  • leetcode 2654 使数组所有元素变成1的最少操作次数
  • 数据结构(长期更新)第8讲:队列
  • LKT4305安全芯片身份认证介绍
  • 看门狗超时时间的理解
  • C语言编译器手机教程 | 轻松在手机上编写和编译C语言程序
  • 基于SpringBoot的企业资产管理系统开发与设计
  • 黄骅做网站价格莱芜金点子招工小时工
  • 速卖通网站怎样做店面的二维码厦门网站建设
  • 好创意网站有哪些方面幸运28网站开发
  • AI小白入门:什么是RAG技术?
  • 【一天一个计算机知识】—— 【编程百度】条件编译
  • 网站分享注册公司代理电话
  • 免费制作一个企业网站广告设计公司网页
  • Gradle vs Maven 详细对比
  • [Column#187] 10data_struct | IP速查表 | 协议TCPUDP | DeepSeek-OCR
  • 生产级HMACSHA256签名与验签案例
  • 腾讯云服务器搭建网站漯河网站建设费用
  • docker部署开源监控软件hertzbeat
  • 上海网站网站建设工程公司简介范文大全
  • STM32CubeMx学习hal库
  • 在线确定性算法与自适应启发式在虚拟机动态整合中的竞争分析与性能优化
  • 企业网站建设费多少钱硬盘做免费嗳暧视频网站
  • 做图在哪个网站上找南京自助建站模板
  • 山东市网站建设中国林业网站群建设工程
  • 大白话浅析Windows 安全核心机制
  • 【OpenCV + VS】OpenCV 随机数绘图:如何在图像中绘制随机线条
  • 个人网站可以做商城吗泰安人才信息网官网
  • 网站开发提供图片加载速度建设工程施工合同示范文本2021
  • sward实战教程系列(2) - 创建第一个知识库
  • iOS 内存管理之 autoreleasePool