资讯详情

基于YOLO11与PyQt5的道路裂缝检测实战:从数据标注到界面部署

📅 2026/10/11 21:26:20 | 华诺云谱 👁 阅读
基于YOLO11与PyQt5的道路裂缝检测实战:从数据标注到界面部署
简介这是一套基于YOLO11深度学习框架的道路裂缝检测系统面向计算机、人工智能、自动化等专业的在校学生、教师及企业开发者可用于毕业设计、课程设计、实战项目演示或算法进阶学习。系统集成PyQt5图形界面支持单类别裂缝识别配套370余张已标注数据集、训练好的模型权重、评估指标曲线以及演示图片与视频开箱即可运行验证。资源包共825个文件以jpg图像、txt标注、png曲线图、pt模型权重、py源码及yaml配置为主另有ui界面文件与qrc资源文件压缩包约386.87MB目录结构清晰便于按模块查阅与二次开发。目前已有186人学习关注。读者可获得完整可复现的检测方案、界面交互源码、训练评估流程与排错参考既能直接用于毕设课设也可在此基础上修改以扩展其他缺陷检测功能。1. 道路裂缝检测这套 YOLO11 方案为什么值得你花一个周末跑通市政巡检的朋友给我看过一段行车记录仪视频三公里路面肉眼能数出十几条明显裂缝但人工逐帧标注要花掉整整两天。更麻烦的是同一段路半年后再拍裂缝宽度变化全靠记忆比对没有量化依据。这就是道路裂缝检测要解决的真实问题——把「看着像裂了」变成「这条裂缝在哪个桩号、多宽、什么走向」。YOLO11 在这里的价值不是刷榜而是它把检测框和分割掩码统一在一个轻量模型里配合 PyQt5 做本地 GUI巡检车上的工控机就能离线跑。这套方案适合三类人做智慧交通落地的算法工程师、需要快速出 demo 的在校研究生、以及想找一个完整深度学习项目练手的 Python 开发者。370 多张标注数据不算多但足够把「数据标注→训练→评估→界面集成」这条链路完整走一遍开箱即用的意义在于你不用从零搭环境而是把精力花在理解每个环节为什么这么设。2. YOLO11 检测裂缝的底层逻辑与数据准备2.1 YOLO11 相比前代在裂缝场景的取舍YOLO11 的网络结构延续了 CSPDarknet 主干加 PAN-FPN neck 的设计但把 C3k2 模块替换了部分 C2f在保持感受野的同时减少了参数量。对裂缝检测来说这个改动很关键裂缝是细长目标宽高比经常超过 10:1如果下采样太激进浅层特征里的边缘信息会在 neck 融合时被稀释。YOLO11 的 SPPF 层放在主干末端用三个不同尺度的最大池化串联能保留更多高频细节。我一般会把输入尺寸设成 640×640但裂缝数据集里如果长裂缝占比高可以试 640×960 的非方形输入代价是推理速度下降约 30%。另一个容易忽略的点是锚框YOLO11 默认用 TaskAlignedAssigner 做动态标签分配不再依赖固定锚框这对裂缝这种形态多变的目标更友好省去了聚类锚框的步骤。2.2 370 张标注数据的组织与增强策略拿到数据集先别急着训练用脚本统计一下标注框的宽高分布。裂缝数据常见的问题是负样本太少模型容易把路面接缝、阴影误判成裂缝。我的做法是从标注文件里提取所有框的宽高比如果 80% 以上集中在 5:1 到 15:1 之间说明数据分布合理如果出现大量接近 1:1 的框大概率是标注时把块状破损也标成了裂缝需要人工复核。import os import yaml from pathlib import Path from collections import Counter # 统计 YOLO 格式标注框的宽高比分布 def analyze_bbox_ratio(label_dir): ratios [] for txt_file in Path(label_dir).glob(*.txt): with open(txt_file, r) as f: for line in f: parts line.strip().split() if len(parts) 5: continue # YOLO 格式: class x_center y_center width height (归一化) w, h float(parts[3]), float(parts[4]) if h 0: ratios.append(round(w / h, 2)) # 按区间统计 bins Counter() for r in ratios: if r 1: bins[1] 1 elif r 3: bins[1-3] 1 elif r 5: bins[3-5] 1 elif r 10: bins[5-10] 1 else: bins[10] 1 return bins result analyze_bbox_ratio(./dataset/labels/train) print(宽高比分布:, dict(result))这段代码遍历训练集标签把宽高比分成五个区间。如果10的占比超过 40%说明长裂缝样本充足可以适当降低马赛克增强的概率因为马赛克会把长裂缝切碎反而破坏形态。数据增强我通常用 Albumentations 做在线增强随机水平翻转、亮度对比度扰动、轻微透视变换。注意不要用垂直翻转路面裂缝的走向有方向性垂直翻转会造出不存在的样本。370 张图按 8:1:1 划分训练、验证、测试训练集约 296 张配合增强后等效样本量能到 1500 左右对小数据集来说够用。2.3 从零配置训练环境的命令清单环境配置是新手翻车最多的地方。PyTorch 版本和 CUDA 驱动不匹配、ultralytics 装成了旧版、OpenCV 冲突这三个问题占了报错的八成。我一般用 conda 建独立环境避免和系统 Python 打架。# 创建并激活环境 conda create -n road_crack python3.10 -y conda activate road_crack # 安装 PyTorch以 CUDA 11.8 为例具体版本按显卡驱动选 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics 和界面依赖 pip install ultralytics8.3.0 pip install pyqt55.15.9 opencv-python4.9.0.80 # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))torch.cuda.is_available()返回 True 才说明 GPU 可用。如果返回 False先检查显卡驱动版本再确认安装的 PyTorch 是不是 CPU 版。ultralytics 版本建议锁在 8.3.xYOLO11 的官方支持从 8.3.0 开始低于这个版本会提示找不到模型配置。PyQt5 用 5.15.9 是因为更高版本在部分 Windows 系统上和高分屏缩放有兼容问题界面控件会模糊。3. 训练、评估与模型导出把 mAP 从 0.6 拉到 0.853.1 训练脚本的关键参数怎么设YOLO11 的训练入口很简洁但参数设错会导致 loss 震荡或者过拟合。我拿这套裂缝数据跑过几轮下面这组参数在 370 张图上比较稳。from ultralytics import YOLO # 加载 YOLO11n 预训练权重n 版参数量最小适合小数据集 model YOLO(yolo11n.pt) results model.train( data./dataset/crack.yaml, # 数据集配置文件路径 epochs150, # 训练轮数小数据集建议 100-200 imgsz640, # 输入尺寸长裂缝多可改 960 batch8, # 显存 8G 以下用 812G 以上可到 16 lr00.01, # 初始学习率YOLO11 默认 0.01 lrf0.01, # 最终学习率因子余弦退火到 lr0*lrf momentum0.937, # SGD 动量 weight_decay0.0005, # 权重衰减防过拟合 warmup_epochs3.0, # 预热轮数前 3 轮线性升温 patience30, # 30 轮无提升则早停 augmentTrue, # 开启内置增强 mosaic0.5, # 马赛克增强概率长裂缝多则调低 mixup0.1, # 混合增强小数据集可开 device0, # GPU 编号CPU 填 cpu workers4, # 数据加载线程数 project./runs/crack, # 结果保存目录 nameexp1 # 实验名 )lr0和lrf配合余弦退火策略学习率从 0.01 平滑降到 0.0001。如果 loss 在前 10 轮就剧烈震荡把lr0降到 0.005。mosaic0.5是我针对裂缝数据特意调低的默认 1.0 会把四张图拼在一起长裂缝被切割后模型学不到连续特征。patience30配合 150 轮实际有效训练通常在 80 到 120 轮之间早停能省不少时间。batch8是 8G 显存的保守值如果报 CUDA out of memory先降 batch 再考虑降 imgsz。3.2 评估指标曲线怎么看才不误判训练完在runs/crack/exp1/下会生成results.png、confusion_matrix.png、PR_curve.png等。很多人只看 mAP50 就下结论其实裂缝检测更该关注 mAP50-95 和召回率。mAP50 只要求 IoU 大于 0.5对细长目标来说这个阈值太宽松框偏一点也能算对。mAP50-95 取 IoU 从 0.5 到 0.95 的平均更能反映定位精度。指标含义裂缝场景参考值异常时先查mAP50IoU0.5 时的平均精度0.85标注质量、类别平衡mAP50-95IoU 0.5~0.95 平均0.55框的定位精度、输入尺寸Precision查准率0.80误检多则看负样本Recall查全率0.75漏检多则看增强强度F1查准查全调和0.78综合判断如果 mAP50 高但 mAP50-95 低说明模型能找到裂缝但框不准这时候把imgsz提到 960 通常有改善。如果 Recall 明显低于 Precision说明漏检多检查一下增强是不是太激进把裂缝特征破坏了。混淆矩阵里如果背景被大量误判为裂缝去验证集里找那些误判图大概率是路面接缝或阴影需要补负样本。3.3 导出 ONNX 和 TensorRT 的时机与命令训练完的.pt权重适合继续训练和验证但部署到工控机或边缘设备时导出成 ONNX 或 TensorRT 能提速 2 到 5 倍。导出时机建议在评估确认指标达标之后因为导出后的模型不再支持训练。# 导出 ONNX动态 batch 方便后续调整 yolo export model./runs/crack/exp1/weights/best.pt formatonnx dynamicTrue opset12 # 导出 TensorRT需要 NVIDIA 显卡和 TensorRT 环境 yolo export model./runs/crack/exp1/weights/best.pt formatengine halfTrue device0opset12是兼容性较好的算子集版本太低会缺算子太高部分推理框架不支持。halfTrue启用 FP16 精度TensorRT 上速度提升明显精度损失通常在 1% 以内。导出的 ONNX 文件可以用 Netron 打开检查输入输出节点确认输入是[batch, 3, 640, 640]输出包含检测框和类别。如果导出报错提示不支持的算子先升级 ultralytics 到最新版再检查 PyTorch 版本。4. PyQt5 界面集成从检测脚本到可交付工具4.1 界面布局与信号槽的对应关系PyQt5 做检测界面核心是把「选图→推理→显示」这条链路用信号槽串起来。我一般用 QMainWindow 做主窗口左侧放 QLabel 显示原图右侧放检测结果底部一排按钮控制加载、检测、保存。关键是要把推理放在 QThread 里否则界面会卡死。from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import QThread, pyqtSignal, Qt import cv2 from ultralytics import YOLO class DetectThread(QThread): # 定义信号传递检测后的图像和统计信息 finished pyqtSignal(object, str) def __init__(self, model, img_path): super().__init__() self.model model self.img_path img_path def run(self): # 在子线程中执行推理避免阻塞界面 results self.model(self.img_path, conf0.25, iou0.45) annotated results[0].plot() # 绘制检测框 num len(results[0].boxes) self.finished.emit(annotated, f检测到 {num} 条裂缝) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.model YOLO(./runs/crack/exp1/weights/best.pt) self.init_ui() def init_ui(self): self.setWindowTitle(道路裂缝检测系统) self.resize(1200, 700) central QWidget() self.setCentralWidget(central) layout QHBoxLayout(central) self.origin_label QLabel(原图区域) self.result_label QLabel(检测结果) for lb in (self.origin_label, self.result_label): lb.setAlignment(Qt.AlignCenter) lb.setStyleSheet(border: 1px solid gray;) lb.setMinimumSize(500, 500) layout.addWidget(self.origin_label) layout.addWidget(self.result_label) btn_layout QVBoxLayout() self.btn_load QPushButton(加载图片) self.btn_detect QPushButton(开始检测) self.btn_save QPushButton(保存结果) self.status QLabel(就绪) for b in (self.btn_load, self.btn_detect, self.btn_save): btn_layout.addWidget(b) btn_layout.addWidget(self.status) layout.addLayout(btn_layout) self.btn_load.clicked.connect(self.load_image) self.btn_detect.clicked.connect(self.start_detect) self.btn_save.clicked.connect(self.save_result) def load_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.png *.jpg *.bmp)) if path: self.img_path path pix QPixmap(path).scaled(500, 500, Qt.KeepAspectRatio) self.origin_label.setPixmap(pix) def start_detect(self): if not hasattr(self, img_path): self.status.setText(请先加载图片) return self.thread DetectThread(self.model, self.img_path) self.thread.finished.connect(self.on_detect_done) self.thread.start() self.status.setText(检测中...) def on_detect_done(self, annotated, msg): # 把 OpenCV 的 BGR 转成 Qt 的 RGB rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.result_label.setPixmap(QPixmap.fromImage(qimg).scaled(500, 500, Qt.KeepAspectRatio)) self.status.setText(msg) self.last_result annotated def save_result(self): if hasattr(self, last_result): cv2.imwrite(result.jpg, self.last_result) self.status.setText(已保存到 result.jpg)DetectThread继承 QThread把推理放在run里通过finished信号把结果传回主线程。conf0.25是置信度阈值低于这个值的框不显示iou0.45是 NMS 的 IoU 阈值裂缝重叠多的时候可以调到 0.5 减少漏检。results[0].plot()返回带框的 numpy 数组格式是 BGR转成 RGB 才能给 QImage 用。信号槽的connect必须在start()之前否则子线程跑完了信号还没接上。4.2 置信度阈值和 NMS 参数在界面上的联动把conf和iou做成滑块放在界面上巡检人员可以根据路况实时调。裂缝密集的路段调低conf到 0.15 减少漏检干净路面调高到 0.4 减少误报。NMS 的iou阈值影响重叠框的合并长裂缝被切成多段时调高iou能让相邻框保留更多。from PyQt5.QtWidgets import QSlider from PyQt5.QtCore import Qt # 在 init_ui 里加两个滑块 self.conf_slider QSlider(Qt.Horizontal) self.conf_slider.setRange(5, 60) # 对应 0.05 到 0.60 self.conf_slider.setValue(25) self.conf_label QLabel(置信度: 0.25) self.conf_slider.valueChanged.connect( lambda v: self.conf_label.setText(f置信度: {v/100:.2f}) ) self.iou_slider QSlider(Qt.Horizontal) self.iou_slider.setRange(10, 80) self.iou_slider.setValue(45) self.iou_label QLabel(NMS IoU: 0.45) self.iou_slider.valueChanged.connect( lambda v: self.iou_label.setText(fNMS IoU: {v/100:.2f}) )滑块值除以 100 得到实际阈值传给model()的conf和iou参数。注意滑块变化时不要立即触发推理等用户点「开始检测」再读当前值否则拖动过程中会反复推理卡顿。如果界面显示检测框颜色太浅可以在plot()里传line_width2加粗线条。4.3 打包成 exe 的依赖收集与体积控制PyQt5 加 PyTorch 打包出来动辄 2G 以上用 PyInstaller 的--exclude-module能砍掉不少。我一般先建一个干净的虚拟环境只装运行必需的包再打包。# 安装打包工具 pip install pyinstaller6.3.0 # 打包命令排除训练相关模块 pyinstaller --noconfirm --windowed --name RoadCrack \ --exclude-module matplotlib \ --exclude-module scipy \ --exclude-module pandas \ --exclude-module torchvision \ --add-data runs/crack/exp1/weights/best.pt;runs/crack/exp1/weights \ main.py--windowed去掉控制台窗口--add-data把模型权重打进去Windows 下用分号分隔源和目标。--exclude-module排除训练和可视化才用的库能省 300 到 500MB。打包后先在本机双击运行确认模型路径用的是相对路径而不是绝对路径否则换台电脑就找不到权重。如果启动报缺少 DLL把 PyQt5 的plugins目录手动拷到 exe 同级。5. 避坑与排查裂缝检测项目里最容易翻车的五件事5.1 训练 loss 正常但验证 mAP 一直是 0现象训练集 loss 稳定下降但验证集 mAP 始终为 0混淆矩阵全空。原因通常是数据配置文件里的nc类别数和names对不上或者标签路径写错导致验证集加载不到标注。解决打开crack.yaml确认train和val指向的目录下同时有images和labels文件夹且标签文件名和图片名一一对应。用yolo checks命令检查数据集配置它会提示缺失的标注文件。5.2 推理时检测框大量重叠在同一位置现象一张图里同一个裂缝被框了七八次框几乎完全重合。原因是 NMS 的iou阈值设得太高或者模型输出的框本身就很密集。解决把iou从默认 0.7 降到 0.45 到 0.5 之间重叠框会被合并。如果降了还有检查训练时boxloss 的权重是不是太低可以在训练参数里加box7.5提高定位损失权重。5.3 PyQt5 界面加载模型后卡死无响应现象点「开始检测」后界面白屏鼠标转圈几秒后恢复但没结果。原因是推理在主线程执行阻塞了 Qt 的事件循环。解决把model()调用放进 QThread 子类通过信号回传结果主线程只负责更新界面。如果已经用了线程还卡检查是不是在子线程里直接操作了 QLabelQt 的界面控件只能在主线程更新。5.4 导出的 ONNX 在别的机器上推理结果全错现象PyTorch 权重验证正常导出 ONNX 后用 onnxruntime 推理框的位置全偏了。原因是导出时没有指定dynamic或者输入尺寸和推理时不一致。解决导出时加dynamicTrue推理时把输入 resize 到和训练相同的尺寸如 640×640并且注意 BGR 和 RGB 的通道顺序。YOLO 训练时用的是 RGBOpenCV 读图默认 BGR推理前要cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。5.5 小数据集上模型过拟合训练集 mAP 0.95 验证集只有 0.5现象训练集指标远高于验证集验证 loss 在 50 轮后开始上升。原因是 370 张图对 YOLO11 来说偏少模型记住了训练样本。解决把mosaic调到 0.3 以下加mixup0.2weight_decay提到 0.001dropout如果在模型配置里可设 0.1。另外早停patience设 20 到 30别等 150 轮跑完。如果还不行用预训练权重做冻结训练前 20 轮只训 head之后再解冻微调。6. 把裂缝检测从 demo 推到可用三个我踩过的进阶技巧第一个技巧是用切片推理处理高分辨率路面图。巡检车拍的图经常是 4000×3000直接 resize 到 640 会丢失细裂缝。我的做法是把大图切成 640×640 的块块之间留 100 像素重叠每块单独推理后再把框映射回原图坐标最后统一做一次 NMS。这样 mAP50-95 能从 0.52 提到 0.61代价是推理时间增加约 4 倍。切片脚本用 OpenCV 的img[y:yh, x:xw]循环就行注意边界处理最后一块不够 640 就补零或者从边缘往回取。第二个技巧是用验证集上的误检图做负样本挖掘。模型在验证集上误报的图把误报区域裁出来当负样本加入训练集再跑 30 轮微调。我试过一轮误检率从 18% 降到 9%。负样本不用标框只要图片里没有裂缝就行YOLO 会把它们当背景学。注意负样本数量别超过正样本的 20%否则模型会偏向预测背景。第三个技巧是评估时按裂缝宽度分档统计。把所有验证集的框按宽度分成细5px、中5-15px、粗15px三档分别算召回率。如果细裂缝召回率明显低说明输入尺寸不够或者浅层特征被削弱把imgsz提到 960 或者换 YOLO11s 更大的模型。这个分档统计能帮你判断模型到底能不能用在需要测宽的巡检场景而不是只看一个笼统的 mAP。技巧适用场景预期收益代价切片推理高分辨率巡检图mAP50-95 0.08推理时间 ×4负样本挖掘误检率高误检率 -50%需人工筛图分档评估需要测宽定位薄弱档位无额外训练我自己的习惯是每训完一版模型先跑分档评估再看误检图最后决定要不要上切片。这三个技巧不用全上按你的实际场景挑。这套 YOLO11 加 PyQt5 的方案从数据到界面再到打包一个周末能跑通但真正用到巡检车上还得在切片和负样本上多花点心思。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑