import tkinter as tk
import cv2
import time
import torch
from ultralytics import YOLO
from PIL import Image, ImageTk
import threading
import queue
import dxcam
import traceback
import ctypes
from tkinter import ttk, messagebox
import os
import glob
import sys
import logitech.lg
from pynput import keyboard
class PIDController:
"""PID控制器"""
def __init__(self, kp=0.0, ki=0.0, kd=0.0):
self.kp = kp
self.ki = ki
self.kd = kd
self.prev_error = (0, 0)
self.integral = (0, 0)
self.last_time = time.time()
def update(self, error):
current_time = time.time()
dt = current_time - self.last_time
self.last_time = current_time
if dt <= 0:
dt = 0.01
dx, dy = error
px = self.kp * dx
py = self.kp * dy
self.integral = (
self.integral[0] + dx * dt,
self.integral[1] + dy * dt
)
ix = self.ki * self.integral[0]
iy = self.ki * self.integral[1]
dx_dt = (dx - self.prev_error[0]) / dt
dy_dt = (dy - self.prev_error[1]) / dt
ddx = self.kd * dx_dt
ddy = self.kd * dy_dt
self.prev_error = (dx, dy)
output_x = px + ix + ddx
output_y = py + iy + ddy
return (output_x, output_y)
def reset(self):
self.prev_error = (0, 0)
self.integral = (0, 0)
self.last_time = time.time()
class ScreenDetector:
def __init__(self, config_path):
# 解析配置文件
self._parse_config(config_path)
# 设备检测与模型加载
self.device = self._determine_device()
self.model = YOLO(self.model_path).to(self.device)
# 屏幕信息初始化
self._init_screen_info()
# 控制参数初始化
self._init_control_params()
# 状态管理
self.stop_event = threading.Event()
self.camera_lock = threading.Lock()
self.target_lock = threading.Lock()
self.offset_lock = threading.Lock()
self.shift_lock = threading.Lock()
# 初始化相机
self._init_camera()
# 初始化键盘监听
self._init_keyboard_listener()
def _parse_config(self, config_path):
"""解析并存储配置参数"""
self.cfg = self._parse_txt_config(config_path)
# 存储常用参数
self.model_path = self.cfg['model_path']
self.model_device = self.cfg['model_device']
self.screen_target_size = int(self.cfg['screen_target_size'])
self.detection_conf_thres = float(self.cfg['detection_conf_thres'])
self.detection_iou_thres = float(self.cfg['detection_iou_thres'])
self.detection_classes = [int(x) for x in self.cfg['detection_classes'].split(',')]
self.visualization_color = tuple(map(int, self.cfg['visualization_color'].split(',')))
self.visualization_line_width = int(self.cfg['visualization_line_width'])
self.visualization_font_scale = float(self.cfg['visualization_font_scale'])
self.visualization_show_conf = bool(self.cfg['visualization_show_conf'])
self.fov_horizontal = float(self.cfg.get('move_fov_horizontal', '90'))
self.mouse_dpi = int(self.cfg.get('move_mouse_dpi', '400'))
self.pid_kp = float(self.cfg.get('pid_kp', '0.5'))
self.pid_ki = float(self.cfg.get('pid_ki', '0.0'))
self.pid_kd = float(self.cfg.get('pid_kd', '0.1'))
def _parse_txt_config(self, path):
"""解析TXT格式的配置文件"""
config = {}
with open(path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip()
return config
def _determine_device(self):
"""确定运行设备"""
if self.model_device == 'auto':
return 'cuda' if torch.cuda.is_available() and torch.cuda.device_count() > 0 else 'cpu'
return self.model_device
def _init_screen_info(self):
"""初始化屏幕信息"""
user32 = ctypes.windll.user32
self.screen_width, self.screen_height = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
self.screen_center = (self.screen_width // 2, self.screen_height // 2)
# 计算截图区域
left = (self.screen_width - self.screen_target_size) // 2
top = (self.screen_height - self.screen_target_size) // 2
self.region = (
max(0, int(left)),
max(0, int(top)),
min(self.screen_width, int(left + self.screen_target_size)),
min(self.screen_height, int(top + self.screen_target_size))
)
def _init_control_params(self):
"""初始化控制参数"""
self.pid_controller = PIDController(
kp=self.pid_kp,
ki=self.pid_ki,
kd=self.pid_kd
)
self.previous_target_info = None
self.closest_target_absolute = None
self.target_offset = None
self.shift_pressed = False
def _init_camera(self):
"""初始化相机"""
try:
with self.camera_lock:
self.camera = dxcam.create(
output_idx=0,
output_color="BGR",
region=self.region
)
self.camera.start(target_fps=120, video_mode=True)
except Exception as e:
print(f"相机初始化失败: {str(e)}")
try:
# 降级模式
with self.camera_lock:
self.camera = dxcam.create()
self.camera.start(target_fps=60, video_mode=True)
except Exception as fallback_e:
print(f"降级模式初始化失败: {str(fallback_e)}")
self.camera = None
def _init_keyboard_listener(self):
"""初始化键盘监听"""
self.keyboard_listener = keyboard.Listener(
on_press=self.on_key_press,
on_release=self.on_key_release
)
self.keyboard_listener.daemon = True
self.keyboard_listener.start()
def on_key_press(self, key):
"""处理按键按下事件"""
try:
if key == keyboard.Key.shift:
with self.shift_lock:
self.shift_pressed = True
self.pid_controller.reset()
except Exception as e:
print(f"键盘按下事件处理错误: {str(e)}")
def on_key_release(self, key):
"""处理按键释放事件"""
try:
if key == keyboard.Key.shift:
with self.shift_lock:
self.shift_pressed = False
except Exception as e:
print(f"键盘释放事件处理错误: {str(e)}")
def calculate_fov_movement(self, dx, dy):
"""基于FOV算法计算鼠标移动量"""
# 计算屏幕对角线长度
screen_diagonal = (self.screen_width ** 2 + self.screen_height ** 2) ** 0.5
# 计算垂直FOV
aspect_ratio = self.screen_width / self.screen_height
fov_vertical = self.fov_horizontal / aspect_ratio
# 计算每像素对应角度
angle_per_pixel_x = self.fov_horizontal / self.screen_width
angle_per_pixel_y = fov_vertical / self.screen_height
# 计算角度偏移
angle_offset_x = dx * angle_per_pixel_x
angle_offset_y = dy * angle_per_pixel_y
# 转换为鼠标移动量
move_x = (angle_offset_x / 360) * self.mouse_dpi
move_y = (angle_offset_y / 360) * self.mouse_dpi
return move_x, move_y
def move_mouse_to_target(self):
"""移动鼠标使准心对准目标点"""
if not self.target_offset:
return
try:
# 获取目标点与屏幕中心的偏移量
with self.offset_lock:
dx, dy = self.target_offset
# 使用FOV算法计算鼠标移动量
move_x, move_y = self.calculate_fov_movement(dx, dy)
# 使用PID控制器平滑移动
pid_output = self.pid_controller.update((move_x, move_y))
move_x_pid, move_y_pid = pid_output
# 使用罗技API移动鼠标
if move_x_pid != 0 or move_y_pid != 0:
logitech.lg.mouse_xy(int(move_x_pid), int(move_y_pid))
except Exception as e:
print(f"移动鼠标时出错: {str(e)}")
def run(self, frame_queue):
"""主检测循环"""
while not self.stop_event.is_set():
try:
# 截图
grab_start = time.perf_counter()
screenshot = self._grab_screenshot()
grab_time = (time.perf_counter() - grab_start) * 1000 # ms
if screenshot is None:
time.sleep(0.001)
continue
# 推理
inference_start = time.perf_counter()
results = self._inference(screenshot)
inference_time = (time.perf_counter() - inference_start) * 1000 # ms
# 处理检测结果
target_info, closest_target_relative, closest_offset = self._process_detection_results(results)
# 更新目标信息
self._update_target_info(target_info, closest_offset)
# 移动鼠标
self._move_mouse_if_needed()
# 可视化处理
annotated_frame = self._visualize_results(results, closest_target_relative) if frame_queue else None
# 放入队列
if frame_queue:
try:
frame_queue.put(
(annotated_frame, len(target_info), inference_time, grab_time, target_info),
timeout=0.01
)
except queue.Full:
pass
except Exception as e:
print(f"检测循环异常: {str(e)}")
traceback.print_exc()
self._reset_camera()
time.sleep(0.5)
def _grab_screenshot(self):
"""安全获取截图"""
with self.camera_lock:
if self.camera:
return self.camera.grab()
return None
def _inference(self, screenshot):
"""执行模型推理"""
return self.model.predict(
screenshot,
conf=self.detection_conf_thres,
iou=self.detection_iou_thres,
classes=self.detection_classes,
device=self.device,
verbose=False
)
def _process_detection_results(self, results):
"""处理检测结果"""
target_info = []
min_distance = float('inf')
closest_target_relative = None
closest_target_absolute = None
closest_offset = None
for box in results[0].boxes:
# 获取边界框坐标
x1, y1, x2, y2 = map(int, box.xyxy[0])
# 计算绝对坐标
x1_abs = x1 + self.region[0]
y1_abs = y1 + self.region[1]
x2_abs = x2 + self.region[0]
y2_abs = y2 + self.region[1]
# 计算目标中心点
target_center = ((x1_abs + x2_abs) // 2, (y1_abs + y2_abs) // 2)
# 计算偏移量
dx = target_center[0] - self.screen_center[0]
dy = target_center[1] - self.screen_center[1]
distance = (dx ** 2 + dy ** 2) ** 0.5
# 更新最近目标
if distance < min_distance:
min_distance = distance
closest_target_relative = ((x1 + x2) // 2, (y1 + y2) // 2)
closest_target_absolute = target_center
closest_offset = (dx, dy)
# 保存目标信息
class_id = int(box.cls)
class_name = self.model.names[class_id]
target_info.append(f"{class_name}:{x1_abs},{y1_abs},{x2_abs},{y2_abs}")
return target_info, closest_target_relative, closest_offset
def _update_target_info(self, target_info, closest_offset):
"""更新目标信息"""
# 检查目标信息是否有变化
if target_info != self.previous_target_info:
self.previous_target_info = target_info.copy()
print(f"{len(target_info)}|{'|'.join(target_info)}")
# 更新目标偏移量
with self.offset_lock:
self.target_offset = closest_offset
def _visualize_results(self, results, closest_target):
"""可视化处理结果"""
frame = results[0].plot(
line_width=self.visualization_line_width,
font_size=self.visualization_font_scale,
conf=self.visualization_show_conf
)
# 绘制最近目标
if closest_target:
# 绘制目标中心点
cv2.circle(
frame,
(int(closest_target[0]), int(closest_target[1])),
3, (0, 0, 255), -1
)
# 计算屏幕中心在截图区域内的相对坐标
screen_center_x = self.screen_center[0] - self.region[0]
screen_center_y = self.screen_center[1] - self.region[1]
# 绘制中心到目标的连线
cv2.line(
frame,
(int(screen_center_x), int(screen_center_y)),
(int(closest_target[0]), int(closest_target[1])),
(0, 255, 0), 1
)
return frame
def _move_mouse_if_needed(self):
"""如果需要则移动鼠标"""
with self.shift_lock:
if self.shift_pressed and self.target_offset:
self.move_mouse_to_target()
def _reset_camera(self):
"""重置相机"""
print("正在重置相机...")
try:
self._init_camera()
except Exception as e:
print(f"相机重置失败: {str(e)}")
traceback.print_exc()
def stop(self):
"""安全停止检测器"""
self.stop_event.set()
self._safe_stop()
if hasattr(self, 'keyboard_listener') and self.keyboard_listener.running:
self.keyboard_listener.stop()
def _safe_stop(self):
"""同步释放资源"""
print("正在安全停止相机...")
try:
with self.camera_lock:
if self.camera:
self.camera.stop()
print("相机已停止")
except Exception as e:
print(f"停止相机时发生错误: {str(e)}")
print("屏幕检测器已停止")
class App:
def __init__(self, root, detector):
self.root = root
self.detector = detector
self.root.title("DXcam Detection")
self.root.geometry(f"{detector.region[2] - detector.region[0]}x{detector.region[3] - detector.region[1] + 50}")
self.root.wm_attributes('-topmost', 1)
# 界面组件
self.canvas = tk.Canvas(root, highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True)
# 性能监控队列
self.frame_queue = queue.Queue(maxsize=3)
# 控制面板
self.control_frame = tk.Frame(root)
self.control_frame.pack(side=tk.BOTTOM, fill=tk.X)
# 性能信息显示
self.info_label = tk.Label(self.control_frame, text="初始化中...", font=("Consolas", 10))
self.info_label.pack(side=tk.TOP, fill=tk.X, padx=5)
# 按钮区域
self.toggle_btn = tk.Button(self.control_frame, text="切换可视化", command=self.toggle_visualization)
self.toggle_btn.pack(side=tk.LEFT, padx=5)
self.settings_btn = tk.Button(self.control_frame, text="设置", command=self.open_settings)
self.settings_btn.pack(side=tk.LEFT, padx=5)
# Shift键状态显示
self.shift_status = tk.Label(self.control_frame, text="Shift状态: 未按下", fg="red", font=("Consolas", 10))
self.shift_status.pack(side=tk.LEFT, padx=10)
# 启动检测线程
self.detection_thread = threading.Thread(target=self.detector.run, args=(self.frame_queue,))
self.detection_thread.daemon = True
self.detection_thread.start()
# 界面更新
self.visualization_enabled = True
self.update_interval = 1 # 1ms更新一次界面
self.update_image()
# 窗口关闭处理
self.root.protocol("WM_DELETE_WINDOW", self.safe_exit)
# 绑定键盘事件
self.root.bind('<KeyPress-Shift_L>', self.update_shift_status)
self.root.bind('<KeyRelease-Shift_L>', self.update_shift_status)
self.root.bind('<KeyPress-Shift_R>', self.update_shift_status)
self.root.bind('<KeyRelease-Shift_R>', self.update_shift_status)
def update_shift_status(self, event=None):
"""更新Shift键状态显示"""
with self.detector.shift_lock:
if self.detector.shift_pressed:
self.shift_status.config(text="Shift状态: 按下", fg="green")
else:
self.shift_status.config(text="Shift状态: 未按下", fg="red")
def toggle_visualization(self):
"""切换可视化状态"""
self.visualization_enabled = not self.visualization_enabled
state = "启用" if self.visualization_enabled else "禁用"
self.info_label.config(text=f"可视化状态: {state}")
self.canvas.delete("all")
if not self.visualization_enabled:
self.canvas.config(bg="black")
def open_settings(self):
"""打开设置窗口"""
SettingsWindow(self.root, self.detector.cfg)
def display_target_info(self, target_info):
"""在画布上显示目标信息"""
# 显示标题
title = "目标类别与坐标"
self.canvas.create_text(10, 10, text=title,
anchor=tk.NW, fill="#00FF00",
font=("Consolas", 11, "bold"))
# 显示目标信息
y_offset = 40
line_height = 20
if target_info:
for i, data in enumerate(target_info):
try:
parts = data.split(":", 1)
if len(parts) == 2:
class_name, coords_str = parts
coords = list(map(int, coords_str.split(',')))
if len(coords) == 4:
display_text = f"{class_name}: [{coords[0]}, {coords[1]}, {coords[2]}, {coords[3]}]"
else:
display_text = f"坐标格式错误: {data}"
else:
display_text = f"数据格式错误: {data}"
except:
display_text = f"解析错误: {data}"
self.canvas.create_text(15, y_offset, text=display_text,
anchor=tk.NW, fill="#00FFFF",
font=("Consolas", 10))
y_offset += line_height
else:
self.canvas.create_text(15, y_offset, text="无检测目标",
anchor=tk.NW, fill="#FF0000",
font=("Consolas", 10))
def update_image(self):
"""更新界面显示"""
try:
# 获取最新数据
latest_data = None
while not self.frame_queue.empty():
latest_data = self.frame_queue.get_nowait()
if latest_data:
# 解包数据
frame, targets_count, inference_time, grab_time, target_info = latest_data
# 单位转换
inference_sec = inference_time / 1000
grab_sec = grab_time / 1000
# 更新显示
if self.visualization_enabled and frame is not None:
# 显示图像
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
self.tk_image = ImageTk.PhotoImage(image=img)
self.canvas.delete("all")
self.canvas.create_image(0, 0, image=self.tk_image, anchor=tk.NW)
else:
# 显示坐标文本
self.canvas.delete("all")
self.display_target_info(target_info)
# 更新性能信息
info_text = (f"目标: {targets_count} | "
f"推理: {inference_sec:.3f}s | "
f"截图: {grab_sec:.3f}s")
self.info_label.config(text=info_text)
except Exception as e:
print(f"更新图像时出错: {str(e)}")
finally:
# 更新Shift键状态
self.update_shift_status()
# 调度下一次更新
self.root.after(self.update_interval, self.update_image)
def safe_exit(self):
"""安全退出程序"""
self.detector.stop()
self.root.after(100, self.root.destroy)
class SettingsWindow(tk.Toplevel):
def __init__(self, parent, config):
super().__init__(parent)
self.title("设置")
self.geometry("400x450")
self.config = config
self.transient(parent)
self.grab_set()
self.create_widgets()
def create_widgets(self):
"""创建设置窗口界面"""
notebook = ttk.Notebook(self)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 模型设置
model_frame = ttk.Frame(notebook)
notebook.add(model_frame, text="模型设置")
self.create_model_settings(model_frame)
# 屏幕设置
screen_frame = ttk.Frame(notebook)
notebook.add(screen_frame, text="屏幕设置")
self.create_screen_settings(screen_frame)
# 检测设置
detection_frame = ttk.Frame(notebook)
notebook.add(detection_frame, text="检测设置")
self.create_detection_settings(detection_frame)
# 移动设置
move_frame = ttk.Frame(notebook)
notebook.add(move_frame, text="移动设置")
self.create_move_settings(move_frame)
# 按钮区域
btn_frame = ttk.Frame(self)
btn_frame.pack(fill=tk.X, padx=10, pady=10)
save_btn = tk.Button(btn_frame, text="保存配置", command=self.save_config)
save_btn.pack(side=tk.RIGHT, padx=5)
cancel_btn = tk.Button(btn_frame, text="取消", command=self.destroy)
cancel_btn.pack(side=tk.RIGHT, padx=5)
def create_model_settings(self, parent):
"""创建模型设置页面"""
# 获取基础路径
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.abspath(__file__))
# 获取模型文件列表
models_dir = os.path.join(base_path, 'models')
model_files = []
if os.path.exists(models_dir):
model_files = glob.glob(os.path.join(models_dir, '*.pt'))
# 处理模型显示名称
model_display_names = [os.path.basename(f) for f in model_files] if model_files else ["未找到模型文件"]
self.model_name_to_path = {os.path.basename(f): f for f in model_files}
# 当前配置的模型处理
current_model_path = self.config['model_path']
current_model_name = os.path.basename(current_model_path)
# 确保当前模型在列表中
if current_model_name not in model_display_names:
model_display_names.append(current_model_name)
self.model_name_to_path[current_model_name] = current_model_path
# 创建UI组件
ttk.Label(parent, text="选择模型:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.model_name = tk.StringVar(value=current_model_name)
model_combo = ttk.Combobox(parent, textvariable=self.model_name, state="readonly", width=30)
model_combo['values'] = model_display_names
model_combo.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="运行设备:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.device_var = tk.StringVar(value=self.config['model_device'])
device_combo = ttk.Combobox(parent, textvariable=self.device_var, state="readonly", width=30)
device_combo['values'] = ('auto', 'cuda', 'cpu')
device_combo.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
def create_screen_settings(self, parent):
"""创建屏幕设置页面"""
ttk.Label(parent, text="显示器编号:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.monitor_var = tk.StringVar(value=self.config.get('screen_monitor', '0'))
ttk.Entry(parent, textvariable=self.monitor_var, width=10).grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="截屏尺寸:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.target_size_var = tk.StringVar(value=self.config['screen_target_size'])
ttk.Entry(parent, textvariable=self.target_size_var, width=10).grid(row=1, column=1, padx=5, pady=5,
sticky=tk.W)
def create_detection_settings(self, parent):
"""创建检测设置页面"""
ttk.Label(parent, text="置信度阈值:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.conf_thres_var = tk.DoubleVar(value=float(self.config['detection_conf_thres']))
conf_scale = ttk.Scale(parent, from_=0.1, to=1.0, variable=self.conf_thres_var,
orient=tk.HORIZONTAL, length=200)
conf_scale.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
self.conf_thres_display = tk.StringVar()
self.conf_thres_display.set(f"{self.conf_thres_var.get():.2f}")
ttk.Label(parent, textvariable=self.conf_thres_display).grid(row=0, column=2, padx=5, pady=5)
self.conf_thres_var.trace_add("write",
lambda *args: self.conf_thres_display.set(f"{self.conf_thres_var.get():.2f}"))
ttk.Label(parent, text="IOU阈值:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.iou_thres_var = tk.DoubleVar(value=float(self.config['detection_iou_thres']))
iou_scale = ttk.Scale(parent, from_=0.1, to=1.0, variable=self.iou_thres_var,
orient=tk.HORIZONTAL, length=200)
iou_scale.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
self.iou_thres_display = tk.StringVar()
self.iou_thres_display.set(f"{self.iou_thres_var.get():.2f}")
ttk.Label(parent, textvariable=self.iou_thres_display).grid(row=1, column=2, padx=5, pady=5)
self.iou_thres_var.trace_add("write",
lambda *args: self.iou_thres_display.set(f"{self.iou_thres_var.get():.2f}"))
ttk.Label(parent, text="检测类别:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
self.classes_var = tk.StringVar(value=self.config['detection_classes'])
ttk.Entry(parent, textvariable=self.classes_var, width=20).grid(row=2, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="(逗号分隔)").grid(row=2, column=2, padx=5, pady=5, sticky=tk.W)
def create_move_settings(self, parent):
"""创建移动设置页面"""
ttk.Label(parent, text="横向FOV(度):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.fov_horizontal_var = tk.StringVar(value=self.config.get('move_fov_horizontal', '90'))
fov_entry = ttk.Entry(parent, textvariable=self.fov_horizontal_var, width=10)
fov_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="鼠标DPI:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.mouse_dpi_var = tk.StringVar(value=self.config.get('move_mouse_dpi', '400'))
dpi_entry = ttk.Entry(parent, textvariable=self.mouse_dpi_var, width=10)
dpi_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
# PID参数设置
ttk.Label(parent, text="PID参数设置", font=("Arial", 10, "bold")).grid(row=2, column=0, columnspan=3, pady=10,
sticky=tk.W)
ttk.Label(parent, text="比例系数(P):").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
self.pid_kp_var = tk.StringVar(value=self.config.get('pid_kp', '0.5'))
kp_entry = ttk.Entry(parent, textvariable=self.pid_kp_var, width=10)
kp_entry.grid(row=3, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="积分系数(I):").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
self.pid_ki_var = tk.StringVar(value=self.config.get('pid_ki', '0.0'))
ki_entry = ttk.Entry(parent, textvariable=self.pid_ki_var, width=10)
ki_entry.grid(row=4, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(parent, text="微分系数(D):").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W)
self.pid_kd_var = tk.StringVar(value=self.config.get('pid_kd', '0.1'))
kd_entry = ttk.Entry(parent, textvariable=self.pid_kd_var, width=10)
kd_entry.grid(row=5, column=1, padx=5, pady=5, sticky=tk.W)
def save_config(self):
"""保存配置到文件"""
try:
model_name = self.model_name.get()
model_path = self.model_name_to_path.get(model_name, model_name)
self.config['model_path'] = model_path
self.config['model_device'] = self.device_var.get()
self.config['screen_monitor'] = self.monitor_var.get()
self.config['screen_target_size'] = self.target_size_var.get()
self.config['detection_conf_thres'] = str(self.conf_thres_var.get())
self.config['detection_iou_thres'] = str(self.iou_thres_var.get())
self.config['detection_classes'] = self.classes_var.get()
# 保存移动设置
self.config['move_fov_horizontal'] = self.fov_horizontal_var.get()
self.config['move_mouse_dpi'] = self.mouse_dpi_var.get()
# 保存PID参数
self.config['pid_kp'] = self.pid_kp_var.get()
self.config['pid_ki'] = self.pid_ki_var.get()
self.config['pid_kd'] = self.pid_kd_var.get()
# 保存为TXT格式
with open('detection_config.txt', 'w', encoding='utf-8') as f:
for key, value in self.config.items():
f.write(f"{key} = {value}\n")
messagebox.showinfo("成功", "配置已保存!重启后生效")
self.destroy()
except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
if __name__ == "__main__":
detector = ScreenDetector('detection_config.txt')
print(f"\nDXcam检测器初始化完成 | 设备: {detector.device.upper()}")
root = tk.Tk()
app = App(root, detector)
root.mainloop()
我需要添加对目标点进行平滑处理的函数,让目标点不抖动。注意是封装好。然后在我推理,检测结果之后再平滑处理,再更新目标信息。注意不是目标框平滑,是目标点平滑。请你严格根据我的代码风格来。把需要修改的函数和需要添加和函数做详细说明
最新发布