ui界面版本

This commit is contained in:
Ray
2025-10-29 10:49:38 +08:00
parent 8294cab51b
commit 3f1dd4e8c1
12 changed files with 1659 additions and 94 deletions

99
CHANGELOG.md Normal file
View File

@@ -0,0 +1,99 @@
# 更新日志
## 最新更新 - 多组配置系统
### 主要变更
#### 1. 新增文件
- **config.py** - 配置管理器,支持多组配置
- **gui_config.py** - 可视化配置界面tkinter
- **preview.py** - 采集卡预览窗口系统
- **launcher.py** - 统一启动器
- **README.md** - 使用文档
- **config.json** - 配置文件(自动生成)
#### 2. 修改的文件
- **main.py** - 完全重写,支持配置系统
- 从配置文件加载参数
- 支持动态读取v值
- 移除硬编码配置
- **utils/mouse.py** - 移除硬编码串口
- 新增 `init_mouse_keyboard()` 函数
- 支持从配置初始化
- **utils/get_image.py** - 支持从配置初始化
- GetImage改为延迟初始化
### 核心功能
1. **多组配置管理**
- 支持创建多个配置组
- 每个配置组独立的串口、采集卡、速度设置
- 可视化切换活动配置
2. **可视化配置界面**
- GUI界面管理所有配置
- 实时编辑和保存
- 支持添加/删除配置组
3. **采集卡预览系统**
- 网格方式预览多个采集卡
- 点击放大查看
- 实时画面更新
4. **灵活的参数配置**
- 串口配置(端口、波特率)
- 采集卡配置(索引、分辨率)
- 移动速度(v值)配置
- 预览窗口配置
### 使用方式
#### 旧版本(已保留 main_old.py
```bash
python main_old.py # 直接运行,使用硬编码配置
```
#### 新版本
```bash
# 方式1: 使用启动器(推荐)
python launcher.py
# 方式2: 直接运行
python main.py # 读取config.json的active配置
```
### 配置示例
```json
{
"groups": [
{
"name": "配置组1",
"serial_port": "COM6",
"serial_baudrate": 9600,
"camera_index": 0,
"camera_width": 1920,
"camera_height": 1080,
"move_velocity": 470, // ← move_to函数中的v值
"active": true
}
]
}
```
### 兼容性
- ✅ 保留所有原有功能
- ✅ YOLO模型无需修改
- ✅ 游戏逻辑完全兼容
- ⚠️ 旧代码会使用默认配置
### 下一步
如需添加更多配置:
1.`config.py``default_config` 中添加新字段
2.`gui_config.py` 中添加对应的输入框
3.`main.py` 中读取并使用新配置

141
README.md Normal file
View File

@@ -0,0 +1,141 @@
# 火炬之光自动化脚本
## 功能特点
-**多配置组管理** - 支持多组配置,可同时管理多套采集卡和串口设置
- 🖥️ **可视化配置** - 提供图形化界面管理所有配置
- 👁️ **采集卡预览** - 多窗口网格预览,点击可放大查看
- ⚙️ **灵活配置** - 所有参数(串口、采集卡、移动速度等)均可配置
- 🤖 **智能自动化** - 基于YOLO目标检测的游戏自动化
## 快速开始
### 1. 安装依赖
```bash
pip install opencv-python ultralytics ddddocr ch9329Comm serial PIL numpy
```
### 2. 启动程序
```bash
python launcher.py
```
启动器提供三个选项:
- **⚙️ 配置管理** - 配置串口、采集卡等参数
- **👁️ 采集卡预览** - 实时预览采集卡画面
- **🚀 启动自动化** - 启动游戏自动化脚本
### 3. 配置设置
首次使用请先点击"配置管理"
1. **添加配置组** - 点击"添加组"创建新配置
2. **设置参数**
- 串口:选择设备串口号(如 COM6
- 波特率设置串口波特率默认9600
- 采集卡索引:选择采集卡设备号
- 采集宽度/高度:设置采集分辨率
- 移动速度设置角色移动速度v值
3. **设为活动** - 选择要使用的配置组并设为活动
4. **保存配置** - 点击"保存配置"
### 4. 预览采集卡
点击"采集卡预览"可以:
- 网格方式查看所有活动的采集卡
- 点击任意窗口可放大查看
- 实时显示采集卡画面
### 5. 启动自动化
配置完成后,点击"启动自动化"开始游戏自动化。
## 配置说明
### 配置组结构
```json
{
"groups": [
{
"name": "配置组1",
"serial_port": "COM6",
"serial_baudrate": 9600,
"camera_index": 0,
"camera_width": 1920,
"camera_height": 1080,
"move_velocity": 470,
"active": false
}
],
"display": {
"preview_window_width": 640,
"preview_window_height": 360,
"preview_columns": 2,
"preview_rows": 2,
"show_preview": true
}
}
```
### 关键参数说明
- **serial_port**: 串口号,如 COM1, COM6
- **serial_baudrate**: 波特率,通常为 9600
- **camera_index**: 采集卡索引0/1/2...
- **camera_width/height**: 采集分辨率
- **move_velocity**: 角色移动速度v值数值越大移动越快
## 文件结构
```
huojv/
├── launcher.py # 启动器
├── main.py # 主程序(自动游戏)
├── config.py # 配置管理器
├── gui_config.py # 配置GUI界面
├── preview.py # 预览窗口
├── utils/
│ ├── get_image.py # 采集卡管理
│ ├── mouse.py # 鼠标控制
│ ├── shizi.py # OCR识别
│ └── ...
├── best.pt # YOLO模型战斗
├── best0.pt # YOLO模型城镇
└── config.json # 配置文件(自动生成)
```
## 使用流程
1. 运行 `python launcher.py`
2. 点击"配置管理"设置参数
3. 点击"采集卡预览"测试采集
4. 点击"启动自动化"开始游戏
5. 享受自动化!
## 注意事项
- 确保采集卡和串口设备已正确连接
- 串口号和采集卡索引需要根据实际情况设置
- 移动速度(v值)需要根据游戏角色属性调整
- 配置文件保存在 `config.json`
## 问题排查
### 无法打开串口
- 检查串口号是否正确
- 确认串口设备已连接
- 检查其他程序是否占用串口
### 无法打开采集卡
- 检查采集卡索引是否正确0, 1, 2...
- 确认采集卡驱动已安装
- 尝试不同的采集卡索引
### 识别不准确
- 调整移动速度(v值)
- 检查采集卡画面是否清晰
- 确认游戏窗口位置正确

22
config.json Normal file
View File

@@ -0,0 +1,22 @@
{
"groups": [
{
"name": "配置组1",
"serial_port": "COM6",
"serial_baudrate": 9600,
"camera_index": 0,
"camera_width": 1920,
"camera_height": 1080,
"move_velocity": 470,
"active": true
}
],
"display": {
"preview_window_width": 640,
"preview_window_height": 360,
"preview_columns": 2,
"preview_rows": 2,
"show_preview": true
}
}

111
config.py Normal file
View File

@@ -0,0 +1,111 @@
import json
import os
class ConfigManager:
"""配置管理器"""
def __init__(self, config_file='config.json'):
self.config_file = config_file
self.default_config = {
"groups": [
{
"name": "配置组1",
"serial_port": "COM6",
"serial_baudrate": 9600,
"camera_index": 0,
"camera_width": 1920,
"camera_height": 1080,
"move_velocity": 470,
"active": False
}
],
"display": {
"preview_window_width": 640,
"preview_window_height": 360,
"preview_columns": 2,
"preview_rows": 2,
"show_preview": True
}
}
self.config = self.load_config()
def load_config(self):
"""加载配置文件"""
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
# 确保有默认结构
if 'groups' not in config:
config['groups'] = self.default_config['groups']
if 'display' not in config:
config['display'] = self.default_config['display']
return config
except Exception as e:
print(f"加载配置文件失败: {e}")
return self.default_config.copy()
return self.default_config.copy()
def save_config(self):
"""保存配置文件"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
print(f"保存配置文件失败: {e}")
return False
def add_group(self, name=None):
"""添加配置组"""
if name is None:
name = f"配置组{len(self.config['groups']) + 1}"
new_group = {
"name": name,
"serial_port": "COM6",
"serial_baudrate": 9600,
"camera_index": len(self.config['groups']),
"camera_width": 1920,
"camera_height": 1080,
"move_velocity": 470,
"active": False
}
self.config['groups'].append(new_group)
return new_group
def delete_group(self, index):
"""删除配置组"""
if 0 <= index < len(self.config['groups']):
del self.config['groups'][index]
return True
return False
def update_group(self, index, **kwargs):
"""更新配置组"""
if 0 <= index < len(self.config['groups']):
self.config['groups'][index].update(kwargs)
return True
return False
def get_active_group(self):
"""获取当前活动的配置组"""
for group in self.config['groups']:
if group.get('active', False):
return group
return None
def set_active_group(self, index):
"""设置活动的配置组"""
for i, group in enumerate(self.config['groups']):
group['active'] = (i == index)
return True
def get_group_by_index(self, index):
"""根据索引获取配置组"""
if 0 <= index < len(self.config['groups']):
return self.config['groups'][index]
return None
# 全局配置管理器实例
config_manager = ConfigManager()

223
gui_config.py Normal file
View File

@@ -0,0 +1,223 @@
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from config import config_manager
import threading
class ConfigGUI:
"""配置GUI界面"""
def __init__(self):
self.root = tk.Tk()
self.root.title("配置管理 - 火炬之光自动化")
self.root.geometry("800x600")
self.selected_index = 0
self.setup_ui()
self.load_current_config()
def setup_ui(self):
"""设置UI界面"""
# 左侧:配置组列表
left_frame = ttk.Frame(self.root, padding="10")
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False)
ttk.Label(left_frame, text="配置组列表", font=('Arial', 12, 'bold')).pack()
# 配置组列表
self.group_listbox = tk.Listbox(left_frame, width=20, height=25)
self.group_listbox.pack(fill=tk.BOTH, expand=True, pady=5)
self.group_listbox.bind('<<ListboxSelect>>', self.on_group_select)
# 按钮
btn_frame = ttk.Frame(left_frame)
btn_frame.pack(fill=tk.X)
ttk.Button(btn_frame, text="添加组", command=self.add_group).pack(fill=tk.X, pady=2)
ttk.Button(btn_frame, text="删除组", command=self.delete_group).pack(fill=tk.X, pady=2)
ttk.Button(btn_frame, text="设为活动", command=self.set_active).pack(fill=tk.X, pady=2)
# 右侧:配置详情
right_frame = ttk.Frame(self.root, padding="10")
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
ttk.Label(right_frame, text="配置详情", font=('Arial', 12, 'bold')).pack()
# 配置项
self.config_vars = {}
# 基本配置
basic_frame = ttk.LabelFrame(right_frame, text="基本配置", padding="10")
basic_frame.pack(fill=tk.X, pady=5)
self.create_entry(basic_frame, "name", "配置组名称:")
self.create_entry(basic_frame, "camera_index", "采集卡索引:")
self.create_entry(basic_frame, "camera_width", "采集宽度:")
self.create_entry(basic_frame, "camera_height", "采集高度:")
# 串口配置
serial_frame = ttk.LabelFrame(right_frame, text="串口配置", padding="10")
serial_frame.pack(fill=tk.X, pady=5)
self.create_entry(serial_frame, "serial_port", "串口:")
self.create_entry(serial_frame, "serial_baudrate", "波特率:")
# 游戏配置
game_frame = ttk.LabelFrame(right_frame, text="游戏配置", padding="10")
game_frame.pack(fill=tk.X, pady=5)
self.create_entry(game_frame, "move_velocity", "移动速度(v值):")
# 预览配置
preview_frame = ttk.LabelFrame(right_frame, text="预览配置", padding="10")
preview_frame.pack(fill=tk.X, pady=5)
self.create_entry(preview_frame, "preview_width", "预览宽度:", prefix="display")
self.create_entry(preview_frame, "preview_height", "预览高度:", prefix="display")
self.create_entry(preview_frame, "preview_columns", "预览列数:", prefix="display")
self.create_entry(preview_frame, "preview_rows", "预览行数:", prefix="display")
# 保存按钮
save_frame = ttk.Frame(right_frame)
save_frame.pack(fill=tk.X, pady=10)
ttk.Button(save_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT, padx=5)
ttk.Button(save_frame, text="启动预览", command=self.start_preview).pack(side=tk.LEFT, padx=5)
ttk.Button(save_frame, text="启动程序", command=self.start_program).pack(side=tk.LEFT, padx=5)
def create_entry(self, parent, key, label, prefix=None):
"""创建输入框"""
frame = ttk.Frame(parent)
frame.pack(fill=tk.X, pady=3)
ttk.Label(frame, text=label, width=15).pack(side=tk.LEFT)
var = tk.StringVar()
entry = ttk.Entry(frame, textvariable=var, width=25)
entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
# 保存变量引用
if prefix:
full_key = f"{prefix}_{key}"
else:
full_key = key
self.config_vars[full_key] = var
def load_current_config(self):
"""加载当前配置到界面"""
# 加载配置组列表
self.update_group_list()
# 加载预览配置
display = config_manager.config.get('display', {})
for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
if key in display:
self.config_vars[f"display_{key}"].set(str(display[key]))
def update_group_list(self):
"""更新配置组列表"""
self.group_listbox.delete(0, tk.END)
for i, group in enumerate(config_manager.config['groups']):
status = "" if group.get('active', False) else " "
self.group_listbox.insert(tk.END, f"{status} {group['name']}")
def on_group_select(self, event):
"""选择配置组"""
selection = self.group_listbox.curselection()
if selection:
self.selected_index = selection[0]
self.load_group_config(self.selected_index)
def load_group_config(self, index):
"""加载配置组详情"""
group = config_manager.get_group_by_index(index)
if group:
for key, var in self.config_vars.items():
if not key.startswith('display_'):
if key in group:
var.set(str(group[key]))
def add_group(self):
"""添加配置组"""
name = simpledialog.askstring("添加配置组", "请输入配置组名称:", initialvalue=f"配置组{len(config_manager.config['groups'])+1}")
if name:
config_manager.add_group(name)
self.update_group_list()
messagebox.showinfo("成功", f"已添加配置组: {name}")
def delete_group(self):
"""删除配置组"""
selection = self.group_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择要删除的配置组")
return
if messagebox.askyesno("确认", "确定要删除选中的配置组吗?"):
config_manager.delete_group(selection[0])
self.update_group_list()
messagebox.showinfo("成功", "配置组已删除")
def set_active(self):
"""设置为活动配置组"""
selection = self.group_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择要设为活动的配置组")
return
config_manager.set_active_group(selection[0])
self.update_group_list()
messagebox.showinfo("成功", "已设置为活动配置组")
def save_config(self):
"""保存当前编辑的配置"""
# 保存当前组的配置
group = config_manager.get_group_by_index(self.selected_index)
if group:
for key, var in self.config_vars.items():
if not key.startswith('display_'):
try:
value = int(var.get()) if var.get().isdigit() else var.get()
group[key] = value
except:
group[key] = var.get()
# 保存预览配置
display = config_manager.config.get('display', {})
for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
var = self.config_vars.get(f"display_{key}")
if var:
try:
display[key] = int(var.get())
except:
pass
config_manager.config['display'] = display
if config_manager.save_config():
messagebox.showinfo("成功", "配置已保存")
else:
messagebox.showerror("错误", "配置保存失败")
def start_preview(self):
"""启动预览窗口"""
# 保存配置
self.save_config()
# 启动预览窗口
from preview import PreviewWindow
preview_thread = threading.Thread(target=lambda: PreviewWindow().run())
preview_thread.daemon = True
preview_thread.start()
def start_program(self):
"""启动主程序"""
# 保存配置
self.save_config()
messagebox.showinfo("提示", "配置已保存,请运行主程序")
def run(self):
"""运行GUI"""
self.root.mainloop()
if __name__ == "__main__":
app = ConfigGUI()
app.run()

68
launcher.py Normal file
View File

@@ -0,0 +1,68 @@
"""
启动器 - 选择启动配置界面或主程序
"""
import tkinter as tk
from tkinter import ttk
import subprocess
import sys
def start_config_gui():
"""启动配置界面"""
subprocess.Popen([sys.executable, "gui_config.py"])
def start_preview():
"""启动预览窗口"""
subprocess.Popen([sys.executable, "preview.py"])
def start_main_program():
"""启动主程序"""
subprocess.Popen([sys.executable, "main.py"])
def create_launcher():
"""创建启动器界面"""
root = tk.Tk()
root.title("火炬之光自动化 - 启动器")
root.geometry("400x300")
root.resizable(False, False)
# 标题
title_frame = tk.Frame(root)
title_frame.pack(fill=tk.X, pady=20)
tk.Label(title_frame, text="火炬之光自动化脚本", font=('Arial', 16, 'bold')).pack()
tk.Label(title_frame, text="请选择要启动的功能", font=('Arial', 10)).pack()
# 按钮区域
button_frame = tk.Frame(root)
button_frame.pack(fill=tk.BOTH, expand=True, padx=40, pady=20)
# 配置界面按钮
config_btn = tk.Button(button_frame, text="⚙️ 配置管理",
command=start_config_gui,
width=30, height=3, font=('Arial', 12))
config_btn.pack(pady=10)
# 预览窗口按钮
preview_btn = tk.Button(button_frame, text="👁️ 采集卡预览",
command=start_preview,
width=30, height=3, font=('Arial', 12))
preview_btn.pack(pady=10)
# 主程序按钮
main_btn = tk.Button(button_frame, text="🚀 启动自动化",
command=start_main_program,
width=30, height=3, font=('Arial', 12))
main_btn.pack(pady=10)
# 说明
info_label = tk.Label(root,
text="提示:首次使用请先点击"配置管理"设置参数",
font=('Arial', 9),
fg='gray')
info_label.pack(side=tk.BOTTOM, pady=10)
root.mainloop()
if __name__ == "__main__":
create_launcher()

69
main.py
View File

@@ -1,37 +1,57 @@
import cv2 import cv2
from utils.get_image import get_image from utils.get_image import GetImage
from utils.mouse import mouse_gui from utils.mouse import init_mouse_keyboard, Mouse_guiji
from ultralytics import YOLO from ultralytics import YOLO
import time import time
import serial import serial
import ch9329Comm import ch9329Comm
import time
import random import random
import math import math
from utils import shizi from utils import shizi
from config import config_manager
# 加载YOLO模型
model = YOLO(r"best.pt").to('cuda') model = YOLO(r"best.pt").to('cuda')
model0 = YOLO(r"best0.pt").to('cuda') model0 = YOLO(r"best0.pt").to('cuda')
# 从配置加载
active_group = config_manager.get_active_group()
if active_group is None:
print("❌ 没有活动的配置组请在gui_config.py中设置")
exit(1)
print(f"📋 使用配置组: {active_group['name']}")
# 初始化串口和鼠标
init_mouse_keyboard(active_group)
# 初始化键盘和鼠标
keyboard = ch9329Comm.keyboard.DataComm() keyboard = ch9329Comm.keyboard.DataComm()
mouse = ch9329Comm.mouse.DataComm(1920, 1080) from utils.mouse import mouse, mouse_gui # 导入已初始化的mouse和mouse_gui
kong_detections = {
'center': None, # 创建全局的mouse_gui实例
'next': None, mouse_gui = Mouse_guiji()
'npc1': None,
'npc2': None, # 初始化采集卡
'npc3': None, get_image = GetImage(
'npc4': None, cam_index=active_group['camera_index'],
'boss': None, width=active_group['camera_width'],
'daojv': [], height=active_group['camera_height']
'gw': [], )
'zhaozi': None
} print(f"✅ 初始化完成 - 串口:{active_group['serial_port']} 采集卡:{active_group['camera_index']}")
# 全局变量
left = 0 left = 0
top = 30 top = 30
k = 0 # 控制转圈的方向 k = 0 # 控制转圈的方向
panduan = False # 是否在图内 panduan = False # 是否在图内
boss_pd = False # 是否到boss关卡 boss_pd = False # 是否到boss关卡
rw = (632, 378) rw = (632, 378)
# 从配置读取移动速度
v = active_group['move_velocity']
def yolo_shibie(im_PIL, detections, model): def yolo_shibie(im_PIL, detections, model):
results = model(im_PIL) # 目标检测 results = model(im_PIL) # 目标检测
for result in results: for result in results:
@@ -56,9 +76,11 @@ def yolo_shibie(im_PIL,detections,model):
RW = [player_x, player_y] RW = [player_x, player_y]
detections[label] = RW detections[label] = RW
return detections return detections
def sq(p1, p2): def sq(p1, p2):
"""计算两点之间的欧式距离""" """计算两点之间的欧式距离"""
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
def process_points(points): def process_points(points):
if not points: if not points:
return None # 空列表情况 return None # 空列表情况
@@ -89,6 +111,7 @@ def process_points(points):
best_point = p best_point = p
return best_point return best_point
def move_randomly(rw, k): def move_randomly(rw, k):
k = k % 4 k = k % 4
suiji_t = float(random.randint(10, 13) / 10) suiji_t = float(random.randint(10, 13) / 10)
@@ -109,8 +132,11 @@ def move_randomly(rw, k):
time.sleep(suiji_t) time.sleep(suiji_t)
keyboard.release() # Release keyboard.release() # Release
return k + 1 return k + 1
def move_to(rw, mb): def move_to(rw, mb):
v=470 """使用配置的v值"""
global v
v = active_group['move_velocity'] # 每次都从配置读取最新值
if rw[0] >= mb[0]: if rw[0] >= mb[0]:
keyboard.send_data("44") keyboard.send_data("44")
time.sleep(float(abs(rw[0] - mb[0]) / v)) time.sleep(float(abs(rw[0] - mb[0]) / v))
@@ -128,7 +154,9 @@ def move_to(rw,mb):
keyboard.send_data("22") keyboard.send_data("22")
time.sleep(float(abs(rw[1] - mb[1]) / v)) time.sleep(float(abs(rw[1] - mb[1]) / v))
keyboard.release() keyboard.release()
i = 0 i = 0
print("🚀 开始自动化...")
while True: while True:
detections = { detections = {
'center': None, 'center': None,
@@ -141,10 +169,10 @@ while True:
'daojv': [], 'daojv': [],
'gw': [], 'gw': [],
'zhaozi': None 'zhaozi': None
} }
im_opencv = get_image.get_frame() # [RGB,PIL] im_opencv = get_image.get_frame() # [RGB,PIL]
detections = yolo_shibie(im_opencv[1], detections, model) detections = yolo_shibie(im_opencv[1], detections, model)
if shizi.tuwai(im_opencv[0]): # 进图算法 if shizi.tuwai(im_opencv[0]): # 进图算法
im_opencv = get_image.get_frame() # [RGB,PIL] im_opencv = get_image.get_frame() # [RGB,PIL]
detections = yolo_shibie(im_opencv[1], detections, model0) detections = yolo_shibie(im_opencv[1], detections, model0)
@@ -181,7 +209,6 @@ while True:
continue continue
else: else:
print("离npc4有点远 点击进入") print("离npc4有点远 点击进入")
move_to(rw, detections['npc4']) move_to(rw, detections['npc4'])
time.sleep(1) time.sleep(1)
im_opencv = get_image.get_frame() # [RGB,PIL] im_opencv = get_image.get_frame() # [RGB,PIL]
@@ -239,7 +266,6 @@ while True:
'daojv': [], 'daojv': [],
'gw': [], 'gw': [],
'zhaozi': None 'zhaozi': None
} }
detections = yolo_shibie(im_opencv[1], detections, model) detections = yolo_shibie(im_opencv[1], detections, model)
if detections['next'] is not None or len(detections['daojv']) != 0 or len(detections['gw']) != 0 or detections['boss'] is not None: if detections['next'] is not None or len(detections['daojv']) != 0 or len(detections['gw']) != 0 or detections['boss'] is not None:
@@ -273,8 +299,6 @@ while True:
mb = (rw[0], rw[1] + 100) mb = (rw[0], rw[1] + 100)
move_to(rw, mb) move_to(rw, mb)
time.sleep(0.25) time.sleep(0.25)
continue continue
elif boss_pd == True and detections['center'] is None and detections['next'] is None: # boss出现了 但是没有中心 elif boss_pd == True and detections['center'] is None and detections['next'] is None: # boss出现了 但是没有中心
@@ -298,3 +322,4 @@ while True:
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1) mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1) time.sleep(1)
continue continue

325
main_new.py Normal file
View File

@@ -0,0 +1,325 @@
import cv2
from utils.get_image import GetImage
from utils.mouse import init_mouse_keyboard, Mouse_guiji
from ultralytics import YOLO
import time
import serial
import ch9329Comm
import random
import math
from utils import shizi
from config import config_manager
# 加载YOLO模型
model = YOLO(r"best.pt").to('cuda')
model0 = YOLO(r"best0.pt").to('cuda')
# 从配置加载
active_group = config_manager.get_active_group()
if active_group is None:
print("❌ 没有活动的配置组请在gui_config.py中设置")
exit(1)
print(f"📋 使用配置组: {active_group['name']}")
# 初始化串口和鼠标
init_mouse_keyboard(active_group)
# 初始化键盘和鼠标
keyboard = ch9329Comm.keyboard.DataComm()
from utils.mouse import mouse, mouse_gui # 导入已初始化的mouse和mouse_gui
# 创建全局的mouse_gui实例
mouse_gui = Mouse_guiji()
# 初始化采集卡
get_image = GetImage(
cam_index=active_group['camera_index'],
width=active_group['camera_width'],
height=active_group['camera_height']
)
print(f"✅ 初始化完成 - 串口:{active_group['serial_port']} 采集卡:{active_group['camera_index']}")
# 全局变量
left = 0
top = 30
k = 0 # 控制转圈的方向
panduan = False # 是否在图内
boss_pd = False # 是否到boss关卡
rw = (632, 378)
# 从配置读取移动速度
v = active_group['move_velocity']
def yolo_shibie(im_PIL, detections, model):
results = model(im_PIL) # 目标检测
for result in results:
for i in range(len(result.boxes.xyxy)):
left, top, right, bottom = result.boxes.xyxy[i]
scalar_tensor = result.boxes.cls[i]
value = scalar_tensor.item()
label = result.names[int(value)]
if label == 'center' or label == 'next' or label == 'boss' or label == 'zhaozi':
player_x = int(left + (right - left) / 2)
player_y = int(top + (bottom - top) / 2) + 30
RW = [player_x, player_y]
detections[label] = RW
elif label == 'daojv' or label == 'gw':
player_x = int(left + (right - left) / 2)
player_y = int(top + (bottom - top) / 2) + 30
RW = [player_x, player_y]
detections[label].append(RW)
elif label == 'npc1' or label == 'npc2' or label == 'npc3' or label == 'npc4':
player_x = int(left + (right - left) / 2)
player_y = int(bottom) + 30
RW = [player_x, player_y]
detections[label] = RW
return detections
def sq(p1, p2):
"""计算两点之间的欧式距离"""
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
def process_points(points):
if not points:
return None # 空列表情况
n = len(points)
if n == 1:
# 只有一个点,直接返回
return points[0]
elif n == 2:
# 两个点取中点
x = (points[0][0] + points[1][0]) / 2
y = (points[0][1] + points[1][1]) / 2
return [x, y]
else:
# 随机选3个点
sample_points = random.sample(points, 3)
# 对每个点计算到这3个点的总距离
min_sum = float('inf')
best_point = None
for p in points:
dist_sum = sum(sq(p, sp) for sp in sample_points)
if dist_sum < min_sum:
min_sum = dist_sum
best_point = p
return best_point
def move_randomly(rw, k):
k = k % 4
suiji_t = float(random.randint(10, 13) / 10)
if k == 0:
keyboard.send_data("66")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 1:
keyboard.send_data("88")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 2:
keyboard.send_data("44")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 3:
keyboard.send_data("22")
time.sleep(suiji_t)
keyboard.release() # Release
return k + 1
def move_to(rw, mb):
"""使用配置的v值"""
global v
v = active_group['move_velocity'] # 每次都从配置读取最新值
if rw[0] >= mb[0]:
keyboard.send_data("44")
time.sleep(float(abs(rw[0] - mb[0]) / v))
keyboard.release() # Release
else:
keyboard.send_data("66")
time.sleep(float(abs(rw[0] - mb[0]) / v))
keyboard.release() # Release
if rw[1] >= mb[1]:
keyboard.send_data("88")
time.sleep(float(abs(rw[1] - mb[1]) / v))
keyboard.release() # Release
else:
keyboard.send_data("22")
time.sleep(float(abs(rw[1] - mb[1]) / v))
keyboard.release()
i = 0
print("🚀 开始自动化...")
while True:
detections = {
'center': None,
'next': None,
'npc1': None,
'npc2': None,
'npc3': None,
'npc4': None,
'boss': None,
'daojv': [],
'gw': [],
'zhaozi': None
}
im_opencv = get_image.get_frame() # [RGB,PIL]
detections = yolo_shibie(im_opencv[1], detections, model)
if shizi.tuwai(im_opencv[0]): # 进图算法
im_opencv = get_image.get_frame() # [RGB,PIL]
detections = yolo_shibie(im_opencv[1], detections, model0)
print('当前在城镇中')
if detections['npc1'] is not None and sq(rw, detections['npc1']) > 80:
print("向npc1靠近")
print(sq(rw, detections['npc1']))
move_to(rw, detections['npc1'])
continue
elif detections['npc1'] is not None and sq(rw, detections['npc1']) <= 80:
print("在npc旁边向上走")
print(sq(rw, detections['npc1']))
mb = (detections['npc1'][0], detections['npc1'][1] - 1010)
move_to(rw, mb)
continue
elif detections['npc3'] is not None and detections['npc4'] is None:
print("在npc3旁边向右走")
mb = (rw[0], detections['npc3'][1] - 50)
move_to(rw, mb)
mb = (rw[0] + 700, rw[1])
move_to(rw, mb)
continue
elif detections['npc4'] is not None:
if sq(detections['npc4'], rw) < 50:
print("离npc4很近 直接进入")
keyboard.send_data("DD")
time.sleep(0.15)
keyboard.release()
time.sleep(1)
im_opencv = get_image.get_frame() # [RGB,PIL]
if shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue
else:
print("离npc4有点远 点击进入")
move_to(rw, detections['npc4'])
time.sleep(1)
im_opencv = get_image.get_frame() # [RGB,PIL]
if shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue
elif shizi.tiaozhan(im_opencv[0]): # 开启挑战
print('进入塔4')
mouse_gui.send_data_absolute(left + 1100, top + 600, may=1)
time.sleep(0.3)
mouse_gui.send_data_absolute(left + 433, top + 455, may=1)
panduan = True
continue
elif shizi.jieshu(im_opencv[0]): # 结束挑战
print('结束挑战')
mouse_gui.send_data_absolute(left + 542, top + 644, may=1)
time.sleep(0.8)
mouse_gui.send_data_absolute(left + 706, top + 454, may=1)
continue
elif panduan: # 图内情况
print("在图内")
if shizi.shuzi(im_opencv[0]):
boss_pd = True
print("进入到boss")
if shizi.fuhuo(im_opencv[0]):
print('点击复活')
mouse_gui.send_data_absolute(left + 536, top + 627, may=1)
mouse_gui.send_data_absolute(rw[0], rw[1], may=0)
continue
if detections['zhaozi'] is not None:
move_to(rw, detections['zhaozi'])
continue
if len(detections['daojv']) != 0:
move_to(rw, process_points(detections['daojv']))
for i in range(3 + len(detections['daojv'])):
keyboard.send_data("AA")
time.sleep(0.15)
keyboard.release()
continue
if shizi.tuichu(im_opencv[0]) and detections['next'] is None and len(detections['daojv']) == 0 and len(detections['gw']) == 0 and boss_pd:
print("识别到可以退出挑战!!!!!!!!!!!!!!!!!!")
for i in range(3):
time.sleep(0.5)
im_opencv = get_image.get_frame() # [RGB,PIL]
detections = {
'center': None,
'next': None,
'npc1': None,
'npc2': None,
'npc3': None,
'npc4': None,
'boss': None,
'daojv': [],
'gw': [],
'zhaozi': None
}
detections = yolo_shibie(im_opencv[1], detections, model)
if detections['next'] is not None or len(detections['daojv']) != 0 or len(detections['gw']) != 0 or detections['boss'] is not None:
break
else:
mouse_gui.send_data_absolute(left + 640, top + 40, may=1) # 点击退出
panduan = False # 退出挑战
boss_pd = False
time.sleep(2.0)
continue
if detections['center'] is None and detections['next'] is None and (len(detections['gw']) != 0 or detections["boss"] is not None): # 识别不到中心情况 但是有怪物
print("未检测到中心点,但是有怪物")
if len(detections['gw']) != 0:
move_to(rw, detections['gw'][0])
time.sleep(0.26)
elif detections['boss'] is not None: # 跟随boss
boss_suiji1 = random.randint(-30, 30)
boss_suiji2 = random.randint(-30, 30)
detections['boss'] = (detections['boss'][0] + boss_suiji1, detections['boss'][1] + boss_suiji2)
move_to(rw, detections['boss'])
time.sleep(0.7)
continue
elif (detections['center'] is not None and detections['next'] is None and len(detections['gw']) != 0) or (boss_pd == True and detections['center'] is not None and detections['next'] is None): # 识别到中心 但是有怪物
if detections['center'][0] >= rw[0] and detections['center'][1] < rw[1]: # 3
mb = (rw[0] + 100, rw[1])
elif detections['center'][0] <= rw[0] and detections['center'][1] < rw[1]: # 4
mb = (rw[0], rw[1] - 100)
elif detections['center'][0] <= rw[0] and detections['center'][1] > rw[1]: # 1
mb = (rw[0] - 100, rw[1])
elif detections['center'][0] >= rw[0] and detections['center'][1] > rw[1]: # 2
mb = (rw[0], rw[1] + 100)
move_to(rw, mb)
time.sleep(0.25)
continue
elif boss_pd == True and detections['center'] is None and detections['next'] is None: # boss出现了 但是没有中心
k = move_randomly(rw, k)
continue
elif detections['next'] is not None:
print('进入下一层啦啦啦啦啦啦')
panduan = True
move_to(rw, detections['next'])
for i in range(2):
keyboard.send_data("DD")
time.sleep(0.15)
keyboard.release()
continue
else:
k = move_randomly(rw, k)
continue
elif shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue

300
main_old.py Normal file
View File

@@ -0,0 +1,300 @@
import cv2
from utils.get_image import get_image
from utils.mouse import mouse_gui
from ultralytics import YOLO
import time
import serial
import ch9329Comm
import time
import random
import math
from utils import shizi
model = YOLO(r"best.pt").to('cuda')
model0 = YOLO(r"best0.pt").to('cuda')
keyboard = ch9329Comm.keyboard.DataComm()
mouse = ch9329Comm.mouse.DataComm(1920, 1080)
kong_detections = {
'center': None,
'next': None,
'npc1': None,
'npc2': None,
'npc3': None,
'npc4': None,
'boss': None,
'daojv': [],
'gw': [],
'zhaozi': None
}
left=0
top=30
k=0#控制转圈的方向
panduan=False#是否在图内
boss_pd=False#是否到boss关卡
rw=(632,378)
def yolo_shibie(im_PIL,detections,model):
results = model(im_PIL)#目标检测
for result in results:
for i in range(len(result.boxes.xyxy)):
left, top, right, bottom = result.boxes.xyxy[i]
scalar_tensor = result.boxes.cls[i]
value = scalar_tensor.item()
label = result.names[int(value)]
if label=='center'or label=='next' or label=='boss' or label=='zhaozi':
player_x = int(left+(right-left)/2)
player_y = int(top+(bottom-top)/2)+30
RW = [player_x, player_y]
detections[label] = RW
elif label=='daojv' or label=='gw':
player_x = int(left + (right - left) / 2)
player_y = int(top + (bottom - top) / 2) + 30
RW = [player_x, player_y]
detections[label].append(RW)
elif label=='npc1' or label=='npc2' or label=='npc3' or label=='npc4':
player_x = int(left+(right-left)/2)
player_y = int(bottom)+30
RW = [player_x, player_y]
detections[label] = RW
return detections
def sq(p1, p2):
"""计算两点之间的欧式距离"""
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
def process_points(points):
if not points:
return None # 空列表情况
n = len(points)
if n == 1:
# 只有一个点,直接返回
return points[0]
elif n == 2:
# 两个点取中点
x = (points[0][0] + points[1][0]) / 2
y = (points[0][1] + points[1][1]) / 2
return [x, y]
else:
# 随机选3个点
sample_points = random.sample(points, 3)
# 对每个点计算到这3个点的总距离
min_sum = float('inf')
best_point = None
for p in points:
dist_sum = sum(sq(p, sp) for sp in sample_points)
if dist_sum < min_sum:
min_sum = dist_sum
best_point = p
return best_point
def move_randomly(rw, k):
k = k % 4
suiji_t=float(random.randint(10,13)/10)
if k == 0:
keyboard.send_data("66")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 1:
keyboard.send_data("88")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 2:
keyboard.send_data("44")
time.sleep(suiji_t)
keyboard.release() # Release
elif k == 3:
keyboard.send_data("22")
time.sleep(suiji_t)
keyboard.release() # Release
return k + 1
def move_to(rw,mb):
v=470
if rw[0]>=mb[0]:
keyboard.send_data("44")
time.sleep(float(abs(rw[0]-mb[0])/v))
keyboard.release() # Release
else:
keyboard.send_data("66")
time.sleep(float(abs(rw[0] - mb[0]) / v))
keyboard.release() # Release
if rw[1]>=mb[1]:
keyboard.send_data("88")
time.sleep(float(abs(rw[1] - mb[1]) / v))
keyboard.release() # Release
else:
keyboard.send_data("22")
time.sleep(float(abs(rw[1] - mb[1]) / v))
keyboard.release()
i=0
while True:
detections = {
'center': None,
'next': None,
'npc1': None,
'npc2': None,
'npc3': None,
'npc4': None,
'boss': None,
'daojv': [],
'gw': [],
'zhaozi':None
}
im_opencv = get_image.get_frame()#[RGB,PIL]
detections=yolo_shibie(im_opencv[1],detections,model)
if shizi.tuwai(im_opencv[0]): # 进图算法
im_opencv = get_image.get_frame() # [RGB,PIL]
detections = yolo_shibie(im_opencv[1], detections, model0)
print('当前在城镇中')
if detections['npc1'] is not None and sq(rw, detections['npc1']) > 80:
print("向npc1靠近")
print(sq(rw, detections['npc1']))
move_to(rw, detections['npc1'])
continue
elif detections['npc1'] is not None and sq(rw, detections['npc1']) <= 80:
print("在npc旁边向上走")
print(sq(rw, detections['npc1']))
mb = (detections['npc1'][0], detections['npc1'][1] - 1010)
move_to(rw, mb)
continue
elif detections['npc3'] is not None and detections['npc4'] is None:
print("在npc3旁边向右走")
mb = (rw[0], detections['npc3'][1]-50)
move_to(rw, mb)
mb = (rw[0] + 700, rw[1])
move_to(rw, mb)
continue
elif detections['npc4'] is not None:
if sq(detections['npc4'], rw) < 50:
print("离npc4很近 直接进入")
keyboard.send_data("DD")
time.sleep(0.15)
keyboard.release()
time.sleep(1)
im_opencv = get_image.get_frame() # [RGB,PIL]
if shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue
else:
print("离npc4有点远 点击进入")
move_to(rw, detections['npc4'])
time.sleep(1)
im_opencv = get_image.get_frame() # [RGB,PIL]
if shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue
elif shizi.tiaozhan(im_opencv[0]):#开启挑战
print('进入塔4')
mouse_gui.send_data_absolute(left+1100,top+600,may=1)
time.sleep(0.3)
mouse_gui.send_data_absolute(left + 433, top + 455,may=1)
panduan = True
continue
elif shizi.jieshu(im_opencv[0]):#结束挑战
print('结束挑战')
mouse_gui.send_data_absolute(left+542,top+644,may=1)
time.sleep(0.8)
mouse_gui.send_data_absolute(left + 706, top + 454,may=1)
continue
elif panduan :#图内情况
print("在图内")
if shizi.shuzi(im_opencv[0]) :
boss_pd = True
print("进入到boss")
if shizi.fuhuo(im_opencv[0]):
print('点击复活')
mouse_gui.send_data_absolute(left + 536, top + 627, may=1)
mouse_gui.send_data_absolute(rw[0], rw[1], may=0)
continue
if detections['zhaozi'] is not None:
move_to(rw,detections['zhaozi'])
continue
if len(detections['daojv'])!=0:
move_to(rw,process_points(detections['daojv']))
for i in range(3+len(detections['daojv'])):
keyboard.send_data("AA")
time.sleep(0.15)
keyboard.release()
continue
if shizi.tuichu(im_opencv[0]) and detections['next'] is None and len(detections['daojv'])==0 and len(detections['gw'])==0 and boss_pd:
print("识别到可以退出挑战!!!!!!!!!!!!!!!!!!")
for i in range(3):
time.sleep(0.5)
im_opencv = get_image.get_frame()#[RGB,PIL]
detections = {
'center': None,
'next': None,
'npc1': None,
'npc2': None,
'npc3': None,
'npc4': None,
'boss': None,
'daojv': [],
'gw': [],
'zhaozi': None
}
detections = yolo_shibie(im_opencv[1], detections,model)
if detections['next'] is not None or len(detections['daojv'])!=0 or len(detections['gw'])!=0 or detections['boss'] is not None:
break
else:
mouse_gui.send_data_absolute(left + 640, top + 40,may=1)#点击退出
panduan = False#退出挑战
boss_pd = False
time.sleep(2.0)
continue
if detections['center'] is None and detections['next'] is None and (len(detections['gw'])!=0 or detections["boss"] is not None):#识别不到中心情况 但是有怪物
print("未检测到中心点,但是有怪物")
if len(detections['gw'])!=0:
move_to(rw,detections['gw'][0])
time.sleep(0.26)
elif detections['boss'] is not None:#跟随boss
boss_suiji1=random.randint(-30,30)
boss_suiji2 = random.randint(-30, 30)
detections['boss']=(detections['boss'][0]+boss_suiji1,detections['boss'][1]+boss_suiji2)
move_to(rw,detections['boss'])
time.sleep(0.7)
continue
elif (detections['center'] is not None and detections['next'] is None and len(detections['gw'])!=0) or (boss_pd==True and detections['center'] is not None and detections['next'] is None) :#识别到中心 但是有怪物
if detections['center'][0]>=rw[0] and detections['center'][1]<rw[1]:#3
mb=(rw[0]+100,rw[1])
elif detections['center'][0]<=rw[0] and detections['center'][1]<rw[1]:#4
mb=(rw[0], rw[1]-100)
elif detections['center'][0]<=rw[0] and detections['center'][1]>rw[1]:#1
mb=(rw[0]-100, rw[1])
elif detections['center'][0]>=rw[0] and detections['center'][1]>rw[1]:#2
mb=(rw[0], rw[1]+100)
move_to(rw, mb)
time.sleep(0.25)
continue
elif boss_pd==True and detections['center'] is None and detections['next'] is None:#boss出现了 但是没有中心
k=move_randomly(rw,k)
continue
elif detections['next'] is not None:
print('进入下一层啦啦啦啦啦啦')
panduan = True
move_to(rw,detections['next'])
for i in range(2):
keyboard.send_data("DD")
time.sleep(0.15)
keyboard.release()
continue
else:
k = move_randomly(rw, k)
continue
elif shizi.daoying(im_opencv[0]):
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
time.sleep(1)
continue

229
preview.py Normal file
View File

@@ -0,0 +1,229 @@
import cv2
import tkinter as tk
from tkinter import Canvas
from PIL import Image, ImageTk
import threading
import numpy as np
from config import config_manager
class PreviewWindow:
"""采集卡预览窗口"""
def __init__(self):
self.config = config_manager.config
self.caps = {}
self.frames = {}
self.large_window = None
self.running = True
def init_cameras(self):
"""初始化所有相机"""
for i, group in enumerate(self.config['groups']):
if group.get('active', True): # 只加载活动的配置
try:
cap = cv2.VideoCapture(group['camera_index'], cv2.CAP_DSHOW)
if cap.isOpened():
cap.set(cv2.CAP_PROP_FRAME_WIDTH, group['camera_width'])
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, group['camera_height'])
self.caps[i] = {
'cap': cap,
'group': group,
'name': group['name']
}
except Exception as e:
print(f"无法打开相机 {group['camera_index']}: {e}")
def capture_frames(self):
"""捕获帧"""
while self.running:
for idx, data in self.caps.items():
ret, frame = data['cap'].read()
if ret:
# 裁剪到配置的区域
height, width = frame.shape[:2]
crop_top = 30
crop_bottom = min(crop_top + 720, height)
crop_width = min(1280, width)
frame = frame[crop_top:crop_bottom, 0:crop_width]
# 转换颜色空间
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frames[idx] = frame_rgb
def create_grid_window(self):
"""创建网格窗口"""
root = tk.Tk()
root.title("采集卡预览 - 点击放大")
root.geometry("1000x700")
# 获取显示配置
display = self.config.get('display', {})
preview_width = display.get('preview_width', 640)
preview_height = display.get('preview_height', 360)
columns = display.get('preview_columns', 2)
rows = display.get('preview_rows', 2)
canvas = Canvas(root, bg='black')
canvas.pack(fill=tk.BOTH, expand=True)
# 存储图像对象
self.photo_objects = {}
def update_frames():
"""更新帧"""
while self.running:
try:
canvas.delete("all")
# 计算每个预览窗口的位置和大小
window_width = root.winfo_width() if root.winfo_width() > 1 else 1000
window_height = root.winfo_height() if root.winfo_height() > 1 else 700
cell_width = window_width // columns
cell_height = window_height // rows
frame_idx = 0
for idx in self.caps.keys():
if idx in self.frames:
row = frame_idx // columns
col = frame_idx % columns
x = col * cell_width
y = row * cell_height
# 调整图像大小
frame = self.frames[idx]
h, w = frame.shape[:2]
scale = min(cell_width / w, cell_height / h) * 0.9
new_w = int(w * scale)
new_h = int(h * scale)
resized_frame = cv2.resize(frame, (new_w, new_h))
# 转换为PIL图像
pil_image = Image.fromarray(resized_frame)
photo = ImageTk.PhotoImage(image=pil_image)
self.photo_objects[idx] = photo
# 绘制图像
center_x = x + cell_width // 2
center_y = y + cell_height // 2
canvas.create_image(center_x, center_y, image=photo, anchor='center')
# 绘制标签
name = self.caps[idx]['name']
canvas.create_text(center_x, y + 20, text=name, fill='white', font=('Arial', 12, 'bold'))
frame_idx += 1
canvas.update()
tk._default_root.update_idletasks()
except Exception as e:
print(f"更新帧错误: {e}")
import time
time.sleep(0.03) # 约30fps
def on_canvas_click(event):
"""点击画布事件"""
window_width = root.winfo_width()
window_height = root.winfo_height()
cell_width = window_width // columns
cell_height = window_height // rows
col = int(event.x // cell_width)
row = int(event.y // cell_height)
index = row * columns + col
# 找到对应的配置
if index < len(self.caps):
idx = list(self.caps.keys())[index]
self.show_large_window(idx)
canvas.bind('<Button-1>', on_canvas_click)
# 启动更新线程
update_thread = threading.Thread(target=update_frames, daemon=True)
update_thread.start()
def on_closing():
"""关闭窗口"""
self.running = False
for data in self.caps.values():
data['cap'].release()
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
def show_large_window(self, idx):
"""显示大窗口"""
if self.large_window is not None and self.large_window.winfo_exists():
self.large_window.destroy()
self.large_window = tk.Tk()
self.large_window.title(f"放大视图 - {self.caps[idx]['name']}")
self.large_window.geometry("1280x720")
canvas = Canvas(self.large_window, bg='black')
canvas.pack(fill=tk.BOTH, expand=True)
photo_obj = {}
def update_large_frame():
"""更新大窗口帧"""
while self.running:
try:
if idx in self.frames and self.large_window.winfo_exists():
canvas.delete("all")
frame = self.frames[idx]
h, w = frame.shape[:2]
# 调整到窗口大小
window_width = self.large_window.winfo_width() if self.large_window.winfo_width() > 1 else 1280
window_height = self.large_window.winfo_height() if self.large_window.winfo_height() > 1 else 720
scale = min(window_width / w, window_height / h)
new_w = int(w * scale)
new_h = int(h * scale)
resized_frame = cv2.resize(frame, (new_w, new_h))
pil_image = Image.fromarray(resized_frame)
photo = ImageTk.PhotoImage(image=pil_image)
photo_obj['img'] = photo
canvas.delete("all")
canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
self.large_window.update()
else:
break
except Exception as e:
print(f"更新大窗口错误: {e}")
break
import time
time.sleep(0.03)
update_thread = threading.Thread(target=update_large_frame, daemon=True)
update_thread.start()
self.large_window.mainloop()
def run(self):
"""运行预览"""
self.init_cameras()
# 启动捕获线程
capture_thread = threading.Thread(target=self.capture_frames, daemon=True)
capture_thread.start()
# 创建并显示网格窗口
import time
time.sleep(0.5) # 等待几帧
self.create_grid_window()
if __name__ == "__main__":
preview = PreviewWindow()
preview.run()

View File

@@ -45,6 +45,8 @@ import threading
class GetImage: class GetImage:
def __init__(self, cam_index=0, width=1920, height=1080): def __init__(self, cam_index=0, width=1920, height=1080):
self.cap = cv2.VideoCapture(cam_index, cv2.CAP_DSHOW) self.cap = cv2.VideoCapture(cam_index, cv2.CAP_DSHOW)
if not self.cap.isOpened():
self.cap = cv2.VideoCapture(cam_index)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
self.frame = None self.frame = None
@@ -70,4 +72,6 @@ class GetImage:
time.sleep(0.2) time.sleep(0.2)
self.cap.release() self.cap.release()
cv2.destroyAllWindows() cv2.destroyAllWindows()
get_image = GetImage()
# get_image 将在main.py中初始化
get_image = None

View File

@@ -1,17 +1,34 @@
import random import random
import time import time
import ch9329Comm import ch9329Comm
import time
import random
import serial import serial
serial.ser = serial.Serial('COM6', 9600) # 开启串口
mouse = ch9329Comm.mouse.DataComm(1920, 1080) # 全局变量由main.py初始化
serial.ser = None
mouse = None
def init_mouse_keyboard(config_group):
"""初始化鼠标和串口"""
global serial, mouse
# 初始化串口
serial.ser = serial.Serial(
config_group['serial_port'],
config_group['serial_baudrate']
)
# 初始化鼠标
mouse = ch9329Comm.mouse.DataComm(
config_group['camera_width'],
config_group['camera_height']
)
print(f"✅ 串口已打开: {config_group['serial_port']} {config_group['serial_baudrate']}")
print(f"✅ 鼠标已初始化: {config_group['camera_width']}x{config_group['camera_height']}")
def bezier_point(t, p0, p1, p2, p3): def bezier_point(t, p0, p1, p2, p3):
"""计算三次贝塞尔曲线上的点""" """计算三次贝塞尔曲线上的点"""
x = (1-t)**3 * p0[0] + 3*(1-t)**2*t*p1[0] + 3*(1-t)*t**2*p2[0] + t**3*p3[0] x = (1-t)**3 * p0[0] + 3*(1-t)**2*t*p1[0] + 3*(1-t)*t**2*p2[0] + t**3*p3[0]
y = (1-t)**3 * p0[1] + 3*(1-t)**2*t*p1[1] + 3*(1-t)*t**2*p2[1] + t**3*p3[1] y = (1-t)**3 * p0[1] + 3*(1-t)**2*t*p1[1] + 3*(1-t)*t**2*p2[1] + t**3*p3[1]
return (x, y) return (x, y)
def move_mouse_bezier(mouse, start, end, duration=1, steps=120): def move_mouse_bezier(mouse, start, end, duration=1, steps=120):
""" """
用贝塞尔曲线模拟鼠标移动(安全版) 用贝塞尔曲线模拟鼠标移动(安全版)
@@ -53,5 +70,6 @@ class Mouse_guiji():
mouse.click1() # 点击右 mouse.click1() # 点击右
self.point=(x,y) self.point=(x,y)
mouse_gui = Mouse_guiji() # mouse_gui 将在main.py中初始化
mouse_gui = None