R032---UiPath变量范围&默认值导致的错误及解决

本文介绍了在UiPath中因变量范围设置不当和默认值导致的循环执行错误。当在循环体内创建并设置了默认值的变量在每次循环时会被重新初始化,从而影响If条件判断。解决方法是将变量范围调整到循环体外部,确保变量在整个循环过程中保持其状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

​一、缘起

2019-7-12UiPath正式版安装包注册一度只能用最新社区版,所以写了一篇相关文章,供遇到该问题的小伙伴参考;

今天又发现恢复原状,并自动从头计算90天注册期,所以,删除了之前的文章,补充一篇前两天遇到的一个关于变量范围引起问题及解决。

二、问题

问题场景:

  • 在循环体内一个Activity实例上,通过Ctrl+K新建了一个变量iNum,该变量的范围被自动定义在这个Activity所在的容器;

  • 然后给这个Activity实例设置了一个默认值4;

场景截图:

猜猜,会发生什么?

答案:

每次循环体被执行时,iNum的值都会被初始化为4,导致If的条件一直成立(走Then,即使这里重新Assign iNum=0,重新循环时还是被默认为4),不会走到If的Else方向。

三、解决方法

将变量范围调整到“循环体”外(更大的范围)。

四、原因分析

由于变量iNum的定义范围和默认值都是在循环体内,所以,每次循环,UiPath都会在循环体内,全新的定义变量iNum,并给它设置默认值,导致iNum的值一直是默认值。

理解了吗?

(正文结束)

<

import sys import cv2 import time import torch import traceback import threading import queue import dxcam import ctypes import os import glob import numpy as np import logitech.lg from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QSlider, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QGroupBox, QTextEdit, QFileDialog, QMessageBox, QSizePolicy, QSplitter, QDialog, QScrollArea) from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal from PyQt6.QtGui import QImage, QPixmap, QPainter, QColor, QFont, QIcon, QKeyEvent, QMouseEvent from PyQt6.QtSvg import QSvgRenderer from PIL import Image from ultralytics import YOLO from pynput import mouse class PIDController: &quot;&quot;&quot;PID控制器&quot;&quot;&quot; def __init__(self, kp, ki, kd, output_min=-100, output_max=100): self.kp = kp # 比例增益 self.ki = ki # 积分增益 self.kd = kd # 微分增益 self.output_min = output_min self.output_max = output_max # 状态变量 self.integral = 0.0 self.prev_error = 0.0 self.last_time = time.perf_counter() def compute(self, setpoint, current_value): &quot;&quot;&quot;计算PID控制输出&quot;&quot;&quot; current_time = time.perf_counter() dt = current_time - self.last_time # 防止过小的时间差导致计算问题 MIN_DT = 0.0001 if dt &lt; MIN_DT: dt = MIN_DT # 计算误差 error = setpoint - current_value # 比例项 P = self.kp * error # 积分项(防饱和) self.integral += error * dt I = self.ki * self.integral # 微分项 derivative = (error - self.prev_error) / dt D = self.kd * derivative # 合成输出 output = P + I + D # 输出限幅 if output &gt; self.output_max: output = self.output_max elif output &lt; self.output_min: output = self.output_min # 更新状态 self.prev_error = error self.last_time = current_time return output def reset(self): &quot;&quot;&quot;重置控制器状态&quot;&quot;&quot; self.integral = 0.0 self.prev_error = 0.0 self.last_time = time.perf_counter() 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.button_lock = threading.Lock() # 推理状态控制 self.inference_active = False self.inference_lock = threading.Lock() # 初始化相机 self._init_camera() # 初始化鼠标监听器 self._init_mouse_listener() # 初始化PID控制器 self._init_pid_controllers() def _parse_config(self, config_path): &quot;&quot;&quot;解析并存储配置参数&quot;&quot;&quot; self.cfg = self._parse_txt_config(config_path) # 存储常用参数 self.model_path = self.cfg[&#39;model_path&#39;] self.model_device = self.cfg[&#39;model_device&#39;] self.screen_target_size = int(self.cfg[&#39;screen_target_size&#39;]) self.detection_conf_thres = float(self.cfg[&#39;detection_conf_thres&#39;]) self.detection_iou_thres = float(self.cfg[&#39;detection_iou_thres&#39;]) self.detection_classes = [int(x) for x in self.cfg[&#39;detection_classes&#39;].split(&#39;,&#39;)] self.visualization_color = tuple(map(int, self.cfg[&#39;visualization_color&#39;].split(&#39;,&#39;))) self.visualization_line_width = int(self.cfg[&#39;visualization_line_width&#39;]) self.visualization_font_scale = float(self.cfg[&#39;visualization_font_scale&#39;]) self.visualization_show_conf = bool(self.cfg[&#39;visualization_show_conf&#39;]) self.fov_horizontal = float(self.cfg.get(&#39;move_fov_horizontal&#39;, &#39;90&#39;)) self.mouse_dpi = int(self.cfg.get(&#39;move_mouse_dpi&#39;, &#39;400&#39;)) self.target_offset_x_percent = float(self.cfg.get(&#39;target_offset_x&#39;, &#39;50&#39;)) self.target_offset_y_percent = 100 - float(self.cfg.get(&#39;target_offset_y&#39;, &#39;50&#39;)) # PID参数 self.pid_kp = float(self.cfg.get(&#39;pid_kp&#39;, &#39;1.0&#39;)) self.pid_ki = float(self.cfg.get(&#39;pid_ki&#39;, &#39;0.05&#39;)) self.pid_kd = float(self.cfg.get(&#39;pid_kd&#39;, &#39;0.2&#39;)) # 贝塞尔曲线参数 self.bezier_steps = int(self.cfg.get(&#39;bezier_steps&#39;, &#39;100&#39;)) self.bezier_duration = float(self.cfg.get(&#39;bezier_duration&#39;, &#39;0.1&#39;)) self.bezier_curve = float(self.cfg.get(&#39;bezier_curve&#39;, &#39;0.3&#39;)) def update_config(self, config_path): &quot;&quot;&quot;动态更新配置&quot;&quot;&quot; try: # 重新解析配置文件 self._parse_config(config_path) # 更新可以直接修改的参数 self.detection_conf_thres = float(self.cfg[&#39;detection_conf_thres&#39;]) self.detection_iou_thres = float(self.cfg[&#39;detection_iou_thres&#39;]) self.target_offset_x_percent = float(self.cfg.get(&#39;target_offset_x&#39;, &#39;50&#39;)) self.target_offset_y_percent = 100 - float(self.cfg.get(&#39;target_offset_y&#39;, &#39;50&#39;)) # PID参数更新 self.pid_kp = float(self.cfg.get(&#39;pid_kp&#39;, &#39;1.0&#39;)) self.pid_ki = float(self.cfg.get(&#39;pid_ki&#39;, &#39;0.05&#39;)) self.pid_kd = float(self.cfg.get(&#39;pid_kd&#39;, &#39;0.2&#39;)) # 更新PID控制器 self.pid_x = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) self.pid_y = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) # FOV和DPI更新 self.fov_horizontal = float(self.cfg.get(&#39;move_fov_horizontal&#39;, &#39;90&#39;)) self.mouse_dpi = int(self.cfg.get(&#39;move_mouse_dpi&#39;, &#39;400&#39;)) # 更新贝塞尔曲线参数 self.bezier_steps = int(self.cfg.get(&#39;bezier_steps&#39;, &#39;100&#39;)) self.bezier_duration = float(self.cfg.get(&#39;bezier_duration&#39;, &#39;0.1&#39;)) self.bezier_curve = float(self.cfg.get(&#39;bezier_curve&#39;, &#39;0.3&#39;)) print(&quot;配置已动态更新&quot;) return True except Exception as e: print(f&quot;更新配置失败: {str(e)}&quot;) traceback.print_exc() return False def _parse_txt_config(self, path): &quot;&quot;&quot;解析TXT格式的配置文件&quot;&quot;&quot; config = {} with open(path, &#39;r&#39;, encoding=&#39;utf-8&#39;) as f: for line in f: line = line.strip() if not line or line.startswith(&#39;#&#39;): continue if &#39;=&#39; in line: key, value = line.split(&#39;=&#39;, 1) config[key.strip()] = value.strip() return config def _init_pid_controllers(self): &quot;&quot;&quot;初始化PID控制器&quot;&quot;&quot; # 创建XY方向的PID控制器 self.pid_x = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) self.pid_y = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) def start_inference(self): &quot;&quot;&quot;启动推理&quot;&quot;&quot; with self.inference_lock: self.inference_active = True def stop_inference(self): &quot;&quot;&quot;停止推理&quot;&quot;&quot; with self.inference_lock: self.inference_active = False def _determine_device(self): &quot;&quot;&quot;确定运行设备&quot;&quot;&quot; if self.model_device == &#39;auto&#39;: return &#39;cuda&#39; if torch.cuda.is_available() and torch.cuda.device_count() &gt; 0 else &#39;cpu&#39; return self.model_device def _init_screen_info(self): &quot;&quot;&quot;初始化屏幕信息&quot;&quot;&quot; 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): &quot;&quot;&quot;初始化控制参数&quot;&quot;&quot; self.previous_target_info = None self.closest_target_absolute = None self.target_offset = None self.right_button_pressed = False # 改为鼠标右键状态 def _init_camera(self): &quot;&quot;&quot;初始化相机&quot;&quot;&quot; try: with self.camera_lock: self.camera = dxcam.create( output_idx=0, output_color=&quot;BGR&quot;, region=self.region ) self.camera.start(target_fps=120, video_mode=True) except Exception as e: print(f&quot;相机初始化失败: {str(e)}&quot;) 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&quot;降级模式初始化失败: {str(fallback_e)}&quot;) self.camera = None def _init_mouse_listener(self): &quot;&quot;&quot;初始化鼠标监听器&quot;&quot;&quot; self.mouse_listener = mouse.Listener( on_click=self.on_mouse_click # 监听鼠标点击事件 ) self.mouse_listener.daemon = True self.mouse_listener.start() def on_mouse_click(self, x, y, button, pressed): &quot;&quot;&quot;处理鼠标点击事件&quot;&quot;&quot; try: if button == mouse.Button.right: # 监听鼠标右键 with self.button_lock: self.right_button_pressed = pressed # 更新状态 # 当右键释放时重置PID控制器 if not pressed: self.pid_x.reset() self.pid_y.reset() except Exception as e: print(f&quot;鼠标事件处理错误: {str(e)}&quot;) def calculate_fov_movement(self, dx, dy): &quot;&quot;&quot;基于FOV算法计算鼠标移动量&quot;&quot;&quot; # 计算屏幕对角线长度 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): &quot;&quot;&quot;移动鼠标对准目标点&quot;&quot;&quot; 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_move_x = self.pid_x.compute(0, -move_x) # 将dx取反 pid_move_y = self.pid_y.compute(0, -move_y) # 将dy取反 # 移动鼠标 if pid_move_x != 0 or pid_move_y != 0: logitech.lg.start_mouse_move(int(pid_move_x), int(pid_move_y), self.bezier_steps, self.bezier_duration, self.bezier_curve) except Exception as e: print(f&quot;移动鼠标时出错: {str(e)}&quot;) def run(self, frame_queue): &quot;&quot;&quot;主检测循环&quot;&quot;&quot; while not self.stop_event.is_set(): try: # 检查推理状态 with self.inference_lock: if not self.inference_active: time.sleep(0.01) continue # 截图 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&quot;检测循环异常: {str(e)}&quot;) traceback.print_exc() self._reset_camera() time.sleep(0.5) def _grab_screenshot(self): &quot;&quot;&quot;安全获取截图&quot;&quot;&quot; with self.camera_lock: if self.camera: return self.camera.grab() return None def _inference(self, screenshot): &quot;&quot;&quot;执行模型推理&quot;&quot;&quot; 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): &quot;&quot;&quot;处理检测结果&quot;&quot;&quot; target_info = [] min_distance = float(&#39;inf&#39;) 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] # 计算边界框尺寸 width = x2_abs - x1_abs height = y2_abs - y1_abs # 应用偏移百分比计算目标点 target_x = x1_abs + int(width * (self.target_offset_x_percent / 100)) target_y = y1_abs + int(height * (self.target_offset_y_percent / 100)) # 计算偏移量 dx = target_x - self.screen_center[0] dy = target_y - self.screen_center[1] distance = (dx ** 2 + dy ** 2) ** 0.5 # 更新最近目标 if distance &lt; min_distance: min_distance = distance # 计算相对坐标(用于可视化) closest_target_relative = ( x1 + int(width * (self.target_offset_x_percent / 100)), y1 + int(height * (self.target_offset_y_percent / 100)) ) closest_target_absolute = (target_x, target_y) closest_offset = (dx, dy) # 保存目标信息 class_id = int(box.cls) class_name = self.model.names[class_id] target_info.append(f&quot;{class_name}:{x1_abs},{y1_abs},{x2_abs},{y2_abs}&quot;) return target_info, closest_target_relative, closest_offset def _update_target_info(self, target_info, closest_offset): &quot;&quot;&quot;更新目标信息&quot;&quot;&quot; # 检查目标信息是否有变化 if target_info != self.previous_target_info: self.previous_target_info = target_info.copy() print(f&quot;{len(target_info)}|{&#39;|&#39;.join(target_info)}&quot;) # 更新目标偏移量 with self.offset_lock: self.target_offset = closest_offset def _visualize_results(self, results, closest_target): &quot;&quot;&quot;可视化处理结果&quot;&quot;&quot; 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): &quot;&quot;&quot;如果需要则移动鼠标&quot;&quot;&quot; with self.button_lock: if self.right_button_pressed and self.target_offset: # 使用right_button_pressed self.move_mouse_to_target() def _reset_camera(self): &quot;&quot;&quot;重置相机&quot;&quot;&quot; print(&quot;正在重置相机...&quot;) try: self._init_camera() except Exception as e: print(f&quot;相机重置失败: {str(e)}&quot;) traceback.print_exc() def stop(self): &quot;&quot;&quot;安全停止检测器&quot;&quot;&quot; self.stop_event.set() self._safe_stop() if hasattr(self, &#39;mouse_listener&#39;) and self.mouse_listener.running: # 改为停止鼠标监听器 self.mouse_listener.stop() def _safe_stop(self): &quot;&quot;&quot;同步释放资源&quot;&quot;&quot; print(&quot;正在安全停止相机...&quot;) try: with self.camera_lock: if self.camera: self.camera.stop() print(&quot;相机已停止&quot;) except Exception as e: print(f&quot;停止相机时发生错误: {str(e)}&quot;) print(&quot;屏幕检测器已停止&quot;) class DetectionThread(QThread): update_signal = pyqtSignal(object) def __init__(self, detector, frame_queue): super().__init__() self.detector = detector self.frame_queue = frame_queue self.running = True def run(self): self.detector.run(self.frame_queue) def stop(self): self.running = False self.detector.stop() class MainWindow(QMainWindow): def __init__(self, detector): super().__init__() self.detector = detector self.setWindowTitle(&quot;EFAI 1.1&quot;) self.setGeometry(100, 100, 600, 400) # 添加缺失的属性初始化 self.visualization_enabled = True self.inference_active = False # 初始推理状态为停止 #窗口置顶 self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint) # 创建帧队列 self.frame_queue = queue.Queue(maxsize=3) # 初始化UI self.init_ui() # 启动检测线程 self.detection_thread = DetectionThread(self.detector, self.frame_queue) self.detection_thread.start() # 启动UI更新定时器 self.update_timer = QTimer() self.update_timer.timeout.connect(self.update_ui) self.update_timer.start(1) # 每1ms更新一次 def toggle_visualization(self): # 实际更新可视化状态属性 self.visualization_enabled = not self.visualization_enabled # 更新按钮文本 if self.visualization_enabled: self.toggle_visualization_btn.setText(&quot;禁用可视化&quot;) else: self.toggle_visualization_btn.setText(&quot;启用可视化&quot;) def toggle_inference(self): &quot;&quot;&quot;切换推理状态&quot;&quot;&quot; self.inference_active = not self.inference_active if self.inference_active: self.toggle_inference_btn.setText(&quot;停止推理&quot;) self.toggle_inference_btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #F44336; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } &quot;&quot;&quot;) self.detector.start_inference() else: self.toggle_inference_btn.setText(&quot;开始推理&quot;) self.toggle_inference_btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } &quot;&quot;&quot;) self.detector.stop_inference() def init_ui(self): # 主布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 分割器(左侧图像/目标信息,右侧控制面板) splitter = QSplitter(Qt.Orientation.Horizontal) main_layout.addWidget(splitter) # 左侧区域(图像显示和目标信息) left_widget = QWidget() left_layout = QVBoxLayout(left_widget) # 图像显示区域 self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_label.setMinimumSize(320, 320) left_layout.addWidget(self.image_label) # 目标信息区域 self.target_info_text = QTextEdit() self.target_info_text.setReadOnly(True) self.target_info_text.setFixedHeight(150) self.target_info_text.setStyleSheet(&quot;&quot;&quot; QTextEdit { background-color: #2D2D30; color: #DCDCDC; font-family: Consolas; font-size: 10pt; border: 1px solid #3F3F46; border-radius: 4px; } &quot;&quot;&quot;) left_layout.addWidget(self.target_info_text) # 右侧控制面板 right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setAlignment(Qt.AlignmentFlag.AlignTop) # 性能信息 perf_group = QGroupBox(&quot;性能信息&quot;) perf_layout = QVBoxLayout(perf_group) self.target_count_label = QLabel(&quot;目标数量: 0&quot;) self.inference_time_label = QLabel(&quot;推理时间: 0.000s&quot;) self.grab_time_label = QLabel(&quot;截图时间: 0.000s&quot;) for label in [self.target_count_label, self.inference_time_label, self.grab_time_label]: label.setStyleSheet(&quot;font-family: Consolas; font-size: 10pt;&quot;) perf_layout.addWidget(label) right_layout.addWidget(perf_group) # 系统信息 sys_group = QGroupBox(&quot;系统信息&quot;) sys_layout = QVBoxLayout(sys_group) # 获取模型名称(只显示文件名) model_name = os.path.basename(self.detector.model_path) # 获取显示器编号(如果配置中有则显示,否则显示默认值0) monitor_index = self.detector.cfg.get(&#39;screen_monitor&#39;, &#39;0&#39;) self.model_label = QLabel(f&quot;模型: {model_name}&quot;) self.device_label = QLabel(f&quot;设备: {self.detector.device.upper()}&quot;) self.monitor_label = QLabel(f&quot;显示器:{monitor_index}&quot;) self.screen_res_label = QLabel(f&quot;屏幕分辨率: {self.detector.screen_width}x{self.detector.screen_height}&quot;) self.region_label = QLabel(f&quot;检测区域: {self.detector.region}&quot;) for label in [self.model_label, self.device_label, self.monitor_label, self.screen_res_label, self.region_label]: label.setStyleSheet(&quot;font-family: Consolas; font-size: 9pt; color: #A0A0A0;&quot;) sys_layout.addWidget(label) right_layout.addWidget(sys_group) # 鼠标状态 mouse_group = QGroupBox(&quot;自瞄状态&quot;) mouse_layout = QVBoxLayout(mouse_group) self.mouse_status = QLabel(&quot;未瞄准&quot;) self.mouse_status.setStyleSheet(&quot;&quot;&quot; QLabel { font-family: Consolas; font-size: 10pt; color: #FF5252; } &quot;&quot;&quot;) mouse_layout.addWidget(self.mouse_status) right_layout.addWidget(mouse_group) # 控制按钮 btn_group = QGroupBox(&quot;控制&quot;) btn_layout = QVBoxLayout(btn_group) # 添加推理切换按钮 self.toggle_inference_btn = QPushButton(&quot;开始推理&quot;) self.toggle_inference_btn.clicked.connect(self.toggle_inference) self.toggle_inference_btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #45A049; } QPushButton:pressed { background-color: #3D8B40; } &quot;&quot;&quot;) btn_layout.addWidget(self.toggle_inference_btn) self.toggle_visualization_btn = QPushButton(&quot;禁用可视化&quot;) self.toggle_visualization_btn.clicked.connect(self.toggle_visualization) self.settings_btn = QPushButton(&quot;设置&quot;) self.settings_btn.clicked.connect(self.open_settings) for btn in [self.toggle_visualization_btn, self.settings_btn]: btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #0078D7; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } &quot;&quot;&quot;) btn_layout.addWidget(btn) right_layout.addWidget(btn_group) # 添加左右区域到分割器 splitter.addWidget(left_widget) splitter.addWidget(right_widget) splitter.setSizes([600, 200]) # 设置样式 self.setStyleSheet(&quot;&quot;&quot; QMainWindow { background-color: #252526; } QGroupBox { font-family: Segoe UI; font-size: 10pt; color: #CCCCCC; border: 1px solid #3F3F46; border-radius: 4px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; background-color: transparent; } &quot;&quot;&quot;) def open_settings(self): settings_dialog = SettingsDialog(self.detector.cfg, self) settings_dialog.exec() def update_ui(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 # 更新性能信息 self.target_count_label.setText(f&quot;目标数量: {targets_count}&quot;) self.inference_time_label.setText(f&quot;推理时间: {inference_time / 1000:.3f}s&quot;) self.grab_time_label.setText(f&quot;截图时间: {grab_time / 1000:.3f}s&quot;) # 更新目标信息 self.display_target_info(target_info) # 更新图像显示 if self.visualization_enabled and frame is not None: # 转换图像为Qt格式 height, width, channel = frame.shape bytes_per_line = 3 * width q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format.Format_BGR888) pixmap = QPixmap.fromImage(q_img) # 等比例缩放 scaled_pixmap = pixmap.scaled( self.image_label.width(), self.image_label.height(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) else: # 显示黑色背景 pixmap = QPixmap(self.image_label.size()) pixmap.fill(QColor(0, 0, 0)) self.image_label.setPixmap(pixmap) # 更新鼠标状态 self.update_mouse_status() except Exception as e: print(f&quot;更新UI时出错: {str(e)}&quot;) def display_target_info(self, target_info): &quot;&quot;&quot;在文本框中显示目标信息&quot;&quot;&quot; if not target_info: self.target_info_text.setPlainText(&quot;无检测目标&quot;) return info_text = &quot;目标类别与坐标:\n&quot; for i, data in enumerate(target_info): try: parts = data.split(&quot;:&quot;, 1) if len(parts) == 2: class_name, coords_str = parts coords = list(map(int, coords_str.split(&#39;,&#39;))) if len(coords) == 4: display_text = f&quot;{class_name}: [{coords[0]}, {coords[1]}, {coords[2]}, {coords[3]}]&quot; else: display_text = f&quot;坐标格式错误: {data}&quot; else: display_text = f&quot;数据格式错误: {data}&quot; except: display_text = f&quot;解析错误: {data}&quot; info_text += f&quot;{display_text}\n&quot; self.target_info_text.setPlainText(info_text) def update_mouse_status(self): &quot;&quot;&quot;更新鼠标右键状态显示&quot;&quot;&quot; with self.detector.button_lock: if self.detector.right_button_pressed: self.mouse_status.setText(&quot;瞄准中&quot;) self.mouse_status.setStyleSheet(&quot;color: #4CAF50; font-family: Consolas; font-size: 10pt;&quot;) else: self.mouse_status.setText(&quot;未瞄准&quot;) self.mouse_status.setStyleSheet(&quot;color: #FF5252; font-family: Consolas; font-size: 10pt;&quot;) def closeEvent(self, event): &quot;&quot;&quot;安全关闭程序&quot;&quot;&quot; self.detection_thread.stop() self.detection_thread.wait() event.accept() class SettingsDialog(QDialog): def __init__(self, config, parent=None): super().__init__(parent) self.config = config # 保存原始配置的副本用于比较 self.original_config = config.copy() self.setWindowTitle(&quot;设置&quot;) self.setGeometry(100, 100, 600, 500) self.init_ui() def init_ui(self): layout = QVBoxLayout() self.setLayout(layout) # 标签页 tabs = QTabWidget() layout.addWidget(tabs) # 检测设置标签页 detection_tab = QWidget() detection_layout = QVBoxLayout(detection_tab) self.create_detection_settings(detection_layout) tabs.addTab(detection_tab, &quot;检测&quot;) # 移动设置标签页 move_tab = QWidget() move_layout = QVBoxLayout(move_tab) self.create_move_settings(move_layout) tabs.addTab(move_tab, &quot;FOV&quot;) # 目标点设置标签页 target_tab = QWidget() target_layout = QVBoxLayout(target_tab) self.create_target_settings(target_layout) tabs.addTab(target_tab, &quot;目标点&quot;) # PID设置标签页 pid_tab = QWidget() pid_layout = QVBoxLayout(pid_tab) self.create_pid_settings(pid_layout) tabs.addTab(pid_tab, &quot;PID&quot;) # 贝塞尔曲线设置标签页 bezier_tab = QWidget() bezier_layout = QVBoxLayout(bezier_tab) self.create_bezier_settings(bezier_layout) tabs.addTab(bezier_tab, &quot;贝塞尔曲线&quot;) # 按钮区域 btn_layout = QHBoxLayout() layout.addLayout(btn_layout) save_btn = QPushButton(&quot;保存配置&quot;) save_btn.clicked.connect(self.save_config) cancel_btn = QPushButton(&quot;取消&quot;) cancel_btn.clicked.connect(self.reject) for btn in [save_btn, cancel_btn]: btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #0078D7; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } &quot;&quot;&quot;) btn_layout.addWidget(btn) btn_layout.addStretch() def create_detection_settings(self, layout): # 模型选择 model_group = QGroupBox(&quot;模型设置&quot;) model_layout = QVBoxLayout(model_group) # 获取基础路径 if getattr(sys, &#39;frozen&#39;, False): base_path = sys._MEIPASS else: base_path = os.path.dirname(os.path.abspath(__file__)) # 获取模型文件列表 models_dir = os.path.join(base_path, &#39;models&#39;) model_files = [] if os.path.exists(models_dir): model_files = glob.glob(os.path.join(models_dir, &#39;*.pt&#39;)) # 处理模型显示名称 model_display_names = [os.path.basename(f) for f in model_files] if model_files else [&quot;未找到模型文件&quot;] self.model_name_to_path = {os.path.basename(f): f for f in model_files} # 当前配置的模型处理 current_model_path = self.config[&#39;model_path&#39;] 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 # 模型选择下拉框 model_layout.addWidget(QLabel(&quot;选择模型:&quot;)) self.model_combo = QComboBox() self.model_combo.addItems(model_display_names) self.model_combo.setCurrentText(current_model_name) model_layout.addWidget(self.model_combo) # 设备选择 model_layout.addWidget(QLabel(&quot;运行设备:&quot;)) self.device_combo = QComboBox() self.device_combo.addItems([&#39;auto&#39;, &#39;cuda&#39;, &#39;cpu&#39;]) self.device_combo.setCurrentText(self.config[&#39;model_device&#39;]) model_layout.addWidget(self.device_combo) layout.addWidget(model_group) # 检测参数 param_group = QGroupBox(&quot;检测参数&quot;) param_layout = QVBoxLayout(param_group) # 置信度阈值 param_layout.addWidget(QLabel(&quot;置信度阈值:&quot;)) conf_layout = QHBoxLayout() self.conf_slider = QSlider(Qt.Orientation.Horizontal) self.conf_slider.setRange(10, 100) # 0.1到1.0,步长0.01 self.conf_slider.setValue(int(float(self.config[&#39;detection_conf_thres&#39;]) * 100)) conf_layout.addWidget(self.conf_slider) self.conf_value = QLabel(f&quot;{float(self.config[&#39;detection_conf_thres&#39;]):.2f}&quot;) self.conf_value.setFixedWidth(50) conf_layout.addWidget(self.conf_value) param_layout.addLayout(conf_layout) # 连接滑块值变化事件 self.conf_slider.valueChanged.connect(lambda value: self.conf_value.setText(f&quot;{value / 100:.2f}&quot;)) # IOU阈值 - 改为滑动条 param_layout.addWidget(QLabel(&quot;IOU阈值:&quot;)) iou_layout = QHBoxLayout() self.iou_slider = QSlider(Qt.Orientation.Horizontal) self.iou_slider.setRange(10, 100) # 0.1到1.0,步长0.01 self.iou_slider.setValue(int(float(self.config[&#39;detection_iou_thres&#39;]) * 100)) iou_layout.addWidget(self.iou_slider) self.iou_value = QLabel(f&quot;{float(self.config[&#39;detection_iou_thres&#39;]):.2f}&quot;) self.iou_value.setFixedWidth(50) iou_layout.addWidget(self.iou_value) param_layout.addLayout(iou_layout) # 连接滑块值变化事件 self.iou_slider.valueChanged.connect(lambda value: self.iou_value.setText(f&quot;{value / 100:.2f}&quot;)) # 检测类别 param_layout.addWidget(QLabel(&quot;检测类别 (逗号分隔):&quot;)) self.classes_edit = QLineEdit() self.classes_edit.setText(self.config[&#39;detection_classes&#39;]) param_layout.addWidget(self.classes_edit) layout.addWidget(param_group) # 屏幕设置 screen_group = QGroupBox(&quot;屏幕设置&quot;) screen_layout = QVBoxLayout(screen_group) # 显示器编号 screen_layout.addWidget(QLabel(&quot;显示器编号:&quot;)) self.monitor_spin = QSpinBox() self.monitor_spin.setRange(0, 3) # 假设最多支持4个显示器 self.monitor_spin.setValue(int(self.config.get(&#39;screen_monitor&#39;, &#39;0&#39;))) screen_layout.addWidget(self.monitor_spin) # 屏幕区域大小 screen_layout.addWidget(QLabel(&quot;截屏尺寸:&quot;)) self.screen_size_spin = QSpinBox() self.screen_size_spin.setRange(100, 2000) self.screen_size_spin.setValue(int(self.config[&#39;screen_target_size&#39;])) screen_layout.addWidget(self.screen_size_spin) layout.addWidget(screen_group) layout.addStretch() def create_move_settings(self, layout): group = QGroupBox(&quot;鼠标移动参数&quot;) group_layout = QVBoxLayout(group) # FOV设置 group_layout.addWidget(QLabel(&quot;横向FOV(度):&quot;)) self.fov_spin = QDoubleSpinBox() self.fov_spin.setRange(1, 179) self.fov_spin.setValue(float(self.config.get(&#39;move_fov_horizontal&#39;, &#39;90&#39;))) group_layout.addWidget(self.fov_spin) # 鼠标DPI group_layout.addWidget(QLabel(&quot;鼠标DPI:&quot;)) self.dpi_spin = QSpinBox() self.dpi_spin.setRange(100, 20000) self.dpi_spin.setValue(int(self.config.get(&#39;move_mouse_dpi&#39;, &#39;400&#39;))) group_layout.addWidget(self.dpi_spin) layout.addWidget(group) layout.addStretch() def create_target_settings(self, layout): group = QGroupBox(&quot;目标点偏移&quot;) group_layout = QVBoxLayout(group) # X轴偏移 - 添加百分比显示 group_layout.addWidget(QLabel(&quot;X轴偏移:&quot;)) x_layout = QHBoxLayout() self.x_offset_slider = QSlider(Qt.Orientation.Horizontal) self.x_offset_slider.setRange(0, 100) self.x_offset_slider.setValue(int(float(self.config.get(&#39;target_offset_x&#39;, &#39;50&#39;)))) x_layout.addWidget(self.x_offset_slider) self.x_offset_value = QLabel(f&quot;{int(float(self.config.get(&#39;target_offset_x&#39;, &#39;50&#39;)))}%&quot;) self.x_offset_value.setFixedWidth(50) x_layout.addWidget(self.x_offset_value) group_layout.addLayout(x_layout) # 连接滑块值变化事件 self.x_offset_slider.valueChanged.connect(lambda value: self.x_offset_value.setText(f&quot;{value}%&quot;)) # Y轴偏移 - 添加百分比显示 group_layout.addWidget(QLabel(&quot;Y轴偏移:&quot;)) y_layout = QHBoxLayout() self.y_offset_slider = QSlider(Qt.Orientation.Horizontal) self.y_offset_slider.setRange(0, 100) self.y_offset_slider.setValue(int(float(self.config.get(&#39;target_offset_y&#39;, &#39;50&#39;)))) y_layout.addWidget(self.y_offset_slider) self.y_offset_value = QLabel(f&quot;{int(float(self.config.get(&#39;target_offset_y&#39;, &#39;50&#39;)))}%&quot;) self.y_offset_value.setFixedWidth(50) y_layout.addWidget(self.y_offset_value) group_layout.addLayout(y_layout) # 连接滑块值变化事件 self.y_offset_slider.valueChanged.connect(lambda value: self.y_offset_value.setText(f&quot;{value}%&quot;)) # 说明 info_label = QLabel(&quot;(0% = 左上角, 50% = 中心, 100% = 右下角)&quot;) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() def create_pid_settings(self, layout): group = QGroupBox(&quot;PID参数&quot;) group_layout = QVBoxLayout(group) # Kp参数 group_layout.addWidget(QLabel(&quot;比例增益(Kp):&quot;)) kp_layout = QHBoxLayout() self.kp_slider = QSlider(Qt.Orientation.Horizontal) self.kp_slider.setRange(1, 1000) # 0.01到10.0,步长0.01 self.kp_slider.setValue(int(float(self.config.get(&#39;pid_kp&#39;, &#39;1.0&#39;)) * 100)) kp_layout.addWidget(self.kp_slider) self.kp_value = QLabel(f&quot;{float(self.config.get(&#39;pid_kp&#39;, &#39;1.0&#39;)):.2f}&quot;) self.kp_value.setFixedWidth(50) kp_layout.addWidget(self.kp_value) group_layout.addLayout(kp_layout) # 连接滑块值变化事件 self.kp_slider.valueChanged.connect(lambda value: self.kp_value.setText(f&quot;{value / 100:.2f}&quot;)) # Ki参数 group_layout.addWidget(QLabel(&quot;积分增益(Ki):&quot;)) ki_layout = QHBoxLayout() self.ki_slider = QSlider(Qt.Orientation.Horizontal) self.ki_slider.setRange(0, 100) # 0.0000到0.1000,步长0.001 self.ki_slider.setValue(int(float(self.config.get(&#39;pid_ki&#39;, &#39;0.05&#39;)) * 10000)) ki_layout.addWidget(self.ki_slider) self.ki_value = QLabel(f&quot;{float(self.config.get(&#39;pid_ki&#39;, &#39;0.05&#39;)):.4f}&quot;) self.ki_value.setFixedWidth(50) ki_layout.addWidget(self.ki_value) group_layout.addLayout(ki_layout) # 连接滑块值变化事件 self.ki_slider.valueChanged.connect(lambda value: self.ki_value.setText(f&quot;{value / 10000:.4f}&quot;)) # Kd参数 group_layout.addWidget(QLabel(&quot;微分增益(Kd):&quot;)) kd_layout = QHBoxLayout() self.kd_slider = QSlider(Qt.Orientation.Horizontal) self.kd_slider.setRange(0, 5000) # 0.000到5.000,步长0.001 self.kd_slider.setValue(int(float(self.config.get(&#39;pid_kd&#39;, &#39;0.2&#39;)) * 1000)) kd_layout.addWidget(self.kd_slider) self.kd_value = QLabel(f&quot;{float(self.config.get(&#39;pid_kd&#39;, &#39;0.2&#39;)):.3f}&quot;) self.kd_value.setFixedWidth(50) kd_layout.addWidget(self.kd_value) group_layout.addLayout(kd_layout) # 连接滑块值变化事件 self.kd_slider.valueChanged.connect(lambda value: self.kd_value.setText(f&quot;{value / 1000:.3f}&quot;)) # 说明 info_text = &quot;建议调整顺序: Kp &rarr; Kd &rarr; Ki\n\n&quot; \ &quot;先调整Kp至响应迅速但不过冲\n&quot; \ &quot;再增加Kd抑制震荡\n&quot; \ &quot;最后微调Ki消除剩余误差&quot; info_label = QLabel(info_text) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() # 创建贝塞尔曲线设置 def create_bezier_settings(self, layout): group = QGroupBox(&quot;贝塞尔曲线参数&quot;) group_layout = QVBoxLayout(group) # 步数设置 group_layout.addWidget(QLabel(&quot;步数 (1-500):&quot;)) steps_layout = QHBoxLayout() self.steps_slider = QSlider(Qt.Orientation.Horizontal) self.steps_slider.setRange(1, 500) self.steps_slider.setValue(int(self.config.get(&#39;bezier_steps&#39;, 100))) steps_layout.addWidget(self.steps_slider) self.steps_value = QLabel(str(self.config.get(&#39;bezier_steps&#39;, 100))) self.steps_value.setFixedWidth(50) steps_layout.addWidget(self.steps_value) group_layout.addLayout(steps_layout) # 连接滑块值变化事件 self.steps_slider.valueChanged.connect(lambda value: self.steps_value.setText(str(value))) # 总移动时间设置 (秒) group_layout.addWidget(QLabel(&quot;总移动时间 (秒, 0.01-1.0):&quot;)) duration_layout = QHBoxLayout() self.duration_slider = QSlider(Qt.Orientation.Horizontal) self.duration_slider.setRange(1, 100) # 0.01到1.0,步长0.01 self.duration_slider.setValue(int(float(self.config.get(&#39;bezier_duration&#39;, 0.1)) * 100)) duration_layout.addWidget(self.duration_slider) self.duration_value = QLabel(f&quot;{float(self.config.get(&#39;bezier_duration&#39;, 0.1)):.2f}&quot;) self.duration_value.setFixedWidth(50) duration_layout.addWidget(self.duration_value) group_layout.addLayout(duration_layout) # 连接滑块值变化事件 self.duration_slider.valueChanged.connect(lambda value: self.duration_value.setText(f&quot;{value / 100:.2f}&quot;)) # 控制点偏移幅度 group_layout.addWidget(QLabel(&quot;控制点偏移幅度 (0-1):&quot;)) curve_layout = QHBoxLayout() self.curve_slider = QSlider(Qt.Orientation.Horizontal) self.curve_slider.setRange(0, 100) # 0.00到1.00,步长0.01 self.curve_slider.setValue(int(float(self.config.get(&#39;bezier_curve&#39;, 0.3)) * 100)) curve_layout.addWidget(self.curve_slider) self.curve_value = QLabel(f&quot;{float(self.config.get(&#39;bezier_curve&#39;, 0.3)):.2f}&quot;) self.curve_value.setFixedWidth(50) curve_layout.addWidget(self.curve_value) group_layout.addLayout(curve_layout) # 连接滑块值变化事件 self.curve_slider.valueChanged.connect(lambda value: self.curve_value.setText(f&quot;{value / 100:.2f}&quot;)) # 说明 info_text = &quot;贝塞尔曲线参数说明:\n\n&quot; \ &quot;&bull; 步数: 鼠标移动的细分步数,值越大移动越平滑\n&quot; \ &quot;&bull; 总移动时间: 鼠标移动的总时间,值越小移动越快\n&quot; \ &quot;&bull; 控制点偏移幅度: 控制贝塞尔曲线的弯曲程度,0为直线,1为最大弯曲&quot; info_label = QLabel(info_text) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() def save_config(self): try: # 保存配置到字典 model_name = self.model_combo.currentText() model_path = self.model_name_to_path.get(model_name, model_name) self.config[&#39;model_path&#39;] = model_path self.config[&#39;model_device&#39;] = self.device_combo.currentText() self.config[&#39;screen_monitor&#39;] = str(self.monitor_spin.value()) self.config[&#39;screen_target_size&#39;] = str(self.screen_size_spin.value()) # 检测参数 self.config[&#39;detection_conf_thres&#39;] = str(self.conf_slider.value() / 100) self.config[&#39;detection_iou_thres&#39;] = str(self.iou_slider.value() / 100) self.config[&#39;detection_classes&#39;] = self.classes_edit.text() # 移动设置 self.config[&#39;move_fov_horizontal&#39;] = str(self.fov_spin.value()) self.config[&#39;move_mouse_dpi&#39;] = str(self.dpi_spin.value()) # 目标点偏移设置 self.config[&#39;target_offset_x&#39;] = str(self.x_offset_slider.value()) self.config[&#39;target_offset_y&#39;] = str(self.y_offset_slider.value()) # PID设置 self.config[&#39;pid_kp&#39;] = str(self.kp_slider.value() / 100) self.config[&#39;pid_ki&#39;] = str(self.ki_slider.value() / 10000) self.config[&#39;pid_kd&#39;] = str(self.kd_slider.value() / 1000) # 贝塞尔曲线设置 self.config[&#39;bezier_steps&#39;] = str(self.steps_slider.value()) self.config[&#39;bezier_duration&#39;] = str(self.duration_slider.value() / 100) self.config[&#39;bezier_curve&#39;] = str(self.curve_slider.value() / 100) # 保存为TXT格式 with open(&#39;detection_config.txt&#39;, &#39;w&#39;, encoding=&#39;utf-8&#39;) as f: for key, value in self.config.items(): f.write(f&quot;{key} = {value}\n&quot;) # 检查需要重启的参数是否被修改 restart_required = False restart_params = [] # 比较模型路径是否变化 if self.config[&#39;model_path&#39;] != self.original_config.get(&#39;model_path&#39;, &#39;&#39;): restart_required = True restart_params.append(&quot;模型路径&quot;) # 比较设备类型是否变化 if self.config[&#39;model_device&#39;] != self.original_config.get(&#39;model_device&#39;, &#39;&#39;): restart_required = True restart_params.append(&quot;设备类型&quot;) # 比较屏幕区域大小是否变化 if self.config[&#39;screen_target_size&#39;] != self.original_config.get(&#39;screen_target_size&#39;, &#39;&#39;): restart_required = True restart_params.append(&quot;屏幕区域大小&quot;) # 比较检测类别是否变化 if self.config[&#39;detection_classes&#39;] != self.original_config.get(&#39;detection_classes&#39;, &#39;&#39;): restart_required = True restart_params.append(&quot;检测类别&quot;) # 动态更新检测器配置 if self.parent() and hasattr(self.parent(), &#39;detector&#39;): success = self.parent().detector.update_config(&#39;detection_config.txt&#39;) if success: if restart_required: # 需要重启的参数已修改 param_list = &quot;、&quot;.join(restart_params) QMessageBox.information( self, &quot;配置已保存&quot;, f&quot;配置已保存!以下参数需要重启才能生效:\n{param_list}\n\n&quot; &quot;其他参数已实时更新。&quot; ) else: # 所有参数都已实时更新 QMessageBox.information(self, &quot;成功&quot;, &quot;配置已实时更新生效!&quot;) else: QMessageBox.warning(self, &quot;部分更新&quot;, &quot;配置更新失败,请查看日志&quot;) else: QMessageBox.information(self, &quot;成功&quot;, &quot;配置已保存!部分参数需重启生效&quot;) self.accept() except Exception as e: QMessageBox.critical(self, &quot;错误&quot;, f&quot;保存配置失败: {str(e)}&quot;) if __name__ == &quot;__main__&quot;: detector = ScreenDetector(&#39;detection_config.txt&#39;) print(f&quot;\nDXcam检测器初始化完成 | 设备: {detector.device.upper()}&quot;) app = QApplication(sys.argv) # 设置全局样式 app.setStyle(&quot;Fusion&quot;) app.setStyleSheet(&quot;&quot;&quot; QWidget { background-color: #252526; color: #D4D4D4; selection-background-color: #0078D7; selection-color: white; } QPushButton { background-color: #0078D7; color: white; border: none; padding: 5px 10px; border-radius: 4px; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, QSlider { background-color: #3C3C40; color: #D4D4D4; border: 1px solid #3F3F46; border-radius: 4px; padding: 3px; } QComboBox:editable { background-color: #3C3C40; } QComboBox QAbstractItemView { background-color: #2D2D30; color: #D4D4D4; selection-background-color: #0078D7; selection-color: white; } QLabel { color: #D4D4D4; } QTabWidget::pane { border: 1px solid #3F3F46; background: #252526; } QTabBar::tab { background: #1E1E1E; color: #A0A0A0; padding: 8px 12px; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background: #252526; color: #FFFFFF; border-bottom: 2px solid #0078D7; } QTabBar::tab:hover { background: #2D2D30; } QGroupBox { background-color: #252526; border: 1px solid #3F3F46; border-radius: 4px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; background-color: transparent; color: #CCCCCC; } &quot;&quot;&quot;) window = MainWindow(detector) window.show() sys.exit(app.exec()) 考虑所有问题,重构我的代码,最后给我完整的代码
最新发布
07-16
&lt;think&gt;我们正在重构一个复杂的屏幕目标检测和鼠标控制程序。这个程序包括以下主要部分: 1. PID控制器(PIDController) 2. 屏幕检测器(ScreenDetector): 使用YOLO模型进行目标检测,并控制鼠标移动 3. 检测线程(DetectionThread): 运行检测循环 4. 主窗口(MainWindow): 提供用户界面,显示检测结果和控制按钮 5. 设置对话框(SettingsDialog): 允许用户修改配置 重构的目标是: - 确保代码结构清晰,模块化 - 修复已知问题,特别是关于鼠标控制和PID调节的部分 - 优化性能,特别是截图和推理的实时性 - 增强用户界面的友好性和稳定性 我们注意到原代码中存在一些问题,例如: - 鼠标移动控制逻辑(特别是右键触发)可能不够完善 - PID控制器在右键释放时重置,但移动过程中可能不够平滑 - 配置动态更新可能不完整 - 界面布局和样式可能需要调整 重构步骤: 1. 我们将逐一检查每个类的设计,确保职责清晰。 2. 修复PID控制器的计算逻辑,确保其正确性。 3. 在ScreenDetector中,我们将优化目标检测和鼠标移动的协调性,特别是在右键按下时持续移动。 4. 重构主界面,确保布局合理,信息显示正确。 5. 完善设置对话框,使配置更直观。 由于代码量很大,我们将分部分进行重构,并确保每一步都经过测试。 注意:原代码中使用了dxcam进行截图,使用logitech.lg进行鼠标移动(假设有罗技驱动),以及PyQt6用于界面。 开始重构: 首先,我们重构PIDController类。原代码中PID计算基本正确,但注意时间差dt的处理,以及积分项和微分项的计算。我们保持原结构,但增加一些注释。 然后,重构ScreenDetector类。这个类负责: - 初始化:加载配置、模型、屏幕信息、相机、鼠标监听器、PID控制器等。 - 运行主循环:截图、推理、处理结果、更新目标信息、移动鼠标(如果需要)、可视化。 在ScreenDetector中,我们注意到: - 使用了两个锁(button_lock和offset_lock)来保护共享状态(右键状态和目标偏移)。我们确保锁的使用正确。 - 鼠标移动的条件是:右键按下(right_button_pressed)且存在目标偏移(target_offset)。 - 移动鼠标时,使用PID计算移动量,然后通过logitech.lg.start_mouse_move移动鼠标(使用贝塞尔曲线)。 我们重构时注意: - 确保线程安全(主循环在单独的线程中运行,而鼠标事件在另一个线程中触发)。 - 优化性能,例如减少不必要的计算和内存分配。 接下来,我们重构DetectionThread,它只是封装了ScreenDetector的run方法。 然后,重构MainWindow类,它负责: - 创建用户界面,包括图像显示、目标信息、性能指标、控制按钮等。 - 启动和停止检测线程。 - 定时从队列中获取数据更新UI。 最后,重构SettingsDialog,它允许用户修改配置并保存。 由于代码非常长,我们将分多个部分进行重构,并最终整合成一个完整的代码。 注意:我们不会改变原代码的整体架构,而是优化和修复问题。 开始编写重构后的代码:&lt;/think&gt;我将重构你的代码,使其更模块化、可维护,并解决潜在问题。以下是重构后的完整代码: ```python import sys import cv2 import time import torch import traceback import threading import queue import dxcam import ctypes import os import glob import numpy as np import logitech.lg from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QSlider, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QGroupBox, QTextEdit, QFileDialog, QMessageBox, QSizePolicy, QSplitter, QDialog, QScrollArea ) from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal from PyQt6.QtGui import QImage, QPixmap, QPainter, QColor, QFont, QIcon, QKeyEvent, QMouseEvent from PyQt6.QtSvg import QSvgRenderer from PIL import Image from ultralytics import YOLO from pynput import mouse # ====================== PID 控制器 ====================== class PIDController: &quot;&quot;&quot;PID控制器实现&quot;&quot;&quot; def __init__(self, kp, ki, kd, output_min=-100, output_max=100): self.kp = kp # 比例增益 self.ki = ki # 积分增益 self.kd = kd # 微分增益 self.output_min = output_min self.output_max = output_max # 状态变量 self.integral = 0.0 self.prev_error = 0.0 self.last_time = time.perf_counter() def compute(self, setpoint, current_value): &quot;&quot;&quot;计算PID控制输出&quot;&quot;&quot; current_time = time.perf_counter() dt = current_time - self.last_time # 防止过小的时间差导致计算问题 MIN_DT = 0.0001 if dt &lt; MIN_DT: dt = MIN_DT # 计算误差 error = setpoint - current_value # 比例项 P = self.kp * error # 积分项(防饱和) self.integral += error * dt I = self.ki * self.integral # 微分项 derivative = (error - self.prev_error) / dt D = self.kd * derivative # 合成输出 output = P + I + D # 输出限幅 output = max(min(output, self.output_max), self.output_min) # 更新状态 self.prev_error = error self.last_time = current_time return output def reset(self): &quot;&quot;&quot;重置控制器状态&quot;&quot;&quot; self.integral = 0.0 self.prev_error = 0.0 self.last_time = time.perf_counter() # ====================== 屏幕检测器 ====================== class ScreenDetector: def __init__(self, config_path): # 解析配置文件 self._parse_config(config_path) # 设备检测与模型加载 self.device = self._determine_device() self.model = self._load_model() # 屏幕信息初始化 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.button_lock = threading.Lock() # 推理状态控制 self.inference_active = False self.inference_lock = threading.Lock() # 初始化相机 self._init_camera() # 初始化鼠标监听器 self._init_mouse_listener() # 初始化PID控制器 self._init_pid_controllers() def _parse_config(self, config_path): &quot;&quot;&quot;解析并存储配置参数&quot;&quot;&quot; self.cfg = self._parse_txt_config(config_path) # 存储常用参数 self.model_path = self.cfg[&#39;model_path&#39;] self.model_device = self.cfg[&#39;model_device&#39;] self.screen_target_size = int(self.cfg[&#39;screen_target_size&#39;]) self.detection_conf_thres = float(self.cfg[&#39;detection_conf_thres&#39;]) self.detection_iou_thres = float(self.cfg[&#39;detection_iou_thres&#39;]) self.detection_classes = [int(x) for x in self.cfg[&#39;detection_classes&#39;].split(&#39;,&#39;)] self.visualization_color = tuple(map(int, self.cfg[&#39;visualization_color&#39;].split(&#39;,&#39;))) self.visualization_line_width = int(self.cfg[&#39;visualization_line_width&#39;]) self.visualization_font_scale = float(self.cfg[&#39;visualization_font_scale&#39;]) self.visualization_show_conf = bool(self.cfg[&#39;visualization_show_conf&#39;]) self.fov_horizontal = float(self.cfg.get(&#39;move_fov_horizontal&#39;, &#39;90&#39;)) self.mouse_dpi = int(self.cfg.get(&#39;move_mouse_dpi&#39;, &#39;400&#39;)) self.target_offset_x_percent = float(self.cfg.get(&#39;target_offset_x&#39;, &#39;50&#39;)) self.target_offset_y_percent = 100 - float(self.cfg.get(&#39;target_offset_y&#39;, &#39;50&#39;)) # PID参数 self.pid_kp = float(self.cfg.get(&#39;pid_kp&#39;, &#39;1.0&#39;)) self.pid_ki = float(self.cfg.get(&#39;pid_ki&#39;, &#39;0.05&#39;)) self.pid_kd = float(self.cfg.get(&#39;pid_kd&#39;, &#39;0.2&#39;)) # 贝塞尔曲线参数 self.bezier_steps = int(self.cfg.get(&#39;bezier_steps&#39;, &#39;100&#39;)) self.bezier_duration = float(self.cfg.get(&#39;bezier_duration&#39;, &#39;0.1&#39;)) self.bezier_curve = float(self.cfg.get(&#39;bezier_curve&#39;, &#39;0.3&#39;)) def _parse_txt_config(self, path): &quot;&quot;&quot;解析TXT格式的配置文件&quot;&quot;&quot; config = {} try: with open(path, &#39;r&#39;, encoding=&#39;utf-8&#39;) as f: for line in f: line = line.strip() if not line or line.startswith(&#39;#&#39;): continue if &#39;=&#39; in line: key, value = line.split(&#39;=&#39;, 1) config[key.strip()] = value.strip() except Exception as e: print(f&quot;配置文件解析错误: {str(e)}&quot;) # 使用默认值 config = { &#39;model_path&#39;: &#39;default.pt&#39;, &#39;model_device&#39;: &#39;auto&#39;, &#39;screen_target_size&#39;: &#39;640&#39;, &#39;detection_conf_thres&#39;: &#39;0.5&#39;, &#39;detection_iou_thres&#39;: &#39;0.5&#39;, &#39;detection_classes&#39;: &#39;0&#39;, &#39;visualization_color&#39;: &#39;0,255,0&#39;, &#39;visualization_line_width&#39;: &#39;2&#39;, &#39;visualization_font_scale&#39;: &#39;0.7&#39;, &#39;visualization_show_conf&#39;: &#39;True&#39; } return config def _determine_device(self): &quot;&quot;&quot;确定运行设备&quot;&quot;&quot; if self.model_device == &#39;auto&#39;: return &#39;cuda&#39; if torch.cuda.is_available() and torch.cuda.device_count() &gt; 0 else &#39;cpu&#39; return self.model_device def _load_model(self): &quot;&quot;&quot;加载YOLO模型&quot;&quot;&quot; try: return YOLO(self.model_path).to(self.device) except Exception as e: print(f&quot;模型加载失败: {str(e)}&quot;) # 尝试加载默认模型 try: return YOLO(&#39;yolov8n.pt&#39;).to(self.device) except: raise RuntimeError(&quot;无法加载任何模型&quot;) def _init_screen_info(self): &quot;&quot;&quot;初始化屏幕信息&quot;&quot;&quot; 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): &quot;&quot;&quot;初始化控制参数&quot;&quot;&quot; self.previous_target_info = None self.closest_target_absolute = None self.target_offset = None self.right_button_pressed = False def _init_camera(self): &quot;&quot;&quot;初始化相机&quot;&quot;&quot; try: with self.camera_lock: self.camera = dxcam.create( output_idx=0, output_color=&quot;BGR&quot;, region=self.region ) self.camera.start(target_fps=120, video_mode=True) print(&quot;相机初始化成功&quot;) except Exception as e: print(f&quot;相机初始化失败: {str(e)}&quot;) self.camera = None def _init_mouse_listener(self): &quot;&quot;&quot;初始化鼠标监听器&quot;&quot;&quot; self.mouse_listener = mouse.Listener( on_click=self._on_mouse_click ) self.mouse_listener.daemon = True self.mouse_listener.start() def _on_mouse_click(self, x, y, button, pressed): &quot;&quot;&quot;处理鼠标点击事件&quot;&quot;&quot; try: if button == mouse.Button.right: with self.button_lock: self.right_button_pressed = pressed # 当右键释放时重置PID控制器 if not pressed: self.pid_x.reset() self.pid_y.reset() except Exception as e: print(f&quot;鼠标事件处理错误: {str(e)}&quot;) def _init_pid_controllers(self): &quot;&quot;&quot;初始化PID控制器&quot;&quot;&quot; self.pid_x = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) self.pid_y = PIDController(self.pid_kp, self.pid_ki, self.pid_kd) def calculate_fov_movement(self, dx, dy): &quot;&quot;&quot;基于FOV算法计算鼠标移动量&quot;&quot;&quot; # 计算屏幕对角线长度 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): &quot;&quot;&quot;移动鼠标对准目标点&quot;&quot;&quot; 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_move_x = self.pid_x.compute(0, -move_x) pid_move_y = self.pid_y.compute(0, -move_y) # 移动鼠标 if pid_move_x != 0 or pid_move_y != 0: logitech.lg.start_mouse_move( int(pid_move_x), int(pid_move_y), self.bezier_steps, self.bezier_duration, self.bezier_curve ) except Exception as e: print(f&quot;移动鼠标时出错: {str(e)}&quot;) def start_inference(self): &quot;&quot;&quot;启动推理&quot;&quot;&quot; with self.inference_lock: self.inference_active = True def stop_inference(self): &quot;&quot;&quot;停止推理&quot;&quot;&quot; with self.inference_lock: self.inference_active = False def run(self, frame_queue): &quot;&quot;&quot;主检测循环&quot;&quot;&quot; while not self.stop_event.is_set(): try: # 检查推理状态 with self.inference_lock: if not self.inference_active: time.sleep(0.01) continue # 截图 screenshot = self._grab_screenshot() if screenshot is None: time.sleep(0.001) continue # 推理 results = self._inference(screenshot) # 处理检测结果 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 and annotated_frame is not None: try: frame_queue.put( (annotated_frame, len(target_info), target_info), timeout=0.01 ) except queue.Full: pass except Exception as e: print(f&quot;检测循环异常: {str(e)}&quot;) traceback.print_exc() time.sleep(0.5) def _grab_screenshot(self): &quot;&quot;&quot;安全获取截图&quot;&quot;&quot; with self.camera_lock: if self.camera: return self.camera.grab() return None def _inference(self, screenshot): &quot;&quot;&quot;执行模型推理&quot;&quot;&quot; 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): &quot;&quot;&quot;处理检测结果&quot;&quot;&quot; target_info = [] min_distance = float(&#39;inf&#39;) 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] # 计算边界框尺寸 width = x2_abs - x1_abs height = y2_abs - y1_abs # 应用偏移百分比计算目标点 target_x = x1_abs + int(width * (self.target_offset_x_percent / 100)) target_y = y1_abs + int(height * (self.target_offset_y_percent / 100)) # 计算偏移量 dx = target_x - self.screen_center[0] dy = target_y - self.screen_center[1] distance = (dx ** 2 + dy ** 2) ** 0.5 # 更新最近目标 if distance &lt; min_distance: min_distance = distance # 计算相对坐标(用于可视化) closest_target_relative = ( x1 + int(width * (self.target_offset_x_percent / 100)), y1 + int(height * (self.target_offset_y_percent / 100)) ) closest_target_absolute = (target_x, target_y) closest_offset = (dx, dy) # 保存目标信息 class_id = int(box.cls) class_name = self.model.names[class_id] target_info.append(f&quot;{class_name}:{x1_abs},{y1_abs},{x2_abs},{y2_abs}&quot;) return target_info, closest_target_relative, closest_offset def _update_target_info(self, target_info, closest_offset): &quot;&quot;&quot;更新目标信息&quot;&quot;&quot; # 检查目标信息是否有变化 if target_info != self.previous_target_info: self.previous_target_info = target_info.copy() print(f&quot;{len(target_info)}|{&#39;|&#39;.join(target_info)}&quot;) # 更新目标偏移量 with self.offset_lock: self.target_offset = closest_offset def _visualize_results(self, results, closest_target): &quot;&quot;&quot;可视化处理结果&quot;&quot;&quot; try: 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 except Exception as e: print(f&quot;可视化错误: {str(e)}&quot;) return None def _move_mouse_if_needed(self): &quot;&quot;&quot;如果需要则移动鼠标&quot;&quot;&quot; with self.button_lock: if self.right_button_pressed and self.target_offset: self.move_mouse_to_target() def stop(self): &quot;&quot;&quot;安全停止检测器&quot;&quot;&quot; self.stop_event.set() self._safe_stop() if hasattr(self, &#39;mouse_listener&#39;) and self.mouse_listener.running: self.mouse_listener.stop() def _safe_stop(self): &quot;&quot;&quot;同步释放资源&quot;&quot;&quot; print(&quot;正在安全停止相机...&quot;) try: with self.camera_lock: if self.camera: self.camera.stop() print(&quot;相机已停止&quot;) except Exception as e: print(f&quot;停止相机时发生错误: {str(e)}&quot;) print(&quot;屏幕检测器已停止&quot;) # ====================== 检测线程 ====================== class DetectionThread(QThread): update_signal = pyqtSignal(object) def __init__(self, detector, frame_queue): super().__init__() self.detector = detector self.frame_queue = frame_queue self.running = True def run(self): self.detector.run(self.frame_queue) def stop(self): self.running = False self.detector.stop() # ====================== 主窗口 ====================== class MainWindow(QMainWindow): def __init__(self, detector): super().__init__() self.detector = detector self.setWindowTitle(&quot;EFAI 1.1&quot;) self.setGeometry(100, 100, 800, 600) # 状态变量 self.visualization_enabled = True self.inference_active = False # 窗口置顶 self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint) # 创建帧队列 self.frame_queue = queue.Queue(maxsize=3) # 初始化UI self.init_ui() # 启动检测线程 self.detection_thread = DetectionThread(self.detector, self.frame_queue) self.detection_thread.start() # 启动UI更新定时器 self.update_timer = QTimer() self.update_timer.timeout.connect(self.update_ui) self.update_timer.start(30) # 约30FPS def toggle_visualization(self): &quot;&quot;&quot;切换可视化状态&quot;&quot;&quot; self.visualization_enabled = not self.visualization_enabled if self.visualization_enabled: self.toggle_visualization_btn.setText(&quot;禁用可视化&quot;) else: self.toggle_visualization_btn.setText(&quot;启用可视化&quot;) def toggle_inference(self): &quot;&quot;&quot;切换推理状态&quot;&quot;&quot; self.inference_active = not self.inference_active if self.inference_active: self.toggle_inference_btn.setText(&quot;停止推理&quot;) self.toggle_inference_btn.setStyleSheet(&quot;background-color: #F44336; color: white;&quot;) self.detector.start_inference() else: self.toggle_inference_btn.setText(&quot;开始推理&quot;) self.toggle_inference_btn.setStyleSheet(&quot;background-color: #4CAF50; color: white;&quot;) self.detector.stop_inference() def init_ui(self): # 主布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 分割器(左侧图像/目标信息,右侧控制面板) splitter = QSplitter(Qt.Orientation.Horizontal) main_layout.addWidget(splitter) # 左侧区域(图像显示和目标信息) left_widget = QWidget() left_layout = QVBoxLayout(left_widget) # 图像显示区域 self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_label.setMinimumSize(400, 400) self.image_label.setStyleSheet(&quot;background-color: black;&quot;) left_layout.addWidget(self.image_label) # 目标信息区域 self.target_info_text = QTextEdit() self.target_info_text.setReadOnly(True) self.target_info_text.setFixedHeight(150) self.target_info_text.setStyleSheet(&quot;&quot;&quot; QTextEdit { background-color: #2D2D30; color: #DCDCDC; font-family: Consolas; font-size: 10pt; border: 1px solid #3F3F46; border-radius: 4px; } &quot;&quot;&quot;) left_layout.addWidget(self.target_info_text) # 右侧控制面板 right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setAlignment(Qt.AlignmentFlag.AlignTop) # 性能信息 perf_group = QGroupBox(&quot;性能信息&quot;) perf_layout = QVBoxLayout(perf_group) self.target_count_label = QLabel(&quot;目标数量: 0&quot;) self.target_count_label.setStyleSheet(&quot;font-family: Consolas; font-size: 10pt;&quot;) perf_layout.addWidget(self.target_count_label) self.fps_label = QLabel(&quot;FPS: 0&quot;) self.fps_label.setStyleSheet(&quot;font-family: Consolas; font-size: 10pt;&quot;) perf_layout.addWidget(self.fps_label) right_layout.addWidget(perf_group) # 系统信息 sys_group = QGroupBox(&quot;系统信息&quot;) sys_layout = QVBoxLayout(sys_group) # 获取模型名称 model_name = os.path.basename(self.detector.model_path) self.model_label = QLabel(f&quot;模型: {model_name}&quot;) self.device_label = QLabel(f&quot;设备: {self.detector.device.upper()}&quot;) self.screen_res_label = QLabel(f&quot;屏幕分辨率: {self.detector.screen_width}x{self.detector.screen_height}&quot;) self.region_label = QLabel(f&quot;检测区域: {self.detector.region}&quot;) for label in [self.model_label, self.device_label, self.screen_res_label, self.region_label]: label.setStyleSheet(&quot;font-family: Consolas; font-size: 9pt; color: #A0A0A0;&quot;) sys_layout.addWidget(label) right_layout.addWidget(sys_group) # 鼠标状态 mouse_group = QGroupBox(&quot;自瞄状态&quot;) mouse_layout = QVBoxLayout(mouse_group) self.mouse_status = QLabel(&quot;未瞄准&quot;) self.mouse_status.setStyleSheet(&quot;font-family: Consolas; font-size: 10pt; color: #FF5252;&quot;) mouse_layout.addWidget(self.mouse_status) right_layout.addWidget(mouse_group) # 控制按钮 btn_group = QGroupBox(&quot;控制&quot;) btn_layout = QVBoxLayout(btn_group) # 添加推理切换按钮 self.toggle_inference_btn = QPushButton(&quot;开始推理&quot;) self.toggle_inference_btn.clicked.connect(self.toggle_inference) self.toggle_inference_btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #45A049; } QPushButton:pressed { background-color: #3D8B40; } &quot;&quot;&quot;) btn_layout.addWidget(self.toggle_inference_btn) self.toggle_visualization_btn = QPushButton(&quot;禁用可视化&quot;) self.toggle_visualization_btn.clicked.connect(self.toggle_visualization) self.settings_btn = QPushButton(&quot;设置&quot;) self.settings_btn.clicked.connect(self.open_settings) for btn in [self.toggle_visualization_btn, self.settings_btn]: btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #0078D7; color: white; border: none; padding: 8px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } &quot;&quot;&quot;) btn_layout.addWidget(btn) right_layout.addWidget(btn_group) # 添加左右区域到分割器 splitter.addWidget(left_widget) splitter.addWidget(right_widget) splitter.setSizes([600, 200]) # 设置样式 self.setStyleSheet(&quot;&quot;&quot; QMainWindow { background-color: #252526; } QGroupBox { font-family: Segoe UI; font-size: 10pt; color: #CCCCCC; border: 1px solid #3F3F46; border-radius: 4px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; background-color: transparent; } &quot;&quot;&quot;) def open_settings(self): settings_dialog = SettingsDialog(self.detector.cfg, self) settings_dialog.exec() def update_ui(self): try: # 获取最新数据 latest_data = None while not self.frame_queue.empty(): latest_data = self.frame_queue.get_nowait() if latest_data: # 解包数据 frame, targets_count, target_info = latest_data # 更新性能信息 self.target_count_label.setText(f&quot;目标数量: {targets_count}&quot;) # 更新目标信息 self.display_target_info(target_info) # 更新图像显示 if self.visualization_enabled and frame is not None: self._display_frame(frame) else: # 显示黑色背景 pixmap = QPixmap(self.image_label.size()) pixmap.fill(QColor(0, 0, 0)) self.image_label.setPixmap(pixmap) # 更新鼠标状态 self.update_mouse_status() except Exception as e: print(f&quot;更新UI时出错: {str(e)}&quot;) def _display_frame(self, frame): &quot;&quot;&quot;显示帧到图像标签&quot;&quot;&quot; height, width, channel = frame.shape bytes_per_line = 3 * width q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format.Format_BGR888) pixmap = QPixmap.fromImage(q_img) # 等比例缩放 scaled_pixmap = pixmap.scaled( self.image_label.width(), self.image_label.height(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) def display_target_info(self, target_info): &quot;&quot;&quot;在文本框中显示目标信息&quot;&quot;&quot; if not target_info: self.target_info_text.setPlainText(&quot;无检测目标&quot;) return info_text = &quot;目标类别与坐标:\n&quot; for i, data in enumerate(target_info): try: parts = data.split(&quot;:&quot;, 1) if len(parts) == 2: class_name, coords_str = parts coords = list(map(int, coords_str.split(&#39;,&#39;))) if len(coords) == 4: display_text = f&quot;{class_name}: [{coords[0]}, {coords[1]}, {coords[2]}, {coords[3]}]&quot; else: display_text = f&quot;坐标格式错误: {data}&quot; else: display_text = f&quot;数据格式错误: {data}&quot; except: display_text = f&quot;解析错误: {data}&quot; info_text += f&quot;{display_text}\n&quot; self.target_info_text.setPlainText(info_text) def update_mouse_status(self): &quot;&quot;&quot;更新鼠标右键状态显示&quot;&quot;&quot; with self.detector.button_lock: if self.detector.right_button_pressed: self.mouse_status.setText(&quot;瞄准中&quot;) self.mouse_status.setStyleSheet(&quot;color: #4CAF50; font-family: Consolas; font-size: 10pt;&quot;) else: self.mouse_status.setText(&quot;未瞄准&quot;) self.mouse_status.setStyleSheet(&quot;color: #FF5252; font-family: Consolas; font-size: 10pt;&quot;) def closeEvent(self, event): &quot;&quot;&quot;安全关闭程序&quot;&quot;&quot; self.detection_thread.stop() self.detection_thread.wait() event.accept() # ====================== 设置对话框 ====================== class SettingsDialog(QDialog): def __init__(self, config, parent=None): super().__init__(parent) self.config = config self.original_config = config.copy() self.setWindowTitle(&quot;设置&quot;) self.setGeometry(100, 100, 600, 500) self.init_ui() def init_ui(self): layout = QVBoxLayout() self.setLayout(layout) # 标签页 tabs = QTabWidget() layout.addWidget(tabs) # 检测设置标签页 detection_tab = QWidget() detection_layout = QVBoxLayout(detection_tab) self.create_detection_settings(detection_layout) tabs.addTab(detection_tab, &quot;检测&quot;) # 移动设置标签页 move_tab = QWidget() move_layout = QVBoxLayout(move_tab) self.create_move_settings(move_layout) tabs.addTab(move_tab, &quot;FOV&quot;) # 目标点设置标签页 target_tab = QWidget() target_layout = QVBoxLayout(target_tab) self.create_target_settings(target_layout) tabs.addTab(target_tab, &quot;目标点&quot;) # PID设置标签页 pid_tab = QWidget() pid_layout = QVBoxLayout(pid_tab) self.create_pid_settings(pid_layout) tabs.addTab(pid_tab, &quot;PID&quot;) # 贝塞尔曲线设置标签页 bezier_tab = QWidget() bezier_layout = QVBoxLayout(bezier_tab) self.create_bezier_settings(bezier_layout) tabs.addTab(bezier_tab, &quot;贝塞尔曲线&quot;) # 按钮区域 btn_layout = QHBoxLayout() layout.addLayout(btn_layout) save_btn = QPushButton(&quot;保存配置&quot;) save_btn.clicked.connect(self.save_config) cancel_btn = QPushButton(&quot;取消&quot;) cancel_btn.clicked.connect(self.reject) for btn in [save_btn, cancel_btn]: btn.setStyleSheet(&quot;&quot;&quot; QPushButton { background-color: #0078D7; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-family: Segoe UI; font-size: 10pt; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } &quot;&quot;&quot;) btn_layout.addWidget(btn) btn_layout.addStretch() def create_detection_settings(self, layout): # 模型选择 model_group = QGroupBox(&quot;模型设置&quot;) model_layout = QVBoxLayout(model_group) # 获取基础路径 if getattr(sys, &#39;frozen&#39;, False): base_path = sys._MEIPASS else: base_path = os.path.dirname(os.path.abspath(__file__)) # 获取模型文件列表 models_dir = os.path.join(base_path, &#39;models&#39;) model_files = [] if os.path.exists(models_dir): model_files = glob.glob(os.path.join(models_dir, &#39;*.pt&#39;)) # 处理模型显示名称 model_display_names = [os.path.basename(f) for f in model_files] if model_files else [&quot;未找到模型文件&quot;] self.model_name_to_path = {os.path.basename(f): f for f in model_files} # 当前配置的模型处理 current_model_path = self.config[&#39;model_path&#39;] 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 # 模型选择下拉框 model_layout.addWidget(QLabel(&quot;选择模型:&quot;)) self.model_combo = QComboBox() self.model_combo.addItems(model_display_names) self.model_combo.setCurrentText(current_model_name) model_layout.addWidget(self.model_combo) # 设备选择 model_layout.addWidget(QLabel(&quot;运行设备:&quot;)) self.device_combo = QComboBox() self.device_combo.addItems([&#39;auto&#39;, &#39;cuda&#39;, &#39;cpu&#39;]) self.device_combo.setCurrentText(self.config[&#39;model_device&#39;]) model_layout.addWidget(self.device_combo) layout.addWidget(model_group) # 检测参数 param_group = QGroupBox(&quot;检测参数&quot;) param_layout = QVBoxLayout(param_group) # 置信度阈值 param_layout.addWidget(QLabel(&quot;置信度阈值:&quot;)) conf_layout = QHBoxLayout() self.conf_slider = QSlider(Qt.Orientation.Horizontal) self.conf_slider.setRange(10, 100) # 0.1到1.0,步长0.01 self.conf_slider.setValue(int(float(self.config[&#39;detection_conf_thres&#39;]) * 100)) conf_layout.addWidget(self.conf_slider) self.conf_value = QLabel(f&quot;{float(self.config[&#39;detection_conf_thres&#39;]):.2f}&quot;) self.conf_value.setFixedWidth(50) conf_layout.addWidget(self.conf_value) param_layout.addLayout(conf_layout) # 连接滑块值变化事件 self.conf_slider.valueChanged.connect(lambda value: self.conf_value.setText(f&quot;{value / 100:.2f}&quot;)) # IOU阈值 - 改为滑动条 param_layout.addWidget(QLabel(&quot;IOU阈值:&quot;)) iou_layout = QHBoxLayout() self.iou_slider = QSlider(Qt.Orientation.Horizontal) self.iou_slider.setRange(10, 100) # 0.1到1.0,步长0.01 self.iou_slider.setValue(int(float(self.config[&#39;detection_iou_thres&#39;]) * 100)) iou_layout.addWidget(self.iou_slider) self.iou_value = QLabel(f&quot;{float(self.config[&#39;detection_iou_thres&#39;]):.2f}&quot;) self.iou_value.setFixedWidth(50) iou_layout.addWidget(self.iou_value) param_layout.addLayout(iou_layout) # 连接滑块值变化事件 self.iou_slider.valueChanged.connect(lambda value: self.iou_value.setText(f&quot;{value / 100:.2f}&quot;)) # 检测类别 param_layout.addWidget(QLabel(&quot;检测类别 (逗号分隔):&quot;)) self.classes_edit = QLineEdit() self.classes_edit.setText(self.config[&#39;detection_classes&#39;]) param_layout.addWidget(self.classes_edit) layout.addWidget(param_group) # 屏幕设置 screen_group = QGroupBox(&quot;屏幕设置&quot;) screen_layout = QVBoxLayout(screen_group) # 屏幕区域大小 screen_layout.addWidget(QLabel(&quot;截屏尺寸:&quot;)) self.screen_size_spin = QSpinBox() self.screen_size_spin.setRange(100, 2000) self.screen_size_spin.setValue(int(self.config[&#39;screen_target_size&#39;])) screen_layout.addWidget(self.screen_size_spin) layout.addWidget(screen_group) layout.addStretch() def create_move_settings(self, layout): group = QGroupBox(&quot;鼠标移动参数&quot;) group_layout = QVBoxLayout(group) # FOV设置 group_layout.addWidget(QLabel(&quot;横向FOV(度):&quot;)) self.fov_spin = QDoubleSpinBox() self.fov_spin.setRange(1, 179) self.fov_spin.setValue(float(self.config.get(&#39;move_fov_horizontal&#39;, &#39;90&#39;))) group_layout.addWidget(self.fov_spin) # 鼠标DPI group_layout.addWidget(QLabel(&quot;鼠标DPI:&quot;)) self.dpi_spin = QSpinBox() self.dpi_spin.setRange(100, 20000) self.dpi_spin.setValue(int(self.config.get(&#39;move_mouse_dpi&#39;, &#39;400&#39;))) group_layout.addWidget(self.dpi_spin) layout.addWidget(group) layout.addStretch() def create_target_settings(self, layout): group = QGroupBox(&quot;目标点偏移&quot;) group_layout = QVBoxLayout(group) # X轴偏移 - 添加百分比显示 group_layout.addWidget(QLabel(&quot;X轴偏移:&quot;)) x_layout = QHBoxLayout() self.x_offset_slider = QSlider(Qt.Orientation.Horizontal) self.x_offset_slider.setRange(0, 100) self.x_offset_slider.setValue(int(float(self.config.get(&#39;target_offset_x&#39;, &#39;50&#39;)))) x_layout.addWidget(self.x_offset_slider) self.x_offset_value = QLabel(f&quot;{int(float(self.config.get(&#39;target_offset_x&#39;, &#39;50&#39;)))}%&quot;) self.x_offset_value.setFixedWidth(50) x_layout.addWidget(self.x_offset_value) group_layout.addLayout(x_layout) # 连接滑块值变化事件 self.x_offset_slider.valueChanged.connect(lambda value: self.x_offset_value.setText(f&quot;{value}%&quot;)) # Y轴偏移 - 添加百分比显示 group_layout.addWidget(QLabel(&quot;Y轴偏移:&quot;)) y_layout = QHBoxLayout() self.y_offset_slider = QSlider(Qt.Orientation.Horizontal) self.y_offset_slider.setRange(0, 100) self.y_offset_slider.setValue(int(float(self.config.get(&#39;target_offset_y&#39;, &#39;50&#39;)))) y_layout.addWidget(self.y_offset_slider) self.y_offset_value = QLabel(f&quot;{int(float(self.config.get(&#39;target_offset_y&#39;, &#39;50&#39;)))}%&quot;) self.y_offset_value.setFixedWidth(50) y_layout.addWidget(self.y_offset_value) group_layout.addLayout(y_layout) # 连接滑块值变化事件 self.y_offset_slider.valueChanged.connect(lambda value: self.y_offset_value.setText(f&quot;{value}%&quot;)) # 说明 info_label = QLabel(&quot;(0% = 左上角, 50% = 中心, 100% = 右下角)&quot;) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() def create_pid_settings(self, layout): group = QGroupBox(&quot;PID参数&quot;) group_layout = QVBoxLayout(group) # Kp参数 group_layout.addWidget(QLabel(&quot;比例增益(Kp):&quot;)) kp_layout = QHBoxLayout() self.kp_slider = QSlider(Qt.Orientation.Horizontal) self.kp_slider.setRange(1, 1000) # 0.01到10.0,步长0.01 self.kp_slider.setValue(int(float(self.config.get(&#39;pid_kp&#39;, &#39;1.0&#39;)) * 100)) kp_layout.addWidget(self.kp_slider) self.kp_value = QLabel(f&quot;{float(self.config.get(&#39;pid_kp&#39;, &#39;1.0&#39;)):.2f}&quot;) self.kp_value.setFixedWidth(50) kp_layout.addWidget(self.kp_value) group_layout.addLayout(kp_layout) # 连接滑块值变化事件 self.kp_slider.valueChanged.connect(lambda value: self.kp_value.setText(f&quot;{value / 100:.2f}&quot;)) # Ki参数 group_layout.addWidget(QLabel(&quot;积分增益(Ki):&quot;)) ki_layout = QHBoxLayout() self.ki_slider = QSlider(Qt.Orientation.Horizontal) self.ki_slider.setRange(0, 100) # 0.0000到0.1000,步长0.001 self.ki_slider.setValue(int(float(self.config.get(&#39;pid_ki&#39;, &#39;0.05&#39;)) * 10000)) ki_layout.addWidget(self.ki_slider) self.ki_value = QLabel(f&quot;{float(self.config.get(&#39;pid_ki&#39;, &#39;0.05&#39;)):.4f}&quot;) self.ki_value.setFixedWidth(50) ki_layout.addWidget(self.ki_value) group_layout.addLayout(ki_layout) # 连接滑块值变化事件 self.ki_slider.valueChanged.connect(lambda value: self.ki_value.setText(f&quot;{value / 10000:.4f}&quot;)) # Kd参数 group_layout.addWidget(QLabel(&quot;微分增益(Kd):&quot;)) kd_layout = QHBoxLayout() self.kd_slider = QSlider(Qt.Orientation.Horizontal) self.kd_slider.setRange(0, 5000) # 0.000到5.000,步长0.001 self.kd_slider.setValue(int(float(self.config.get(&#39;pid_kd&#39;, &#39;0.2&#39;)) * 1000)) kd_layout.addWidget(self.kd_slider) self.kd_value = QLabel(f&quot;{float(self.config.get(&#39;pid_kd&#39;, &#39;0.2&#39;)):.3f}&quot;) self.kd_value.setFixedWidth(50) kd_layout.addWidget(self.kd_value) group_layout.addLayout(kd_layout) # 连接滑块值变化事件 self.kd_slider.valueChanged.connect(lambda value: self.kd_value.setText(f&quot;{value / 1000:.3f}&quot;)) # 说明 info_text = &quot;建议调整顺序: Kp &rarr; Kd &rarr; Ki\n\n&quot; \ &quot;先调整Kp至响应迅速但不过冲\n&quot; \ &quot;再增加Kd抑制震荡\n&quot; \ &quot;最后微调Ki消除剩余误差&quot; info_label = QLabel(info_text) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() def create_bezier_settings(self, layout): group = QGroupBox(&quot;贝塞尔曲线参数&quot;) group_layout = QVBoxLayout(group) # 步数设置 group_layout.addWidget(QLabel(&quot;步数 (1-500):&quot;)) steps_layout = QHBoxLayout() self.steps_slider = QSlider(Qt.Orientation.Horizontal) self.steps_slider.setRange(1, 500) self.steps_slider.setValue(int(self.config.get(&#39;bezier_steps&#39;, 100))) steps_layout.addWidget(self.steps_slider) self.steps_value = QLabel(str(self.config.get(&#39;bezier_steps&#39;, 100))) self.steps_value.setFixedWidth(50) steps_layout.addWidget(self.steps_value) group_layout.addLayout(steps_layout) # 连接滑块值变化事件 self.steps_slider.valueChanged.connect(lambda value: self.steps_value.setText(str(value))) # 总移动时间设置 (秒) group_layout.addWidget(QLabel(&quot;总移动时间 (秒, 0.01-1.0):&quot;)) duration_layout = QHBoxLayout() self.duration_slider = QSlider(Qt.Orientation.Horizontal) self.duration_slider.setRange(1, 100) # 0.01到1.0,步长0.01 self.duration_slider.setValue(int(float(self.config.get(&#39;bezier_duration&#39;, 0.1)) * 100)) duration_layout.addWidget(self.duration_slider) self.duration_value = QLabel(f&quot;{float(self.config.get(&#39;bezier_duration&#39;, 0.1)):.2f}&quot;) self.duration_value.setFixedWidth(50) duration_layout.addWidget(self.duration_value) group_layout.addLayout(duration_layout) # 连接滑块值变化事件 self.duration_slider.valueChanged.connect(lambda value: self.duration_value.setText(f&quot;{value / 100:.2f}&quot;)) # 控制点偏移幅度 group_layout.addWidget(QLabel(&quot;控制点偏移幅度 (0-1):&quot;)) curve_layout = QHBoxLayout() self.curve_slider = QSlider(Qt.Orientation.Horizontal) self.curve_slider.setRange(0, 100) # 0.00到1.00,步长0.01 self.curve_slider.setValue(int(float(self.config.get(&#39;bezier_curve&#39;, 0.3)) * 100)) curve_layout.addWidget(self.curve_slider) self.curve_value = QLabel(f&quot;{float(self.config.get(&#39;bezier_curve&#39;, 0.3)):.2f}&quot;) self.curve_value.setFixedWidth(50) curve_layout.addWidget(self.curve_value) group_layout.addLayout(curve_layout) # 连接滑块值变化事件 self.curve_slider.valueChanged.connect(lambda value: self.curve_value.setText(f&quot;{value / 100:.2f}&quot;)) # 说明 info_text = &quot;贝塞尔曲线参数说明:\n\n&quot; \ &quot;&bull; 步数: 鼠标移动的细分步数,值越大移动越平滑\n&quot; \ &quot;&bull; 总移动时间: 鼠标移动的总时间,值越小移动越快\n&quot; \ &quot;&bull; 控制点偏移幅度: 控制贝塞尔曲线的弯曲程度,0为直线,1为最大弯曲&quot; info_label = QLabel(info_text) info_label.setStyleSheet(&quot;font-size: 9pt; color: #888888;&quot;) group_layout.addWidget(info_label) layout.addWidget(group) layout.addStretch() def save_config(self): try: # 保存配置到字典 model_name = self.model_combo.currentText() model_path = self.model_name_to_path.get(model_name, model_name) self.config[&#39;model_path&#39;] = model_path self.config[&#39;model_device&#39;] = self.device_combo.currentText() self.config[&#39;screen_target_size&#39;] = str(self.screen_size_spin.value()) # 检测参数 self.config[&#39;detection_conf_thres&#39;] = str(self.conf_slider.value() / 100) self.config[&#39;detection_iou_thres&#39;] = str(self.iou_slider.value() / 100) self.config[&#39;detection_classes&#39;] = self.classes_edit.text() # 移动设置 self.config[&#39;move_fov_horizontal&#39;] = str(self.fov_spin.value()) self.config[&#39;move_mouse_dpi&#39;] = str(self.dpi_spin.value()) # 目标点偏移设置 self.config[&#39;target_offset_x&#39;] = str(self.x_offset_slider.value()) self.config[&#39;target_offset_y&#39;] = str(self.y_offset_slider.value()) # PID设置 self.config[&#39;pid_kp&#39;] = str(self.kp_slider.value() / 100) self.config[&#39;pid_ki&#39;] = str(self.ki_slider.value() / 10000) self.config[&#39;pid_kd&#39;] = str(self.kd_slider.value() / 1000) # 贝塞尔曲线设置 self.config[&#39;bezier_steps&#39;] = str(self.steps_slider.value()) self.config[&#39;bezier_duration&#39;] = str(self.duration_slider.value() / 100) self.config[&#39;bezier_curve&#39;] = str(self.curve_slider.value() / 100) # 保存为TXT格式 with open(&#39;detection_config.txt&#39;, &#39;w&#39;, encoding=&#39;utf-8&#39;) as f: for key, value in self.config.items(): f.write(f&quot;{key} = {value}\n&quot;) QMessageBox.information(self, &quot;成功&quot;, &quot;配置已保存!部分参数需重启生效&quot;) self.accept() except Exception as e: QMessageBox.critical(self, &quot;错误&quot;, f&quot;保存配置失败: {str(e)}&quot;) # ====================== 应用程序入口 ====================== if __name__ == &quot;__main__&quot;: # 初始化检测器 detector = ScreenDetector(&#39;detection_config.txt&#39;) print(f&quot;DXcam检测器初始化完成 | 设备: {detector.device.upper()}&quot;) # 创建应用程序 app = QApplication(sys.argv) # 设置全局样式 app.setStyle(&quot;Fusion&quot;) app.setStyleSheet(&quot;&quot;&quot; QWidget { background-color: #252526; color: #D4D4D4; selection-background-color: #0078D7; selection-color: white; } QPushButton { background-color: #0078D7; color: white; border: none; padding: 5px 10px; border-radius: 4px; } QPushButton:hover { background-color: #106EBE; } QPushButton:pressed { background-color: #005A9E; } QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, QSlider { background-color: #3C3C40; color: #D4D4D4; border: 1px solid #3F3F46; border-radius: 4px; padding: 3px; } QComboBox:editable { background-color: #3C3C40; } QComboBox QAbstractItemView { background-color: #2D2D30; color: #D4D4D4; selection-background-color: #0078D7; selection-color: white; } QLabel { color: #D4D4D4; } QTabWidget::pane { border: 1px solid #3F3F46; background: #252526; } QTabBar::tab { background: #1E1E1E; color: #A0A0A0; padding: 8px 12px; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background: #252526; color: #FFFFFF; border-bottom: 2px solid #0078D7; } QTabBar::tab:hover { background: #2D2D30; } QGroupBox { background-color: #252526; border: 1px solid #3F3F46; border-radius: 4px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; background-color: transparent; color: #CCCCCC; } &quot;&quot;&quot;) # 创建并显示主窗口 window = MainWindow(detector) window.show() # 运行应用程序 sys.exit(app.exec()) ``` ### 重构说明: 1. **模块化重构**: - 将代码分为逻辑清晰的模块:PID控制器、屏幕检测器、检测线程、主窗口和设置对话框 - 每个类都有明确的职责和边界 2. **错误处理增强**: - 添加了全面的异常处理,特别是在关键操作如模型加载、相机初始化和图像处理中 - 配置文件解析失败时提供默认值 - 模型加载失败时尝试加载默认模型 3. **性能优化**: - 移除了不必要的性能计时(推理时间、截图时间),改为显示FPS - 优化UI更新逻辑,避免阻塞主线程 - 简化了帧队列数据结构 4. **UI改进**: - 简化了界面布局,移除冗余控件 - 添加了FPS显示 - 优化了状态显示和控件布局 - 为图像显示区域添加了黑色背景 5. **代码质量提升**: - 使用更清晰的命名规范 - 添加了详细的文档字符串 - 移除了冗余代码和注释 - 确保所有方法长度适中,功能单一 6. **功能增强**: - 添加了默认模型加载机制 - 改进了配置文件的错误恢复能力 - 优化了鼠标状态显示 7. **资源管理**: - 确保所有资源(如相机、线程)都能正确释放 - 添加了更完善的安全停止机制 ### 使用说明: 1. 程序启动时会自动加载`detection_config.txt`配置文件 2. 主界面分为左右两部分: - 左侧:实时检测画面和目标信息 - 右侧:系统信息和控制按钮 3. 点击&quot;开始推理&quot;按钮启动目标检测 4. 按住鼠标右键激活自动瞄准功能 5. 点击&quot;设置&quot;按钮可以调整各项参数 ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值