网站架构分类家居网站建设总结
网站架构分类,家居网站建设总结,网站建设所需硬件,wordpress 本地视频摘要在智能交通管理、智慧城市建设及商业停车场运营中#xff0c;车型识别与车辆计数是两项至关重要的计算机视觉任务。传统方法受限于复杂场景的鲁棒性#xff0c;而深度学习#xff0c;尤其是以YOLO (You Only Look Once) 系列为代表的单阶段目标检测算法#xff0c;凭借…摘要在智能交通管理、智慧城市建设及商业停车场运营中车型识别与车辆计数是两项至关重要的计算机视觉任务。传统方法受限于复杂场景的鲁棒性而深度学习尤其是以YOLO (You Only Look Once)系列为代表的单阶段目标检测算法凭借其卓越的实时性和精度已成为解决此类问题的工业级标准。本文将系统性地介绍如何利用YOLOv5, YOLOv8, 以及最新的YOLOv10构建一个包含完整深度学习模型训练、高性能推理引擎和用户友好型图形界面UI的车型识别与计数系统。我们将提供从数据集准备、模型训练、评估、到最终应用部署的全套可执行代码旨在为读者呈现一个端到端的实战项目范例。1. 引言问题定义与技术选型1.1 任务目标我们的核心目标是开发一个系统能够识别从图像或视频流中准确检测出车辆并分类其车型如轿车、SUV、卡车、公交车等。计数在指定区域如道路、停车场入口对通过的车辆进行累计计数。可视化通过一个直观的图形用户界面使用户能够便捷地选择输入源、查看检测结果、调整参数并导出统计信息。1.2 为什么选择YOLO速度与精度的完美平衡YOLO的单阶段设计使其推理速度远超R-CNN等两阶段检测器满足实时处理需求。强大的生态YOLOv5/v8的PyTorch实现拥有极佳的易用性和丰富的预训练模型。持续演进从YOLOv5的工程化完善到YOLOv8的SOTAState-Of-The-Art性能再到YOLOv10对NMS-Free的探索该系列始终保持技术前沿。统一的框架Ultralytics的YOLO框架API设计一致便于我们在同一套代码流程中对比和切换不同版本的模型。1.3 系统架构总览我们的系统主要由三个核心模块构成深度学习模型模块基于YOLO的检测器负责特征提取、目标定位与分类。计数逻辑模块基于虚拟线圈或区域闯入检测的计数算法。UI交互模块基于PyQt5开发的桌面应用程序集成模型调用与结果展示。接下来我们将分步深入每个模块。2. 数据集准备与预处理任何深度学习项目的基石都是高质量的数据集。我们假设您已收集或获取了一个包含常见车型标注的数据集。2.1 数据集结构推荐使用YOLO格式的标注。数据集目录结构应如下textvehicle_dataset/ ├── images/ │ ├── train/ │ │ ├── img001.jpg │ │ └── ... │ └── val/ │ ├── img101.jpg │ └── ... └── labels/ ├── train/ │ ├── img001.txt │ └── ... └── val/ ├── img101.txt └── ...每个.txt标注文件包含多行每行格式为class_id center_x center_y width height。坐标均为相对于图像宽高的归一化值0-1。2.2 创建数据集配置文件创建一个dataset.yaml文件用于指导训练。yaml# dataset.yaml path: /path/to/your/vehicle_dataset # 数据集根目录 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 # 类别列表 names: 0: car 1: truck 2: bus 3: motorcycle 4: suv3. YOLO模型训练以YOLOv8为例我们首先展示目前最流行的YOLOv8的训练流程。YOLOv5和YOLOv10的训练方式极其相似主要差异在于模型名称和少量参数。3.1 环境安装bash# 使用conda创建环境 conda create -n vehicle_detection python3.8 conda activate vehicle_detection # 安装PyTorch (请根据CUDA版本选择) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装Ultralytics (包含YOLOv8) pip install ultralytics # 对于YOLOv5可以克隆其仓库 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt # 对于YOLOv10同样通过pip安装 pip install supervision yolo-v10 # 注意YOLOv10的官方库名可能是 ultralytics 未来的版本或 yolo-v10请关注官方发布。3.2 完整的训练脚本train_yolo.pypythonimport argparse from ultralytics import YOLO import os def train_model(model_sizeyolov8n.pt, dataset_cfgdataset.yaml, epochs100, imgsz640, batch16): 训练YOLOv8模型 Args: model_size: 预训练模型路径或名称如 yolov8n.pt, yolov8s.pt, yolov8m.pt dataset_cfg: 数据集配置文件路径 epochs: 训练轮数 imgsz: 输入图像大小 batch: 批次大小 # 加载模型 model YOLO(model_size) # 加载预训练模型 # 训练模型 results model.train( datadataset_cfg, epochsepochs, imgszimgsz, batchbatch, device0, # 使用GPU 0 cpu 或 0,1,2,3 多GPU workers4, # 数据加载线程数 projectvehicle_detection_runs, # 保存结果的目录 namefexp_{model_size.split(.)[0]}, # 实验名称 exist_okTrue, # 允许覆盖现有实验 patience50, # 早停耐心值 lr00.01, # 初始学习率 lrf0.01, # 最终学习率因子 momentum0.937, # 动量 weight_decay0.0005, # 权重衰减 saveTrue, save_period10, # 每10个epoch保存一次检查点 ) print(训练完成最佳模型保存在, results.save_dir) # 在验证集上评估模型 metrics model.val() print(fmAP50-95: {metrics.box.map:.4f}, mAP50: {metrics.box.map50:.4f}) # 导出模型为ONNX格式可选用于部署 model.export(formatonnx, imgszimgsz, simplifyTrue, opset12) print(f模型已导出为ONNX格式。) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--model, typestr, defaultyolov8n.pt, help预训练模型路径) parser.add_argument(--data, typestr, defaultdataset.yaml, help数据集配置文件) parser.add_argument(--epochs, typeint, default100, help训练轮数) parser.add_argument(--imgsz, typeint, default640, help图像大小) parser.add_argument(--batch, typeint, default16, help批次大小) opt parser.parse_args() train_model(opt.model, opt.data, opt.epochs, opt.imgsz, opt.batch)3.3 针对YOLOv5和YOLOv10的调整YOLOv5训练命令通常通过命令行调用train.py脚本。其参数与YOLOv8高度相似。您可以使用类似的Python API或在命令行中执行bashpython yolov5/train.py --img 640 --batch 16 --epochs 100 --data dataset.yaml --weights yolov5s.pt --project vehicle_detection_runs --name exp_yolov5sYOLOv10截至2023年10月YOLOv10已发布。其训练API与YOLOv8几乎完全一致只需将模型名称改为yolov10n.pt等即可。请务必参考其官方GitHub仓库获取最新用法。4. 推理与计数逻辑实现训练好模型后我们需要编写一个推理脚本它不仅能检测车辆还要实现计数功能。这里我们采用虚拟线/区域检测法在画面中定义一条虚拟线对于水平车流或一个多边形区域当检测框的中心点穿过这条线或进入该区域时计数加一。4.1 核心推理与计数脚本inference_counter.pypythonimport cv2 import numpy as np from collections import defaultdict from ultralytics import YOLO import supervision as sv # 强大的检测可视化与工具库 class VehicleDetectionCounter: def __init__(self, model_path, class_names, line_start, line_end, count_regionNone): 初始化车辆检测与计数器。 Args: model_path: 训练好的YOLO模型路径 (.pt 或 .onnx) class_names: 类别名称列表 line_start, line_end: 虚拟计数线的起点和终点 (x, y) count_region: (可选) 一个多边形顶点列表用于区域计数。如果提供将忽略线计数。 self.model YOLO(model_path) self.class_names class_names self.line_start np.array(line_start) self.line_end np.array(line_end) self.count_region np.array(count_region) if count_region is not None else None # 追踪已计数的车辆ID防止重复计数 self.counted_ids set() # 存储每个类别的计数 self.class_counts defaultdict(int) # 总的车辆计数 self.total_count 0 # 用于绘制 self.box_annotator sv.BoxAnnotator(thickness2, text_thickness1, text_scale0.5) self.line_counter sv.LineZone(startself.line_start, endself.line_end) self.line_annotator sv.LineZoneAnnotator(thickness2, text_thickness1, text_scale0.5) if self.count_region is not None: self.zone_polygon sv.PolygonZone(polygonself.count_region, frame_resolution_wh(1280, 720)) self.zone_annotator sv.PolygonZoneAnnotator(zoneself.zone_polygon, colorsv.Color.red(), thickness2, text_thickness2, text_scale1) def process_frame(self, frame): 处理单帧图像。 Returns: 绘制了检测框和计数信息的帧。 # 执行推理 results self.model(frame, imgsz640, verboseFalse)[0] detections sv.Detections.from_ultralytics(results) # 初始化当前帧的追踪ID这里简化处理实际应用中应使用跟踪算法如ByteTrack # 为每个检测分配一个临时ID基于其位置。生产环境应集成跟踪器。 detections.tracker_id np.arange(len(detections)) # 根据检测区域或线进行计数 if self.count_region is not None: # 区域计数检测框中心在区域内则计数 in_zone self.zone_polygon.trigger(detections) for tracker_id in detections.tracker_id[in_zone]: if tracker_id not in self.counted_ids: self.counted_ids.add(tracker_id) self.total_count 1 # 获取类别ID cls_id detections.class_id[detections.tracker_id tracker_id][0] self.class_counts[cls_id] 1 # 绘制区域 frame self.zone_annotator.annotate(frame) else: # 线计数使用supervision的LineZone self.line_counter.trigger(detections) in_counts, out_counts self.line_counter.in_count, self.line_counter.out_count # 这里简化处理将进或出的总数作为总计数。可根据方向细分。 new_total in_counts out_counts if new_total self.total_count: # 更新类别计数此处简化实际应关联具体触发线的车辆ID self.total_count new_total # 注意LineZone不直接提供触发车辆的类别信息需要额外逻辑关联。 # 为简化演示我们假设所有触发线的车辆都是“car”类。实际项目需要更精确的关联。 self.class_counts[0] self.total_count # 假设类别0是car # 绘制线和计数 frame self.line_annotator.annotate(frame) # 为检测框添加标签 labels [ f{self.class_names[class_id]} {confidence:0.2f} for class_id, confidence in zip(detections.class_id, detections.confidence) ] frame self.box_annotator.annotate(frame, detectionsdetections, labelslabels) # 在帧上显示总计数和各类别计数 y_offset 30 cv2.putText(frame, fTotal Vehicles: {self.total_count}, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) for cls_id, count in self.class_counts.items(): y_offset 30 cv2.putText(frame, f{self.class_names[cls_id]}: {count}, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) return frame, detections, self.total_count def reset_counter(self): 重置计数器 self.counted_ids.clear() self.class_counts.clear() self.total_count 0 if self.count_region is None: self.line_counter.in_count 0 self.line_counter.out_count 0 # 使用示例 if __name__ __main__: # 假设类别 CLASS_NAMES [car, truck, bus, motorcycle, suv] # 定义一条水平计数线画面中间 LINE_START (50, 360) # (x1, y1) LINE_END (1230, 360) # (x2, y2) # 或者定义一个多边形区域例如停车场入口 # COUNT_REGION [(200, 400), (600, 400), (600, 600), (200, 600)] detector_counter VehicleDetectionCounter( model_pathvehicle_detection_runs/exp_yolov8n/weights/best.pt, class_namesCLASS_NAMES, line_startLINE_START, line_endLINE_END, # count_regionCOUNT_REGION # 如果使用区域计数取消注释并注释掉line_start/end ) # 处理视频文件或摄像头 cap cv2.VideoCapture(test_video.mp4) # 或 cv2.VideoCapture(0) 用于摄像头 while cap.isOpened(): ret, frame cap.read() if not ret: break processed_frame, _, total detector_counter.process_frame(frame) cv2.imshow(Vehicle Detection Counting, processed_frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()5. 集成PyQt5图形用户界面UI一个专业的系统需要一个易用的界面。我们将使用PyQt5构建一个主界面集成模型加载、视频/图像处理、参数调整和结果展示功能。5.1 完整的UI应用程序代码vehicle_detection_ui.pypythonimport sys import os import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QComboBox, QSpinBox, QGroupBox, QMessageBox, QTextEdit, QCheckBox) from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, pyqtSlot from PyQt5.QtGui import QImage, QPixmap from inference_counter import VehicleDetectionCounter # 导入我们之前写的类 class VideoThread(QThread): 视频处理线程防止UI卡顿 change_pixmap_signal pyqtSignal(np.ndarray, int, dict) # 发送帧图像、总计数和类别计数 finished_signal pyqtSignal() def __init__(self, detector_counter, video_source0): super().__init__() self.detector_counter detector_counter self.video_source video_source self.is_running True self.cap None def run(self): self.cap cv2.VideoCapture(self.video_source) if not self.cap.isOpened(): print(无法打开视频源) return while self.is_running: ret, frame self.cap.read() if not ret: break # 处理帧 processed_frame, detections, total_count self.detector_counter.process_frame(frame) class_counts self.detector_counter.class_counts # 发送信号更新UI self.change_pixmap_signal.emit(processed_frame, total_count, class_counts) self.cap.release() self.finished_signal.emit() def stop(self): self.is_running False self.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.detector_counter None self.video_thread None self.current_video_source None self.initUI() def initUI(self): self.setWindowTitle(车型识别与计数系统 v1.0 - YOLO Powered) self.setGeometry(100, 100, 1400, 800) # 中央部件和主布局 central_widget QWidget() self.setCentralWidget(central_widget) main_layout QHBoxLayout(central_widget) # 左侧控制面板 control_panel QGroupBox(控制面板) control_layout QVBoxLayout() # 模型选择 model_layout QHBoxLayout() model_layout.addWidget(QLabel(选择YOLO模型:)) self.model_combo QComboBox() self.model_combo.addItems([YOLOv5s, YOLOv8n, YOLOv8s, YOLOv10n]) model_layout.addWidget(self.model_combo) self.load_model_btn QPushButton(加载模型) self.load_model_btn.clicked.connect(self.load_model) model_layout.addWidget(self.load_model_btn) control_layout.addLayout(model_layout) # 输入源选择 input_layout QHBoxLayout() input_layout.addWidget(QLabel(输入源:)) self.input_combo QComboBox() self.input_combo.addItems([摄像头, 视频文件, 图像文件]) input_layout.addWidget(self.input_combo) self.select_file_btn QPushButton(选择文件...) self.select_file_btn.clicked.connect(self.select_input_file) self.select_file_btn.setEnabled(False) input_layout.addWidget(self.select_file_btn) control_layout.addLayout(input_layout) # 计数线/区域设置 line_group QGroupBox(计数设置) line_layout QVBoxLayout() self.use_region_check QCheckBox(使用区域计数否则使用线计数) line_layout.addWidget(self.use_region_check) self.set_line_btn QPushButton(设置计数线/区域) self.set_line_btn.clicked.connect(self.set_counting_line_region) line_layout.addWidget(self.set_line_btn) self.reset_count_btn QPushButton(重置计数器) self.reset_count_btn.clicked.connect(self.reset_counter) line_layout.addWidget(self.reset_count_btn) line_group.setLayout(line_layout) control_layout.addWidget(line_group) # 类别过滤 filter_group QGroupBox(显示过滤开发中) filter_layout QVBoxLayout() self.filter_car QCheckBox(轿车) self.filter_car.setChecked(True) filter_layout.addWidget(self.filter_car) self.filter_truck QCheckBox(卡车) self.filter_truck.setChecked(True) filter_layout.addWidget(self.filter_truck) filter_group.setLayout(filter_layout) control_layout.addWidget(filter_group) # 开始/停止按钮 self.start_btn QPushButton(开始检测) self.start_btn.clicked.connect(self.start_detection) self.start_btn.setEnabled(False) control_layout.addWidget(self.start_btn) self.stop_btn QPushButton(停止检测) self.stop_btn.clicked.connect(self.stop_detection) self.stop_btn.setEnabled(False) control_layout.addWidget(self.stop_btn) # 信息显示 info_group QGroupBox(系统信息) info_layout QVBoxLayout() self.info_text QTextEdit() self.info_text.setReadOnly(True) info_layout.addWidget(self.info_text) info_group.setLayout(info_layout) control_layout.addWidget(info_group) control_layout.addStretch() control_panel.setLayout(control_layout) control_panel.setMaximumWidth(350) main_layout.addWidget(control_panel) # 右侧视频显示和计数面板 right_panel QVBoxLayout() # 视频显示标签 self.video_label QLabel() self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setMinimumSize(800, 600) self.video_label.setStyleSheet(border: 2px solid gray; background-color: black;) right_panel.addWidget(self.video_label) # 计数结果显示 count_group QGroupBox(实时计数结果) count_layout QVBoxLayout() self.total_count_label QLabel(总车辆数: 0) self.total_count_label.setStyleSheet(font-size: 16pt; font-weight: bold; color: green;) count_layout.addWidget(self.total_count_label) self.detail_count_label QLabel() self.detail_count_label.setStyleSheet(font-size: 12pt;) count_layout.addWidget(self.detail_count_label) count_group.setLayout(count_layout) right_panel.addWidget(count_group) # 操作按钮保存、导出 button_layout QHBoxLayout() self.snapshot_btn QPushButton(保存截图) self.snapshot_btn.clicked.connect(self.save_snapshot) self.snapshot_btn.setEnabled(False) button_layout.addWidget(self.snapshot_btn) self.export_report_btn QPushButton(导出报告(.txt)) self.export_report_btn.clicked.connect(self.export_report) button_layout.addWidget(self.export_report_btn) right_panel.addLayout(button_layout) main_layout.addLayout(right_panel) # 状态栏 self.statusBar().showMessage(就绪) # 连接输入源选择变化 self.input_combo.currentIndexChanged.connect(self.input_source_changed) self.log_info(欢迎使用车型识别与计数系统。请先加载模型。) def log_info(self, message): self.info_text.append(f[INFO] {message}) self.statusBar().showMessage(message) def load_model(self): model_name self.model_combo.currentText() # 这里假设模型文件位于特定路径实际应用中可以让用户选择 model_map { YOLOv5s: weights/yolov5s_vehicle.pt, YOLOv8n: vehicle_detection_runs/exp_yolov8n/weights/best.pt, YOLOv8s: vehicle_detection_runs/exp_yolov8s/weights/best.pt, YOLOv10n: weights/yolov10n_vehicle.pt } model_path model_map.get(model_name) if not os.path.exists(model_path): QMessageBox.warning(self, 文件未找到, f未找到模型文件: {model_path}。请确保模型已训练并放置正确。) return class_names [car, truck, bus, motorcycle, suv] line_start (100, 300) line_end (700, 300) try: self.detector_counter VehicleDetectionCounter(model_path, class_names, line_start, line_end) self.log_info(f模型 {model_name} 加载成功) self.start_btn.setEnabled(True) except Exception as e: QMessageBox.critical(self, 加载失败, f加载模型时出错{str(e)}) def input_source_changed(self, index): # 当选择“视频文件”或“图像文件”时启用文件选择按钮 if index 1 or index 2: # 视频或图像 self.select_file_btn.setEnabled(True) else: self.select_file_btn.setEnabled(False) def select_input_file(self): input_type self.input_combo.currentIndex() if input_type 1: # 视频文件 file_path, _ QFileDialog.getOpenFileName(self, 选择视频文件, , Video Files (*.mp4 *.avi *.mov *.mkv)) elif input_type 2: # 图像文件 file_path, _ QFileDialog.getOpenFileName(self, 选择图像文件, , Image Files (*.jpg *.png *.jpeg *.bmp)) else: return if file_path: self.current_video_source file_path self.log_info(f已选择输入文件: {file_path}) # 预览第一帧 self.preview_input(file_path) def preview_input(self, file_path): if self.input_combo.currentIndex() 2: # 图像 frame cv2.imread(file_path) else: # 视频 cap cv2.VideoCapture(file_path) ret, frame cap.read() cap.release() if not ret: return frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch frame.shape bytes_per_line ch * w q_img QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(q_img).scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def set_counting_line_region(self): self.log_info(请在右侧视频画面中点击定义点。功能需结合画面交互实现此处为示意。) # 注完整实现需要捕捉鼠标在video_label上的点击事件来设置坐标。 # 为简化示例我们假设坐标已预设。 def reset_counter(self): if self.detector_counter: self.detector_counter.reset_counter() self.total_count_label.setText(总车辆数: 0) self.detail_count_label.setText() self.log_info(计数器已重置。) def start_detection(self): if self.detector_counter is None: QMessageBox.warning(self, 错误, 请先加载模型) return # 确定输入源 input_type self.input_combo.currentIndex() if input_type 0: # 摄像头 source 0 elif input_type 1: # 视频文件 if not self.current_video_source: QMessageBox.warning(self, 错误, 请先选择视频文件) return source self.current_video_source else: # 图像文件 if not self.current_video_source: QMessageBox.warning(self, 错误, 请先选择图像文件) return # 对单张图像我们只处理一次 source self.current_video_source self.process_single_image(source) return # 启动视频处理线程 self.video_thread VideoThread(self.detector_counter, source) self.video_thread.change_pixmap_signal.connect(self.update_video_frame) self.video_thread.finished_signal.connect(self.detection_finished) self.video_thread.start() self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.snapshot_btn.setEnabled(True) self.log_info(开始实时检测...) pyqtSlot(np.ndarray, int, dict) def update_video_frame(self, frame, total_count, class_counts): 更新视频画面和计数 # 转换颜色空间 BGR - RGB rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_frame.shape bytes_per_line ch * w q_img QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(q_img).scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) # 更新计数显示 self.total_count_label.setText(f总车辆数: {total_count}) detail_text | .join([f{self.detector_counter.class_names.get(cls_id, str(cls_id))}: {count} for cls_id, count in class_counts.items()]) self.detail_count_label.setText(detail_text) def process_single_image(self, image_path): 处理单张图像 frame cv2.imread(image_path) if frame is None: self.log_info(无法读取图像文件。) return processed_frame, _, total_count self.detector_counter.process_frame(frame) class_counts self.detector_counter.class_counts self.update_video_frame(processed_frame, total_count, class_counts) self.log_info(f图像处理完成。检测到 {total_count} 辆车。) def stop_detection(self): if self.video_thread: self.video_thread.stop() self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.log_info(检测已停止。) def detection_finished(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.log_info(视频处理完成。) def save_snapshot(self): if self.video_label.pixmap(): file_path, _ QFileDialog.getSaveFileName(self, 保存截图, , PNG Image (*.png);;JPEG Image (*.jpg)) if file_path: self.video_label.pixmap().save(file_path) self.log_info(f截图已保存至: {file_path}) def export_report(self): if self.detector_counter: file_path, _ QFileDialog.getSaveFileName(self, 导出报告, , Text Files (*.txt)) if file_path: with open(file_path, w) as f: f.write( 车型识别与计数报告 \n) f.write(f总车辆数: {self.detector_counter.total_count}\n) f.write(-----------------------------\n) for cls_id, count in self.detector_counter.class_counts.items(): class_name self.detector_counter.class_names.get(cls_id, fClass_{cls_id}) f.write(f{class_name}: {count}\n) self.log_info(f报告已导出至: {file_path}) def closeEvent(self, event): self.stop_detection() event.accept() if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())