news 2026/4/6 18:29:31

基于YOLO系列的远距离停车位检测系统:从算法原理到完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLO系列的远距离停车位检测系统:从算法原理到完整实现

摘要

随着城市汽车保有量的快速增长,停车难问题日益凸显。传统的停车位检测方法通常依赖于近距离传感器或人工巡查,存在效率低、覆盖范围有限等问题。本文提出了一种基于YOLOv5/v6/v7/v8深度学习模型的远距离停车位检测系统,能够从高空视角或较远距离准确识别停车位占用状态。系统采用改进的YOLO算法,结合自定义UI界面和高效的数据处理流程,实现了对大面积停车场的实时监控。本文将详细阐述系统架构、算法优化、数据集构建、训练策略以及完整实现代码,为智慧城市建设提供技术参考。

1. 引言

1.1 研究背景

在城市交通管理领域,停车位检测是智能交通系统的重要组成部分。传统的停车检测方法包括地磁传感器、超声波传感器、摄像头定点监控等,但这些方法存在部署成本高、维护困难、覆盖范围有限等缺点。随着计算机视觉技术的发展,基于深度学习的视觉检测方法逐渐成为主流解决方案。

1.2 YOLO算法发展概述

YOLO(You Only Look Once)系列算法自2015年提出以来,经历了多个版本的迭代优化:

  • YOLOv5:Ultralytics公司推出的工业级实现,以易用性和高性能著称

  • YOLOv6:美团视觉智能部推出的专注于工业应用的目标检测框架

  • YOLOv7:在速度和精度上达到新的平衡,引入高效层聚合网络

  • YOLOv8:最新版本,提供更灵活的架构和更好的性能表现

1.3 系统创新点

本系统的创新之处在于:

  1. 远距离检测能力:优化网络结构以处理远距离、小目标检测

  2. 多版本YOLO支持:兼容主流YOLO版本,便于对比和选择

  3. 完整系统集成:包含数据预处理、模型训练、推理部署和UI界面

  4. 实际场景优化:针对停车场特定场景进行算法优化

2. 系统架构设计

2.1 整体架构

text

┌─────────────────────────────────────────────────────┐ │ 用户界面层 │ │ (PyQt5/Tkinter Web界面) │ ├─────────────────────────────────────────────────────┤ │ 应用服务层 │ │ (检测引擎、结果处理、数据存储) │ ├─────────────────────────────────────────────────────┤ │ 深度学习模型层 │ │ (YOLOv5/v6/v7/v8模型加载与推理) │ ├─────────────────────────────────────────────────────┤ │ 数据处理层 │ │ (图像预处理、数据增强、后处理) │ ├─────────────────────────────────────────────────────┤ │ 硬件支持层 │ │ (GPU加速、摄像头接入、云平台) │ └─────────────────────────────────────────────────────┘

2.2 技术栈

  • 深度学习框架:PyTorch 1.7+

  • 模型实现:YOLOv5/v6/v7/v8

  • 界面开发:PyQt5/Tkinter

  • 数据处理:OpenCV, Pillow, NumPy

  • 部署工具:ONNX, TensorRT (可选)

3. 数据集准备与处理

3.1 参考数据集

  1. PKLot数据集:包含约700,000张停车位图像,标注精细

  2. CNRPark+EXT数据集:意大利帕尔马大学提供,涵盖不同天气条件

  3. Smart Parking Dataset:包含不同角度和距离的停车场景

  4. 自定义数据集:针对特定场景采集的数据

3.2 数据标注规范

使用LabelImg或CVAT工具进行标注,类别包括:

  • occupied:车位被占用

  • vacant:空车位

  • partially_occupied:部分占用(如摩托车)

标注格式采用YOLO格式:

text

<class_id> <x_center> <y_center> <width> <height>

3.3 数据增强策略

针对远距离检测特点,采用以下增强方法:

python

import albumentations as A from albumentations.pytorch import ToTensorV2 def get_train_transforms(image_size=640): return A.Compose([ A.RandomResizedCrop(height=image_size, width=image_size, scale=(0.5, 1.0)), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.1), A.RandomBrightnessContrast(p=0.3), A.HueSaturationValue(p=0.3), A.GaussNoise(p=0.1), A.CLAHE(p=0.1), A.RandomGamma(p=0.1), A.ImageCompression(quality_lower=70, quality_upper=100, p=0.2), A.OneOf([ A.MotionBlur(p=0.2), A.MedianBlur(blur_limit=3, p=0.1), A.Blur(blur_limit=3, p=0.1), ], p=0.2), A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=10, p=0.5), ToTensorV2(p=1.0) ], bbox_params=A.BboxParams( format='yolo', label_fields=['class_labels'], min_visibility=0.3 )) def get_val_transforms(image_size=640): return A.Compose([ A.Resize(height=image_size, width=image_size), ToTensorV2(p=1.0) ], bbox_params=A.BboxParams( format='yolo', label_fields=['class_labels'] ))

4. YOLO模型实现与优化

4.1 YOLOv5/v6/v7/v8统一接口设计

python

import torch import numpy as np from pathlib import Path from typing import List, Tuple, Dict, Optional import cv2 class YOLOParkDetector: def __init__(self, model_type='yolov8', model_path=None, device='cuda'): """ 初始化YOLO停车检测器 Args: model_type: 模型类型 ('yolov5', 'yolov6', 'yolov7', 'yolov8') model_path: 模型权重路径 device: 运行设备 """ self.model_type = model_type self.device = device if torch.cuda.is_available() and device == 'cuda' else 'cpu' self.model = self._load_model(model_path) self.class_names = ['occupied', 'vacant', 'partially_occupied'] self.colors = [(0, 0, 255), (0, 255, 0), (255, 255, 0)] def _load_model(self, model_path): """加载YOLO模型""" if self.model_type == 'yolov5': return self._load_yolov5(model_path) elif self.model_type == 'yolov6': return self._load_yolov6(model_path) elif self.model_type == 'yolov7': return self._load_yolov7(model_path) elif self.model_type == 'yolov8': return self._load_yolov8(model_path) else: raise ValueError(f"Unsupported model type: {self.model_type}") def _load_yolov5(self, model_path): """加载YOLOv5模型""" from models.yolov5 import Model # 加载自定义YOLOv5模型 model = torch.hub.load('ultralytics/yolov5', 'custom', path=model_path, force_reload=True) model.to(self.device) model.eval() return model def _load_yolov8(self, model_path): """加载YOLOv8模型""" from ultralytics import YOLO model = YOLO(model_path) return model def preprocess(self, image: np.ndarray, img_size: int = 640): """图像预处理""" # 保持宽高比的resize h, w = image.shape[:2] scale = min(img_size / h, img_size / w) new_h, new_w = int(h * scale), int(w * scale) resized = cv2.resize(image, (new_w, new_h)) # 填充到正方形 padded = np.full((img_size, img_size, 3), 114, dtype=np.uint8) padded[:new_h, :new_w] = resized # 归一化 padded = padded.astype(np.float32) / 255.0 # 转换为torch tensor tensor = torch.from_numpy(padded).permute(2, 0, 1).unsqueeze(0) return tensor.to(self.device), (scale, (img_size - new_w) // 2, (img_size - new_h) // 2) def postprocess(self, predictions, orig_shape, params, conf_thresh=0.25, iou_thresh=0.45): """后处理:NMS和坐标转换""" scale, pad_x, pad_y = params if self.model_type == 'yolov8': # YOLOv8输出处理 boxes = predictions[0].boxes if len(boxes) == 0: return [] detections = [] for box in boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() conf = box.conf[0].cpu().numpy() cls = int(box.cls[0].cpu().numpy()) # 去除填充并还原到原始尺寸 x1 = max(0, (x1 - pad_x) / scale) y1 = max(0, (y1 - pad_y) / scale) x2 = min(orig_shape[1], (x2 - pad_x) / scale) y2 = min(orig_shape[0], (y2 - pad_y) / scale) if conf > conf_thresh: detections.append({ 'bbox': [x1, y1, x2, y2], 'confidence': float(conf), 'class_id': cls, 'class_name': self.class_names[cls] }) else: # 其他YOLO版本处理 # ... 具体实现 return detections def detect(self, image: np.ndarray, conf_thresh=0.25): """执行检测""" # 预处理 tensor, params = self.preprocess(image) # 推理 with torch.no_grad(): if self.model_type == 'yolov8': results = self.model(tensor) predictions = results else: predictions = self.model(tensor) # 后处理 detections = self.postprocess(predictions, image.shape[:2], params, conf_thresh) return detections

4.2 针对远距离检测的优化

4.2.1 小目标检测增强

python

class EnhancedYOLO(nn.Module): def __init__(self, base_model): super().__init__() self.base_model = base_model # 添加小目标检测头 self.small_object_head = nn.Sequential( nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512), nn.LeakyReLU(0.1), nn.Conv2d(512, 256, 3, padding=1), nn.BatchNorm2d(256), nn.LeakyReLU(0.1), nn.Conv2d(256, 3 * (5 + len(classes)), 1) # 3个anchor ) # 注意力机制 self.cbam = CBAM(512) def forward(self, x): features = self.base_model.backbone(x) # 应用注意力 enhanced_features = self.cbam(features[-1]) # 小目标检测 small_obj_out = self.small_object_head(enhanced_features) # 与原始输出融合 outputs = self.base_model.head(features) outputs.append(small_obj_out) return outputs class CBAM(nn.Module): """Convolutional Block Attention Module""" def __init__(self, channels, reduction=16): super().__init__() self.channel_attention = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels // reduction, 1), nn.ReLU(), nn.Conv2d(channels // reduction, channels, 1), nn.Sigmoid() ) self.spatial_attention = nn.Sequential( nn.Conv2d(2, 1, 7, padding=3), nn.Sigmoid() ) def forward(self, x): # 通道注意力 ca = self.channel_attention(x) x_ca = x * ca # 空间注意力 avg_pool = torch.mean(x_ca, dim=1, keepdim=True) max_pool = torch.max(x_ca, dim=1, keepdim=True)[0] concat = torch.cat([avg_pool, max_pool], dim=1) sa = self.spatial_attention(concat) x_sa = x_ca * sa return x_sa
4.2.2 多尺度特征融合

python

class MultiScaleFusion(nn.Module): """多尺度特征融合模块""" def __init__(self, in_channels): super().__init__() # 特征金字塔 self.lateral_convs = nn.ModuleList() self.fpn_convs = nn.ModuleList() for i in range(len(in_channels)): lateral_conv = nn.Conv2d(in_channels[i], 256, 1) fpn_conv = nn.Sequential( nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU() ) self.lateral_convs.append(lateral_conv) self.fpn_convs.append(fpn_conv) def forward(self, features): # 自底向上 laterals = [conv(f) for conv, f in zip(self.lateral_convs, features)] # 自顶向下 used_features = [laterals[-1]] for i in range(len(laterals)-2, -1, -1): lateral = laterals[i] top_down = F.interpolate( used_features[-1], size=lateral.shape[2:], mode='nearest' ) fused = lateral + top_down used_features.append(fused) used_features = used_features[::-1] # FPN输出 outputs = [] for i, (conv, feat) in enumerate(zip(self.fpn_convs, used_features)): outputs.append(conv(feat)) return outputs

5. 模型训练策略

5.1 训练配置

python

import yaml from torch.optim import AdamW, SGD from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR class ParkingTrainer: def __init__(self, config_path): with open(config_path, 'r') as f: self.config = yaml.safe_load(f) self.setup_hyperparameters() def setup_hyperparameters(self): """设置训练超参数""" self.hyperparams = { 'lr0': 0.01, # 初始学习率 'lrf': 0.01, # 最终学习率系数 'momentum': 0.937, 'weight_decay': 0.0005, 'warmup_epochs': 3, 'warmup_momentum': 0.8, 'warmup_bias_lr': 0.1, 'box': 0.05, # box损失权重 'cls': 0.5, # 分类损失权重 'cls_pw': 1.0, # 分类正样本权重 'obj': 1.0, # 目标损失权重 'obj_pw': 1.0, # 目标正样本权重 'iou_t': 0.20, # IoU阈值 'anchor_t': 4.0, # anchor阈值 'fl_gamma': 0.0, # Focal loss gamma } def create_optimizer(self, model): """创建优化器""" optimizer = AdamW( model.parameters(), lr=self.hyperparams['lr0'], weight_decay=self.hyperparams['weight_decay'] ) scheduler = CosineAnnealingLR( optimizer, T_max=self.config['epochs'], eta_min=self.hyperparams['lr0'] * self.hyperparams['lrf'] ) return optimizer, scheduler def compute_loss(self, predictions, targets): """计算损失函数""" # 回归损失 giou_loss = self.compute_giou_loss(predictions[..., :4], targets[..., :4]) # 置信度损失 obj_loss = self.compute_obj_loss(predictions[..., 4], targets[..., 4]) # 分类损失 cls_loss = self.compute_cls_loss(predictions[..., 5:], targets[..., 5]) total_loss = ( self.hyperparams['box'] * giou_loss + self.hyperparams['obj'] * obj_loss + self.hyperparams['cls'] * cls_loss ) return total_loss, {'giou': giou_loss, 'obj': obj_loss, 'cls': cls_loss} def compute_giou_loss(self, pred_boxes, target_boxes): """计算GIoU损失""" # GIoU实现 pass

5.2 训练脚本

python

import argparse import torch from torch.utils.data import DataLoader from tqdm import tqdm import wandb def train(model, train_loader, val_loader, config, device): """训练主函数""" # 初始化wandb if config.use_wandb: wandb.init(project="parking-detection", config=config) # 优化器 optimizer, scheduler = ParkingTrainer(config).create_optimizer(model) # 训练循环 best_map = 0.0 for epoch in range(config.epochs): # 训练阶段 model.train() train_loss = 0.0 progress_bar = tqdm(train_loader, desc=f'Epoch {epoch+1}/{config.epochs}') for batch_idx, (images, targets) in enumerate(progress_bar): images = images.to(device) targets = targets.to(device) # 前向传播 predictions = model(images) loss, loss_dict = trainer.compute_loss(predictions, targets) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() # 更新进度条 train_loss += loss.item() progress_bar.set_postfix({ 'loss': loss.item(), 'giou': loss_dict['giou'].item(), 'cls': loss_dict['cls'].item() }) # wandb记录 if config.use_wandb and batch_idx % config.log_interval == 0: wandb.log({ 'train/loss': loss.item(), 'train/giou_loss': loss_dict['giou'].item(), 'train/cls_loss': loss_dict['cls'].item(), 'lr': optimizer.param_groups[0]['lr'] }) # 学习率调整 scheduler.step() # 验证阶段 if (epoch + 1) % config.val_interval == 0: val_metrics = validate(model, val_loader, device, config) # 保存最佳模型 if val_metrics['mAP@0.5'] > best_map: best_map = val_metrics['mAP@0.5'] torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'best_map': best_map, 'config': config }, f'checkpoints/best_model.pth') # wandb记录验证指标 if config.use_wandb: wandb.log({ 'val/mAP': val_metrics['mAP@0.5'], 'val/mAP@0.5:0.95': val_metrics['mAP@0.5:0.95'], 'val/precision': val_metrics['precision'], 'val/recall': val_metrics['recall'] }) return model def validate(model, val_loader, device, config): """验证函数""" model.eval() all_predictions = [] all_targets = [] with torch.no_grad(): for images, targets in tqdm(val_loader, desc='Validating'): images = images.to(device) predictions = model(images) # 后处理 detections = non_max_suppression(predictions, conf_thres=config.conf_thres, iou_thres=config.iou_thres) all_predictions.extend(detections) all_targets.extend(targets) # 计算指标 metrics = compute_metrics(all_predictions, all_targets, config) return metrics

6. UI界面设计

6.1 PyQt5主界面

python

import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QComboBox, QSlider, QSpinBox, QGroupBox, QTextEdit, QTabWidget, QTableWidget, QTableWidgetItem) from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QThread from PyQt5.QtGui import QImage, QPixmap, QFont import cv2 import numpy as np class DetectionThread(QThread): """检测线程""" detection_done = pyqtSignal(list, np.ndarray) def __init__(self, detector, image): super().__init__() self.detector = detector self.image = image def run(self): detections = self.detector.detect(self.image) self.detection_done.emit(detections, self.image) class ParkingDetectionGUI(QMainWindow): def __init__(self, config): super().__init__() self.config = config self.detector = None self.current_image = None self.camera = None self.init_ui() self.init_detector() def init_ui(self): """初始化UI""" self.setWindowTitle("远距离停车位检测系统 v1.0") self.setGeometry(100, 100, 1400, 800) # 中心部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QHBoxLayout(central_widget) # 左侧面板 - 图像显示 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) # 图像显示区域 self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(800, 600) self.image_label.setStyleSheet("border: 2px solid gray;") left_layout.addWidget(self.image_label) # 控制按钮 control_layout = QHBoxLayout() self.load_btn = QPushButton("加载图像") self.load_btn.clicked.connect(self.load_image) self.camera_btn = QPushButton("开启摄像头") self.camera_btn.clicked.connect(self.toggle_camera) self.detect_btn = QPushButton("开始检测") self.detect_btn.clicked.connect(self.start_detection) self.export_btn = QPushButton("导出结果") self.export_btn.clicked.connect(self.export_results) control_layout.addWidget(self.load_btn) control_layout.addWidget(self.camera_btn) control_layout.addWidget(self.detect_btn) control_layout.addWidget(self.export_btn) left_layout.addLayout(control_layout) # 右侧面板 - 控制与信息 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) # 模型选择 model_group = QGroupBox("模型设置") model_layout = QVBoxLayout() self.model_combo = QComboBox() self.model_combo.addItems(["YOLOv5", "YOLOv6", "YOLOv7", "YOLOv8"]) self.model_combo.currentTextChanged.connect(self.change_model) self.confidence_slider = QSlider(Qt.Horizontal) self.confidence_slider.setRange(1, 99) self.confidence_slider.setValue(25) self.confidence_slider.valueChanged.connect(self.update_confidence) self.confidence_label = QLabel("置信度阈值: 0.25") model_layout.addWidget(QLabel("选择模型:")) model_layout.addWidget(self.model_combo) model_layout.addWidget(self.confidence_label) model_layout.addWidget(self.confidence_slider) model_group.setLayout(model_layout) right_layout.addWidget(model_group) # 检测结果统计 stats_group = QGroupBox("检测统计") stats_layout = QVBoxLayout() self.total_label = QLabel("总车位: 0") self.occupied_label = QLabel("占用: 0") self.vacant_label = QLabel("空闲: 0") self.occupancy_label = QLabel("占用率: 0%") stats_layout.addWidget(self.total_label) stats_layout.addWidget(self.occupied_label) stats_layout.addWidget(self.vacant_label) stats_layout.addWidget(self.occupancy_label) stats_group.setLayout(stats_layout) right_layout.addWidget(stats_group) # 详细信息表格 info_group = QGroupBox("检测详情") info_layout = QVBoxLayout() self.detection_table = QTableWidget() self.detection_table.setColumnCount(5) self.detection_table.setHorizontalHeaderLabels([ "ID", "类型", "置信度", "位置", "状态" ]) info_layout.addWidget(self.detection_table) info_group.setLayout(info_layout) right_layout.addWidget(info_group) # 日志区域 log_group = QGroupBox("系统日志") log_layout = QVBoxLayout() self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(150) log_layout.addWidget(self.log_text) log_group.setLayout(log_layout) right_layout.addWidget(log_group) # 添加到主布局 main_layout.addWidget(left_panel, 70) main_layout.addWidget(right_panel, 30) # 状态栏 self.statusBar().showMessage("就绪") # 定时器(用于摄像头) self.timer = QTimer() self.timer.timeout.connect(self.update_camera_frame) def init_detector(self): """初始化检测器""" model_type = self.model_combo.currentText().lower() model_path = f"models/{model_type}_parking.pt" try: self.detector = YOLOParkDetector( model_type=model_type, model_path=model_path, device='cuda' if torch.cuda.is_available() else 'cpu' ) 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, "选择图像", "", "图像文件 (*.jpg *.jpeg *.png *.bmp)" ) if file_path: self.current_image = cv2.imread(file_path) if self.current_image is not None: self.display_image(self.current_image) self.log_message(f"已加载图像: {file_path}") def display_image(self, image, detections=None): """显示图像和检测结果""" display_image = image.copy() if detections: for det in detections: bbox = det['bbox'] class_id = det['class_id'] confidence = det['confidence'] # 绘制边界框 color = self.detector.colors[class_id] cv2.rectangle(display_image, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), color, 2) # 添加标签 label = f"{det['class_name']} {confidence:.2f}" cv2.putText(display_image, label, (int(bbox[0]), int(bbox[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # 转换为Qt格式显示 h, w, ch = display_image.shape bytes_per_line = ch * w qt_image = QImage(display_image.data, w, h, bytes_per_line, QImage.Format_RGB888).rgbSwapped() self.image_label.setPixmap(QPixmap.fromImage(qt_image).scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def start_detection(self): """开始检测""" if self.current_image is None: self.log_message("请先加载图像") return if self.detector is None: self.log_message("检测器未初始化") return # 创建检测线程 self.detection_thread = DetectionThread(self.detector, self.current_image) self.detection_thread.detection_done.connect(self.on_detection_done) self.detection_thread.start() self.statusBar().showMessage("检测中...") def on_detection_done(self, detections, image): """检测完成回调""" self.display_image(image, detections) self.update_statistics(detections) self.update_detection_table(detections) self.statusBar().showMessage("检测完成") def update_statistics(self, detections): """更新统计信息""" total = len(detections) occupied = sum(1 for d in detections if d['class_name'] == 'occupied') vacant = sum(1 for d in detections if d['class_name'] == 'vacant') occupancy_rate = (occupied / total * 100) if total > 0 else 0 self.total_label.setText(f"总车位: {total}") self.occupied_label.setText(f"占用: {occupied}") self.vacant_label.setText(f"空闲: {vacant}") self.occupancy_label.setText(f"占用率: {occupancy_rate:.1f}%") def update_detection_table(self, detections): """更新检测表格""" self.detection_table.setRowCount(len(detections)) for i, det in enumerate(detections): bbox = det['bbox'] position = f"({bbox[0]:.0f}, {bbox[1]:.0f}, {bbox[2]:.0f}, {bbox[3]:.0f})" self.detection_table.setItem(i, 0, QTableWidgetItem(str(i+1))) self.detection_table.setItem(i, 1, QTableWidgetItem(det['class_name'])) self.detection_table.setItem(i, 2, QTableWidgetItem(f"{det['confidence']:.3f}")) self.detection_table.setItem(i, 3, QTableWidgetItem(position)) self.detection_table.setItem(i, 4, QTableWidgetItem( "已占用" if det['class_name'] == 'occupied' else "空闲")) def toggle_camera(self): """切换摄像头""" if self.camera is None: self.camera = cv2.VideoCapture(0) if self.camera.isOpened(): self.timer.start(30) # 30ms间隔 self.camera_btn.setText("关闭摄像头") self.log_message("摄像头已开启") else: self.timer.stop() self.camera.release() self.camera = None self.camera_btn.setText("开启摄像头") self.log_message("摄像头已关闭") def update_camera_frame(self): """更新摄像头帧""" if self.camera is not None: ret, frame = self.camera.read() if ret: self.current_image = frame # 实时检测(简化版) if self.detector and self.detect_btn.isChecked(): detections = self.detector.detect(frame) self.display_image(frame, detections) self.update_statistics(detections) else: self.display_image(frame) def log_message(self, message): """记录日志""" self.log_text.append(f"[{QDateTime.currentDateTime().toString('hh:mm:ss')}] {message}") def change_model(self, model_name): """切换模型""" self.init_detector() def update_confidence(self, value): """更新置信度阈值""" confidence = value / 100 self.confidence_label.setText(f"置信度阈值: {confidence:.2f}") if self.detector: self.detector.conf_thresh = confidence def export_results(self): """导出结果""" # 实现导出功能 pass def main(): app = QApplication(sys.argv) config = { 'model_path': 'models/', 'conf_thres': 0.25, 'iou_thres': 0.45 } window = ParkingDetectionGUI(config) window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()

6.2 Web界面版本(可选)

python

from flask import Flask, render_template, Response, jsonify, request import cv2 import numpy as np import json app = Flask(__name__) class WebParkingDetection: def __init__(self): self.detector = None self.camera = None def init_detector(self): """初始化检测器""" # 类似桌面版的实现 pass @app.route('/') def index(): return render_template('index.html') @app.route('/video_feed') def video_feed(): """视频流""" def generate(): camera = cv2.VideoCapture(0) while True: success, frame = camera.read() if not success: break else: # 检测处理 ret, buffer = cv2.imencode('.jpg', frame) frame = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/detect', methods=['POST']) def detect(): """检测API""" if 'image' not in request.files: return jsonify({'error': 'No image provided'}), 400 file = request.files['image'] image = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) # 执行检测 detections = detector.detect(image) return jsonify({ 'detections': detections, 'count': len(detections) }) if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)

7. 系统部署与优化

7.1 模型量化与加速

python

import onnx import onnxruntime as ort import tensorrt as trt class ModelOptimizer: def __init__(self, model_path): self.model_path = model_path def to_onnx(self, output_path, input_shape=(1, 3, 640, 640)): """转换为ONNX格式""" model = torch.load(self.model_path, map_location='cpu') model.eval() dummy_input = torch.randn(input_shape) torch.onnx.export( model, dummy_input, output_path, export_params=True, opset_version=11, do_constant_folding=True, input_names=['input'], output_names=['output'], dynamic_axes={ 'input': {0: 'batch_size'}, 'output': {0: 'batch_size'} } ) # 简化模型 onnx_model = onnx.load(output_path) onnx.checker.check_model(onnx_model) return output_path def optimize_with_tensorrt(self, onnx_path, output_path): """使用TensorRT优化""" logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) # 创建网络 network = builder.create_network( 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) # 解析ONNX parser = trt.OnnxParser(network, logger) with open(onnx_path, 'rb') as f: parser.parse(f.read()) # 配置 config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 构建引擎 serialized_engine = builder.build_serialized_network(network, config) with open(output_path, 'wb') as f: f.write(serialized_engine) return output_path def quantize_model(self, model, calibration_data): """模型量化""" model.eval() model.qconfig = torch.quantization.get_default_qconfig('fbgemm') # 准备量化 torch.quantization.prepare(model, inplace=True) # 校准 with torch.no_grad(): for data in calibration_data: model(data) # 转换 torch.quantization.convert(model, inplace=True) return model

7.2 性能评估

python

import time from sklearn.metrics import precision_recall_curve, average_precision_score import matplotlib.pyplot as plt class PerformanceEvaluator: def __init__(self, detector, test_dataset): self.detector = detector self.test_dataset = test_dataset def evaluate_speed(self, num_iterations=100): """评估推理速度""" times = [] for i in range(num_iterations): # 随机选择测试图像 idx = np.random.randint(len(self.test_dataset)) image, _ = self.test_dataset[idx] # 计时 start_time = time.perf_counter() _ = self.detector.detect(image) end_time = time.perf_counter() times.append(end_time - start_time) avg_time = np.mean(times) fps = 1.0 / avg_time return { 'average_inference_time': avg_time, 'fps': fps, 'min_time': np.min(times), 'max_time': np.max(times) } def evaluate_accuracy(self): """评估检测精度""" all_predictions = [] all_targets = [] for image, target in tqdm(self.test_dataset, desc='Evaluating'): predictions = self.detector.detect(image) # 转换为评估格式 pred_boxes = [p['bbox'] for p in predictions] pred_scores = [p['confidence'] for p in predictions] pred_labels = [p['class_id'] for p in predictions] all_predictions.append({ 'boxes': pred_boxes, 'scores': pred_scores, 'labels': pred_labels }) all_targets.append(target) # 计算mAP metrics = self.compute_map(all_predictions, all_targets) return metrics def compute_map(self, predictions, targets, iou_threshold=0.5): """计算mAP""" # 实现mAP计算逻辑 pass def plot_precision_recall(self, predictions, targets): """绘制PR曲线""" # 收集所有预测和真实值 all_pred_scores = [] all_pred_labels = [] all_true_labels = [] for pred, target in zip(predictions, targets): # 处理逻辑 # 计算每个类别的AP ap_per_class = {} for class_id in range(num_classes): precision, recall, _ = precision_recall_curve( all_true_labels == class_id, all_pred_scores[all_pred_labels == class_id] ) ap = average_precision_score( all_true_labels == class_id, all_pred_scores[all_pred_labels == class_id] ) ap_per_class[class_id] = ap # 绘制曲线 plt.plot(recall, precision, label=f'Class {class_id} (AP={ap:.3f})') plt.xlabel('Recall') plt.ylabel('Precision') plt.title('Precision-Recall Curve') plt.legend() plt.grid() return plt.gcf(), ap_per_class
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 12:18:28

Log-Lottery 3D球体抽奖应用:打造沉浸式年会抽奖体验终极指南

Log-Lottery 3D球体抽奖应用&#xff1a;打造沉浸式年会抽奖体验终极指南 【免费下载链接】log-lottery &#x1f388;&#x1f388;&#x1f388;&#x1f388;年会抽奖程序&#xff0c;threejsvue3 3D球体动态抽奖应用。 项目地址: https://gitcode.com/gh_mirrors/lo/log-…

作者头像 李华
网站建设 2026/4/3 12:20:12

Qwen命令行工具终极指南:从零开始快速掌握AI对话利器

Qwen命令行工具终极指南&#xff1a;从零开始快速掌握AI对话利器 【免费下载链接】Qwen The official repo of Qwen (通义千问) chat & pretrained large language model proposed by Alibaba Cloud. 项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen 你是否…

作者头像 李华
网站建设 2026/4/5 2:44:46

摆脱论文查重烦恼:7款热门AI降重平台实测效果对比分析

&#xfffd;&#xfffd; 论文查重工具核心特点对比 工具名称 查重速度 数据库覆盖 价格区间 适用场景 特色功能 AIcheck 极快 超全 中高 深度查重/学术规范检测 实时降重/AIGC检测 知网 中等 最全 高 终稿定稿查重 高校认可度高 维普 快 较全 中 中期查…

作者头像 李华
网站建设 2026/4/4 0:43:48

Windows虚拟显示器驱动终极清理指南:深度卸载与系统优化方案

Windows虚拟显示器驱动终极清理指南&#xff1a;深度卸载与系统优化方案 【免费下载链接】Virtual-Display-Driver Add virtual monitors to your windows 10/11 device! Works with VR, OBS, Sunshine, and/or any desktop sharing software. 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/4/2 21:55:42

PDFJS性能优化:加载速度提升300%的秘诀

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个优化版的PDFJS查看器&#xff0c;重点提升大文件处理性能。实现&#xff1a;1. 智能分片加载技术 2. 内存高效缓存机制 3. WebAssembly加速渲染 4. 自适应分辨率调整 5. 加…

作者头像 李华
网站建设 2026/4/4 0:41:02

用JSONPATH快速构建API响应过滤器

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个API响应过滤工具原型&#xff0c;允许用户&#xff1a;1)输入API端点URL 2)用JSONPATH指定要提取的数据结构 3)实时预览过滤结果 4)生成可直接使用的代码片段(Node.js/Pyt…

作者头像 李华