吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8088|回复: 128
上一主题 下一主题
收起左侧

[Python 转载] 图像识别点击 [图像识别自动点击按键精灵] 修改版

    [复制链接]
跳转到指定楼层
楼主
gzsklsskszngc 发表于 2024-9-19 23:49 回帖奖励
本帖最后由 gzsklsskszngc 于 2024-12-4 21:26 编辑

首先感谢原贴作者提供的代码:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1965479&highlight=%CD%BC%CF%F1
我在原代码的基础上做了以下小修改
上代码:
[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import filedialog, simpledialog, ttk, messagebox
from PIL import Image, ImageTk, ImageGrab
import cv2
import numpy as np
import pyautogui
import time
import threading
import logging
from logging.handlers import RotatingFileHandler
import os
import json
import atexit
from datetime import datetime
import shutil
import win32api, win32con
  
class ImageRecognitionApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图像识别与点击")
        self.image_list = []  # 存储 (图像路径, 步骤名称, 相似度, 键盘输入, 鼠标点击坐标, 等待时间, 条件, 跳转步骤)
        self.screenshot_area = None  # 用于存储截图区域
        self.rect = None  # 用于存储 Canvas 上的矩形
        self.start_x = None
        self.start_y = None
        self.canvas = None
        self.running = False  # 控制脚本是否在运行
        self.thread = None  # 用于保存线程
        self.hotkey = '<F1>'  # 默认热键
        self.similarity_threshold = 0.8  # 默认相似度阈值
        self.delay_time = 0.1  # 默认延迟时间
        self.loop_count = 1  # 默认循环次数
        self.screenshot_folder = 'screenshots'  # 截图保存文件夹
        self.paused = False  # 控制脚本是否暂停
        self.copied_item = None
        self.config_filename = 'config.json'  # 默认配置文件名
        self.start_step_index = 0  # 初始化
        self.follow_current_step = tk.BooleanVar(value=False)  # 控制是否跟随当前步骤
        self.init_ui()
        self.tree.image_refs = []  # 初始化 image_refs 属性
        self.init_logging()
        self.bind_arrow_keys()
        self.create_context_menu()
        atexit.register(self.cleanup_on_exit)
  
    def init_ui(self):
        # 主框架布局
        self.main_frame = tk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
  
        # 左侧布局:框选截图、运行脚本按钮
        self.left_frame = tk.Frame(self.main_frame)
        self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
  
        # 框选截图按钮(微信风格截图)
        self.screenshot_button = tk.Button(self.left_frame, text="框选截图", command=self.prepare_capture_screenshot)
        self.screenshot_button.pack(pady=5)
  
        # 删除选中图片按钮
        self.delete_button = tk.Button(self.left_frame, text="删除图片", command=self.delete_selected_image)
        self.delete_button.pack(pady=5)
  
        # 运行/停止脚本按钮
        self.toggle_run_button = tk.Button(self.left_frame, text="开始运行", command=self.toggle_script)
        self.toggle_run_button.pack(pady=5)
  
        # 保存配置按钮
        self.save_config_button = tk.Button(self.left_frame, text="保存配置", command=self.save_config)
        self.save_config_button.pack(pady=5)
  
        # 设置热键按钮
        self.set_hotkey_button = tk.Button(self.left_frame, text="设置热键", command=self.set_hotkey)
        self.set_hotkey_button.pack(pady=5)
  
        # 手动加载配置按钮
        self.load_config_button = tk.Button(self.left_frame, text="加载配置", command=self.load_config_manually)
        self.load_config_button.pack(pady=5)
  
        # 循环次数输入框
        self.loop_count_label = tk.Label(self.left_frame, text="循环次数:")
        self.loop_count_label.pack(pady=5)
        self.loop_count_entry = tk.Entry(self.left_frame)
        self.loop_count_entry.insert(0, str(self.loop_count))
        self.loop_count_entry.pack(pady=5)
  
        # 添加测试匹配按钮
        self.test_match_button = tk.Button(self.left_frame, text="测试匹配", command=self.test_single_match)
        self.test_match_button.pack(pady=5)
  
        # 添加允许最小化和跟随当前步骤的勾选框
        self.allow_minimize_var = tk.BooleanVar(value=True)  # 默认允许最小化
        self.allow_minimize_checkbox = tk.Checkbutton(self.left_frame, text="允许最小化", variable=self.allow_minimize_var)
        self.allow_minimize_checkbox.pack(side=tk.LEFT, pady=5)
  
        self.follow_step_checkbox = tk.Checkbutton(self.left_frame, text="焦点跟随", variable=self.follow_current_step)
        self.follow_step_checkbox.pack(side=tk.LEFT, pady=5)  # 跟随当前步骤勾选框
  
        # 右侧布局:框架分为预览区和树形视图区
        self.right_frame = tk.Frame(self.main_frame)
        self.right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)
  
        # 创建右侧预览面板
        self.preview_panel = tk.Frame(self.right_frame, width=120, height=120,
                                    relief=tk.GROOVE, borderwidth=1)  # 添加边框效果
        self.preview_panel.pack(side=tk.RIGHT, fill=tk.Y, padx=(5, 0))  # 调整右侧边距
        self.preview_panel.pack_propagate(False)
         
        # 使用更现代的标签样式
        self.preview_label = tk.Label(self.preview_panel, text="图像预览",
                                    font=('Arial', 9), fg='#666666')
        self.preview_label.pack(pady=3)
         
        # 创建预览图像容器
        self.preview_container = tk.Frame(self.preview_panel, 
                                        width=100, height=100,
                                        relief=tk.SUNKEN, borderwidth=1)
        self.preview_container.pack(pady=2)
        self.preview_container.pack_propagate(False)
         
        self.preview_image_label = tk.Label(self.preview_container, bg='#f5f5f5')
        self.preview_image_label.pack(expand=True)
  
        # 使用 Treeview 来显示图片和等待时间(调整包装方式)
        self.tree = ttk.Treeview(self.right_frame, columns=(
            "图片", "步骤名称", "相似度", "键盘输入", "鼠标点击坐标", "等待时间", "条件", "跳转步骤"
        ), show='headings')
        self.tree.heading("图片", text="图片")
        self.tree.heading("步骤名称", text="步骤名称")
        self.tree.heading("相似度", text="相似度")
        self.tree.heading("键盘输入", text="键盘输入")
        self.tree.heading("鼠标点击坐标", text="鼠标点击坐标(F2)")
        self.tree.heading("等待时间", text="等待时间 (毫秒)")
        self.tree.heading("条件", text="条件")
        self.tree.heading("跳转步骤", text="跳转步骤")
  
        # 设置列宽和对齐方式(居中)
        self.tree.column("图片", width=100, anchor='center')
        self.tree.column("步骤名称", width=100, anchor='center')
        self.tree.column("相似度", width=80, anchor='center')
        self.tree.column("键盘输入", width=100, anchor='center')
        self.tree.column("鼠标点击坐标", width=130, anchor='center')
        self.tree.column("等待时间", width=100, anchor='center')
        self.tree.column("条件", width=80, anchor='center')
        self.tree.column("跳转步骤", width=80, anchor='center')
          
        # 创建垂直滚动条
        self.scrollbar = ttk.Scrollbar(self.right_frame, orient="vertical", command=self.tree.yview)
        self.scrollbar.pack(side="right", fill="y")
  
        # 配置 Treeview 使用滚动条并调整其位置
        self.tree.configure(yscrollcommand=self.scrollbar.set)
        self.tree.pack(side="left", fill=tk.BOTH, expand=True)
  
        # 绑定热键
        self.root.bind(self.hotkey, self.toggle_script)
        self.root.bind('<F2>', self.get_mouse_position)  # 绑定 F2 键获取鼠标位置
  
        # 初始化上下文菜单
        self.tree.unbind('<Double-1>')
        self.tree.unbind('<Double-3>')
        self.tree.unbind('<Double-2>')
  
        # 为上下文菜单添加此绑定
        self.tree.bind('<Button-3>', self.show_context_menu)  # 右键点击
        self.tree.bind('<Button-1>', self.on_treeview_select)  # 左键点击
  
        # 在 Treeview 创建后添加选择事件绑定
        self.tree.bind('<<TreeviewSelect>>', self.on_treeview_select)
  
    def init_logging(self): # 初始化日志
        handler = RotatingFileHandler('app.log', maxBytes=10*1024*1024, backupCount=5)  # 创建日志文件处理器
        logging.basicConfig(handlers=[handler], level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # 配置日志格式
  
    def prepare_capture_screenshot(self):
        # 在截图前计算新的步骤编号
        existing_steps = set()
        for item in self.image_list:
            step_name = item[1]
            if step_name.startswith("步骤"):
                try:
                    num = int(step_name[2:])  # 提取"步骤"后面的数字
                    existing_steps.add(num)
                except ValueError:
                    continue
          
        # 找到最小的未使用编号
        new_step_num = 1
        while new_step_num in existing_steps:
            new_step_num += 1
          
        # 保存新步骤编号,供 on_button_release 使用
        self._next_step_num = new_step_num
  
        # 隐藏主窗口
        self.root.withdraw()
        time.sleep(0.5)
  
        # 创建一个全屏幕的透明窗口,用于捕获框选区域
        self.top = tk.Toplevel(self.root)
        self.top.attributes('-fullscreen', True)
        self.top.attributes('-alpha', 0.3)  # 透明度设置
  
        # 在窗口上创建 Canvas
        self.canvas = tk.Canvas(self.top, cursor="cross", bg='grey')
        self.canvas.pack(fill=tk.BOTH, expand=True)
  
        # 绑定鼠标事件
        self.canvas.bind("<ButtonPress-1>", self.on_button_press)
        self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
  
    def on_button_press(self, event):
        # 记录起始点坐标
        self.start_x = event.x
        self.start_y = 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):
        # 动态更新矩形框的大小
        cur_x, cur_y = (event.x, event.y)
        self.canvas.coords(self.rect, self.start_x, self.start_y, cur_x, cur_y)
  
    def on_button_release(self, event):
        # 记录终点坐标
        end_x = event.x
        end_y = 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))

        # 使用规则 "截图(时间).png" 命名截图文件避免重复
        timestamp = datetime.now().strftime("%H%M%S")  # 使用当前时间作为文件名
        screenshot_path = os.path.join(self.screenshot_folder, f"JT_{timestamp}.png")

        # 确保截图文件夹存在
        os.makedirs(self.screenshot_folder, exist_ok=True)

        # 截图指定区域
        screenshot = ImageGrab.grab(bbox)
        screenshot.save(screenshot_path)

        # 计算截图区域的中心坐标
        center_x = (min(self.start_x, end_x) + max(self.start_x, end_x)) // 2
        center_y = (min(self.start_y, end_y) + max(self.start_y, end_y)) // 2
        mouse_click_coordinates = f"{center_x},{center_y}"  # 使用中心坐标

        # 更新图像列表
        selected_item = self.tree.selection()  # 获取当前选中的项
        step_name = f"步骤{len(self.image_list) + 1}"  # 生成递增的步骤名称
        if selected_item:   
            selected_index = self.tree.index(selected_item[0])  
            self.image_list.insert(selected_index, (screenshot_path, step_name, 0.8, "", mouse_click_coordinates, 100, "", ""))  # 在选中项之前插入新项
        else:
            self.image_list.append((screenshot_path, step_name, 0.8, "", mouse_click_coordinates, 100, "", ""))  # 在列表末尾添加新项
        self.update_image_listbox()  # 更新图像列表框

        # 关闭全屏透明窗口
        self.top.destroy()
        self.root.deiconify()
  
    def update_image_listbox(self):
        try:
            # 保存当前预览状态
            current_preview = None
            if hasattr(self.preview_image_label, 'image'):
                current_preview = self.preview_image_label.image
             
            # 保存当前选中的项目
            selected_items = self.tree.selection()
            focused_item = self.tree.focus()
  
            # 清空旧的列表项
            for row in self.tree.get_children():
                self.tree.delete(row)
  
            # 插入新项,显示图片名称和等待时间
            for index, item in enumerate(self.image_list):
                try:
                    if not item or len(item) < 1:  # 检查项目是否有效
                        continue
                      
                    img_path = item[0]
                    if not os.path.exists(img_path):
                        print(f"警告:图像文件不存在 {img_path}")
                        logging.warning(f"图像文件不存在 {img_path}")
                        continue
  
                    # 确保每个项目都有 8 个元素,如果没有,用空字符串填充
                    full_item = list(item)
                    while len(full_item) < 8:
                        full_item.append("")
                      
                    img_path, step_name, similarity_threshold, keyboard_input, mouse_click_coordinates, wait_time, condition, jump_to = full_item
  
                    # 加载图像并创建缩略图
                    try:
                        image = Image.open(img_path)
                        image.thumbnail((50, 50))  # 调整缩略图大小
                        photo = ImageTk.PhotoImage(image)
  
                        # 插入所有数据
                        tree_item = self.tree.insert("", tk.END, values=(
                            os.path.basename(img_path), 
                            step_name, 
                            similarity_threshold, 
                            keyboard_input, 
                            mouse_click_coordinates, 
                            wait_time,
                            condition,
                            jump_to
                        ), image=photo)
                        self.tree.image_refs.append(photo)  # 保持对图像的引用
  
                    except Exception as e:
                        print(f"处理图像时出错 {img_path}: {e}")
                        logging.error(f"处理图像时出错 {img_path}: {e}")
  
                except Exception as e:
                    print(f"处理列表项时出错: {e}")
                    logging.error(f"处理列表项时出错: {e}")
  
            # 恢复选择状态
            if selected_items:
                for item in selected_items:
                    try:
                        self.tree.selection_add(item)
                    except:
                        pass
            if focused_item:
                try:
                    self.tree.focus(focused_item)
                except:
                    pass
  
            # 恢复预览状态
            if current_preview:
                self.preview_image_label.config(image=current_preview)
                self.preview_image_label.image = current_preview
             
        except Exception as e:
            print(f"更新图像列表时出错: {e}")
            logging.error(f"更新图像列表时出错: {e}")
            self.reset_to_initial_state()
  
    def delete_selected_image(self):
        try:
            selected_item = self.tree.selection()
            if not selected_item:
                messagebox.showinfo("提示", "请先选择要删除的项目")
                return
  
            selected_index = self.tree.index(selected_item[0])
            if 0 <= selected_index < len(self.image_list):
                selected_image = self.image_list[selected_index]
                img_path = selected_image[0]
                  
                # 检查是否正在使用配置文件
                if hasattr(self, 'config_filename') and os.path.exists(self.config_filename):
                    # 显示确认对话框
                    result = messagebox.askyesnocancel(
                        "确认删除",
                        f"当正在使用配置文件\n{self.config_filename}\n\n是否删除该步骤并更新配置文件?\n\n选择:\n"
                        f"是 - 删除步骤并更新配置文件(同时删除图片文件)\n"
                        f"否 - 仅删除步骤(保留图片文件)\n"
                        f"取消 - 不执行任何操作"
                    )
                      
                    if result is None:  # 用户点击取消
                        return
                      
                    # 删除图像列表中的项目
                    del self.image_list[selected_index]
                      
                    if result:  # 用户点击是,更新配置文件并删除图片
                        try:
                            # 读取现有配置
                            with open(self.config_filename, 'r') as f:
                                config = json.load(f)
                              
                            # 更新配置中的图像列表
                            config['image_list'] = self.image_list
                              
                            # 保存更新后的配置
                            with open(self.config_filename, 'w') as f:
                                json.dump(config, f)
                                  
                            # 检查是否有其他项目引用相同的图像文件
                            is_referenced = any(item[0] == img_path for item in self.image_list)
                              
                            # 如果没有其他引用,则删除图像文件
                            if not is_referenced and os.path.exists(img_path):
                                try:
                                    os.remove(img_path)
                                    print(f"图像文件已删除: {img_path}")
                                    logging.info(f"图像文件已删除: {img_path}")
                                except Exception as e:
                                    print(f"删除图像文件时出错: {e}")
                                    logging.error(f"删除图像文件时出错: {e}")
                              
                            print(f"配置文件已更新: {self.config_filename}")
                            logging.info(f"配置文件已更新: {self.config_filename}")
                            messagebox.showinfo("成功", "步骤已删除,配置文件已更新,图片已删除")
                        except Exception as e:
                            error_msg = f"更新配置文件时出错: {str(e)}"
                            print(error_msg)
                            logging.error(error_msg)
                            messagebox.showerror("错误", error_msg)
                    else:  # 用户点击否,仅删除步骤,保留图片
                        messagebox.showinfo("成功", "步骤已删除(图片文件已保留)")
                else:
                    # 如果没有使用配置文件,直接删除步骤和图片
                    if messagebox.askyesno("确认删除", "是否删除该步骤和对应的图片文件?"):
                        del self.image_list[selected_index]
                        # 检查是否有其他项目引用相同的图像文件
                        is_referenced = any(item[0] == img_path for item in self.image_list)
                        if not is_referenced and os.path.exists(img_path):
                            try:
                                os.remove(img_path)
                                print(f"图像文件已删除: {img_path}")
                                logging.info(f"图像文件已删除: {img_path}")
                            except Exception as e:
                                print(f"删除图像文件时出错: {e}")
                                logging.error(f"删除图像文件时出错: {e}")
                  
                self.update_image_listbox()
            else:
                print("选中的索引超出范围")
                logging.warning("选中的索引超出范围")
        except Exception as e:
            print(f"删除图像时出错: {e}")
            logging.error(f"删除图像时出错: {e}")
            self.reset_to_initial_state()
  
    def toggle_script(self, event=None):
        if not self.running:
            self.start_step_index = 0  # 确保从第一步开始
            self.start_script_thread()
            self.toggle_run_button.config(text="停止运行")
            if self.allow_minimize_var.get():  # 检查勾选框状态
                self.root.iconify()  # 最小化主窗口
            else:
                self.root.lift()  # 确保主窗口在最上层
                self.root.attributes('-topmost', True)  # 设置为最上层窗口
        else:
            self.stop_script()
            self.toggle_run_button.config(text="开始运行")
            self.root.attributes('-topmost', False)  # 取消最上层设置
  
    def start_script_thread(self):
        if not self.running:
            self.running = True
            self.thread = threading.Thread(target=self.run_script, daemon=True)
            self.thread.start()
  
    def run_script(self):
        try:
            self.loop_count = int(self.loop_count_entry.get())
            if self.loop_count < 0:
                raise ValueError("循环次数不能为负数")
        except ValueError as e:
            messagebox.showerror("输入错误", f"请输入有效的非负整数作为循环次数: {str(e)}")
            self.running = False
            self.root.after(0, self.update_ui_after_stop)
            return
  
        print(f"开始执行脚本,从步骤 {self.start_step_index} 开始,循环次数:{self.loop_count}")
        logging.info(f"开始执行脚本,从步骤 {self.start_step_index} 开始,循环次数:{self.loop_count}")
  
        current_loop = 0
  
        while self.running and (current_loop < self.loop_count or self.loop_count == 0):
            if self.paused:
                time.sleep(0.1)
                continue
        
            index = self.start_step_index
            while index < len(self.image_list) and self.running:
                current_step = self.image_list[index]
                img_path, img_name, similarity_threshold, keyboard_input, mouse_click_coordinates, wait_time = current_step[:6]
                condition = current_step[6] if len(current_step) > 6 else None
                jump_to = current_step[7] if len(current_step) > 7 else None
                
                # 执行一次图像匹配
                if mouse_click_coordinates:
                    match_result = self.match_and_click(img_path, similarity_threshold)
                elif os.path.exists(img_path):
                    match_result = self.match_and_click(img_path, similarity_threshold)
                else:
                    match_result = False

                # 等待时间处理
                if wait_time > 0:
                    time.sleep(wait_time / 1000.0)  # 将毫秒转换为秒

                # 处理键盘输入中的延时
                if keyboard_input:
                    commands = self.parse_keyboard_input(keyboard_input)
                    for cmd in commands:
                        if isinstance(cmd, float):  # 延时
                            time.sleep(cmd)
                        else:
                            # 处理其他键盘输入
                            pyautogui.press(cmd)

                # 处理条件跳转
                if condition and jump_to:
                    should_jump = False
                    if condition == "True" and match_result:
                        should_jump = True
                        print(f"条件为True且匹配成功,从{img_name}跳转到{jump_to}")
                    elif condition == "False" and not match_result:
                        should_jump = True
                        print(f"条件为False且匹配失败,从{img_name}跳转到{jump_to}")
                    
                    if should_jump:
                        # 查找跳转目标步骤的索引
                        for i, step in enumerate(self.image_list):
                            if step[1] == jump_to:
                                index = i
                                break
                        continue
                
                # 如果没有跳转且匹配失败,则持续尝试匹配
                if not match_result and not (condition and jump_to):
                    while not match_result and self.running:
                        time.sleep(wait_time / 1000.0)
                        if mouse_click_coordinates:
                            match_result = self.match_and_click(img_path, similarity_threshold)
                        elif os.path.exists(img_path):
                            match_result = self.match_and_click(img_path, similarity_threshold)

                index += 1

            current_loop += 1

        self.running = False
        self.root.after(0, self.update_ui_after_stop)
  
    def start_script_thread(self):
        if not self.running:
            self.running = True
            self.thread = threading.Thread(target=self.run_script, daemon=True)
            self.thread.start()
  
    def stop_script(self):
        self.running = False
        if self.thread is not None:
            self.thread.join(timeout=1)  # 等待线程结束,最多等待2秒
            if self.thread.is_alive():
                print("警告:脚本线程未能在1秒内停止")
                logging.warning("脚本线程未能在1秒内停运行")
        self.thread = None
        print("脚本已停止")
        logging.info("脚本已停止")
        self.root.after(0, self.update_ui_after_stop)
  
    def update_ui_after_stop(self):
        self.toggle_run_button.config(text="开始运行")
        self.root.deiconify()  # 恢复主窗口
  
    def move_item_up(self, event=None):
        selected_item = self.tree.selection()   
        if selected_item:
            selected_index = self.tree.index(selected_item[0])
            if selected_index > 0:
                  
                self.image_list[selected_index], self.image_list[selected_index - 1] = self.image_list[selected_index - 1], self.image_list[selected_index]
                self.update_image_listbox()
  
                  
                item_id = self.tree.get_children()[selected_index - 1]
                self.tree.selection_set(item_id)
                self.tree.focus(item_id)
  
    def move_item_down(self, event=None):
        selected_item = self.tree.selection()
        if selected_item:
            selected_index = self.tree.index(selected_item[0])
            if selected_index < len(self.image_list) - 1:
                  
                self.image_list[selected_index], self.image_list[selected_index + 1] = self.image_list[selected_index + 1], self.image_list[selected_index]
                self.update_image_listbox()
  
                  
                item_id = self.tree.get_children()[selected_index + 1]
                self.tree.selection_set(item_id)
                self.tree.focus(item_id)
  
    def match_and_click(self, template_path, similarity_threshold):
        # 获取当前步骤的完整信息
        selected_index = next((i for i, item in enumerate(self.image_list) if item[0] == template_path), None)
        if selected_index is not None:
            current_step = self.image_list[selected_index]
            mouse_coordinates = current_step[4]  # 获取鼠标坐标
            keyboard_input = current_step[3]  # 获取键盘输入
        else:
            mouse_coordinates = ""
            keyboard_input = ""
  
        # 获取屏幕截图
        screenshot = pyautogui.screenshot()
        screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
  
        # 读取模板图像
        template = cv2.imread(template_path)
  
        # 执行模板匹配
        result = cv2.matchTemplate(screenshot, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  
        # 如果相似度超过阈值
        if max_val >= similarity_threshold:
            # 先处理鼠标点击
            if mouse_coordinates:
                try:
                    if ":" in mouse_coordinates:
                        action, coords, _ = mouse_coordinates.split(":")
                        x, y = map(int, coords.split(","))
 
                        # 执行相应的鼠标操作
                        if action == "click":
                            pyautogui.click(x, y)
                        elif action == "rightClick":
                            # 使用 win32api 来模拟真实的右键点击
                            win32api.SetCursorPos((x, y))
                            # 模拟右键按下
                            win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN, x, y, 0, 0)
                            time.sleep(0.1)  # 短暂延迟
                            # 模拟右键释放
                            win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP, x, y, 0, 0)
                        elif action == "doubleClick":
                            pyautogui.doubleClick(x, y)
                        elif action == "mouseDown":
                            pyautogui.mouseDown(x, y)
                        elif action == "mouseUp":
                            pyautogui.mouseUp(x, y)
                    else:
                        # 处理旧格式的坐标(向后兼容)
                        x, y = map(int, mouse_coordinates.split(','))
                        pyautogui.click(x, y)
 
                    print(f"执行鼠标操作:{mouse_coordinates}")
                    logging.info(f"执行鼠标操作:{mouse_coordinates}")
                    return True
                except Exception as e:
                    print(f"鼠标操作时出错: {e}")
                    logging.error(f"鼠标操作时出错: {e}")
                    return False
  
            # 再处理键盘输入
            if keyboard_input:
                try:
                    time.sleep(0.5)  # 等待点击后的界面响应
                     
                    # 解析并执行键盘输入
                    commands = self.parse_keyboard_input(keyboard_input)
                    for cmd in commands:
                        if isinstance(cmd, tuple):  # 组合键
                            pyautogui.hotkey(*cmd)
                        elif isinstance(cmd, float):  # 延时
                            time.sleep(cmd)
                        else:  # 普通按键或特殊键
                            pyautogui.press(cmd)
                        time.sleep(0.1)  # 按键间短暂延时
                         
                    print(f"执行键盘输入:{keyboard_input}")
                    logging.info(f"执行键盘输入:{keyboard_input}")
                except Exception as e:
                    print(f"键盘输入时出错: {e}")
                    logging.error(f"键盘输入时出错: {e}")
  
            return True
        else:
            print(f"未找到匹配,最大相似度:{max_val}")
            logging.info(f"未找到匹配,最大相似度:{max_val}")
            return False
  
    def parse_keyboard_input(self, input_str):
        """解析键盘输入字符串,返回命令列表"""
        commands = []
        i = 0
        while i < len(input_str):
            if input_str[i] == '{':
                end = input_str.find('}', i)
                if end != -1:
                    cmd = input_str[i+1:end]
                    if cmd.startswith('delay:'):
                        # 处理延时命令
                        try:
                            delay_ms = int(cmd.split(':')[1])
                            commands.append(float(delay_ms / 1000))
                        except ValueError:
                            print(f"无效的延时值: {cmd}")
                    elif '+' in cmd:
                        # 处理组合键
                        keys = cmd.split('+')
                        commands.append(tuple(keys))
                    else:
                        # 处理特殊键
                        commands.append(cmd)
                    i = end + 1
                    continue
            # 处理普通字符
            commands.append(input_str[i])
            i += 1
        return commands
  
    def edit_keyboard_input(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
  
            # 创建置顶的对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("修改键盘输入")
            dialog.transient(self.root)
            dialog.grab_set()
            dialog.attributes('-topmost', True)
  
            # 创建主框架
            main_frame = tk.Frame(dialog)
            main_frame.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
  
            # 创建输入框和标签
            input_frame = tk.Frame(main_frame)
            input_frame.pack(fill=tk.X, pady=5)
            tk.Label(input_frame, text="键盘输入:").pack(side=tk.LEFT)
            entry = tk.Entry(input_frame, width=30)
            entry.insert(0, selected_image[3])
            entry.pack(side=tk.LEFT, padx=5)
  
            # 创建特殊键按钮框架
            special_keys_frame = tk.LabelFrame(main_frame, text="特殊键", padx=5, pady=5)
            special_keys_frame.pack(fill=tk.X, pady=5)
  
            special_keys = [
                'enter', 'tab', 'space', 'backspace', 'delete',
                'esc', 'home', 'end', 'pageup', 'pagedown',
                'up', 'down', 'left', 'right'
            ]
  
            # 创建特殊键按钮
            for i, key in enumerate(special_keys):
                btn = tk.Button(special_keys_frame, text=key.upper(),
                              command=lambda k=key: add_special_key(f"{{{k}}}"))
                btn.grid(row=i//7, column=i%7, padx=2, pady=2, sticky='ew')
  
            # 创建组合键框架
            combo_keys_frame = tk.LabelFrame(main_frame, text="组合键", padx=5, pady=5)
            combo_keys_frame.pack(fill=tk.X, pady=5)
  
            # 创建常用组合键按钮
            combo_keys = [
                ('复制', 'ctrl+c'), ('粘贴', 'ctrl+v'), ('剪切', 'ctrl+x'),
                ('全选', 'ctrl+a'), ('保存', 'ctrl+s'), ('撤销', 'ctrl+z')
            ]
  
            for i, (name, combo) in enumerate(combo_keys):
                btn = tk.Button(combo_keys_frame, text=name,
                              command=lambda c=combo: add_special_key(f"{{{c}}}"))
                btn.grid(row=i//3, column=i%3, padx=2, pady=2, sticky='ew')
  
            # 创建修饰键框架
            modifier_keys_frame = tk.LabelFrame(main_frame, text="修饰键", padx=5, pady=5)
            modifier_keys_frame.pack(fill=tk.X, pady=5)
  
            modifier_keys = ['ctrl', 'alt', 'shift', 'win']
            for i, key in enumerate(modifier_keys):
                btn = tk.Button(modifier_keys_frame, text=key.upper(),
                              command=lambda k=key: add_special_key(f"{{{k}}}"))
                btn.grid(row=0, column=i, padx=2, pady=2, sticky='ew')
  
            # 创建功能键框架
            function_keys_frame = tk.LabelFrame(main_frame, text="功能键", padx=5, pady=5)
            function_keys_frame.pack(fill=tk.X, pady=5)
  
            for i in range(12):
                btn = tk.Button(function_keys_frame, text=f"F{i+1}",
                              command=lambda k=i+1: add_special_key(f"{{f{k}}}"))
                btn.grid(row=i//6, column=i%6, padx=2, pady=2, sticky='ew')
  
            def add_special_key(key):
                current_pos = entry.index(tk.INSERT)
                entry.insert(current_pos, key)
                entry.focus_set()
  
            def save_input():
                new_keyboard_input = entry.get()
                self.image_list[selected_index] = (
                    selected_image[0], selected_image[1], selected_image[2],
                    new_keyboard_input, selected_image[4], selected_image[5],
                    selected_image[6], selected_image[7]
                )
                self.update_image_listbox()
                dialog.destroy()
  
            # 添加说明文本
            help_text = """
支持的输入格式:
1. 普通文本:直接输入
2. 特殊键:使用花括号,如 {enter}, {tab}
3. 组合键:使用加号连接,如 {ctrl+c}, {alt+tab}
4. 按键序列:直接连接,如 {ctrl+c}{enter}
5. 延时:{delay:1000} 表示延时1秒
            """
            tk.Label(main_frame, text=help_text, justify=tk.LEFT,
                    font=('Arial', 9)).pack(pady=5)
  
            # 添加保存和取消按钮
            button_frame = tk.Frame(main_frame)
            button_frame.pack(fill=tk.X, pady=10)
            tk.Button(button_frame, text="保存", command=save_input).pack(side=tk.RIGHT, padx=5)
            tk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT)
  
            # 居中显示对话框
            dialog.update_idletasks()
            x = self.root.winfo_x() + (self.root.winfo_width() - dialog.winfo_width()) // 2
            y = self.root.winfo_y() + (self.root.winfo_height() - dialog.winfo_height()) // 2
            dialog.geometry(f"+{x}+{y}")
  
    def set_hotkey(self):
        # 创建一个置顶的对话框
        dialog = tk.Toplevel(self.root)
        dialog.title("设置热键")
        dialog.transient(self.root)  # 将对话框设置为主窗口的临时窗口
        dialog.grab_set()  # 模态对话框
        dialog.attributes('-topmost', True)  # 设置窗口置顶
          
        # 创建输入框和标签
        tk.Label(dialog, text="请输入新的热键(例如:<F1>):").pack(pady=5)
        entry = tk.Entry(dialog)
        entry.insert(0, self.hotkey)
        entry.pack(pady=5)
          
        def save_hotkey():
            new_hotkey = entry.get()
            try:
                self.root.unbind(self.hotkey)
                self.root.bind(new_hotkey, self.toggle_script)
                self.hotkey = new_hotkey
                print(f"热键已更改为 {self.hotkey}")
                logging.info(f"热键已更改为 {self.hotkey}")
                messagebox.showinfo("设置热键", f"热键已更改为 {self.hotkey}")
                dialog.destroy()
            except tk.TclError as e:
                print(f"绑定热键失败: {e}")
                logging.error(f"绑定热键失败: {e}")
                messagebox.showerror("设置热键失败", f"无法绑定热键: {new_hotkey}")
          
        # 添加确定按钮
        tk.Button(dialog, text="确定", command=save_hotkey).pack(pady=10)
          
        # 居中显示对话框
        dialog.geometry("+%d+%d" % (
            self.root.winfo_rootx() + self.root.winfo_width()//2 - dialog.winfo_reqwidth()//2,
            self.root.winfo_rooty() + self.root.winfo_height()//2 - dialog.winfo_reqheight()//2
        ))
  
    def save_config(self):
        config = {
            'hotkey': self.hotkey,
            'similarity_threshold': self.similarity_threshold,
            'delay_time': self.delay_time,
            'loop_count': self.loop_count,
            'image_list': [img for img in self.image_list if os.path.exists(img[0])]
        }
        filename = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON files", "*.json")])
        if filename:
            try:
                with open(filename, 'w') as f:
                    json.dump(config, f)
                print(f"配置已保存到 {filename}")
                logging.info(f"配置已保存到 {filename}")
                self.config_filename = filename
                # 添加成功提示
                messagebox.showinfo("保存成功", f"配置已成功保存到:\n{filename}")
            except Exception as e:
                error_msg = f"保存配置时出错: {str(e)}"
                print(error_msg)
                logging.error(error_msg)
                messagebox.showerror("保存失败", error_msg)
  
    def load_config(self):
        try:
            if not os.path.exists(self.config_filename):
                raise FileNotFoundError(f"配置文件不存在: {self.config_filename}")
  
            # 先读取配置文件
            with open(self.config_filename, 'r') as f:
                config = json.load(f)
              
            # 在应用任何更改之前,先验证所有图像文件
            missing_images = []
            for img_data in config.get('image_list', []):
                if not os.path.exists(img_data[0]):
                    missing_images.append(img_data[0])
              
            # 如果有任何图像文件不存在,直接返回,不做任何更改
            if missing_images:
                error_message = f"&#65533;&#65533;&#65533;置加载失败。以下图像文&#65533;&#65533;不存在:\n" + "\n".join(missing_images)
                messagebox.showerror("错误", error_message)
                logging.error(error_message)
                return False
              
            # 只有当所有图像文件都存在时,才应用配置
            self.image_list = config.get('image_list', [])
            self.hotkey = config.get('hotkey', '<F1>')
            self.similarity_threshold = config.get('similarity_threshold', 0.8)
            self.delay_time = config.get('delay_time', 0.1)
            self.loop_count = config.get('loop_count', 1)
              
            # 清空并重新填充 Treeview
            for item in self.tree.get_children():
                self.tree.delete(item)
              
            for img_data in self.image_list:
                # 确保每个项目都有 8 个元素
                while len(img_data) < 8:
                    img_data = img_data + ("",)
                      
                # 加载图像并创建缩略图
                try:
                    image = Image.open(img_data[0])
                    image.thumbnail((50, 50))
                    photo = ImageTk.PhotoImage(image)
                      
                    # 插入到 Treeview
                    tree_item = self.tree.insert("", tk.END, values=(
                        os.path.basename(img_data[0]),  # &#65533;&#65533;片名称
                        img_data[1],  # 步骤名称
                        img_data[2],  # 相似度
                        img_data[3],  # 键盘输入
                        img_data[4],  # 鼠标点击坐标
                        img_data[5],  # 等待时间
                        img_data[6],  # 条件
                        img_data[7]   # 跳转步骤
                    ), image=photo)
                    self.tree.image_refs.append(photo)
                except Exception as e:
                    print(f"处理图像时出错 {img_data[0]}: {e}")
                    logging.error(f"处理图像时出错 {img_data[0]}: {e}")
              
            # 更新循环次数输入框
            self.loop_count_entry.delete(0, tk.END)
            self.loop_count_entry.insert(0, str(self.loop_count))
              
            # 重新绑定热键
            self.root.unbind(self.hotkey)
            self.root.bind(self.hotkey, self.toggle_script)
              
            print(f"配置已从 {self.config_filename} 加载")
            logging.info(f"配置已从 {self.config_filename} 加载")
              
            # 显示成功消息
            messagebox.showinfo("成功", f"配置已成功加载:\n{self.config_filename}")
            return True
              
        except Exception as e:
            error_message = f"加载配置时出错: {str(e)}"
            print(error_message)
            logging.error(error_message)
            messagebox.showerror("错误", error_message)
            return False
  
    def reset_to_initial_state(self):
        """重置程序到初始状态"""
        try:
            # 重置所有变量到初始值
            self.hotkey = '<F1>'
            self.similarity_threshold = 0.8
            self.delay_time = 0.1
            self.loop_count = 1
            self.image_list = []
            self.running = False
            self.paused = False
            self.start_step_index = 0
              
            # 重置界面元素
            self.update_image_listbox()
            self.root.bind(self.hotkey, self.toggle_script)
            self.loop_count_entry.delete(0, tk.END)
            self.loop_count_entry.insert(0, str(self.loop_count))
            self.toggle_run_button.config(text="开始运行")
            self.follow_current_step.set(False)
            self.allow_minimize_var.set(True)
              
            # 清空临时文件
            if os.path.exists(self.screenshot_folder):
                try:
                    shutil.rmtree(self.screenshot_folder)
                    os.makedirs(self.screenshot_folder)
                except Exception as e:
                    print(f"清理截图文件夹时出错: {e}")
                    logging.error(f"清理截图文件夹时出错: {e}")
              
            print("程序已重置为初始状态")
            logging.info("程序已重置为初始状态")
            messagebox.showinfo("重置", "程序已重置为初始状态")
        except Exception as e:
            print(f"重置程序状态时出错: {e}")
            logging.error(f"重置程序状态时出错: {e}")
  
    def load_config_manually(self):
        """手动加载配置文件"""
        file_path = filedialog.askopenfilename(filetypes=[("JSON files", "*.json")])
        if file_path:
            # 保存当前配置文件路径
            old_config_filename = self.config_filename
            self.config_filename = file_path
              
            # 尝试加载新配置
            if not self.load_config():
                # 如果加载失败,恢复原来的配置文件路径
                self.config_filename = old_config_filename
                # 重置到初始状态
                self.reset_to_initial_state()
  
    def get_mouse_position(self, event=None):
        # 获取当前鼠标位置
        x, y = pyautogui.position()
        messagebox.showinfo("鼠标位置", f"当前鼠标位置: ({x}, {y})")
  
        # 将鼠标位置存储到当前选中的图像中
        selected_item = self.tree.selection()
        if selected_item:
            selected_index = self.tree.index(selected_item[0])
            selected_image = self.image_list[selected_index]
            self.image_list[selected_index] = (selected_image[0], selected_image[1], selected_image[2], selected_image[3], f"{x},{y}", selected_image[5], selected_image[6], selected_image[7])
            self.update_image_listbox()
  
    def cleanup_on_exit(self):
        try:
            # 退出程序时删除未保存的图像
            if not os.path.exists(self.config_filename):
                for item in self.image_list:
                    img_path = item[0]
                    if os.path.exists(img_path):
                        try:
                            os.remove(img_path)
                            print(f"图像文件已删除: {img_path}")
                            logging.info(f"图像文件已删除: {img_path}")
                        except Exception as e:
                            print(f"删除图像文件时出错: {e}")
                            logging.error(f"删除图像文件时出错: {e}")
        except Exception as e:
            print(f"清理时出错: {e}")
            logging.error(f"清理时出错: {e}")
            self.reset_to_initial_state()
  
    def bind_arrow_keys(self):
        self.tree.bind('<Up>', self.move_item_up)
        self.tree.bind('<Down>', self.move_item_down)
  
    def move_item_up(self, event):
        selected_items = self.tree.selection()
        if not selected_items:
            return
  
        for item in selected_items:
            index = self.tree.index(item)
            if index > 0:
                self.tree.move(item, '', index - 1)
                self.image_list.insert(index - 1, self.image_list.pop(index))
  
        # 确保第一个选定项目可见
        self.tree.see(selected_items[0])
  
        # 阻止事件传播
        return "break"
  
    def move_item_down(self, event):
        selected_items = self.tree.selection()
        if not selected_items:
            return
  
        for item in reversed(selected_items):
            index = self.tree.index(item)
            if index < len(self.image_list) - 1:
                self.tree.move(item, '', index + 1)
                self.image_list.insert(index + 1, self.image_list.pop(index))
  
        # 确保最后一项可见
        self.tree.see(selected_items[-1])
  
        # 阻止事件传播
        return "break"
  
    def test_single_match(self):
        selected_items = self.tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择一个项目进行测试")
            return
  
        selected_item = selected_items[0]
        selected_index = self.tree.index(selected_item)
        img_data = self.image_list[selected_index]
          
        # 使用新的解包方式,适应可能存在的额外字段
        img_path, img_name, similarity_threshold = img_data[:3]
  
        # 执行匹配测试
        matched, location = self.match_template(img_path, float(similarity_threshold))
  
        if matched:
            messagebox.showinfo("匹配结果", f"成功匹配到图像 '{img_name}'\n位置: {location}")
        else:
            messagebox.showinfo("匹配结果", f"未能匹配到图像 '{img_name}'")
  
    def match_template(self, template_path, similarity_threshold):
        # 截取当前屏幕
        screenshot = pyautogui.screenshot()
        screenshot = np.array(screenshot)
        screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)
  
        # 读取模板图像
        template = cv2.imread(template_path, 0)
        if template is None:
            print(f"无法读取图像: {template_path}")
            logging.error(f"无法读取图像: {template_path}")
            return False, None
  
        # 转换截图为灰度图像
        gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
  
        # 进行模板匹配
        result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  
        if max_val >= similarity_threshold:
            # 计算匹配位置的中心点
            h, w = template.shape
            center_x = max_loc[0] + w // 2
            center_y = max_loc[1] + h // 2
            return True, (center_x, center_y)
        else:
            return False, None
  
    def create_context_menu(self):
        self.context_menu = tk.Menu(self.root, tearoff=0, postcommand=self.update_context_menu)
        self.context_menu.add_command(label="编辑步骤名称", command=self.edit_image_name)
        self.context_menu.add_command(label="编辑相似度", command=self.edit_similarity_threshold)
        self.context_menu.add_command(label="编辑键盘输入", command=self.edit_keyboard_input)
        self.context_menu.add_command(label="编辑鼠标操作", command=self.edit_mouse_action)  # 添加这一行
        self.context_menu.add_command(label="编辑等待时间", command=self.edit_wait_time)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="复制", command=self.copy_item)
        self.context_menu.add_command(label="粘贴", command=self.paste_item)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="从此步骤开始运行", command=self.start_from_step)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="设置条件跳转", command=self.set_condition_jump)
  
    def update_context_menu(self):
        # 在显示菜单前更新菜单状态
        selected = self.tree.selection()
        # 可以在这里根据选择状态启用/禁用某些菜单项
  
    def start_from_step(self):
        selected_items = self.tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择一个项目进行操作")
            return
  
        # 获取当前选中的步骤索引
        selected_item = selected_items[0]
        self.start_step_index = self.tree.index(selected_item)  # 使用当前选中的步骤索引
        self.start_script_thread()  # 启动脚本
  
        # 更新按钮文本以指示脚本正在运行
        self.toggle_run_button.config(text="停止运行")
  
    def show_context_menu(self, event):
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_clear()
            self.tree.selection_set(item)
            self.tree.focus(item)
            # 使用after方法延迟显示菜单
            self.root.after(1, lambda: self.context_menu.post(event.x_root, event.y_root))
        return "break"
  
    def copy_item(self):
        selected_items = self.tree.selection()
        if selected_items:
            index = self.tree.index(selected_items[0])
            original_item = self.image_list[index]
              
            # 创建像文件的副本
            new_image_path = self.create_image_copy(original_item[0])
              
            # 创建新的元组,使用新的图像路径
            self.copied_item = (new_image_path,) + tuple(original_item[1:])
  
    def create_image_copy(self, original_path):
        # 创建图像文件的副本
        base_name = os.path.basename(original_path)
        name, ext = os.path.splitext(base_name)
        new_name = f"{name}_copy{ext}"
        new_path = os.path.join(self.screenshot_folder, new_name)
        shutil.copy2(original_path, new_path)
        return new_path
      
    def paste_item(self):
        if self.copied_item:
            target = self.tree.focus()
            if target:
                target_index = self.tree.index(target)
                # 使用与复制的项相同的数据创新元组
                new_item = tuple(self.copied_item)
                self.image_list.insert(target_index + 1, new_item)  # 插入到下方
                self.update_image_listbox()
  
    def edit_image_name(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
  
            new_image_name = simpledialog.askstring("修改步骤名称", "请输入新的步骤名称:", initialvalue=selected_image[1])
            if new_image_name is not None:
                self.image_list[selected_index] = (selected_image[0], new_image_name, selected_image[2], selected_image[3], selected_image[4], selected_image[5], selected_image[6], selected_image[7])
                self.update_image_listbox()
  
    def edit_similarity_threshold(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
  
            new_similarity_threshold = simpledialog.askfloat("修改相似度", "请输入新的相似度(0.1 - 1.0):", initialvalue=selected_image[2], minvalue=0.1, maxvalue=1.0)
            if new_similarity_threshold is not None:
                self.image_list[selected_index] = (selected_image[0], selected_image[1], new_similarity_threshold, selected_image[3], selected_image[4], selected_image[5], selected_image[6], selected_image[7])
                self.update_image_listbox()
  
    def edit_keyboard_input(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
  
            # 创建置顶的对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("修改键盘输入")
            dialog.transient(self.root)
            dialog.grab_set()
            dialog.attributes('-topmost', True)
  
            # 创建主框架
            main_frame = tk.Frame(dialog)
            main_frame.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
  
            # 创建输入框和标签
            input_frame = tk.Frame(main_frame)
            input_frame.pack(fill=tk.X, pady=5)
            tk.Label(input_frame, text="键盘输入:").pack(side=tk.LEFT)
            entry = tk.Entry(input_frame, width=30)
            entry.insert(0, selected_image[3])
            entry.pack(side=tk.LEFT, padx=5)
  
            # 创建特殊键按钮框架
            special_keys_frame = tk.LabelFrame(main_frame, text="特殊键", padx=5, pady=5)
            special_keys_frame.pack(fill=tk.X, pady=5)
  
            special_keys = [
                'enter', 'tab', 'space', 'backspace', 'delete',
                'esc', 'home', 'end', 'pageup', 'pagedown',
                'up', 'down', 'left', 'right'
            ]
  
            # 创建特殊键按钮
            for i, key in enumerate(special_keys):
                btn = tk.Button(special_keys_frame, text=key.upper(),
                              command=lambda k=key: add_special_key(f"{{{k}}}"))
                btn.grid(row=i//7, column=i%7, padx=2, pady=2, sticky='ew')
  
            # 创建组合键框架
            combo_keys_frame = tk.LabelFrame(main_frame, text="组合键", padx=5, pady=5)
            combo_keys_frame.pack(fill=tk.X, pady=5)
  
            # 创建常用组合键按钮
            combo_keys = [
                ('复制', 'ctrl+c'), ('粘贴', 'ctrl+v'), ('剪切', 'ctrl+x'),
                ('全选', 'ctrl+a'), ('保存', 'ctrl+s'), ('撤销', 'ctrl+z')
            ]
  
            for i, (name, combo) in enumerate(combo_keys):
                btn = tk.Button(combo_keys_frame, text=name,
                              command=lambda c=combo: add_special_key(f"{{{c}}}"))
                btn.grid(row=i//3, column=i%3, padx=2, pady=2, sticky='ew')
  
            # 创建修饰键框架
            modifier_keys_frame = tk.LabelFrame(main_frame, text="修饰键", padx=5, pady=5)
            modifier_keys_frame.pack(fill=tk.X, pady=5)
  
            modifier_keys = ['ctrl', 'alt', 'shift', 'win']
            for i, key in enumerate(modifier_keys):
                btn = tk.Button(modifier_keys_frame, text=key.upper(),
                              command=lambda k=key: add_special_key(f"{{{k}}}"))
                btn.grid(row=0, column=i, padx=2, pady=2, sticky='ew')
  
            # 创建功能键框架
            function_keys_frame = tk.LabelFrame(main_frame, text="功能键", padx=5, pady=5)
            function_keys_frame.pack(fill=tk.X, pady=5)
  
            for i in range(12):
                btn = tk.Button(function_keys_frame, text=f"F{i+1}",
                              command=lambda k=i+1: add_special_key(f"{{f{k}}}"))
                btn.grid(row=i//6, column=i%6, padx=2, pady=2, sticky='ew')
  
            def add_special_key(key):
                current_pos = entry.index(tk.INSERT)
                entry.insert(current_pos, key)
                entry.focus_set()
  
            def save_input():
                new_keyboard_input = entry.get()
                self.image_list[selected_index] = (
                    selected_image[0], selected_image[1], selected_image[2],
                    new_keyboard_input, selected_image[4], selected_image[5],
                    selected_image[6], selected_image[7]
                )
                self.update_image_listbox()
                dialog.destroy()
  
            # 添加说明文本
            help_text = """
支持的输入格式:
1. 普通文本:直接输入
2. 特殊键:使用花括号,如 {enter}, {tab}
3. 组合键:使用加号连接,如 {ctrl+c}, {alt+tab}
4. 按键序列:直接连接,如 {ctrl+c}{enter}
5. 延时:{delay:1000} 表示延时1秒
            """
            tk.Label(main_frame, text=help_text, justify=tk.LEFT,
                    font=('Arial', 9)).pack(pady=5)
  
            # 添加保存和取消按钮
            button_frame = tk.Frame(main_frame)
            button_frame.pack(fill=tk.X, pady=10)
            tk.Button(button_frame, text="保存", command=save_input).pack(side=tk.RIGHT, padx=5)
            tk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT)
  
            # 居中显示对话框
            dialog.update_idletasks()
            x = self.root.winfo_x() + (self.root.winfo_width() - dialog.winfo_width()) // 2
            y = self.root.winfo_y() + (self.root.winfo_height() - dialog.winfo_height()) // 2
            dialog.geometry(f"+{x}+{y}")
  
    def edit_wait_time(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
  
            new_wait_time = simpledialog.askinteger("修改等待时间", "请输入新的等待时间(毫秒):", initialvalue=selected_image[5])
            if new_wait_time is not None:
                self.image_list[selected_index] = (selected_image[0], selected_image[1], selected_image[2], selected_image[3], selected_image[4], new_wait_time, selected_image[6], selected_image[7])
                self.update_image_listbox()
  
    def set_condition_jump(self):
        selected_items = self.tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择一个项目")
            return
  
        selected_item = selected_items[0]
        selected_index = self.tree.index(selected_item)
        selected_image = list(self.image_list[selected_index])  # 转换为列表以便修改
  
        # 创建设置条件跳转的对话框
        dialog = tk.Toplevel(self.root)
        dialog.title("设置条件跳转")
        dialog.geometry("300x150")
        dialog.transient(self.root)
        dialog.grab_set()
        dialog.attributes('-topmost', True)  # 设置窗口置顶
  
        # 条件选择
        condition_frame = tk.Frame(dialog)
        condition_frame.pack(pady=5)
        tk.Label(condition_frame, text="条件:").pack(side=tk.LEFT)
        condition_var = tk.StringVar(value=selected_image[6] if len(selected_image) > 6 else "")
        condition_combo = ttk.Combobox(condition_frame, textvariable=condition_var, values=["True", "False"], width=10)
        condition_combo.pack(side=tk.LEFT)
  
        # 跳转步骤选择
        jump_frame = tk.Frame(dialog)
        jump_frame.pack(pady=5)
        tk.Label(jump_frame, text="跳转到步骤:").pack(side=tk.LEFT)
        jump_var = tk.StringVar(value=selected_image[7] if len(selected_image) > 7 else "")
        step_names = [img[1] for img in self.image_list]  # 获取所有步骤名称
        jump_combo = ttk.Combobox(jump_frame, textvariable=jump_var, values=step_names, width=15)
        jump_combo.pack(side=tk.LEFT)
  
        def save_condition():
            condition = condition_var.get()
            jump_to = jump_var.get()
              
            # 确保选择了有效的条件和跳转步骤
            if not condition or not jump_to:
                messagebox.showwarning("警告", "请选择条件和跳转步骤")
                return
                  
            try:
                # 确保列表长度至少为8
                while len(selected_image) < 8:
                    selected_image.append("")
                  
                # 更新条件和跳转步骤
                selected_image[6] = condition
                selected_image[7] = jump_to
                  
                # 更新 image_list 中的数据
                self.image_list[selected_index] = tuple(selected_image)
                  
                # 立即更新显示
                self.update_image_listbox()
                  
                # 选中更新后的项
                items = self.tree.get_children()
                if selected_index < len(items):
                    self.tree.selection_set(items[selected_index])
                    self.tree.focus(items[selected_index])
                  
                # 打印日志确认更新
                print(f"已更新条件跳转设置:条件={condition}, 跳转到={jump_to}")
                logging.info(f"已更新条件跳转设置:条件={condition}, 跳转到={jump_to}")
                  
                dialog.destroy()
            except Exception as e:
                error_msg = f"保存条件跳转设置时出错: {str(e)}"
                print(error_msg)
                logging.error(error_msg)
                messagebox.showerror("错误", error_msg)
  
        # 保存按钮
        tk.Button(dialog, text="保存", command=save_condition).pack(pady=10)
  
        # 等待窗口绘制完成
        dialog.update_idletasks()
          
        # 计算居中位置
        x = self.root.winfo_x() + (self.root.winfo_width() - dialog.winfo_width()) // 2
        y = self.root.winfo_y() + (self.root.winfo_height() - dialog.winfo_height()) // 2
          
        # 设置窗口位置
        dialog.geometry(f"+{x}+{y}")
  
    def on_treeview_select(self, event):
        """当 Treeview 选择改变时更新预览图像"""
        selected_items = self.tree.selection()
        if not selected_items:
            # 清空预览
            self.preview_image_label.config(image='')
            return
         
        selected_item = selected_items[0]
        selected_index = self.tree.index(selected_item)
         
        try:
            # 获取选中项的图像路径
            img_path = self.image_list[selected_index][0]
             
            # 检查文件是否存在
            if not os.path.exists(img_path):
                self.preview_image_label.config(image='')
                return
             
            # 加载原始图像
            original_image = Image.open(img_path)
             
            # 获取原始尺寸
            original_width, original_height = original_image.size
             
            # 设置最大预览尺寸
            max_width = 90
            max_height = 90
             
            # 计算缩放比例
            width_ratio = max_width / original_width
            height_ratio = max_height / original_height
            scale_ratio = min(width_ratio, height_ratio)
             
            # 计算新的尺寸
            new_width = int(original_width * scale_ratio)
            new_height = int(original_height * scale_ratio)
             
            # 调整图像大小
            resized_image = original_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
            # 创建 PhotoImage 对象
            photo = ImageTk.PhotoImage(resized_image)
             
            # 更新预览标签
            self.preview_image_label.config(image=photo)
            self.preview_image_label.image = photo  # 保持引用以防止垃圾回收
             
            # 添加尺寸信息到预览标签
            self.preview_label.config(text=f"图像预览 ({original_width}x{original_height})")
             
        except Exception as e:
            print(f"预览图像时出错: {e}")
            logging.error(f"预览图像时出错: {e}")
            self.preview_image_label.config(image='')
  
    def edit_mouse_action(self):
        selected_items = self.tree.selection()
        if selected_items:
            selected_item = selected_items[0]
            selected_index = self.tree.index(selected_item)
            selected_image = self.image_list[selected_index]
 
            # 创建置顶的对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("设置鼠标操作")
            dialog.transient(self.root)
            dialog.grab_set()
            dialog.attributes('-topmost', True)
 
            # 创建主框架
            main_frame = tk.Frame(dialog)
            main_frame.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
 
            # 创建坐标输入框
            coord_frame = tk.Frame(main_frame)
            coord_frame.pack(fill=tk.X, pady=5)
            tk.Label(coord_frame, text="坐标 (x,y):").pack(side=tk.LEFT)
            coord_entry = tk.Entry(coord_frame, width=20)
            coord_entry.pack(side=tk.LEFT, padx=5)
 
            # 解析现有的鼠标操作数据
            current_action = "click"
            current_coords = ""
             
            if selected_image[4]:  # 如果有现有的鼠标操作数据
                try:
                    if ":" in selected_image[4]:
                        action, coords, _ = selected_image[4].split(":")
                        current_action = action
                        current_coords = coords
                    else:
                        current_coords = selected_image[4]
                except:
                    pass
             
            coord_entry.insert(0, current_coords)
 
            # 创建鼠标动作选择框架
            action_frame = tk.LabelFrame(main_frame, text="鼠标动作", padx=5, pady=5)
            action_frame.pack(fill=tk.X, pady=5)
 
            # 鼠标动作类型
            action_var = tk.StringVar(value=current_action)
            actions = [
                ("左键单机", "click"),
                ("右键单击", "rightClick"),
                ("双击", "doubleClick"),
                ("按住", "mouseDown"),
                ("释放", "mouseUp")
            ]
 
            for text, value in actions:
                tk.Radiobutton(action_frame, text=text, value=value, 
                              variable=action_var).pack(anchor=tk.W)
 
            def save_mouse_action():
                try:
                    # 获取坐标
                    coords = coord_entry.get().strip()
                    if not coords or "," not in coords:
                        messagebox.showerror("错误", "请输入有效的坐标 (x,y)")
                        return
 
                    # 验证坐标格式
                    try:
                        x, y = map(int, coords.split(","))
                    except ValueError:
                        messagebox.showerror("错误", "坐标必须是整数")
                        return
 
                    # 获取动作类型
                    action = action_var.get()
 
                    # 构建鼠标操作字符串
                    mouse_action = f"{action}:{coords}:0.0"
 
                    # 更新图像列表
                    self.image_list[selected_index] = (
                        selected_image[0], selected_image[1], selected_image[2],
                        selected_image[3], mouse_action, selected_image[5],
                        selected_image[6], selected_image[7]
                    )
                    self.update_image_listbox()
                    dialog.destroy()
 
                except Exception as e:
                    messagebox.showerror("错误", f"保存鼠标操作时出错: {str(e)}")
 
            # 添加说明文本
            help_text = """
支持的鼠标操作:
1. 左键单击:在指定位置单击左键
2. 右键单击:在指定位置单击右键
3. 双击:在指定位置双击左键
4. 按住:按住鼠标左键
5. 释放:释放鼠标左键
 
注意:
- 先提前使用F2获取鼠标位置
- 坐标格式为"x,y"(例如:100,200)
        """
            tk.Label(main_frame, text=help_text, justify=tk.LEFT,
                    font=('Arial', 9)).pack(pady=5)
 
            # 添加保存和取消按钮
            button_frame = tk.Frame(main_frame)
            button_frame.pack(fill=tk.X, pady=10)
            tk.Button(button_frame, text="保存", command=save_mouse_action).pack(side=tk.RIGHT, padx=5)
            tk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT)
 
            # 居中显示对话框
            dialog.update_idletasks()
            x = self.root.winfo_x() + (self.root.winfo_width() - dialog.winfo_width()) // 2
            y = self.root.winfo_y() + (self.root.winfo_height() - dialog.winfo_height()) // 2
            dialog.geometry(f"+{x}+{y}")
 
if __name__ == "__main__":
    root = tk.Tk()
    app = ImageRecognitionApp(root)
    root.mainloop()

本来不想打包的,毕竟这玩意打包后有点大(python通病)

2024/09/30更新
1.增加鼠标按坐标位置点击
使用方法:选中列表的相应图像,按下F2获取鼠标位置的坐标,点击确认后自动将坐标插入到图片中,当脚本运行到该行时,只点击鼠标坐标。
2.修复删除图像后相应的图像文件还存在的问题

2024/10/08更新
1.去掉上传图像功能, 感觉这个功能的存在没有必要
2.修复手动点击停止运行时程序偶尔卡死的问题
3.保存配置增加自定义配置文件名
4.增加列表上下移动功能,按上下键盘可移动指定的行,可单行或多行同时移动
5.修改循环次数,如果为0则无限循环运行
6.如果没有保存配置,退出程序后将不保留本次框选的所有的图像
7.现在程序在运行时会自动最小化到任务栏,避免遮住匹配的项目

2024/10/09更新
1.增加测试匹配功能,用来测试框选的图像是否成功匹配到,如果匹配失败可适当调整相似度
2.为了节省资源,增加复制粘贴脚本行功能(上下两条相同的脚本行可实现双击打开效果)
3.现在将所有的编辑功能都集成到右键菜单去了


2024/10/11更新
延迟功能回归


2024/10/18紧急更新
修复一些列错误问题,新版本链接同上

2024/10/23更新
1.增加程序在运行时是否允许最小化的选择
2.增加焦点跟随,当运行脚本时会直观的看到当前运行到哪一步骤,例如在运行时卡住的情况下可以看到是卡在哪一步。
3.增加从某一步骤开始运行脚本的功能,已集成到右键菜单。
4.去掉图片路径显示
5.修复当鼠标坐标存在时无论是否匹配到图像都去点击的问题
6.修正键盘输入时区分大小写
其它一些细节优化和小BUG修复

已重新上传下载地址
目前任在维护软件,如有建议欢迎提出,脱离本软件主题的无视。
后期功能预告:
实现更复杂的条件逻辑,如IF-ELSE语句和OCR功能,允许基于屏幕文字内容进行操作,但这是大工程,有空再慢慢去完善了。
反正源码我也放出来了,有能力的自己也可以去做。



2024/10/28 更新
1.增加按条件(真/假)跳转到指定步骤
2.修改删除图片的逻辑
3.修改加载配置文件失败的逻辑
其它优化和BUG修复


2024/11/01 更新
增加局部循环功能,允许指定步骤范围进行重复执行。
使用方法:
例如,如果你想让步骤2到步骤4循环执行3次:
选中步骤2
右键 -> 设置循环区间
在弹出窗口中:
选择"步骤4"作为结束步骤
输入循环次数"3"

最近很忙,没有时间关注,简单的做了下更新。
2024.12/4
增加图像预览
扩展鼠标和键盘的操作
源码已跟新
下载地址:https://wwts.lanzoub.com/iYVD82h7u5gd密码:cknr

免费评分

参与人数 33吾爱币 +36 热心值 +30 收起 理由
tianbowen0 + 1 + 1 高分屏下,更改高DPI设置后,图像预览和框选截图的区域完全不一致,需要楼 ...
zxc237283 + 1 + 1 赞!!!!
etz2008 + 1 + 1 12/4号更新的能打包吗?谢谢
xiaocao + 1 + 1 如果把循环次数延时秒数就好
寂寞才说爱 + 1 + 1 谢谢@Thanks!
tdjsmzzz + 1 + 1 谢谢@Thanks!
碧波江影 + 1 + 1 谢谢@Thanks!
mikelang + 1 + 1 我很赞同!
笨蛋笨蛋笨蛋! + 1 + 1 大佬 能添加个识别到图片后自动按键盘的某个键的功能吗
23923235 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
初雪樱 + 1 + 1 谢谢@Thanks!
wazyl + 1 + 1 我很赞同!
Msy丿平 + 1 + 1 谢谢@Thanks!
zc55100 + 1 + 1 我很赞同!
alohayan + 1 + 1 谢谢@Thanks!
Livemy + 1 用心讨论,共获提升!
irfox + 1 我很赞同!
治不好的拖延症 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
soar550 + 1 + 1 谢谢@Thanks!
ruanxiaoqi + 1 + 1 我很赞同!
fcml45 + 1 + 1 谢谢@Thanks!
yueguangxiaxian + 1 + 1 鼓励转贴优秀软件安全工具和文档!
wapjsx + 1 + 1 谢谢@Thanks!
kingzswang + 1 + 1 我很赞同!
小色虫 + 1 大佬,来做个quicker动作
MiluChild + 1 + 1 用心讨论,共获提升!
lkl2425572 + 1 期待成品,感谢楼主
yjn866y + 1 + 1 热心回复!
chesterche + 1 可以后台吗?
helh0275 + 1 + 1 谢谢@Thanks!
zhufengwan + 1 + 1 谢谢@Thanks!
lgc81034 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

来自 2#
zkpy 发表于 2024-9-20 04:13
本帖最后由 zkpy 于 2024-9-21 22:48 编辑

[Asm] 纯文本查看 复制代码
import tkinter as tk
from tkinter import filedialog, simpledialog, ttk, messagebox
from PIL import Image, ImageTk, ImageGrab, ImageEnhance
import cv2
import numpy as np
import pyautogui
import time
import random
import threading
import logging
import os
import json
import tkinter.scrolledtext as scrolledtext
import queue

class QueueHandler(logging.Handler):
    def __init__(self, log_queue):
        super().__init__()
        self.log_queue = log_queue

    def emit(self, record):
        self.log_queue.put(self.format(record))

class ImageRecognitionApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图像识别与点击")
        self.image_list = []
        self.init_variables()
        self.init_ui()
        self.log_queue = queue.Queue()
        self.init_logging()
        self.load_config()
        self.root.after(100, self.check_log_queue)
 
    def init_variables(self):
        self.screenshot_area = None
        self.rect = None
        self.start_x = self.start_y = None
        self.canvas = None
        self.running = False
        self.thread = None
        self.hotkey = '<F1>'
        self.similarity_threshold = 0.8
        self.delay_time = 0.1
        self.loop_count = 1
        self.selected_image = None
        self.preview_photo = None  # Added to store the reference to the preview image
 
    def init_ui(self):
        self.main_frame = tk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.left_right_frame = tk.Frame(self.main_frame)
        self.left_right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.create_left_frame()
        self.create_right_frame()
        self.create_log_area()  # Moved to the right

        self.root.bind(self.hotkey, self.toggle_script)
 
    def create_left_frame(self):
        self.left_frame = tk.Frame(self.left_right_frame)
        self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
 
        buttons = [
            ("上传图像", self.batch_upload_images),
            ("框选截图", self.prepare_capture_screenshot),
            ("删除图片", self.delete_selected_image),
            ("保存配置", self.save_config),
            ("设置热键", self.set_hotkey),
            ("加载配置", self.load_config_manually)
        ]
 
        for text, command in buttons:
            tk.Button(self.left_frame, text=text, command=command).pack(pady=5)
 
        self.toggle_run_button = tk.Button(self.left_frame, text="开始运行", command=self.toggle_script)
        self.toggle_run_button.pack(pady=5)
 
        self.create_loop_count_input()
 
    def create_loop_count_input(self):
        tk.Label(self.left_frame, text="循环次数:").pack(pady=5)
        self.loop_count_entry = tk.Entry(self.left_frame)
        self.loop_count_entry.insert(0, str(self.loop_count))
        self.loop_count_entry.pack(pady=5)
 
    def create_right_frame(self):
        self.right_frame = tk.Frame(self.left_right_frame)
        self.right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
 
        self.create_tree_view()
        self.create_preview_area()
 
    def create_tree_view(self):
        self.tree_frame = tk.Frame(self.right_frame)
        self.tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
 
        columns = ("图片", "步骤名称", "相似度", "键盘输入")
        self.tree = ttk.Treeview(self.tree_frame, columns=columns, show='headings')
         
        for col in columns:
            self.tree.heading(col, text=col)
            self.tree.column(col, width=100)
 
        self.tree.column("图片", width=200)
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, pady=5)
        self.tree.bind('<<TreeviewSelect>>', self.on_treeview_select)
        self.tree.bind('<Double-1>', self.edit_image_name)
        self.tree.bind('<Double-3>', self.edit_similarity_threshold)
        self.tree.bind('<Double-2>', self.edit_keyboard_input)
 
        self.scrollbar = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.configure(yscrollcommand=self.scrollbar.set)
 
    def create_preview_area(self):
        self.preview_frame = tk.Frame(self.right_frame)
        self.preview_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, pady=10)
 
        tk.Label(self.preview_frame, text="图片预览").pack()
        self.preview_image = tk.Label(self.preview_frame)
        self.preview_image.pack()
 
    def create_log_area(self):
        self.log_frame = tk.Frame(self.main_frame)
        self.log_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)

        tk.Label(self.log_frame, text="日志输出").pack()

        self.log_area = scrolledtext.ScrolledText(self.log_frame, wrap=tk.WORD, width=30, height=20)
        self.log_area.pack(fill=tk.Y, expand=True)
        self.log_area.config(state=tk.DISABLED)  # Set to read-only

    def check_log_queue(self):
        while not self.log_queue.empty():
            message = self.log_queue.get()
            self.log_message(message)
        self.root.after(100, self.check_log_queue)

    def log_message(self, message):
        self.log_area.config(state=tk.NORMAL)
        self.log_area.insert(tk.END, message + "\n")
        self.log_area.see(tk.END)
        self.log_area.config(state=tk.DISABLED)

    def init_logging(self):
        logging.getLogger().setLevel(logging.INFO)
        
        # 设置文件处理器
        file_handler = logging.FileHandler('app.log')
        file_handler.setLevel(logging.INFO)
        file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        file_handler.setFormatter(file_formatter)
        
        # 设置队列处理器(用于GUI显示)
        queue_handler = QueueHandler(self.log_queue)
        queue_handler.setLevel(logging.INFO)
        queue_formatter = logging.Formatter('%(message)s')  # 简化的格式,只显示消息
        queue_handler.setFormatter(queue_formatter)
        
        # 获取 root logger 并添加处理器
        logger = logging.getLogger()
        logger.addHandler(file_handler)
        logger.addHandler(queue_handler)

    def batch_upload_images(self):
        file_paths = filedialog.askopenfilenames(filetypes=[("Image files", "*.jpg *.png")])
        if file_paths:
            for file_path in file_paths:
                self.image_list.append((file_path, os.path.basename(file_path), 0.8, ""))
            self.update_image_listbox()
 
    def prepare_capture_screenshot(self):
        self.root.withdraw()
        time.sleep(0.5)
        self.create_screenshot_window()
 
    def create_screenshot_window(self):
        self.top = tk.Toplevel(self.root)
        self.top.attributes('-fullscreen', True, '-alpha', 0.3)
        self.canvas = tk.Canvas(self.top, cursor="cross", bg='grey')
        self.canvas.pack(fill=tk.BOTH, expand=True)
        self.canvas.bind("<ButtonPress-1>", self.on_button_press)
        self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
 
    def on_button_press(self, event):
        self.start_x = event.x
        self.start_y = 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):
        cur_x, cur_y = (event.x, event.y)
        self.canvas.coords(self.rect, self.start_x, self.start_y, cur_x, cur_y)
 
    def on_button_release(self, event):
        end_x = event.x
        end_y = 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))
 
        timestamp = f"JT{random.randint(100000, 999999)}.png"
        screenshot_path = timestamp
 
        screenshot = ImageGrab.grab(bbox)
        screenshot.save(screenshot_path)
 
        self.image_list.append((screenshot_path, timestamp, 0.8, ""))
        self.update_image_listbox()
 
        self.top.destroy()
        self.root.deiconify()
 
    def update_image_listbox(self):
        for row in self.tree.get_children():
            self.tree.delete(row)
 
        self.tree.image_refs = []  # Reset the image reference list
 
        for img_path, img_name, similarity_threshold, keyboard_input in self.image_list:
            image = Image.open(img_path)
            image.thumbnail((50, 50))
            photo = ImageTk.PhotoImage(image)
            self.tree.insert("", tk.END, values=(img_path, img_name, similarity_threshold, keyboard_input), image=photo)
            self.tree.image_refs.append(photo)  # Save the image reference
 
        if self.tree.selection():
            self.on_treeview_select(None)
 
    def on_treeview_select(self, event):
        selected_items = self.tree.selection()
        if selected_items:
            item = selected_items[0]
            img_path = self.tree.item(item, "values")[0]
            self.display_preview(img_path)
 
    def display_preview(self, img_path):
        try:
            image = Image.open(img_path)
            image.thumbnail((300, 300))
            self.preview_photo = ImageTk.PhotoImage(image)  # Use instance variable to store reference
            self.preview_image.config(image=self.preview_photo)
        except Exception as e:
            logging.error(f"无法加载图片: {e}")
            self.preview_image.config(image=None)
            self.preview_photo = None
 
    def edit_image_name(self, event):
        selected_item = self.tree.selection()[0]
        selected_index = self.tree.index(selected_item)
        selected_image = self.image_list[selected_index]
 
        new_image_name = simpledialog.askstring("修改步骤名称", "请输入新的步骤名称:", initialvalue=selected_image[1])
        if new_image_name is not None:
            self.image_list[selected_index] = (selected_image[0], new_image_name, selected_image[2], selected_image[3])
            self.update_image_listbox()
 
    def edit_similarity_threshold(self, event):
        selected_item = self.tree.selection()[0]
        selected_index = self.tree.index(selected_item)
        selected_image = self.image_list[selected_index]
 
        new_similarity_threshold = simpledialog.askfloat("修改相似度", "请输入新的相似度(0.1 - 1.0):", initialvalue=selected_image[2], minvalue=0.1, maxvalue=1.0)
        if new_similarity_threshold is not None:
            self.image_list[selected_index] = (selected_image[0], selected_image[1], new_similarity_threshold, selected_image[3])
            self.update_image_listbox()
 
    def edit_keyboard_input(self, event):
        selected_item = self.tree.selection()[0]
        selected_index = self.tree.index(selected_item)
        selected_image = self.image_list[selected_index]
 
        new_keyboard_input = simpledialog.askstring("修改键盘输入", "请输入新的键盘输入:", initialvalue=selected_image[3])
        if new_keyboard_input is not None:
            self.image_list[selected_index] = (selected_image[0], selected_image[1], selected_image[2], new_keyboard_input)
            self.update_image_listbox()
 
    def delete_selected_image(self):
        selected_item = self.tree.selection()
        if selected_item:
            selected_index = self.tree.index(selected_item[0])
            del self.image_list[selected_index]
            self.update_image_listbox()
 
    def toggle_script(self, event=None):
        if not self.running:
            self.start_script_thread()
            self.toggle_run_button.config(text="停止运行")
            self.root.after(100, self.check_script_status)
        else:
            self.stop_script()
        
        # Added to update UI
        self.root.update()
 
    def start_script_thread(self):
        if not self.running:
            self.running = True
            self.thread = threading.Thread(target=self.run_script, daemon=True)
            self.thread.start()
 
    def check_script_status(self):
        if self.running:
            self.root.after(100, self.check_script_status)
        else:
            self.toggle_run_button.config(text="开始运行")
 
    def run_script(self):
        self.loop_count = int(self.loop_count_entry.get())
        current_loop = 0
 
        while self.running and current_loop < self.loop_count:
            logging.info(f"开始第 {current_loop + 1} 次循环")
            for img_path, img_name, similarity_threshold, keyboard_input in self.image_list:
                if not self.running:
                    return
                # logging.info(f"正在识别图像: {img_name}")
                if self.match_and_click(img_path, similarity_threshold):
                    if keyboard_input:
                        pyautogui.write(keyboard_input)
                        logging.info(f"输入键盘内容: {keyboard_input}")
                time.sleep(self.delay_time)
            current_loop += 1
        
        self.running = False
        logging.info("脚本运行完成")
 
    def stop_script(self):
        self.running = False
        if self.thread is not None:
            self.thread.join(timeout=2)  # Wait for thread to finish up to 2 seconds
            if self.thread.is_alive():
                logging.warning("警告:线程未能在2秒内停止")
        logging.info("脚本已停止")
        self.toggle_run_button.config(text="开始运行")
 
    def match_and_click(self, template_path, similarity_threshold):
        screenshot = pyautogui.screenshot()
        screenshot = np.array(screenshot)
        screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)
 
        template = cv2.imread(template_path, 0)
        if template is None:
            logging.error(f"无法读取图像: {template_path}")
            return False
 
        gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
 
        result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
        loc = np.where(result >= similarity_threshold)
 
        found = False
        for pt in zip(*loc[::-1]):
            click_x = pt[0] + template.shape[1] // 2
            click_y = pt[1] + template.shape[0] // 2
            pyautogui.click(click_x, click_y)
            found = True
            logging.info(f"找到并点击了图像: {template_path} 在位置 ({click_x}, {click_y})")
            break
 
        if not found:
            # ogging.warning(f"未找到匹配区域: {template_path}")
            return False
 
        return True
 
    def set_hotkey(self):
        new_hotkey = simpledialog.askstring("设置热键", "请输入新的热键(例如:<F1>):", initialvalue=self.hotkey)
        if new_hotkey is not None:
            self.hotkey = new_hotkey
            self.root.bind(self.hotkey, self.toggle_script)
            logging.info(f"热键已设置为: {self.hotkey}")
 
    def save_config(self):
        config = {
            "image_list": self.image_list,
            "hotkey": self.hotkey,
            "screenshot_area": self.screenshot_area,
            "similarity_threshold": self.similarity_threshold,
            "delay_time": self.delay_time,
            "loop_count": self.loop_count
        }
        with open('config.json', 'w') as f:
            json.dump(config, f)
        logging.info("配置已保存")
 
    def load_config(self):
        if os.path.exists('config.json'):
            with open('config.json', 'r') as f:
                config = json.load(f)
                self.image_list = config.get("image_list", [])
                self.hotkey = config.get("hotkey", '<F1>')
                self.screenshot_area = config.get("screenshot_area", None)
                self.similarity_threshold = config.get("similarity_threshold", 0.8)
                self.delay_time = config.get("delay_time", 0.1)
                self.loop_count = config.get("loop_count", 1)
                self.loop_count_entry.delete(0, tk.END)
                self.loop_count_entry.insert(0, str(self.loop_count))
                self.root.bind(self.hotkey, self.toggle_script)
                self.update_image_listbox()
            logging.info("配置已加载")
 
    def load_config_manually(self):
        file_path = filedialog.askopenfilename(filetypes=[("JSON files", "*.json")])
        if file_path:
            with open(file_path, 'r') as f:
                config = json.load(f)
                self.image_list = config.get("image_list", [])
                self.hotkey = config.get("hotkey", '<F1>')
                self.screenshot_area = config.get("screenshot_area", None)
                self.similarity_threshold = config.get("similarity_threshold", 0.8)
                self.delay_time = config.get("delay_time", 0.1)
                self.loop_count = config.get("loop_count", 1)
                self.loop_count_entry.delete(0, tk.END)
                self.loop_count_entry.insert(0, str(self.loop_count))
                self.root.bind(self.hotkey, self.toggle_script)
                self.update_image_listbox()
            logging.info("配置已加载")
 
if __name__ == "__main__":
    root = tk.Tk()
    app = ImageRecognitionApp(root)
    root.mainloop()

已添加图片预览

吧友提出无法更改停止运行问题,看了下,确实存在.
这里简单说明下,由于python线程的特殊性,已做优化改进!
顺手填上了日志输出!
有的地方可能未注意到,遇到了在优化!
推荐
helh0275 发表于 2024-9-23 23:40
感谢大佬进行增补,但你这没有每个图片每个动作的时限设定,完全就是一通狂点
推荐
 楼主| gzsklsskszngc 发表于 2024-12-5 20:35 |楼主
yjohjot 发表于 2024-12-5 13:38
楼主  你这个是顺序识别的 有没有办法同步识别?  如果步骤1图片一直没识别到 永远不会识别步骤2的图片     ...

程序默认是一直等到图像出现为止,但你可以设置条件啊!比如我先设置上一个延迟5秒钟,然后条件设置如果步骤1为"False",那就跳转到步骤2。很好理解吧?就是当点击完上一步,五秒钟后如果没有识别到下一步的图像出现,那就直接跳过它,执行下一步骤。
推荐
 楼主| gzsklsskszngc 发表于 2024-11-1 12:29 |楼主
18591406028 发表于 2024-11-1 10:15
想交流下关于关于图片识别那块:用的是什么方法?
我以前也写过一个类似于按键精灵的工具,自己玩游戏用, ...

主要使用了OpenCV的模板匹配方法,归一化相关系数匹配,这样能够提高计算速度。
再将图像转换为灰度图进行匹配,并使用RGB到BGR的颜色空间转换,适应OpenCV的格式要求,也能减少计算量。
这样的优点是:
实现简单,计算速度快
对目标的完整匹配效果好
不需要训练过程

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
18591406028 + 1 + 1 用心讨论,共获提升!

查看全部评分

推荐
 楼主| gzsklsskszngc 发表于 2024-9-24 20:43 |楼主
helh0275 发表于 2024-9-23 23:40
感谢大佬进行增补,但你这没有每个图片每个动作的时限设定,完全就是一通狂点

没有时限,就是以最快的速度去点击。程序是识别到下一步的图像出现时才去点,如果想要延迟,可以以别的图像作为参照,例如当你点击网页上的登录按钮之后要等到什么图像出现时再点击下一个地方。我修改的目的就是以最快的速度,不浪费一分一秒。目前我自己就用在工作的一些测试任务上,非常的爽。
4#
ID08 发表于 2024-9-19 23:59
6666,大佬厉害&#128077;&#127995;,学习进步
5#
kim945 发表于 2024-9-20 00:11
这我一直用按键精灵一点点试着做的...为了每天填写各种单位上报的网站..早要是有这个,起码我能省20个小时T_T
6#
helh0275 发表于 2024-9-20 01:06
老大就不能顺手打个包?伪装成exe的样子
7#
yueguangxiaxian 发表于 2024-9-20 06:45
希望有编译好的,谢谢楼主
8#
yjn866y 发表于 2024-9-20 08:43
学习使人进步。。。
9#
wasm2023 发表于 2024-9-20 08:49
学习了,感谢分享
10#
Rx0 发表于 2024-9-20 09:05
这种软件,还是得 高速、大批量 处理才更实用。可以参考Duplicate Cleaner 来做。
11#
vyuermeng 发表于 2024-9-20 09:41
我运行了一下  框选截图   点运行  鼠标会移到对应的图标上  但没有点击操作,是我配置的不对吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-11 13:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表