main.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import pyautogui
  2. import cv2
  3. import numpy as np
  4. import time
  5. import threading
  6. from pynput import keyboard
  7. from pynput.mouse import Button
  8. import os
  9. import subprocess
  10. class MacImageClicker:
  11. def __init__(self):
  12. self.is_monitoring = False
  13. self.target_image = None
  14. self.confidence_threshold = 0.95
  15. self.hotkey = keyboard.Key.f8
  16. self.check_interval = 0.5
  17. # macOS 特定设置
  18. pyautogui.FAILSAFE = True
  19. pyautogui.PAUSE = 0.1
  20. # 查找目标图片
  21. self.target_image_path = self.find_target_image()
  22. def find_target_image(self):
  23. """在项目根目录查找目标图片"""
  24. current_dir = os.getcwd()
  25. image_files = []
  26. # 查找所有可能的图片文件
  27. for file in os.listdir(current_dir):
  28. if file.lower().endswith(('.png', '.jpg', '.jpeg')):
  29. image_files.append(file)
  30. if not image_files:
  31. print("在项目根目录未找到任何图片文件 (png, jpg, jpeg)")
  32. return None
  33. # 优先选择包含 target 关键字的图片
  34. target_files = [f for f in image_files if 'target' in f.lower()]
  35. if target_files:
  36. selected = target_files[0]
  37. else:
  38. selected = image_files[0]
  39. full_path = os.path.join(current_dir, selected)
  40. print(f"找到目标图片: {selected}")
  41. return full_path
  42. def load_target_image(self):
  43. """加载目标图片"""
  44. if not self.target_image_path:
  45. print("未找到目标图片文件")
  46. return False
  47. try:
  48. self.target_image = cv2.imread(self.target_image_path)
  49. if self.target_image is not None:
  50. print(f"成功加载目标图片: {os.path.basename(self.target_image_path)}")
  51. print(f"图片尺寸: {self.target_image.shape[1]}x{self.target_image.shape[0]}")
  52. return True
  53. else:
  54. print("图片加载失败,可能是格式不支持")
  55. return False
  56. except Exception as e:
  57. print(f"加载图片时出错: {e}")
  58. return False
  59. def get_screenshot(self):
  60. """获取屏幕截图 - macOS 版本"""
  61. try:
  62. # 使用 pyautogui 截图,在 macOS 上兼容性更好
  63. screenshot = pyautogui.screenshot()
  64. screenshot_cv = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
  65. return screenshot_cv
  66. except Exception as e:
  67. print(f"截图失败: {e}")
  68. return None
  69. def find_and_click(self):
  70. """在屏幕上查找目标图片并点击"""
  71. if self.target_image is None:
  72. return False
  73. try:
  74. # 获取屏幕截图
  75. screenshot = self.get_screenshot()
  76. if screenshot is None:
  77. return False
  78. # 模板匹配
  79. result = cv2.matchTemplate(screenshot, self.target_image, cv2.TM_CCOEFF_NORMED)
  80. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  81. # 调试信息(可选,可以注释掉以减少输出)
  82. # print(f"当前匹配度: {max_val:.3f}")
  83. if max_val >= self.confidence_threshold:
  84. # 计算目标中心位置
  85. h, w = self.target_image.shape[:2]
  86. center_x = max_loc[0] + w // 2
  87. center_y = max_loc[1] + h // 2
  88. # 在 macOS 上使用 pyautogui 点击
  89. current_pos = pyautogui.position()
  90. pyautogui.moveTo(center_x, center_y, duration=0.1)
  91. pyautogui.click()
  92. # 可选:移回原位置
  93. # pyautogui.moveTo(current_pos.x, current_pos.y, duration=0.1)
  94. print(f"✅ 点击位置: ({center_x}, {center_y}), 相似度: {max_val:.3f}")
  95. return True
  96. except Exception as e:
  97. print(f"查找或点击时出错: {e}")
  98. return False
  99. def monitoring_loop(self):
  100. """监控循环"""
  101. print("监控线程启动")
  102. while True:
  103. if self.is_monitoring:
  104. self.find_and_click()
  105. time.sleep(self.check_interval)
  106. def toggle_monitoring(self):
  107. """切换监控状态"""
  108. self.is_monitoring = not self.is_monitoring
  109. if self.is_monitoring:
  110. # 开启监控时确保图片已加载
  111. if self.target_image is None:
  112. if not self.load_target_image():
  113. self.is_monitoring = False
  114. print("❌ 无法加载目标图片,监控未开启")
  115. return
  116. status = "🟢 开启" if self.is_monitoring else "🔴 关闭"
  117. print(f"监控状态: {status}")
  118. if self.is_monitoring:
  119. print("开始监控屏幕,寻找目标图片...")
  120. def on_press(self, key):
  121. """键盘按下事件"""
  122. if key == self.hotkey:
  123. self.toggle_monitoring()
  124. elif key == keyboard.Key.esc:
  125. # ESC 键退出程序
  126. print("退出程序")
  127. os._exit(0)
  128. def check_mac_permissions(self):
  129. """检查 macOS 权限"""
  130. try:
  131. # 尝试截图来测试权限
  132. test_screenshot = pyautogui.screenshot()
  133. print("✅ 屏幕录制权限: 已授权")
  134. return True
  135. except Exception:
  136. print("❌ 屏幕录制权限: 未授权")
  137. print("请前往: 系统设置 > 隐私与安全性 > 屏幕录制")
  138. print("为终端或您使用的 IDE 开启屏幕录制权限")
  139. return False
  140. def start(self):
  141. """启动程序"""
  142. print("🖥️ macOS 图像点击工具")
  143. print("=" * 40)
  144. # 检查权限
  145. if not self.check_mac_permissions():
  146. return
  147. # 查找并加载目标图片
  148. if not self.load_target_image():
  149. print("请确保项目根目录有图片文件 (png, jpg, jpeg)")
  150. return
  151. print(f"🎯 目标图片: {os.path.basename(self.target_image_path)}")
  152. print(f"📏 相似度阈值: {self.confidence_threshold}")
  153. print(f"🎹 控制快捷键:")
  154. print(f" - F8: 开启/关闭监控")
  155. print(f" - ESC: 退出程序")
  156. print("=" * 40)
  157. print("等待指令...")
  158. # 启动监控线程
  159. monitor_thread = threading.Thread(target=self.monitoring_loop, daemon=True)
  160. monitor_thread.start()
  161. # 启动键盘监听
  162. try:
  163. with keyboard.Listener(on_press=self.on_press) as listener:
  164. listener.join()
  165. except KeyboardInterrupt:
  166. print("\n程序被用户中断")
  167. except Exception as e:
  168. print(f"程序出错: {e}")
  169. if __name__ == "__main__":
  170. # 确保程序在 macOS 上运行
  171. import platform
  172. if platform.system() != 'Darwin':
  173. print("警告: 这个工具是针对 macOS 优化的")
  174. print("但检测到您正在运行:", platform.system())
  175. print("继续运行可能遇到兼容性问题")
  176. response = input("是否继续? (y/n): ")
  177. if response.lower() != 'y':
  178. exit()
  179. clicker = MacImageClicker()
  180. clicker.start()