基于PySide6的YOLOv8/11目标检测GUI界面——智能安全帽检测系统

发布于:2025-04-20 ⋅ 阅读:(21) ⋅ 点赞:(0)

📖 前言

在工业安全领域,智能安全帽检测是保障工人生命安全的重要技术手段。本文将介绍如何利用YOLOv8/YOLOv11目标检测算法PySide6 GUI框架,开发一套功能完整的智能安全帽检测系统。系统支持:

  • 动态切换检测模型(YOLOv8n/v8s/v11等)
  • 实时调整置信度(Confidence)和IOU阈值
  • 支持摄像头、图像、视频三种检测模式
  • 实时统计佩戴/未佩戴安全帽人数
  • 可视化系统日志及报警记录

通过本文,您将掌握PySide6界面设计与YOLO深度学习模型结合的实战技巧,并了解多线程处理、实时数据可视化等关键技术。

🎥 效果展示


检测结果如下图

🔧 实现步骤与核心代码

首先,需要安装PySide6库,可以使用以下命令进行安装:

pip install pyside6

接下来,可以创建一个Python脚本

import sys
import os
import time
import cv2
import numpy as np
from threading import Thread, Lock
from queue import Queue
from PySide6 import QtWidgets, QtCore, QtGui
from PIL import Image
from ultralytics import YOLO

# 常量定义
DEFAULT_MODEL_PATH = 'weights/v11/helmet_v11_shuffleNetV2_CCFM_Bifformer.engine'
SETTINGS_FILE = "app_settings.ini"


class MWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self._load_settings()
        self._setup_ui()
        self._setup_vars()
        self._setup_connections()
        self._apply_styles()
        self._update_controls_state(False)

        # 初始化YOLO模型
        self.model_lock = Lock()
        self.load_model(self.model_path)

        # 启动处理线程
        Thread(target=self.frame_analyze_thread, daemon=True).start()

    def _load_settings(self):
        """加载应用设置"""
        self.settings = QtCore.QSettings(SETTINGS_FILE, QtCore.QSettings.Format.IniFormat)
        self.model_path = self.settings.value("model/path", DEFAULT_MODEL_PATH)
        self.conf_threshold = float(self.settings.value("model/conf", 0.45))
        self.iou_threshold = float(self.settings.value("model/iou", 0.5))
        geometry = self.settings.value("window/geometry")
        if geometry:
            try:
                if isinstance(geometry, str):
                    geometry = QtCore.QByteArray.fromHex(geometry.encode())
                if isinstance(geometry, QtCore.QByteArray):
                    self.restoreGeometry(geometry)
            except Exception:
                pass

    def _save_settings(self):
        """保存应用设置"""
        self.settings.setValue("model/path", self.model_path)
        self.settings.setValue("model/conf", self.conf_threshold)
        self.settings.setValue("model/iou", self.iou_threshold)
        geometry_bytes = self.saveGeometry()
        self.settings.setValue("window/geometry", geometry_bytes.toHex().data().decode())
        self.settings.sync()

    def _setup_ui(self):
        """设置用户界面"""
        self.setWindowTitle('智能安全帽检测系统')
        self.setWindowIcon(QtGui.QIcon("helmet_icon.png"))

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QtWidgets.QVBoxLayout(central_widget)
        main_layout.setContentsMargins(10, 10, 10, 10)
        main_layout.setSpacing(10)

        # 视频显示区域
        video_container = QtWidgets.QWidget()
        video_layout = QtWidgets.QHBoxLayout(video_container)
        video_layout.setContentsMargins(0, 0, 0, 0)
        video_layout.setSpacing(10)
        self.video_label_ori = self._create_video_label("实时视频流")
        self.video_label_proc = self._create_video_label("分析结果")
        video_layout.addWidget(self.video_label_ori, 1)
        video_layout.addWidget(self.video_label_proc, 1)

        # 控制面板
        control_panel = QtWidgets.QFrame()
        control_panel.setObjectName("controlPanel")
        control_panel.setFixedWidth(350)
        control_layout = QtWidgets.QVBoxLayout(control_panel)
        control_layout.setContentsMargins(10, 10, 10, 10)
        control_layout.setSpacing(15)

        # 模型设置组
        model_group = QtWidgets.QGroupBox("模型设置")
        model_layout = QtWidgets.QFormLayout(model_group)
        model_layout.setSpacing(10)

        # 模型路径选择
        self.model_path_label = QtWidgets.QLabel(os.path.basename(self.model_path))
        self.model_path_label.setToolTip(self.model_path)
        self.model_path_label.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse |
                                                      QtCore.Qt.TextInteractionFlag.TextSelectableByKeyboard)
        self.model_select_btn = QtWidgets.QPushButton("模型")
        model_layout.addRow(self.model_select_btn, self.model_path_label)

        # 置信度滑动条
        self.conf_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.conf_slider.setRange(0, 100)
        self.conf_slider.setValue(int(self.conf_threshold * 100))
        self.conf_value_label = QtWidgets.QLabel(f"{self.conf_threshold:.2f}")
        conf_layout = QtWidgets.QHBoxLayout()
        conf_layout.addWidget(self.conf_slider)
        conf_layout.addWidget(self.conf_value_label)
        model_layout.addRow("置信度:", conf_layout)

        # IOU滑动条
        self.iou_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.iou_slider.setRange(0, 100)
        self.iou_slider.setValue(int(self.iou_threshold * 100))
        self.iou_value_label = QtWidgets.QLabel(f"{self.iou_threshold:.2f}")
        iou_layout = QtWidgets.QHBoxLayout()
        iou_layout.addWidget(self.iou_slider)
        iou_layout.addWidget(self.iou_value_label)
        model_layout.addRow("IOU阈值:", iou_layout)

        # 统计信息组
        stats_group = QtWidgets.QGroupBox("实时统计")
        stats_layout = QtWidgets.QFormLayout(stats_group)
        stats_layout.setSpacing(10)
        self.total_count_label = QtWidgets.QLabel("0")
        self.helmet_count_label = QtWidgets.QLabel("0")
        self.no_helmet_label = QtWidgets.QLabel("0")
        stats_layout.addRow("总人数:", self.total_count_label)
        stats_layout.addRow("佩戴安全帽:", self.helmet_count_label)
        stats_layout.addRow("未佩戴安全帽:", self.no_helmet_label)

        # 控制按钮组
        btn_group = QtWidgets.QGroupBox("设备控制")
        btn_layout = QtWidgets.QGridLayout(btn_group)
        btn_layout.setSpacing(10)
        self.cam_btn = self._create_tool_button("摄像头", "camera-video")
        self.video_btn = self._create_tool_button("图片/视频", "document-open")
        self.snapshot_btn = self._create_tool_button("截图", "camera-photo")
        self.stop_btn = self._create_tool_button("停止", "media-playback-stop")
        btn_layout.addWidget(self.cam_btn, 0, 0)
        btn_layout.addWidget(self.video_btn, 0, 1)
        btn_layout.addWidget(self.snapshot_btn, 1, 0)
        btn_layout.addWidget(self.stop_btn, 1, 1)

        # 日志区域
        log_group = QtWidgets.QGroupBox("系统日志")
        log_layout = QtWidgets.QVBoxLayout(log_group)
        self.log_text = QtWidgets.QTextBrowser()
        self.log_text.setObjectName("logText")
        self.log_text.setMaximumHeight(250)
        log_layout.addWidget(self.log_text)

        control_layout.addWidget(model_group)
        control_layout.addWidget(stats_group)
        control_layout.addWidget(btn_group)
        control_layout.addWidget(log_group)
        control_layout.addStretch(1)

        main_layout.addWidget(video_container, 3)
        main_layout.addWidget(control_panel, 1)

        self.status_bar_label = QtWidgets.QLabel("就绪")
        self.statusBar().addPermanentWidget(self.status_bar_label)
        self.statusBar().setObjectName("statusBar")

        # 添加标题标签
        title_label = QtWidgets.QLabel("智能安全帽检测系统")
        title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                font-size: 30pt;
                font-weight: bold;
                color: #66B2FF;
                padding: 15px 0;
                border-bottom: 2px solid #555555;
            }
        """)
        main_layout.addWidget(title_label)

        # 添加水平容器放置视频和控制面板
        content_widget = QtWidgets.QWidget()
        content_layout = QtWidgets.QHBoxLayout(content_widget)
        content_layout.setContentsMargins(0, 0, 0, 0)
        content_layout.addWidget(video_container, 3)
        content_layout.addWidget(control_panel, 1)
        main_layout.addWidget(content_widget)

    def _setup_vars(self):
        """初始化变量"""
        self.cap = None
        self.frame_queue = Queue(maxsize=3)
        self.is_processing = False
        self.fps = 0
        self.frame_count = 0
        self.start_time = time.time()
        self.stats = {'total': 0, 'helmet': 0, 'no_helmet': 0}
        self.last_processed_frame = None
        self.model = None
        self._model_ready = False  # 添加这一行

        # 定时器
        self.timer = QtCore.QTimer()
        self.timer.setInterval(30)  # 约33fps

        self.current_image = None  # 新增:保存当前打开的原始图片
        self.is_image_mode = False  # 新增:标记当前是否为图片模式

    def _setup_connections(self):
        """连接信号与槽"""
        self.cam_btn.clicked.connect(self.start_camera)
        self.video_btn.clicked.connect(self.open_image_or_video_file)
        self.snapshot_btn.clicked.connect(self.take_snapshot)
        self.stop_btn.clicked.connect(self.stop)
        self.timer.timeout.connect(self.update_frame)
        self.model_select_btn.clicked.connect(self._select_model)
        self.conf_slider.valueChanged.connect(self.update_conf_threshold)
        self.iou_slider.valueChanged.connect(self.update_iou_threshold)

    def _create_video_label(self, placeholder_text):
        """创建视频显示标签"""
        label = QtWidgets.QLabel(placeholder_text)
        label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        label.setMinimumSize(480, 360)
        label.setObjectName("videoLabel")
        label.setProperty("placeholderText", placeholder_text)
        return label

    def _create_tool_button(self, text, icon_name):
        """创建工具按钮"""
        btn = QtWidgets.QPushButton(text)
        icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon())
        if not icon.isNull():
            btn.setIcon(icon)
        btn.setIconSize(QtCore.QSize(20, 20))
        btn.setObjectName("toolButton")
        return btn

    def _apply_styles(self):
        """应用样式表"""
        style_sheet = """
            QMainWindow { background-color: #2D2D2D; }
            QWidget#controlPanel { background-color: #3C3C3C; border-radius: 8px; }
            QGroupBox { color: #E0E0E0; font-weight: bold; border: 1px solid #555555; 
                        border-radius: 6px; margin-top: 10px; padding: 15px 5px 5px 5px; }
            QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; 
                              left: 10px; padding: 0 5px; color: #66B2FF; }
            QLabel { color: #D0D0D0; padding: 2px; }
            QLabel#videoLabel { background-color: #1A1A1A; border: 1px solid #444444; 
                               border-radius: 8px; color: #777777; font-size: 16pt; font-weight: bold; }
            QPushButton#toolButton { background-color: #555555; color: white; border: none; 
                                     padding: 10px; border-radius: 5px; min-height: 30px; text-align: center; }
            QPushButton#toolButton:hover { background-color: #6A6A6A; }
            QPushButton#toolButton:pressed { background-color: #4A4A4A; }
            QPushButton#toolButton:disabled { background-color: #404040; color: #888888; }
            QPushButton { background-color: #007ACC; color: white; border: none; 
                          padding: 8px 15px; border-radius: 4px; }
            QPushButton:hover { background-color: #0090F0; }
            QPushButton:pressed { background-color: #0060A0; }
            QTextBrowser#logText { background-color: #252525; color: #C0C0C0; 
                                   border: 1px solid #444444; border-radius: 4px; 
                                   font-family: Consolas, Courier New, monospace; }
            QStatusBar { background-color: #252525; color: #AAAAAA; }
            QStatusBar::item { border: none; }
            QToolTip { background-color: #FFFFE1; color: black; border: 1px solid #AAAAAA; 
                        padding: 4px; border-radius: 3px; }
        """
        self.setStyleSheet(style_sheet)

    def load_model(self, model_path):
        """加载YOLO模型"""
        try:
            with self.model_lock:
                self.model = YOLO(model_path)
            self.log(f"模型加载成功: {os.path.basename(model_path)}", "success")
            self._model_ready = True
            self._update_controls_state(False)
        except Exception as e:
            self.log(f"模型加载失败: {str(e)}", "error")
            self._model_ready = False
            self._update_controls_state(False, force_disable_sources=True)

    def start_camera(self):
        """启动摄像头"""
        if not self.cap:
            self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
            if self.cap.isOpened():
                self.timer.start()
                self.is_processing = True
                self.log("摄像头已启动")
                self._update_controls_state(True)
                self.start_time = time.time()
                self.frame_count = 0
            else:
                self.log("无法打开摄像头", "error")

    def open_image_or_video_file(self):
        """打开图片或视频文件"""
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "打开图片或视频文件", "",
            "视频文件 (*.mp4 *.avi *.mov *.mkv);;图片文件 (*.jpg *.jpeg *.png *.bmp);;所有文件 (*.*)"
        )
        if path:
            file_extension = os.path.splitext(path)[1].lower()
            if file_extension in ['.mp4', '.avi', '.mov', '.mkv']:  # 视频文件
                self.cap = cv2.VideoCapture(path)
                if self.cap.isOpened():
                    self.timer.start()
                    self.is_processing = True
                    self.log(f"已打开视频文件: {path}")
                    self._update_controls_state(True)
                    self.start_time = time.time()
                    self.frame_count = 0
                else:
                    self.log("无法打开视频文件", "error")
            elif file_extension in ['.jpg', '.jpeg', '.png', '.bmp']:  # 图片文件
                image = cv2.imread(path)
                if image is not None:
                    self._process_image(image)
                    self.log(f"已加载图片文件: {path}")
                else:
                    self.log("无法打开图片文件", "error")
            else:
                self.log("不支持的文件格式", "warning")

    def update_frame(self):
        """更新视频帧"""
        ret, frame = self.cap.read()
        if not ret:
            self.stop()
            return

        # 计算FPS
        self.frame_count += 1
        if self.frame_count % 10 == 0:
            self.fps = 10 / (time.time() - self.start_time)
            self.start_time = time.time()
            h, w = frame.shape[:2]
            self.status_bar_label.setText(f"运行中 | FPS: {self.fps:.1f} | 分辨率: {w}x{h}")

        # 显示原始帧
        self._display_frame(frame, self.video_label_ori, "Original")

        # 添加到处理队列
        if self.frame_queue.qsize() < 3:
            self.frame_queue.put(frame.copy())

    def _display_frame(self, frame, label, label_name="Unknown"):
        """显示帧到标签"""
        try:
            if frame.ndim == 3 and frame.shape[2] == 3:
                rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                q_image_format = QtGui.QImage.Format.Format_RGB888
                bytes_per_line = 3 * frame.shape[1]
            elif frame.ndim == 2:
                rgb_frame = frame
                q_image_format = QtGui.QImage.Format.Format_Grayscale8
                bytes_per_line = frame.shape[1]
            else:
                return

            h, w = rgb_frame.shape[:2]
            if not rgb_frame.flags['C_CONTIGUOUS']:
                rgb_frame = np.ascontiguousarray(rgb_frame)

            qimg = QtGui.QImage(rgb_frame.data, w, h, bytes_per_line, q_image_format)
            label.setProperty("image_ref", qimg)
            pixmap = QtGui.QPixmap.fromImage(qimg)

            label_size = label.size()
            if label_size.width() > 0 and label_size.height() > 0:
                scaled_pixmap = pixmap.scaled(label_size, QtCore.Qt.AspectRatioMode.KeepAspectRatio,
                                              QtCore.Qt.TransformationMode.SmoothTransformation)
                label.setPixmap(scaled_pixmap)
            else:
                label.setPixmap(pixmap)

        except Exception as e:
            label.setText(f"显示错误 ({label_name})")

    def frame_analyze_thread(self):
        """帧分析线程"""
        while True:
            if not self.frame_queue.empty():
                frame = self.frame_queue.get()

                # 如果是图片,直接处理
                if self.is_image_mode:
                    # 使用模型推理(带当前参数)
                    with self.model_lock:
                        results = self.model.predict(
                            frame,
                            conf=self.conf_threshold,
                            iou=self.iou_threshold,
                            verbose=False
                        )[0]

                    # 绘制结果
                    proc_frame = results.plot(line_width=2, labels=True)
                    self.last_processed_frame = proc_frame

                    # 更新统计信息
                    self.update_stats(results)

                    # 显示处理结果
                    self._display_frame(proc_frame, self.video_label_proc, "Processed")

                    # 跳过视频处理逻辑
                    continue

                # 使用模型推理(带参数)
                with self.model_lock:
                    results = self.model.predict(
                        frame,
                        conf=self.conf_threshold,
                        iou=self.iou_threshold,
                        verbose=False
                    )[0]

                # 更新统计信息
                self.update_stats(results)

                # 绘制结果
                proc_frame = results.plot(line_width=2, labels=True)
                self.last_processed_frame = proc_frame
                self._display_frame(proc_frame, self.video_label_proc, "Processed")

                # 限制处理频率
                time.sleep(0.01)

    def _process_image(self, image):
        """处理图片并显示结果"""
        self.is_image_mode = True  # 标记为图片模式
        self.current_image = image.copy()  # 保存原始图片副本
        self.is_processing = True
        self._update_controls_state(True)
        # 使用模型推理(带参数)
        with self.model_lock:
            results = self.model.predict(
                image,
                conf=self.conf_threshold,
                iou=self.iou_threshold,
                verbose=False
            )[0]

        # 更新统计信息
        self.update_stats(results)

        # 绘制结果
        proc_image = results.plot(line_width=2, labels=True)
        self._display_frame(image, self.video_label_ori, "Original")
        self.last_processed_frame = proc_image
        self._display_frame(proc_image, self.video_label_proc, "Processed")

    def _select_model(self):
        """选择模型文件"""
        if self.is_processing:
            QtWidgets.QMessageBox.warning(self, "警告", "请先停止当前处理,再选择新模型。")
            return

        filter_str = "YOLO 模型 (*.pt *.engine *.onnx);;所有文件 (*.*)"
        start_dir = os.path.dirname(self.model_path) if os.path.dirname(self.model_path) else ""
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择模型文件", start_dir, filter_str
        )

        if path and path != self.model_path:
            self.model_path = path
            self.model_path_label.setText(os.path.basename(path))
            self.model_path_label.setToolTip(path)
            self.log(f"选择新模型: {path}", "info")
            self.load_model(path)
            self._save_settings()  # 保存新模型路径

    def update_stats(self, results):
        """更新统计信息"""
        helmet_count = 0
        no_helmet_count = 0

        for box in results.boxes:
            cls = int(box.cls)
            if cls == 1:
                helmet_count += 1
            elif cls == 0:
                no_helmet_count += 1

        self.stats = {
            'total': helmet_count + no_helmet_count,
            'helmet': helmet_count,
            'no_helmet': no_helmet_count
        }

        # 更新界面
        self.total_count_label.setText(str(self.stats['total']))
        self.helmet_count_label.setText(str(self.stats['helmet']))
        self.no_helmet_label.setText(str(self.stats['no_helmet']))

    def update_conf_threshold(self, value):
        """更新置信度阈值"""
        self.conf_threshold = value / 100.0
        self.conf_value_label.setText(f"{self.conf_threshold:.2f}")
        self.log(f"置信度阈值更新为: {self.conf_threshold:.2f}")
        self._trigger_async_reprocess()  # 新增:触发异步处理

    def update_iou_threshold(self, value):
        """更新IOU阈值"""
        self.iou_threshold = value / 100.0
        self.iou_value_label.setText(f"{self.iou_threshold:.2f}")
        self.log(f"IOU阈值更新为: {self.iou_threshold:.2f}")
        self._trigger_async_reprocess()  # 新增:触发异步处理

    def _trigger_async_reprocess(self):
        """触发异步重新处理(线程安全)"""
        if self.is_image_mode and self.current_image is not None:
            # 清空队列确保只处理最新图片
            with self.frame_queue.mutex:
                self.frame_queue.queue.clear()
            # 将原始图片放入处理队列
            self.frame_queue.put(self.current_image.copy())

    def take_snapshot(self):
        """截图"""
        if self.last_processed_frame is not None:
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            suggested_filename = f"snapshot_{timestamp}.png"
            path, _ = QtWidgets.QFileDialog.getSaveFileName(
                self, "保存截图", suggested_filename, "PNG图像 (*.png);;JPEG图像 (*.jpg *.jpeg)"
            )
            if path:
                try:
                    save_frame = cv2.cvtColor(self.last_processed_frame, cv2.COLOR_RGB2BGR)
                    success = Image.fromarray(save_frame)
                    success.save(path)
                    if success:
                        self.log(f"截图已保存: {path}", "success")
                    else:
                        self.log(f"截图保存失败: {path}", "error")
                except Exception as e:
                    self.log(f"保存截图时出错: {str(e)}", "error")

    def log(self, message, level="info"):
        """记录日志"""
        color_map = {
            "info": "#A0A0FF",
            "success": "#77FF77",
            "warning": "#FFFF77",
            "error": "#FF7777"
        }
        color = color_map.get(level, "#E0E0E0")
        timestamp = time.strftime('%H:%M:%S')
        formatted_message = f'<span style="color: #888;">[{timestamp}]</span> <span style="color:{color};">{message}</span>'
        self.log_text.append(formatted_message)
        self.log_text.moveCursor(QtGui.QTextCursor.MoveOperation.End)

    def _update_controls_state(self, is_running, force_disable_sources=False):
        """更新控件状态"""
        disable_start_buttons = is_running or not self._model_ready or force_disable_sources
        self.cam_btn.setDisabled(disable_start_buttons)
        self.video_btn.setDisabled(disable_start_buttons)
        self.model_select_btn.setDisabled(is_running)
        self.stop_btn.setEnabled(is_running)
        self.snapshot_btn.setEnabled(is_running and self._model_ready)

    def stop(self):
        """停止处理"""
        self.is_image_mode = False  # 新增:重置图片模式标记
        self.current_image = None  # 新增:清空缓存图片
        self.timer.stop()
        self.is_processing = False

        if self.cap:
            self.cap.release()
            self.cap = None

        self.frame_queue.queue.clear()
        self.video_label_ori.setText(self.video_label_ori.property("placeholderText"))
        self.video_label_proc.setText(self.video_label_proc.property("placeholderText"))

        self.last_processed_frame = None

        self._update_controls_state(False)
        self.status_bar_label.setText("已停止")
        self.log("系统已停止")

    def _select_model(self):
        """选择模型文件"""
        if self.is_processing:
            QtWidgets.QMessageBox.warning(self, "警告", "请先停止当前处理,再选择新模型。")
            return

        filter_str = "YOLO 模型 (*.pt *.engine *.onnx);;所有文件 (*.*)"
        start_dir = os.path.dirname(self.model_path) if os.path.dirname(self.model_path) else ""
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择模型文件", start_dir, filter_str
        )

        if path and path != self.model_path:
            self.model_path = path
            self.model_path_label.setText(os.path.basename(path))
            self.model_path_label.setToolTip(path)
            self.log(f"选择新模型: {path}", "info")
            self.load_model(path)

    def closeEvent(self, event):
        """关闭事件处理"""
        self.stop()
        self._save_settings()
        event.accept()


if __name__ == "__main__":
    if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
        QtWidgets.QApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
    if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
        QtWidgets.QApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True)

    app = QtWidgets.QApplication(sys.argv)

    # 创建占位图标(如果不存在)
    if not os.path.exists("helmet_icon.png"):
        img = QtGui.QImage(64, 64, QtGui.QImage.Format.Format_ARGB32)
        img.fill(QtGui.QColor("blue"))
        painter = QtGui.QPainter(img)
        painter.setPen(QtGui.QColor("white"))
        painter.setFont(QtGui.QFont("Arial", 20))
        painter.drawText(img.rect(), QtCore.Qt.AlignmentFlag.AlignCenter, "H")
        painter.end()
        img.save("helmet_icon.png")

    # 创建模型目录(如果不存在)
    weights_dir = os.path.dirname(DEFAULT_MODEL_PATH)
    if weights_dir and not os.path.exists(weights_dir):
        os.makedirs(weights_dir)

    window = MWindow()
    window.show()
    sys.exit(app.exec())


网站公告

今日签到

点亮在社区的每一天
去签到