更新了2.0,新增了以下设置,ps个人感觉完美了
1,以往ctrl+q键不能进行启动,现在改为了f1键可以缩为最小化进行启动与关闭
2,新增鼠标光标的隐藏位置可以自己填写屏幕坐标代码
下载地址.txt
(39 Bytes, 下载次数: 4)
2025-11-1 13:34 上传
点击文件名下载附件

[Python] 纯文本查看 复制代码import tkinter as tk
from tkinter import filedialog, simpledialog, ttk, messagebox
from PIL import Image, ImageGrab
import cv2
import numpy as np
import pyautogui
import time
import random
import threading
import keyboard
import os
import json
class ImageRecognitionApp:
def __init__(self, root):
self.root = root
self.root.title("图像识别与点击")
self.image_list = [] # 存储 (图像路径, 等待时间)
self.default_wait_time = 100 # 默认等待时间 100 毫秒
self.threshold = 0.9 # 默认匹配度阈值
self.screenshot_area = None
self.rect = None
self.start_x = None
self.start_y = None
self.canvas = None
self.running = False
self.thread = None
self.stop_requested = False
self.custom_hotkey = None
self.registered_hotkey = None
screen_width, screen_height = pyautogui.size()
self.hide_position = (screen_width - 10, screen_height - 10) # 默认隐藏鼠标位置
self.last_folder = os.path.expanduser("~") # 默认文件夹
self.data_file = "images.json" # 保存数据文件
self.init_ui()
self.root.minsize(600, 400)
self.set_default_hotkey()
self.load_images() # 加载上次保存的图片
self.root.protocol("WM_DELETE_WINDOW", self.on_close) # 安全关闭
# =================== 数据保存与加载 ===================
def load_images(self):
if os.path.exists(self.data_file):
try:
with open(self.data_file, "r", encoding="utf-8") as f:
data = json.load(f)
self.image_list = data.get("image_list", [])
self.last_folder = data.get("last_folder", self.last_folder)
self.threshold = data.get("threshold", 0.9)
self.threshold_slider.set(self.threshold)
self.current_threshold_label.config(text=f"当前值: {self.threshold:.2f}")
# 加载隐藏鼠标位置
self.hide_position = tuple(data.get("hide_position", self.hide_position))
self.hide_pos_entry.delete(0, tk.END)
self.hide_pos_entry.insert(0, f"{self.hide_position[0]},{self.hide_position[1]}")
self.update_image_listbox()
self.safe_set_status(f"已加载 {len(self.image_list)} 张图片")
except Exception as e:
self.safe_set_status(f"加载图片列表失败: {e}")
def save_images(self):
try:
with open(self.data_file, "w", encoding="utf-8") as f:
json.dump({
"image_list": self.image_list,
"last_folder": self.last_folder,
"threshold": self.threshold,
"hide_position": self.hide_position
}, f, ensure_ascii=False, indent=2)
except Exception as e:
self.safe_set_status(f"保存图片列表失败: {e}")
# =================== 公共方法 ===================
def safe_set_status(self, text):
self.root.after(0, lambda: self.status_var.set(text))
# 设置默认快捷键 F1
def set_default_hotkey(self):
self.custom_hotkey = 'f1'
self.register_hotkey()
self.hotkey_label.config(text=f"当前快捷键: {self.custom_hotkey}")
# 注册全局热键
def register_hotkey(self):
if self.registered_hotkey:
keyboard.remove_hotkey(self.registered_hotkey)
try:
self.registered_hotkey = keyboard.add_hotkey(self.custom_hotkey, self.toggle_script_by_hotkey)
return True
except Exception as e:
messagebox.showerror("快捷键错误", f"无法注册快捷键 {self.custom_hotkey}: {e}")
return False
# 全局快捷键触发
def toggle_script_by_hotkey(self):
if self.running:
self.stop_script()
else:
if not self.image_list:
self.safe_set_status("请先添加至少一张图片")
return
self.start_script_thread()
self.safe_set_status("脚本运行中... 使用快捷键停止")
def on_close(self):
self.save_images()
if self.registered_hotkey:
keyboard.remove_hotkey(self.registered_hotkey)
self.root.destroy()
# =================== UI 初始化 ===================
def init_ui(self):
main_frame = tk.Frame(self.root, padx=10, pady=10)
main_frame.pack(fill=tk.BOTH, expand=True)
button_frame = tk.LabelFrame(main_frame, text="操作面板", padx=10, pady=10)
button_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
button_width = 15
tk.Button(button_frame, text="上传图像", command=self.upload_image, width=button_width).pack(pady=5, fill=tk.X)
tk.Button(button_frame, text="框选截图", command=self.prepare_capture_screenshot, width=button_width).pack(pady=5, fill=tk.X)
tk.Button(button_frame, text="删除图片", command=self.delete_selected_image, width=button_width).pack(pady=5, fill=tk.X)
tk.Button(button_frame, text="清空全部", command=self.clear_all_images, width=button_width).pack(pady=5, fill=tk.X)
self.toggle_run_button = tk.Button(button_frame, text="开始运行", command=self.toggle_script, width=button_width)
self.toggle_run_button.pack(pady=5, fill=tk.X)
# 快捷键设置
hotkey_frame = tk.LabelFrame(button_frame, text="快捷键设置", padx=10, pady=10)
hotkey_frame.pack(pady=10, fill=tk.X)
self.hotkey_label = tk.Label(hotkey_frame, text="当前快捷键: f1")
self.hotkey_label.pack(pady=5)
tk.Button(hotkey_frame, text="设置自定义快捷键", command=self.set_custom_hotkey, width=button_width).pack(pady=5, fill=tk.X)
# 隐藏鼠标位置设置 - 使用输入框
tk.Label(button_frame, text="鼠标点击后移动位置 (x,y):").pack(pady=(10,2))
self.hide_pos_entry = tk.Entry(button_frame, width=20)
self.hide_pos_entry.pack(pady=2)
self.hide_pos_entry.insert(0, f"{self.hide_position[0]},{self.hide_position[1]}")
tk.Button(button_frame, text="更新位置", command=self.update_hide_position_from_entry, width=button_width).pack(pady=5, fill=tk.X)
# 阈值控制
threshold_frame = tk.LabelFrame(button_frame, text="匹配度阈值", padx=10, pady=10)
threshold_frame.pack(pady=10, fill=tk.X)
self.current_threshold_label = tk.Label(threshold_frame, text=f"当前值: {self.threshold:.2f}", font=("Arial", 10, "bold"))
self.current_threshold_label.pack(pady=(0, 5))
self.threshold_slider = tk.Scale(threshold_frame, from_=0.5, to=1.0, resolution=0.01,
orient=tk.HORIZONTAL, showvalue=False, length=180,
command=self.update_threshold)
self.threshold_slider.set(self.threshold)
self.threshold_slider.pack(fill=tk.X)
scale_labels = tk.Frame(threshold_frame)
scale_labels.pack(fill=tk.X)
tk.Label(scale_labels, text="0.5").pack(side=tk.LEFT)
tk.Label(scale_labels, text="0.75").pack(side=tk.LEFT, padx=45)
tk.Label(scale_labels, text="1.0").pack(side=tk.RIGHT)
# 图像列表
list_frame = tk.LabelFrame(main_frame, text="图像列表", padx=10, pady=10)
list_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
self.tree = ttk.Treeview(list_frame, columns=("图片", "等待时间"), show='headings')
self.tree.heading("图片", text="图片")
self.tree.heading("等待时间", text="等待时间 (毫秒)")
self.tree.column("图片", width=250)
self.tree.column("等待时间", width=100)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.tree.bind('', self.edit_wait_time)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# =================== 功能区 ===================
def set_custom_hotkey(self):
hotkey = simpledialog.askstring("自定义快捷键", "请输入快捷键组合 (例如: ctrl+q 或 alt+s,需小写):", initialvalue=self.custom_hotkey)
if hotkey:
self.custom_hotkey = hotkey.strip()
if self.register_hotkey():
self.hotkey_label.config(text=f"当前快捷键: {self.custom_hotkey}")
self.safe_set_status(f"已设置快捷键: {self.custom_hotkey}")
def update_hide_position_from_entry(self):
pos = self.hide_pos_entry.get().strip()
try:
x, y = map(int, pos.split(','))
screen_width, screen_height = pyautogui.size()
if 0 ", self.cancel_screenshot)
self.canvas = tk.Canvas(self.top, cursor="cross", bg='grey')
self.canvas.pack(fill=tk.BOTH, expand=True)
self.canvas.bind("[B]", self.on_button_press)
self.canvas.bind("[B]", self.on_mouse_drag)
self.canvas.bind("[B]", self.on_button_release)
def cancel_screenshot(self, event=None):
if self.top:
self.top.destroy()
self.root.deiconify()
self.safe_set_status("截图已取消")
def on_button_press(self, event):
self.start_x, self.start_y = event.x, event.y
if self.rect:
self.canvas.delete(self.rect)
self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline='red', width=2)
def on_mouse_drag(self, event):
self.canvas.coords(self.rect, self.start_x, self.start_y, event.x, event.y)
def on_button_release(self, event):
end_x, end_y = event.x, event.y
bbox = (min(self.start_x, end_x), min(self.start_y, end_y),
max(self.start_x, end_x), max(self.start_y, end_y))
screenshot_path = os.path.join("screenshots", f"JT{random.randint(100000, 999999)}.png")
ImageGrab.grab(bbox).save(screenshot_path)
self.image_list.append((screenshot_path, self.default_wait_time))
self.update_image_listbox()
self.top.destroy()
self.root.deiconify()
self.safe_set_status(f"已保存截图: {screenshot_path}")
def update_image_listbox(self):
for row in self.tree.get_children():
self.tree.delete(row)
for img_path, wait_time in self.image_list:
self.tree.insert("", tk.END, values=(img_path, wait_time))
def clear_all_images(self):
if messagebox.askyesno("确认", "确定清空所有图像?"):
self.image_list.clear()
self.update_image_listbox()
self.safe_set_status("已清空所有图像")
def edit_wait_time(self, event):
selected_item = self.tree.selection()[0]
idx = self.tree.index(selected_item)
path, old_time = self.image_list[idx]
new_time = simpledialog.askinteger("修改等待时间", "请输入新的等待时间(毫秒):", initialvalue=old_time)
if new_time is not None:
self.image_list[idx] = (path, new_time)
self.update_image_listbox()
self.safe_set_status(f"已更新等待时间: {new_time} 毫秒")
def delete_selected_image(self):
selected_item = self.tree.selection()
if selected_item:
idx = self.tree.index(selected_item[0])
deleted = self.image_list.pop(idx)
self.update_image_listbox()
self.safe_set_status(f"已删除图像: {deleted[0]}")
# =================== 运行与控制 ===================
def toggle_script(self):
if not self.running:
if not self.image_list:
messagebox.showwarning("警告", "请先添加至少一个图像")
return
self.start_script_thread()
self.toggle_run_button.config(text="停止运行", state=tk.NORMAL)
self.safe_set_status("脚本运行中... 使用快捷键停止")
else:
self.stop_script()
self.toggle_run_button.config(text="开始运行", state=tk.NORMAL)
def start_script_thread(self):
if not self.running:
self.running = True
self.stop_requested = False
self.thread = threading.Thread(target=self.run_script, daemon=True)
self.thread.start()
def run_script(self):
while self.running and not self.stop_requested and self.image_list:
for img_path, wait_time in self.image_list:
if not self.running or self.stop_requested:
break
self.safe_set_status(f"处理中: {img_path}...")
self.match_and_click(img_path)
sleep_interval, total_sleep = 0.1, wait_time / 1000.0
slept = 0
while slept gray_screen.shape[0] or template.shape[1] > gray_screen.shape[1]:
self.safe_set_status(f"模板比屏幕大,无法匹配: {template_path}")
return
result = cv2.matchTemplate(gray_screen, template, cv2.TM_CCOEFF_NORMED)
loc = np.where(result >= self.threshold)
found = False
for pt in zip(*loc[::-1]):
click_x, click_y = pt[0] + template.shape[1] // 2, pt[1] + template.shape[0] // 2
pyautogui.click(click_x, click_y)
found = True
break
if not found:
self.safe_set_status(f"未找到匹配区域: {template_path} (阈值: {self.threshold:.2f})")
else:
try:
pyautogui.moveTo(*self.hide_position, duration=0.2)
except Exception as e:
self.safe_set_status(f"移动鼠标出错: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = ImageRecognitionApp(root)
root.mainloop()

