[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import filedialog, messagebox
import pyautogui
import time
import threading
import keyboard
import cv2
import numpy as np
import os
import json
import logging
# 日志记录器配置
logging.basicConfig(level=logging.DEBUG)
# 全局变量
monitoring = False
target_images = [] # 存储目标图像 (文件名, 图像数据)
threshold = 0.8 # 图像匹配的相似度阈值
monitoring_interval = 0.5 # 扫描等待时间(秒)
click_wait_time = 0.3 # 点击等待时间(秒)
blinking_color = "red" # 默认圆点颜色为红色
# 文件名前缀
FILE_PREFIX = "target_image_"
# Function to save settings
def save_settings():
settings = {
'threshold': threshold,
'monitoring_interval': monitoring_interval,
'click_wait_time': click_wait_time
}
try:
with open('settings.json', 'w') as f:
json.dump(settings, f)
logging.info("Settings saved successfully.")
except Exception as e:
logging.error(f"Error saving settings: {e}")
# Function to load settings
def load_settings():
global threshold, monitoring_interval, click_wait_time
try:
with open('settings.json', 'r') as f:
settings = json.load(f)
threshold = settings.get('threshold', 0.8)
monitoring_interval = settings.get('monitoring_interval', 0.5)
click_wait_time = settings.get('click_wait_time', 0.3)
logging.info("Settings loaded successfully.")
except Exception as e:
logging.error(f"Error loading settings: {e}")
# Call load_settings at startup
load_settings()
# Function to save target images
def save_target_images():
try:
for file_name, img_data in target_images:
np.save(f"{FILE_PREFIX}{file_name}", img_data)
logging.info(f"Saved {len(target_images)} images.")
except Exception as e:
logging.error(f"Error saving target images: {e}")
# Function to load saved images
def load_saved_images():
global target_images
try:
target_images.clear() # 清空现有的图像列表
for file_name in os.listdir("."): # 遍历当前文件夹中的文件
if file_name.startswith(FILE_PREFIX) and file_name.endswith(".npy"):
file_path = os.path.join(".", file_name)
img_data = np.load(file_path, allow_pickle=True) # 加载图像数据,允许使用 pickle
original_file_name = file_name[len(FILE_PREFIX):].replace(".npy", "") # 去掉扩展名和前缀
target_images.append((original_file_name, img_data)) # 将文件名和图像数据存储在列表中
logging.info(f"Loaded {len(target_images)} images.")
update_selected_images_listbox() # 更新显示
except Exception as e:
logging.error(f"Error loading saved images: {e}")
target_images = [] # 加载失败时清空列表
# Function to update selected images listbox
def update_selected_images_listbox():
selected_images_listbox.delete(0, tk.END)
for idx, (file_name, _) in enumerate(target_images, start=1):
selected_images_listbox.insert(tk.END, f"{idx}. {file_name}")
# 绑定右键点击事件
selected_images_listbox.bind('<Button-3>', show_context_menu)
# Function to clear target images
def clear_target_images():
global target_images
try:
folder_path = os.getcwd() # 获取当前工作目录
target_images.clear()
for file_name in os.listdir(folder_path):
if file_name.startswith(FILE_PREFIX) and file_name.endswith(".npy"):
file_path = os.path.join(folder_path, file_name)
os.remove(file_path)
# 重新加载已保存的图像
load_saved_images()
# 更新显示已选图像列表框
update_selected_images_listbox()
messagebox.showinfo("Info", "已清除所有目标图像。")
except Exception as e:
logging.error(f"清除目标图像时出错: {e}")
messagebox.showerror("错误", "清除图像时发生错误。")
# Function to load target image
def load_target_image():
global target_images
try:
file_paths = filedialog.askopenfilenames(
title="选择目标图像",
filetypes=[("Image files", "*.jpg *.png *.jpeg *.bmp *.tiff")]
)
if file_paths:
target_images.clear() # 清空已选择的图像列表
for file_path in file_paths:
file_name = os.path.basename(file_path)
target_image = cv2.imread(file_path, cv2.IMREAD_COLOR)
if target_image is None:
messagebox.showerror("Error", "无法加载图像文件。")
else:
target_images.append((file_name, target_image)) # 存储 (文件名, 图像数据)
logging.info(f"Loaded image: {file_path}")
# 保存已选择的图像数据
save_target_images()
# 更新已选图像列表框
update_selected_images_listbox()
# 弹出提示信息
num_images = len(target_images)
messagebox.showinfo("Info", f"已成功导入 {num_images} 张图片。")
except Exception as e:
logging.error(f"Error loading target images: {e}")
messagebox.showerror("Error", "加载图像时出现错误。")
# Function to delete selected image
def delete_selected_image(index):
global target_images
try:
file_name, _ = target_images.pop(index)
os.remove(f"{FILE_PREFIX}{file_name}.npy")
update_selected_images_listbox() # 更新列表框
messagebox.showinfo("Info", f"已删除图像: {file_name}")
except Exception as e:
logging.error(f"删除图像时出错: {e}")
messagebox.showerror("错误", f"删除图像时发生错误: {e}")
# Function to show context menu
def show_context_menu(event):
try:
# 获取选中的项目索引
index = selected_images_listbox.nearest(event.y)
selected_images_listbox.selection_set(index)
# 创建上下文菜单
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(
label="删除",
command=lambda: delete_selected_image(index)
)
context_menu.post(event.x_root, event.y_root)
except Exception as e:
logging.error(f"显示上下文菜单时出错: {e}")
# Function to monitor screen
def monitor_screen():
global monitoring, target_images, threshold, monitoring_interval, click_wait_time
while monitoring:
screenshot = pyautogui.screenshot()
screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
for file_name, target_image in target_images:
if isinstance(target_image, np.ndarray):
result = cv2.matchTemplate(screenshot, target_image, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val >= threshold:
logging.info(f"图像匹配度: {max_val}, 位置: {max_loc}")
target_height, target_width = target_image.shape[:2]
click_position = (max_loc[0] + target_width // 2, max_loc[1] + target_height // 2)
pyautogui.click(click_position)
time.sleep(click_wait_time) # 等待点击间隔时间
# 将匹配信息添加到匹配度显示框
current_time = time.strftime("%H:%M:%S") # 获取当前时间
formatted_info = f"时间: {current_time} | 文件名: {file_name} | 匹配度: {int(max_val * 100)}%"
add_match_info(formatted_info)
# 将鼠标移动到屏幕中心
screen_center = (pyautogui.size()[0] // 2, pyautogui.size()[1] // 2)
pyautogui.moveTo(screen_center)
time.sleep(monitoring_interval) # 控制监控间隔时间
# Function to add match information to the match info listbox
def add_match_info(info):
match_info_listbox.insert(tk.END, info)
if match_info_listbox.size() > 5:
match_info_listbox.delete(0) # 保持仅显示最新的5条信息
match_info_listbox.see(tk.END) # 滚动到最新的信息
# Function to toggle monitoring
def toggle_monitoring():
global monitoring, target_images
if monitoring:
monitoring = False
status_label.config(text="监控状态: 已停止")
else:
if not target_images:
messagebox.showwarning("Warning", "请先选择至少一个目标图像。")
return
monitoring = True
monitor_thread = threading.Thread(target=monitor_screen, daemon=True)
monitor_thread.start()
status_label.config(text="监控状态: 正在运行")
# 启动或停止闪烁效果
start_blinking() if monitoring else stop_blinking()
# Function to increase threshold
def increase_threshold():
global threshold
threshold = min(1.0, threshold + 0.01)
threshold_label.config(text=f"当前匹配阈值: {int(threshold * 100)}%")
save_settings() # 保存设置到文件
# Function to decrease threshold
def decrease_threshold():
global threshold
threshold = max(0.0, threshold - 0.01)
threshold_label.config(text=f"当前匹配阈值: {int(threshold * 100)}%")
save_settings() # 保存设置到文件
# Function to increase monitoring interval
def increase_monitoring_interval():
global monitoring_interval
monitoring_interval = round(min(10.0, monitoring_interval + 0.1), 1)
monitoring_interval_label.config(text=f"扫描等待时间: {monitoring_interval}秒")
save_settings() # 保存设置到文件
# Function to decrease monitoring interval
def decrease_monitoring_interval():
global monitoring_interval
monitoring_interval = round(max(0.1, monitoring_interval - 0.1), 1)
monitoring_interval_label.config(text=f"扫描等待时间: {monitoring_interval}秒")
save_settings() # 保存设置到文件
# Function to increase click wait time
def increase_click_wait_time():
global click_wait_time
click_wait_time = round(min(5.0, click_wait_time + 0.1), 1)
click_wait_time_label.config(text=f"点击等待时间: {click_wait_time}秒")
save_settings() # 保存设置到文件
# Function to decrease click wait time
def decrease_click_wait_time():
global click_wait_time
click_wait_time = round(max(0.1, click_wait_time - 0.1), 1)
click_wait_time_label.config(text=f"点击等待时间: {click_wait_time}秒")
save_settings() # 保存设置到文件
# 创建主窗口
root = tk.Tk()
root.title("自动监控工具")
# 创建标签和按钮
instruction_label = tk.Label(root, text="选择一个或多个目标图像进行监控")
instruction_label.pack(pady=10)
select_button = tk.Button(root, text="选择图像", command=load_target_image)
select_button.pack(pady=5)
# 新增清除图像按钮
clear_button = tk.Button(root, text="清除所有图像", command=clear_target_images)
clear_button.pack(pady=5)
# 已选图像显示框和匹配信息显示框的父框架
info_frame = tk.Frame(root)
info_frame.pack(pady=10)
# 已选图像显示框
selected_images_frame = tk.Frame(info_frame)
selected_images_frame.pack(side=tk.LEFT, padx=5)
selected_images_label = tk.Label(selected_images_frame, text="已选图像:")
selected_images_label.pack(side=tk.TOP)
# 调整显示框的宽度和高度
selected_images_listbox = tk.Listbox(selected_images_frame, width=40, height=5)
selected_images_listbox.pack()
# 实时匹配信息显示框
match_info_frame = tk.Frame(info_frame)
match_info_frame.pack(side=tk.LEFT, padx=5)
match_info_label = tk.Label(match_info_frame, text="实时匹配信息:")
match_info_label.pack(side=tk.TOP)
# 匹配信息显示栏,可以显示5条信息
match_info_listbox = tk.Listbox(match_info_frame, width=40, height=5)
match_info_listbox.pack()
# 加载已保存的图像
load_saved_images() # 在启动时加载已保存的图像
update_selected_images_listbox() # 更新显示
# 添加状态标签和闪烁的圆点
status_frame = tk.Frame(root)
status_frame.pack(pady=10)
status_label = tk.Label(status_frame, text="监控状态: 未开始")
status_label.pack(side=tk.LEFT)
# 添加 Canvas 用于绘制圆点
canvas = tk.Canvas(status_frame, width=20, height=20, bg='white')
canvas.pack(side=tk.LEFT, padx=10)
circle_id = canvas.create_oval(5, 5, 15, 15, fill=blinking_color) # 初始为红色
def blink():
global blinking_color, monitoring
if monitoring:
# 切换颜色
new_color = "green" if blinking_color == "red" else "red"
canvas.itemconfig(circle_id, fill=new_color)
blinking_color = new_color
root.after(500, blink) # 每 500 毫秒切换一次颜色
def start_blinking():
blink() # 启动闪烁
def stop_blinking():
canvas.itemconfig(circle_id, fill="red") # 停止时固定为红色
threshold_frame = tk.Frame(root)
threshold_frame.pack(pady=5)
threshold_label = tk.Label(threshold_frame, text=f"当前匹配阈值: {int(threshold * 100)}%")
threshold_label.pack(side=tk.LEFT)
increase_button = tk.Button(threshold_frame, text="+", command=increase_threshold)
increase_button.pack(side=tk.LEFT, padx=5)
decrease_button = tk.Button(threshold_frame, text="-", command=decrease_threshold)
decrease_button.pack(side=tk.LEFT, padx=5)
# 监控间隔和点击等待时间框架
options_frame = tk.Frame(root)
options_frame.pack(pady=5)
monitoring_interval_label = tk.Label(options_frame, text=f"扫描等待时间: {monitoring_interval}秒")
monitoring_interval_label.pack(side=tk.LEFT)
monitoring_interval_increase_button = tk.Button(options_frame, text="+", command=increase_monitoring_interval)
monitoring_interval_increase_button.pack(side=tk.LEFT, padx=5)
monitoring_interval_decrease_button = tk.Button(options_frame, text="-", command=decrease_monitoring_interval)
monitoring_interval_decrease_button.pack(side=tk.LEFT, padx=5)
click_wait_time_label = tk.Label(options_frame, text=f"点击等待时间: {click_wait_time}秒")
click_wait_time_label.pack(side=tk.LEFT)
click_wait_time_increase_button = tk.Button(options_frame, text="+", command=increase_click_wait_time)
click_wait_time_increase_button.pack(side=tk.LEFT, padx=5)
click_wait_time_decrease_button = tk.Button(options_frame, text="-", command=decrease_click_wait_time)
click_wait_time_decrease_button.pack(side=tk.LEFT, padx=5)
# 功能说明标签
function_label = tk.Label(root, text=(
"程序功能说明:\n"
"1. 点击 '选择图像' 选择一个或多个目标图像\n"
"2. 按 F12 开始/停止监控。\n"
"3. 监控过程中,如屏幕中找到与目标图像相似的区域,程序将在该位置执行点击操作。\n"
"4. 程序会自动保存已导入图像以及参数设置,并在下次启动时自动载入。"
))
function_label.pack(pady=10)
# 绑定 F12 键来切换监控状态
keyboard.add_hotkey('F12', toggle_monitoring)
# 启动主循环
root.mainloop()
# 运行此行以确保程序关闭时删除键盘快捷键监听器
keyboard.unhook_all_hotkeys()