摘要随着智能交通系统和智慧城市建设的快速发展行人车辆检测与计数技术成为计算机视觉领域的重要研究方向。本文将详细介绍基于YOLOYou Only Look Once系列算法的行人车辆检测与计数系统包含YOLOv5、YOLOv8和YOLOv10三个版本对比并实现基于PySide6的图形用户界面。文章将从理论基础、算法对比、数据集处理、模型训练、系统实现到完整代码展示提供超过5000字的详细技术解析。1. 引言1.1 研究背景在现代化城市管理中实时准确的行人车辆检测与计数对于交通流量分析、安全管理、城市规划等方面具有重要意义。传统的检测方法存在精度低、实时性差等问题而基于深度学习的目标检测算法为此提供了新的解决方案。1.2 YOLO算法发展历程YOLO系列算法自2016年问世以来经历了多次重大改进。YOLOv5以其易用性和高效性广受欢迎YOLOv8在精度和速度上进一步优化而YOLOv10则代表了最新的技术进展在保持实时性的同时大幅提升了检测精度。2. YOLO算法理论基础2.1 YOLO核心思想YOLO算法的核心思想是将目标检测视为回归问题直接在单次前向传播中预测边界框和类别概率。与传统的两阶段检测器如R-CNN系列相比YOLO实现了端到端的检测显著提高了检测速度。2.2 YOLOv5架构特点YOLOv5采用了CSPDarknet53作为骨干网络SPPSpatial Pyramid Pooling和PANetPath Aggregation Network作为特征融合模块。其主要创新包括自适应锚框计算马赛克数据增强自动学习边界框2.3 YOLOv8改进之处YOLOv8在YOLOv5基础上进行了多项重要改进无锚框Anchor-Free检测新的骨干网络设计改进的损失函数更高效的特征金字塔2.4 YOLOv10最新突破YOLOv10是YOLO系列的最新版本主要创新包括双分配策略训练一致性匹配策略无NMSNon-Maximum Suppression推理增强的特征提取网络3. 系统设计与实现3.1 整体架构本系统采用模块化设计主要包括以下组件数据预处理模块模型训练模块推理检测模块计数统计模块图形用户界面模块3.2 技术栈深度学习框架PyTorch 1.10图形界面PySide6图像处理OpenCV, Pillow数据处理NumPy, Pandas可视化Matplotlib, Seaborn4. 数据集准备与预处理4.1 数据集选择推荐使用以下公开数据集COCO数据集通用目标检测CityPersons行人检测UA-DETRAC车辆检测自定义数据集4.2 数据预处理代码pythonimport os import cv2 import numpy as np import yaml from sklearn.model_selection import train_test_split import shutil class DatasetPreprocessor: def __init__(self, dataset_path, output_path): self.dataset_path dataset_path self.output_path output_path self.classes [person, car, bus, truck, bicycle, motorcycle] def organize_coco_format(self): 将数据集组织为COCO格式 # 创建目录结构 dirs [images/train, images/val, images/test, labels/train, labels/val, labels/test] for dir_path in dirs: os.makedirs(os.path.join(self.output_path, dir_path), exist_okTrue) def convert_annotations(self, annotation_path, image_dir, output_dir): 转换标注格式为YOLO格式 # 实现标注格式转换逻辑 pass def create_dataset_yaml(self): 创建数据集配置文件 data { path: self.output_path, train: images/train, val: images/val, test: images/test, nc: len(self.classes), names: self.classes } yaml_path os.path.join(self.output_path, dataset.yaml) with open(yaml_path, w) as f: yaml.dump(data, f) return yaml_path def data_augmentation(self, image, bboxes): 数据增强 # 实现马赛克增强、混合、随机翻转等 return image, bboxes5. YOLO模型训练实现5.1 YOLOv5训练代码pythonimport torch import yaml from torch.utils.data import DataLoader import matplotlib.pyplot as plt from tqdm import tqdm import warnings warnings.filterwarnings(ignore) class YOLOv5Trainer: def __init__(self, model_cfgyolov5s.yaml, data_cfgdataset.yaml): self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.model self.build_model(model_cfg) self.data_cfg data_cfg self.hyp self.load_hyperparameters() def build_model(self, cfg_path): 构建YOLOv5模型 from models.yolo import Model with open(cfg_path) as f: cfg yaml.safe_load(f) model Model(cfg).to(self.device) return model def load_hyperparameters(self): 加载超参数 hyp { lr0: 0.01, # 初始学习率 lrf: 0.01, # 最终学习率系数 momentum: 0.937, weight_decay: 0.0005, warmup_epochs: 3.0, warmup_momentum: 0.8, warmup_bias_lr: 0.1, box: 0.05, cls: 0.5, cls_pw: 1.0, obj: 1.0, obj_pw: 1.0, iou_t: 0.20, anchor_t: 4.0, fl_gamma: 0.0, hsv_h: 0.015, hsv_s: 0.7, hsv_v: 0.4, degrees: 0.0, translate: 0.1, scale: 0.5, shear: 0.0, perspective: 0.0, flipud: 0.0, fliplr: 0.5, mosaic: 1.0, mixup: 0.0, copy_paste: 0.0 } return hyp def train_epoch(self, dataloader, optimizer, epoch): 训练一个epoch self.model.train() total_loss 0 pbar tqdm(dataloader, descfEpoch {epoch}) for batch_idx, (imgs, targets) in enumerate(pbar): imgs imgs.to(self.device) targets targets.to(self.device) # 前向传播 pred self.model(imgs) loss, loss_items self.compute_loss(pred, targets) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() pbar.set_postfix({loss: loss.item()}) return total_loss / len(dataloader) def compute_loss(self, pred, targets): 计算损失函数 # YOLOv5的损失函数包含三部分边界框损失、置信度损失、分类损失 pass def train(self, epochs100, batch_size16): 完整训练过程 # 准备数据 dataset self.load_dataset() dataloader DataLoader(dataset, batch_sizebatch_size, shuffleTrue) # 优化器 optimizer torch.optim.SGD(self.model.parameters(), lrself.hyp[lr0], momentumself.hyp[momentum], weight_decayself.hyp[weight_decay]) # 学习率调度器 scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) # 训练循环 losses [] for epoch in range(epochs): avg_loss self.train_epoch(dataloader, optimizer, epoch) losses.append(avg_loss) # 验证 if epoch % 10 0: val_loss self.validate() print(fEpoch {epoch}: Train Loss{avg_loss:.4f}, Val Loss{val_loss:.4f}) # 保存模型 if epoch % 20 0: self.save_model(epoch, avg_loss) scheduler.step() # 绘制损失曲线 self.plot_losses(losses) def validate(self): 验证模型 self.model.eval() # 实现验证逻辑 pass def save_model(self, epoch, loss): 保存模型 checkpoint { epoch: epoch, model_state_dict: self.model.state_dict(), optimizer_state_dict: optimizer.state_dict(), loss: loss, } torch.save(checkpoint, fyolov5_epoch_{epoch}_loss_{loss:.4f}.pt)5.2 YOLOv8训练代码pythonfrom ultralytics import YOLO import torch import os class YOLOv8Trainer: def __init__(self, model_sizeyolov8n.pt): self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.model YOLO(model_size) def train(self, data_yaml, epochs100, imgsz640): 训练YOLOv8模型 # 训练参数配置 args { data: data_yaml, epochs: epochs, imgsz: imgsz, batch: 16, device: self.device, workers: 8, optimizer: SGD, lr0: 0.01, lrf: 0.01, momentum: 0.937, weight_decay: 0.0005, warmup_epochs: 3, warmup_momentum: 0.8, box: 7.5, cls: 0.5, dfl: 1.5, pretrained: True, verbose: True, save: True, save_period: 10, cache: False, project: runs/train, name: yolov8_training, exist_ok: False, patience: 100, freeze: None, } # 开始训练 results self.model.train(**args) return results def evaluate(self, model_path, data_yaml): 评估模型性能 model YOLO(model_path) metrics model.val(datadata_yaml) print(fmAP50-95: {metrics.box.map}) print(fmAP50: {metrics.box.map50}) print(fmAP75: {metrics.box.map75}) return metrics5.3 YOLOv10训练代码pythonimport torch from torch.utils.data import DataLoader import yaml class YOLOv10Trainer: def __init__(self, model_cfgyolov10n.yaml): self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.model self.load_model(model_cfg) def load_model(self, cfg_path): 加载YOLOv10模型 # YOLOv10模型加载逻辑 # 注意YOLOv10官方代码尚未完全开源此处为示例实现 pass def train(self, dataset, epochs100): 训练YOLOv10模型 # 实现YOLOv10特定的训练逻辑 # 包括双分配策略和一致性匹配 pass def post_training_optimization(self, model_path): 后训练优化 # YOLOv10特有的无NMS优化 pass6. PySide6图形界面实现6.1 主窗口设计pythonimport sys import cv2 from PySide6.QtWidgets import * from PySide6.QtCore import * from PySide6.QtGui import * import numpy as np from pathlib import Path class DetectionApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(行人车辆检测与计数系统) self.setGeometry(100, 100, 1400, 800) # 初始化变量 self.current_image None self.current_video None self.cap None self.timer QTimer() self.model None self.is_processing False # 统计信息 self.counts { person: 0, car: 0, bus: 0, truck: 0, total: 0 } self.init_ui() self.setup_connections() def init_ui(self): 初始化用户界面 # 中央部件 central_widget QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout QHBoxLayout(central_widget) # 左侧面板 - 图像显示 left_panel QFrame() left_panel.setFrameStyle(QFrame.Box | QFrame.Raised) left_layout QVBoxLayout(left_panel) # 图像显示区域 self.image_label QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet(border: 2px solid #cccccc; background-color: #f0f0f0;) self.image_label.setMinimumSize(800, 600) left_layout.addWidget(self.image_label) # 控制按钮区域 control_layout QHBoxLayout() self.btn_load_image QPushButton(加载图像) self.btn_load_video QPushButton(加载视频) self.btn_start_camera QPushButton(开启摄像头) self.btn_start_detect QPushButton(开始检测) self.btn_stop_detect QPushButton(停止检测) self.btn_export_results QPushButton(导出结果) for btn in [self.btn_load_image, self.btn_load_video, self.btn_start_camera, self.btn_start_detect, self.btn_stop_detect, self.btn_export_results]: btn.setFixedHeight(40) control_layout.addWidget(btn) left_layout.addLayout(control_layout) # 右侧面板 - 控制与显示 right_panel QFrame() right_panel.setFrameStyle(QFrame.Box | QFrame.Raised) right_panel.setFixedWidth(400) right_layout QVBoxLayout(right_panel) # 模型选择区域 model_group QGroupBox(模型选择) model_layout QVBoxLayout() self.combo_model QComboBox() self.combo_model.addItems([YOLOv5, YOLOv8, YOLOv10]) self.combo_model.setCurrentText(YOLOv8) self.combo_model_size QComboBox() self.combo_model_size.addItems([Nano (n), Small (s), Medium (m), Large (l), Extra Large (x)]) self.combo_model_size.setCurrentText(Medium (m)) self.btn_load_model QPushButton(加载模型) self.btn_load_model.setStyleSheet(background-color: #4CAF50; color: white;) model_layout.addWidget(QLabel(模型类型:)) model_layout.addWidget(self.combo_model) model_layout.addWidget(QLabel(模型大小:)) model_layout.addWidget(self.combo_model_size) model_layout.addWidget(self.btn_load_model) model_group.setLayout(model_layout) # 检测参数区域 param_group QGroupBox(检测参数) param_layout QFormLayout() self.slider_confidence QSlider(Qt.Horizontal) self.slider_confidence.setRange(10, 95) self.slider_confidence.setValue(50) self.label_confidence QLabel(0.50) self.slider_iou QSlider(Qt.Horizontal) self.slider_iou.setRange(10, 95) self.slider_iou.setValue(45) self.label_iou QLabel(0.45) param_layout.addRow(置信度阈值:, self.slider_confidence) param_layout.addRow(, self.label_confidence) param_layout.addRow(IoU阈值:, self.slider_iou) param_layout.addRow(, self.label_iou) param_group.setLayout(param_layout) # 计数结果显示区域 count_group QGroupBox(检测统计) count_layout QVBoxLayout() self.table_counts QTableWidget() self.table_counts.setColumnCount(2) self.table_counts.setHorizontalHeaderLabels([类别, 数量]) self.table_counts.setRowCount(5) for i, category in enumerate([person, car, bus, truck, total]): self.table_counts.setItem(i, 0, QTableWidgetItem(category)) self.table_counts.setItem(i, 1, QTableWidgetItem(0)) count_layout.addWidget(self.table_counts) # 实时数据显示 self.label_fps QLabel(FPS: 0.0) self.label_frame_count QLabel(帧数: 0) self.label_detection_time QLabel(检测时间: 0ms) count_layout.addWidget(self.label_fps) count_layout.addWidget(self.label_frame_count) count_layout.addWidget(self.label_detection_time) count_group.setLayout(count_layout) # 日志区域 log_group QGroupBox(运行日志) log_layout QVBoxLayout() self.text_log QTextEdit() self.text_log.setReadOnly(True) self.text_log.setMaximumHeight(150) log_layout.addWidget(self.text_log) log_group.setLayout(log_layout) # 将所有组件添加到右侧布局 right_layout.addWidget(model_group) right_layout.addWidget(param_group) right_layout.addWidget(count_group) right_layout.addWidget(log_group) right_layout.addStretch() # 将左右面板添加到主布局 main_layout.addWidget(left_panel) main_layout.addWidget(right_panel) def setup_connections(self): 设置信号与槽连接 self.btn_load_image.clicked.connect(self.load_image) self.btn_load_video.clicked.connect(self.load_video) self.btn_start_camera.clicked.connect(self.start_camera) self.btn_start_detect.clicked.connect(self.start_detection) self.btn_stop_detect.clicked.connect(self.stop_detection) self.btn_load_model.clicked.connect(self.load_model) self.btn_export_results.clicked.connect(self.export_results) self.slider_confidence.valueChanged.connect(self.update_confidence_label) self.slider_iou.valueChanged.connect(self.update_iou_label) self.timer.timeout.connect(self.process_frame) def load_model(self): 加载模型 model_type self.combo_model.currentText() model_size self.combo_model_size.currentText().split()[0].lower() try: if model_type YOLOv5: import torch model_path fyolov5{model_size}.pt self.model torch.hub.load(ultralytics/yolov5, custom, pathmodel_path, force_reloadTrue) elif model_type YOLOv8: from ultralytics import YOLO model_path fyolov8{model_size}.pt self.model YOLO(model_path) else: # YOLOv10 # YOLOv10模型加载逻辑 pass self.log_message(f✅ {model_type} 模型加载成功) except Exception as e: self.log_message(f❌ 模型加载失败: {str(e)}) def load_image(self): 加载图像文件 file_path, _ QFileDialog.getOpenFileName( self, 选择图像文件, , Image Files (*.png *.jpg *.jpeg *.bmp) ) if file_path: self.current_image cv2.imread(file_path) self.display_image(self.current_image) self.log_message(f 加载图像: {Path(file_path).name}) def load_video(self): 加载视频文件 file_path, _ QFileDialog.getOpenFileName( self, 选择视频文件, , Video Files (*.mp4 *.avi *.mov *.mkv) ) if file_path: self.current_video file_path self.cap cv2.VideoCapture(file_path) self.log_message(f 加载视频: {Path(file_path).name}) def start_camera(self): 开启摄像头 self.cap cv2.VideoCapture(0) if not self.cap.isOpened(): self.log_message(❌ 无法打开摄像头) return self.log_message( 摄像头已开启) def start_detection(self): 开始检测 if self.model is None: self.log_message(⚠️ 请先加载模型) return self.is_processing True self.reset_counts() if self.cap is not None: self.timer.start(30) # 30ms间隔约33FPS self.log_message( 开始实时检测) elif self.current_image is not None: self.process_single_image() def process_single_image(self): 处理单张图像 if self.current_image is not None and self.model is not None: start_time time.time() # 根据模型类型调用不同的检测方法 if isinstance(self.model, YOLO): # YOLOv8 results self.model(self.current_image, confself.slider_confidence.value()/100, iouself.slider_iou.value()/100) processed_img results[0].plot() boxes results[0].boxes else: # YOLOv5 results self.model(self.current_image) processed_img np.squeeze(results.render()) boxes results.xyxy[0] # 更新计数 self.update_counts(boxes) # 显示结果 self.display_image(processed_img) elapsed_time (time.time() - start_time) * 1000 self.label_detection_time.setText(f检测时间: {elapsed_time:.1f}ms) self.log_message(f✅ 图像检测完成耗时 {elapsed_time:.1f}ms) def process_frame(self): 处理视频帧 if self.cap is not None and self.cap.isOpened(): ret, frame self.cap.read() if ret: start_time time.time() # 检测 if isinstance(self.model, YOLO): # YOLOv8 results self.model(frame, confself.slider_confidence.value()/100, iouself.slider_iou.value()/100) processed_frame results[0].plot() boxes results[0].boxes else: # YOLOv5 results self.model(frame) processed_frame np.squeeze(results.render()) boxes results.xyxy[0] # 更新计数 self.update_counts(boxes) # 计算FPS elapsed_time (time.time() - start_time) * 1000 fps 1000 / elapsed_time if elapsed_time 0 else 0 # 在图像上显示FPS和计数 cv2.putText(processed_frame, fFPS: {fps:.1f}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) # 显示检测结果 self.display_image(processed_frame) # 更新UI显示 self.label_fps.setText(fFPS: {fps:.1f}) self.label_detection_time.setText(f检测时间: {elapsed_time:.1f}ms) def update_counts(self, boxes): 更新计数统计 # 重置计数 for key in self.counts: if key ! total: self.counts[key] 0 # 根据检测结果更新计数 if hasattr(boxes, cls): # YOLOv8格式 classes boxes.cls.cpu().numpy() if boxes.cls is not None else [] for cls in classes: cls_name self.model.names[int(cls)] if cls_name in self.counts: self.counts[cls_name] 1 elif boxes is not None and len(boxes) 0: # YOLOv5格式 for box in boxes: cls int(box[5]) cls_name self.model.names[cls] if cls_name in self.counts: self.counts[cls_name] 1 # 计算总数 self.counts[total] sum([self.counts[k] for k in [person, car, bus, truck]]) # 更新表格显示 for i, category in enumerate([person, car, bus, truck, total]): self.table_counts.item(i, 1).setText(str(self.counts[category])) def display_image(self, image): 显示图像到QLabel if image is not None: # 将OpenCV图像转换为Qt图像 height, width, channel image.shape bytes_per_line 3 * width qt_image QImage(image.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped() # 缩放图像以适应标签大小 scaled_image qt_image.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.image_label.setPixmap(QPixmap.fromImage(scaled_image)) def reset_counts(self): 重置计数 for key in self.counts: self.counts[key] 0 for i in range(self.table_counts.rowCount()): self.table_counts.item(i, 1).setText(0) def update_confidence_label(self, value): 更新置信度标签 self.label_confidence.setText(f{value/100:.2f}) def update_iou_label(self, value): 更新IoU标签 self.label_iou.setText(f{value/100:.2f}) def stop_detection(self): 停止检测 self.is_processing False self.timer.stop() if self.cap is not None: self.cap.release() self.cap None self.log_message(⏹️ 检测已停止) def export_results(self): 导出结果 if not any(self.counts.values()): self.log_message(⚠️ 没有检测结果可导出) return file_path, _ QFileDialog.getSaveFileName( self, 保存结果, detection_results.csv, CSV Files (*.csv) ) if file_path: import csv with open(file_path, w, newline) as f: writer csv.writer(f) writer.writerow([类别, 数量]) for category, count in self.counts.items(): writer.writerow([category, count]) self.log_message(f 结果已导出到: {file_path}) def log_message(self, message): 添加日志消息 timestamp QDateTime.currentDateTime().toString(hh:mm:ss) self.text_log.append(f[{timestamp}] {message}) def closeEvent(self, event): 关闭事件 self.stop_detection() event.accept() # 主程序入口 def main(): app QApplication(sys.argv) app.setStyle(Fusion) # 设置应用样式 app.setStyleSheet( QMainWindow { background-color: #f0f0f0; } QPushButton { background-color: #0078d7; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #005a9e; } QGroupBox { font-weight: bold; border: 2px solid #cccccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } ) window DetectionApp() window.show() sys.exit(app.exec()) if __name__ __main__: main()7. 模型性能对比与优化7.1 性能对比实验pythonimport pandas as pd import matplotlib.pyplot as plt import seaborn as sns class ModelComparator: def __init__(self, models_config): self.models_config models_config self.results [] def run_comparison(self, test_dataset): 运行模型对比测试 for model_name, model_info in self.models_config.items(): print(f\n正在测试 {model_name}...) # 加载模型 if model_name YOLOv5: model torch.hub.load(ultralytics/yolov5, custom, pathmodel_info[path]) elif model_name YOLOv8: from ultralytics import YOLO model YOLO(model_info[path]) elif model_name YOLOv10: # YOLOv10模型加载 pass # 测试模型性能 metrics self.evaluate_model(model, test_dataset) metrics[model] model_name self.results.append(metrics) return self.analyze_results() def evaluate_model(self, model, dataset): 评估模型性能 metrics { mAP50: 0, mAP50-95: 0, fps: 0, params: 0, gflops: 0 } # 实现评估逻辑 return metrics def analyze_results(self): 分析结果并生成报告 df pd.DataFrame(self.results) # 绘制性能对比图 fig, axes plt.subplots(2, 2, figsize(12, 10)) # mAP对比 ax1 axes[0, 0] df.plot(xmodel, y[mAP50, mAP50-95], kindbar, axax1) ax1.set_title(mAP对比) ax1.set_ylabel(mAP) # 速度对比 ax2 axes[0, 1] df.plot(xmodel, yfps, kindbar, colororange, axax2) ax2.set_title(FPS对比) ax2.set_ylabel(FPS) # 模型大小对比 ax3 axes[1, 0] df.plot(xmodel, yparams, kindbar, colorgreen, axax3) ax3.set_title(参数量对比 (百万)) ax3.set_ylabel(参数量) # 计算量对比 ax4 axes[1, 1] df.plot(xmodel, ygflops, kindbar, colorred, axax4) ax4.set_title(计算量对比 (GFLOPs)) ax4.set_ylabel(GFLOPs) plt.tight_layout() plt.savefig(model_comparison.png, dpi300) plt.show() return df