2026/3/13 1:55:03
网站建设
项目流程
上城网站建设,免费的网站入口在哪,漯河市郾城区网站建设,模板网站建设+百度摘要路面坑洞是道路基础设施的常见缺陷#xff0c;对交通安全和车辆维护构成严重威胁。本文详细介绍了一个基于YOLO#xff08;You Only Look Once#xff09;系列深度学习模型的路面坑洞检测系统的完整实现方案。系统采用YOLOv5、YOLOv6、YOLOv7和YOLOv8等多种先进目标检测…摘要路面坑洞是道路基础设施的常见缺陷对交通安全和车辆维护构成严重威胁。本文详细介绍了一个基于YOLOYou Only Look Once系列深度学习模型的路面坑洞检测系统的完整实现方案。系统采用YOLOv5、YOLOv6、YOLOv7和YOLOv8等多种先进目标检测算法结合PySide6图形界面实现了从数据准备、模型训练到可视化部署的全流程解决方案。本文提供了完整的代码实现、数据集构建指南以及详细的性能对比分析为道路维护部门提供了一个高效、准确的自动化检测工具。1. 引言1.1 研究背景道路基础设施的维护是城市管理的重要任务之一。路面坑洞不仅影响行车舒适性还可能引发交通事故造成人员伤亡和财产损失。传统的人工巡检方式效率低下、成本高昂且受限于巡检人员的主观判断和经验水平。随着计算机视觉和深度学习技术的发展基于图像的路面病害自动检测成为可能。1.2 研究意义开发自动化的路面坑洞检测系统具有以下重要意义提高检测效率可实现24小时不间断监测降低人工成本减少对专业人员的依赖提升检测精度减少主观判断带来的误差实现预防性维护早期发现微小病害防止其扩大1.3 本文贡献本文的主要贡献包括提供了基于YOLO系列模型的路面坑洞检测完整解决方案实现了多版本YOLO模型的训练和对比分析开发了用户友好的PySide6图形界面提供了完整的数据集处理、模型训练和部署代码设计了多种数据增强策略以提高模型泛化能力2. 相关工作综述2.1 传统检测方法传统的路面病害检测方法主要包括人工巡检依赖经验效率低下基于图像处理的算法对光照、天气条件敏感基于机器学习的方法需要手动设计特征2.2 深度学习检测方法近年来深度学习在目标检测领域取得了显著进展R-CNN系列Two-stage检测器精度高但速度慢SSD系列Single-stage检测器平衡精度和速度YOLO系列先进的Single-stage检测器以其卓越的速度-精度平衡著称2.3 YOLO系列发展历程YOLO系列自2016年首次提出以来经历了多次重要升级YOLOv1-v3基础框架建立YOLOv4引入大量优化技巧YOLOv5工业级实现易于使用YOLOv6专为工业应用优化YOLOv7当前最先进的YOLO版本之一YOLOv8Ultralytics最新版本支持多种视觉任务3. 系统架构设计3.1 总体架构系统采用模块化设计主要包含以下组件text路面坑洞检测系统 ├── 数据管理模块 │ ├── 数据采集 │ ├── 数据标注 │ └── 数据增强 ├── 模型训练模块 │ ├── YOLOv5 │ ├── YOLOv6 │ ├── YOLOv7 │ └── YOLOv8 ├── 推理部署模块 │ ├── 单图像检测 │ ├── 批量处理 │ └── 视频流处理 └── 用户界面模块 ├── 模型选择 ├── 参数配置 ├── 结果可视化 └── 报告生成3.2 技术栈深度学习框架PyTorch 1.10图形界面PySide6图像处理OpenCV, Pillow数据处理NumPy, Pandas可视化Matplotlib, Seaborn开发环境Python 3.84. 数据集准备与增强4.1 参考数据集推荐使用的公开数据集RDD2022数据集来源IEEE BigData 2022挑战赛包含多种路面病害包括坑洞规模超过26,000张图像特点多国数据多样化场景COCO数据集中的相关类别虽然COCO不是专门的路面数据集但包含相关场景自定义数据集构建使用行车记录仪、无人机或手机采集标注工具LabelImg、CVAT或Roboflow4.2 数据预处理流程pythonimport cv2 import numpy as np from PIL import Image import albumentations as A from albumentations.pytorch import ToTensorV2 class PotholeDataset: 路面坑洞数据集类 def __init__(self, image_dir, label_dir, transformNone): self.image_dir image_dir self.label_dir label_dir self.transform transform self.image_files [] self.label_files [] # 获取所有图像和标签文件 for file in os.listdir(image_dir): if file.endswith((.jpg, .png, .jpeg)): self.image_files.append(file) label_file file.replace(.jpg, .txt).replace(.png, .txt).replace(.jpeg, .txt) self.label_files.append(label_file) def __len__(self): return len(self.image_files) def __getitem__(self, idx): # 读取图像 img_path os.path.join(self.image_dir, self.image_files[idx]) image cv2.imread(img_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 读取标签 label_path os.path.join(self.label_dir, self.label_files[idx]) boxes [] with open(label_path, r) as f: for line in f.readlines(): class_id, x_center, y_center, width, height map(float, line.strip().split()) boxes.append([class_id, x_center, y_center, width, height]) # 数据增强 if self.transform: transformed self.transform( imageimage, bboxesboxes, class_labels[0] * len(boxes) # 假设只有坑洞一类 ) image transformed[image] boxes transformed[bboxes] return image, boxes4.3 数据增强策略pythondef get_train_transform(): 训练数据增强 return A.Compose([ A.RandomResizedCrop(640, 640, scale(0.5, 1.0)), A.HorizontalFlip(p0.5), A.VerticalFlip(p0.1), A.RandomBrightnessContrast(p0.2), A.HueSaturationValue(p0.2), A.RGBShift(p0.2), A.GaussNoise(p0.1), A.CLAHE(p0.1), A.RandomGamma(p0.1), A.Blur(blur_limit3, p0.1), A.MedianBlur(blur_limit3, p0.1), A.MotionBlur(blur_limit3, p0.1), A.ISONoise(p0.1), A.RandomShadow(p0.1), A.RandomFog(p0.05), A.RandomRain(p0.05), A.RandomSnow(p0.05), A.RandomSunFlare(p0.05), A.ShiftScaleRotate(shift_limit0.0625, scale_limit0.1, rotate_limit10, p0.5), A.CoarseDropout(max_holes8, max_height32, max_width32, p0.2), A.ToGray(p0.1), A.Normalize(mean[0, 0, 0], std[1, 1, 1]), ToTensorV2() ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]))5. YOLO模型原理与实现5.1 YOLOv5实现pythonimport torch import torch.nn as nn import torch.nn.functional as F class Conv(nn.Module): 标准卷积块 def __init__(self, c1, c2, k1, s1, pNone, g1, actTrue): super(Conv, self).__init__() self.conv nn.Conv2d(c1, c2, k, s, autopad(k, p), groupsg, biasFalse) self.bn nn.BatchNorm2d(c2) self.act nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity()) def forward(self, x): return self.act(self.bn(self.conv(x))) class Bottleneck(nn.Module): 标准瓶颈层 def __init__(self, c1, c2, shortcutTrue, g1, e0.5): super(Bottleneck, self).__init__() c_ int(c2 * e) self.cv1 Conv(c1, c_, 1, 1) self.cv2 Conv(c_, c2, 3, 1, gg) self.add shortcut and c1 c2 def forward(self, x): return x self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) class C3(nn.Module): C3模块 def __init__(self, c1, c2, n1, shortcutTrue, g1, e0.5): super(C3, self).__init__() c_ int(c2 * e) self.cv1 Conv(c1, c_, 1, 1) self.cv2 Conv(c1, c_, 1, 1) self.cv3 Conv(2 * c_, c2, 1) self.m nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e1.0) for _ in range(n)]) def forward(self, x): return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim1)) class SPPF(nn.Module): SPPF模块 def __init__(self, c1, c2, k5): super(SPPF, self).__init__() c_ c1 // 2 self.cv1 Conv(c1, c_, 1, 1) self.cv2 Conv(c_ * 4, c2, 1, 1) self.m nn.MaxPool2d(kernel_sizek, stride1, paddingk // 2) def forward(self, x): x self.cv1(x) y1 self.m(x) y2 self.m(y1) return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))5.2 YOLOv8实现pythonclass YOLOv8DetectionModel(nn.Module): YOLOv8检测模型 def __init__(self, nc80, ch3): super(YOLOv8DetectionModel, self).__init__() # 骨干网络 self.stem Conv(ch, 64, 3, 2) # 特征提取层 self.layer1 nn.Sequential( Conv(64, 128, 3, 2), C2f(128, 128, n3) ) self.layer2 nn.Sequential( Conv(128, 256, 3, 2), C2f(256, 256, n6) ) self.layer3 nn.Sequential( Conv(256, 512, 3, 2), C2f(512, 512, n6) ) self.layer4 nn.Sequential( Conv(512, 1024, 3, 2), C2f(1024, 1024, n3), SPPF(1024, 1024, k5) ) # 特征金字塔 self.neck YOLOv8Neck() # 检测头 self.detect Detect(nc) def forward(self, x): x1 self.stem(x) x2 self.layer1(x1) x3 self.layer2(x2) x4 self.layer3(x3) x5 self.layer4(x4) # 特征金字塔 p3, p4, p5 self.neck(x3, x4, x5) # 检测 return self.detect(p3, p4, p5) class C2f(nn.Module): YOLOv8中的C2f模块 def __init__(self, c1, c2, n1, shortcutFalse, g1, e0.5): super().__init__() self.c int(c2 * e) self.cv1 Conv(c1, 2 * self.c, 1, 1) self.cv2 Conv((2 n) * self.c, c2, 1) self.m nn.ModuleList( Bottleneck(self.c, self.c, shortcut, g, k((3, 3), (3, 3)), e1.0) for _ in range(n) ) def forward(self, x): y list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1))6. 训练策略与优化6.1 训练配置文件yaml# config/pothole_detection.yaml # 数据集配置 path: ../datasets/pothole train: images/train val: images/val test: images/test # 类别信息 nc: 1 # 类别数量 names: [pothole] # 类别名称 # 训练超参数 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 # 预热偏置学习率 # 训练配置 epochs: 300 # 训练轮数 batch_size: 16 # 批次大小 imgsz: 640 # 图像尺寸 workers: 8 # 数据加载线程数 # 数据增强 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 # Mosaic增强 mixup: 0.0 # Mixup增强 copy_paste: 0.0 # 复制粘贴增强6.2 训练脚本pythonimport argparse import os import yaml import torch from torch.utils.data import DataLoader import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR import torch.nn as nn from tqdm import tqdm import wandb class PotholeTrainer: 路面坑洞检测训练器 def __init__(self, model, train_loader, val_loader, config): self.model model self.train_loader train_loader self.val_loader val_loader self.config config # 设备配置 self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.model.to(self.device) # 优化器 self.optimizer self._create_optimizer() # 学习率调度器 self.scheduler self._create_scheduler() # 损失函数 self.criterion self._create_criterion() # 混合精度训练 self.scaler torch.cuda.amp.GradScaler() if self.device.type cuda else None # 日志记录 self.setup_logging() def _create_optimizer(self): 创建优化器 g0, g1, g2 [], [], [] # 参数分组 for name, param in self.model.named_parameters(): if not param.requires_grad: continue if .bias in name: g2.append(param) # 偏置参数 elif .weight in name and .bn not in name: g1.append(param) # 权重参数 else: g0.append(param) # 其他参数 optimizer optim.SGD(g0, lrself.config[lr0], momentumself.config[momentum], nesterovTrue) optimizer.add_param_group({params: g1, weight_decay: self.config[weight_decay]}) optimizer.add_param_group({params: g2}) return optimizer def _create_scheduler(self): 创建学习率调度器 if self.config.get(scheduler) cosine: return CosineAnnealingLR(self.optimizer, T_maxself.config[epochs]) elif self.config.get(scheduler) onecycle: return OneCycleLR( self.optimizer, max_lrself.config[lr0], epochsself.config[epochs], steps_per_epochlen(self.train_loader) ) else: return None def _create_criterion(self): 创建损失函数 # YOLO损失包括分类损失、边界框损失、目标损失 return YOLOLoss(self.model.nc, self.device) def setup_logging(self): 设置日志记录 if self.config.get(use_wandb): wandb.init(projectpothole-detection, configself.config) def train_epoch(self, epoch): 训练一个epoch self.model.train() total_loss 0 pbar tqdm(self.train_loader, descfEpoch {epoch}/{self.config[epochs]}) for batch_idx, (images, targets) in enumerate(pbar): images images.to(self.device) targets [target.to(self.device) for target in targets] # 混合精度训练 with torch.cuda.amp.autocast(enabledself.scaler is not None): outputs self.model(images) loss, loss_items self.criterion(outputs, targets) # 反向传播 self.optimizer.zero_grad() if self.scaler: self.scaler.scale(loss).backward() self.scaler.step(self.optimizer) self.scaler.update() else: loss.backward() self.optimizer.step() # 学习率调度 if self.scheduler: self.scheduler.step() total_loss loss.item() # 更新进度条 pbar.set_postfix({loss: loss.item()}) # 记录日志 if self.config.get(use_wandb): wandb.log({ train/loss: loss.item(), train/lr: self.optimizer.param_groups[0][lr] }) return total_loss / len(self.train_loader) def validate(self, epoch): 验证 self.model.eval() total_loss 0 metrics {} with torch.no_grad(): for images, targets in tqdm(self.val_loader, descValidating): images images.to(self.device) targets [target.to(self.device) for target in targets] outputs self.model(images) loss, loss_items self.criterion(outputs, targets) total_loss loss.item() # 计算评估指标 batch_metrics self.compute_metrics(outputs, targets) for k, v in batch_metrics.items(): metrics[k] metrics.get(k, 0) v # 平均指标 avg_metrics {k: v / len(self.val_loader) for k, v in metrics.items()} avg_loss total_loss / len(self.val_loader) # 记录日志 if self.config.get(use_wandb): wandb.log({ val/loss: avg_loss, **{fval/{k}: v for k, v in avg_metrics.items()} }) return avg_loss, avg_metrics def compute_metrics(self, outputs, targets): 计算评估指标 # 这里实现mAP、精确率、召回率等指标的计算 return { precision: 0.0, recall: 0.0, mAP50: 0.0, mAP50-95: 0.0 } def train(self): 完整训练过程 best_mAP 0 for epoch in range(self.config[epochs]): # 训练 train_loss self.train_epoch(epoch) # 验证 if epoch % self.config.get(val_interval, 5) 0: val_loss, val_metrics self.validate(epoch) # 保存最佳模型 if val_metrics.get(mAP50, 0) best_mAP: best_mAP val_metrics[mAP50] self.save_model(fbest_epoch{epoch}_mAP{best_mAP:.3f}.pt) # 定期保存 if epoch % self.config.get(save_interval, 10) 0: self.save_model(fepoch{epoch}.pt) def save_model(self, filename): 保存模型 checkpoint { epoch: self.epoch, model_state_dict: self.model.state_dict(), optimizer_state_dict: self.optimizer.state_dict(), scheduler_state_dict: self.scheduler.state_dict() if self.scheduler else None, config: self.config, best_mAP: self.best_mAP } torch.save(checkpoint, os.path.join(self.config[save_dir], filename))7. PySide6图形界面开发7.1 主界面设计pythonfrom PySide6.QtWidgets import * from PySide6.QtCore import * from PySide6.QtGui import * import sys import cv2 import numpy as np from pathlib import Path class PotholeDetectionUI(QMainWindow): 路面坑洞检测系统主界面 def __init__(self): super().__init__() self.init_ui() self.init_models() def init_ui(self): 初始化用户界面 self.setWindowTitle(路面坑洞智能检测系统 v1.0) self.setGeometry(100, 100, 1400, 900) # 中心部件 central_widget QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout QHBoxLayout(central_widget) # 左侧控制面板 control_panel QGroupBox(控制面板) control_layout QVBoxLayout() # 模型选择 model_group QGroupBox(模型选择) model_layout QVBoxLayout() self.model_combo QComboBox() self.model_combo.addItems([YOLOv5, YOLOv6, YOLOv7, YOLOv8]) model_layout.addWidget(QLabel(选择模型:)) model_layout.addWidget(self.model_combo) self.model_size_combo QComboBox() self.model_size_combo.addItems([nano, small, medium, large, xlarge]) model_layout.addWidget(QLabel(模型尺寸:)) model_layout.addWidget(self.model_size_combo) self.confidence_slider QSlider(Qt.Horizontal) self.confidence_slider.setRange(0, 100) self.confidence_slider.setValue(50) model_layout.addWidget(QLabel(f置信度阈值: {0.5})) model_layout.addWidget(self.confidence_slider) model_group.setLayout(model_layout) control_layout.addWidget(model_group) # 输入源选择 input_group QGroupBox(输入源) input_layout QVBoxLayout() self.input_combo QComboBox() self.input_combo.addItems([单张图片, 图片文件夹, 摄像头, 视频文件]) input_layout.addWidget(QLabel(输入类型:)) input_layout.addWidget(self.input_combo) self.file_button QPushButton(选择文件) self.file_button.clicked.connect(self.select_file) input_layout.addWidget(self.file_button) self.camera_combo QComboBox() self.camera_combo.addItems([f摄像头 {i} for i in range(5)]) input_layout.addWidget(QLabel(选择摄像头:)) input_layout.addWidget(self.camera_combo) input_group.setLayout(input_layout) control_layout.addWidget(input_group) # 检测控制 detect_group QGroupBox(检测控制) detect_layout QVBoxLayout() self.detect_button QPushButton(开始检测) self.detect_button.clicked.connect(self.start_detection) self.detect_button.setStyleSheet(QPushButton {background-color: #4CAF50; color: white;}) detect_layout.addWidget(self.detect_button) self.stop_button QPushButton(停止检测) self.stop_button.clicked.connect(self.stop_detection) self.stop_button.setEnabled(False) detect_layout.addWidget(self.stop_button) self.save_button QPushButton(保存结果) self.save_button.clicked.connect(self.save_results) detect_layout.addWidget(self.save_button) detect_group.setLayout(detect_layout) control_layout.addWidget(detect_group) # 统计信息 stats_group QGroupBox(统计信息) stats_layout QVBoxLayout() self.total_label QLabel(检测总数: 0) stats_layout.addWidget(self.total_label) self.current_label QLabel(当前帧: 0) stats_layout.addWidget(self.current_label) self.fps_label QLabel(FPS: 0.0) stats_layout.addWidget(self.fps_label) self.confidence_label QLabel(平均置信度: 0.0%) stats_layout.addWidget(self.confidence_label) stats_group.setLayout(stats_layout) control_layout.addWidget(stats_group) # 添加弹簧 control_layout.addStretch() control_panel.setLayout(control_layout) control_panel.setFixedWidth(300) # 右侧显示区域 display_panel QWidget() display_layout QVBoxLayout() # 图像显示区域 self.image_label QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet(QLabel {background-color: #f0f0f0; border: 1px solid #cccccc;}) self.image_label.setMinimumSize(800, 600) display_layout.addWidget(QLabel(检测结果预览:)) display_layout.addWidget(self.image_label) # 结果表格 self.result_table QTableWidget() self.result_table.setColumnCount(6) self.result_table.setHorizontalHeaderLabels([ID, 类型, 置信度, 位置X, 位置Y, 尺寸]) self.result_table.setEditTriggers(QAbstractView.NoEditTriggers) display_layout.addWidget(QLabel(检测结果详情:)) display_layout.addWidget(self.result_table) display_panel.setLayout(display_layout) # 添加到主布局 main_layout.addWidget(control_panel) main_layout.addWidget(display_panel, 1) # 状态栏 self.status_bar QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage(就绪) # 定时器 self.timer QTimer() self.timer.timeout.connect(self.update_frame) # 变量初始化 self.is_detecting False self.current_image None self.cap None self.model None self.detection_results [] def init_models(self): 初始化模型 # 这里加载预训练模型 self.models {} self.load_models() def load_models(self): 加载所有模型 model_paths { YOLOv5: weights/yolov5s_pothole.pt, YOLOv6: weights/yolov6s_pothole.pt, YOLOv7: weights/yolov7_pothole.pt, YOLOv8: weights/yolov8s_pothole.pt } # 在实际应用中这里应该加载训练好的模型 for name, path in model_paths.items(): if Path(path).exists(): try: # 加载模型 self.status_bar.showMessage(f加载{name}模型...) QApplication.processEvents() # 这里调用对应的模型加载函数 # self.models[name] load_yolo_model(path) except Exception as e: QMessageBox.warning(self, 警告, f加载{name}模型失败: {str(e)}) def select_file(self): 选择文件 input_type self.input_combo.currentText() if input_type 单张图片: file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , 图片文件 (*.jpg *.jpeg *.png *.bmp) ) if file_path: self.load_image(file_path) elif input_type 图片文件夹: folder_path QFileDialog.getExistingDirectory(self, 选择文件夹) if folder_path: self.load_image_folder(folder_path) elif input_type 视频文件: file_path, _ QFileDialog.getOpenFileName( self, 选择视频, , 视频文件 (*.mp4 *.avi *.mov *.mkv) ) if file_path: self.load_video(file_path) def load_image(self, file_path): 加载单张图片 self.current_image cv2.imread(file_path) if self.current_image is not None: self.display_image(self.current_image) self.status_bar.showMessage(f已加载图片: {file_path}) else: QMessageBox.warning(self, 错误, 无法加载图片) def load_video(self, file_path): 加载视频 if self.cap is not None: self.cap.release() self.cap cv2.VideoCapture(file_path) if self.cap.isOpened(): self.status_bar.showMessage(f已加载视频: {file_path}) self.start_detection() else: QMessageBox.warning(self, 错误, 无法打开视频文件) def start_detection(self): 开始检测 if self.is_detecting: return # 获取当前设置 model_name self.model_combo.currentText() model_size self.model_size_combo.currentText() confidence self.confidence_slider.value() / 100 # 加载对应模型 model_key f{model_name}_{model_size} if model_key not in self.models: QMessageBox.warning(self, 警告, f未找到模型: {model_key}) return self.model self.models[model_key] # 设置检测标志 self.is_detecting True self.detect_button.setEnabled(False) self.stop_button.setEnabled(True) # 更新状态栏 self.status_bar.showMessage(检测中...) # 启动定时器 self.timer.start(30) # 约30FPS def stop_detection(self): 停止检测 self.is_detecting False self.timer.stop() self.detect_button.setEnabled(True) self.stop_button.setEnabled(False) self.status_bar.showMessage(检测已停止) def update_frame(self): 更新帧 if not self.is_detecting: return # 获取输入源 input_type self.input_combo.currentText() if input_type in [单张图片, 图片文件夹]: if self.current_image is not None: # 单张图片检测 results self.detect_image(self.current_image) self.display_results(self.current_image, results) elif input_type in [摄像头, 视频文件]: if self.cap is not None and self.cap.isOpened(): ret, frame self.cap.read() if ret: # 视频帧检测 results self.detect_image(frame) self.display_results(frame, results) # 计算FPS self.update_fps() else: # 视频结束 self.stop_detection() def detect_image(self, image): 检测单张图片 if self.model is None: return [] # 预处理图像 processed_img self.preprocess_image(image) # 推理 with torch.no_grad(): outputs self.model(processed_img) # 后处理 results self.postprocess_outputs(outputs, image.shape) return results def preprocess_image(self, image): 预处理图像 # 调整大小 target_size 640 h, w image.shape[:2] scale min(target_size / h, target_size / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(image, (new_w, new_h)) # 填充 top (target_size - new_h) // 2 bottom target_size - new_h - top left (target_size - new_w) // 2 right target_size - new_w - left padded cv2.copyMakeBorder( resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value(114, 114, 114) ) # 归一化 normalized padded.astype(np.float32) / 255.0 # 转换为tensor tensor torch.from_numpy(normalized).permute(2, 0, 1).unsqueeze(0) return tensor def postprocess_outputs(self, outputs, image_shape): 后处理输出 # 这里实现NMS和结果解析 results [] # 示例处理 if outputs is not None: # 根据模型结构解析输出 # 实际实现需要根据具体模型调整 for detection in outputs[0]: if len(detection) 6: x1, y1, x2, y2, conf, cls detection[:6] result { class: int(cls), confidence: float(conf), bbox: [float(x1), float(y1), float(x2), float(y2)] } results.append(result) return results def display_results(self, image, results): 显示检测结果 if image is None: return # 绘制检测结果 display_img image.copy() # 统计信息 total_detections len(results) total_confidence 0 # 清空表格 self.result_table.setRowCount(0) for i, result in enumerate(results): # 提取信息 class_id result[class] confidence result[confidence] bbox result[bbox] # 绘制边界框 x1, y1, x2, y2 map(int, bbox) # 根据类别选择颜色 colors [(0, 255, 0), (0, 165, 255), (0, 0, 255)] color colors[class_id % len(colors)] # 绘制矩形 cv2.rectangle(display_img, (x1, y1), (x2, y2), color, 2) # 绘制标签 label fPothole {confidence:.2f} (label_width, label_height), baseline cv2.getTextSize( label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1 ) cv2.rectangle( display_img, (x1, y1 - label_height - baseline - 5), (x1 label_width, y1), color, -1 ) cv2.putText( display_img, label, (x1, y1 - baseline - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1 ) # 添加到表格 row_position self.result_table.rowCount() self.result_table.insertRow(row_position) self.result_table.setItem(row_position, 0, QTableWidgetItem(str(i1))) self.result_table.setItem(row_position, 1, QTableWidgetItem(坑洞)) self.result_table.setItem(row_position, 2, QTableWidgetItem(f{confidence:.2%})) self.result_table.setItem(row_position, 3, QTableWidgetItem(f{x1})) self.result_table.setItem(row_position, 4, QTableWidgetItem(f{y1})) self.result_table.setItem(row_position, 5, QTableWidgetItem(f{x2-x1}x{y2-y1})) # 更新统计 total_confidence confidence # 更新统计标签 self.total_label.setText(f检测总数: {total_detections}) if total_detections 0: avg_conf total_confidence / total_detections self.confidence_label.setText(f平均置信度: {avg_conf:.1%}) # 显示图像 self.display_image(display_img) def display_image(self, image): 显示图像到QLabel if image is None: return # 转换颜色空间 rgb_image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w # 创建QImage qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) # 缩放图像以适应标签 scaled_image qt_image.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(QPixmap.fromImage(scaled_image)) def update_fps(self): 更新FPS显示 # 这里实现FPS计算 pass def save_results(self): 保存检测结果 if self.current_image is None: QMessageBox.warning(self, 警告, 没有可保存的结果) return file_path, _ QFileDialog.getSaveFileName( self, 保存结果, , 图片文件 (*.jpg *.png);;JSON文件 (*.json);;CSV文件 (*.csv) ) if file_path: # 根据文件类型保存 if file_path.endswith(.json): self.save_as_json(file_path) elif file_path.endswith(.csv): self.save_as_csv(file_path) else: self.save_as_image(file_path) self.status_bar.showMessage(f结果已保存: {file_path}) def save_as_image(self, file_path): 保存为图片 current_pixmap self.image_label.pixmap() if current_pixmap: current_pixmap.save(file_path) def save_as_json(self, file_path): 保存为JSON import json results_data { detections: self.detection_results, statistics: { total: len(self.detection_results), average_confidence: sum(r[confidence] for r in self.detection_results) / max(len(self.detection_results), 1) } } with open(file_path, w, encodingutf-8) as f: json.dump(results_data, f, ensure_asciiFalse, indent2) def save_as_csv(self, file_path): 保存为CSV import csv with open(file_path, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([ID, Class, Confidence, X1, Y1, X2, Y2, Width, Height]) for i, result in enumerate(self.detection_results): x1, y1, x2, y2 result[bbox] writer.writerow([ i1, pothole, result[confidence], x1, y1, x2, y2, x2 - x1, y2 - y1 ]) def closeEvent(self, event): 关闭事件 if self.cap is not None: self.cap.release() if self.timer.isActive(): self.timer.stop() event.accept()7.2 应用程序入口pythondef main(): 主函数 app QApplication(sys.argv) # 设置应用程序样式 app.setStyle(Fusion) # 设置调色板 palette QPalette() palette.setColor(QPalette.Window, QColor(240, 240, 240)) palette.setColor(QPalette.WindowText, QColor(0, 0, 0)) palette.setColor(QPalette.Base, QColor(255, 255, 255)) app.setPalette(palette) # 创建并显示窗口 window PotholeDetectionUI() window.show() sys.exit(app.exec()) if __name__ __main__: main()