坑洼、裂缝这类路面缺陷,人工巡检成本高、漏检率不低,尤其到了雨季,道路病害会快速扩散。这次我们来看一个把 YOLOv8 目标检测和 PyQt5 桌面界面结合起来的路面缺陷检测系统,识别对象就是坑洼、破损等马路表面问题。它不依赖云服务,模型跑在本地,可以通过图片、视频或摄像头实时检测,也能批量处理文件夹里的图像,核心价值是给道路养护、市政巡检、工程验收提供一个可离线运行的辅助筛查工具。
这个项目最值得关注的地方有三个:第一,技术栈明确,YOLOv8 负责检测,PyQt5 负责图形界面,两者通过 Python 环境整合,工程上很常见;第二,可扩展性强,训练好的best.pt权重文件既能嵌入 PyQt5 桌面端,也能单独做批量推理或启动 API 服务;第三,硬件门槛相对可控,模型可以从yolov8n到yolov8x自由选择,显存占用不同,普通办公电脑也能用 CPU 跑推理,只是速度不如 GPU。本文会带你完整走一遍环境准备、依赖安装、数据集准备、模型训练、PyQt5 界面开发、批量任务和 API 调用流程,最后给出常见问题排查方法。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | Python 桌面应用 + 深度学习目标检测 |
| 技术栈 | YOLOv8(Ultralytics)、PyQt5、OpenCV |
| 检测对象 | 路面坑洼、破损缺陷等 |
| 模型来源 | 公开 COCO 预训练权重或自训练权重best.pt |
| 图像输入 | 单张图片、视频文件、摄像头实时画面 |
| 批量任务 | 支持对目录内图片批量推理 |
| 接口 API | 可通过 Flask 或 FastAPI 二次封装 HTTP 接口 |
| 推荐硬件 | GPU 优先,GTX 16 系及以上可流畅训练小模型;CPU 可推理 |
| 显存占用 | 取决于模型规格,yolov8n较低,yolov8m及以上明显升高,需以实测为准 |
| 启动方式 | Python 脚本启动 PyQt5 窗口 / 命令行调用模型 |
| 适合场景 | 道路巡检、市政设施普查、工程质量辅助验收 |
关于显存占用,不同模型规格之间差别很大。按 Ultralytics 公开的模型规模和社区常见测试来看,yolov8n在 640 输入尺寸下显存占用约 2G 左右,yolov8s约 4G 级别,yolov8m及以上需要更大显存。具体数值受批量大小、输入分辨率、是否开启训练模式影响,建议以本机实际测试为准,不要照搬一切网上的数字。
2. 适用场景与使用边界
这类检测系统适合的典型场景包括:
- 市政道路日常巡检,用巡检车记录路面视频后切片批量识别。
- 工程质量验收,对新建路段的路面破损情况进行辅助筛查。
- 高校科研和课程设计,用完整项目学习 YOLOv8 训练与 PyQt5 桌面开发。
- 小型团队自研路面养护工具,在本地完成缺陷分类和标注统计。
不适合的场景也要说清楚。对于深度较大的结构性损坏、塌陷或带有复杂背景遮挡的坑洼,单靠 2D 目标检测无法判断病害深度,系统只能做到“发现疑似坑洼”,不能替代现场人工复检。另外,如果要在夜间或雨天使用,需要准备足够好的补光设备和样本数据,否则漏检率会上升。
合规和隐私方面,道路巡检数据通常包含车牌、行人面部、周边建筑等敏感信息。在采集、标注、存储和展示环节要遵守所在地区和单位的隐私管理规定。不要把巡检数据随意上传到外部云服务;如果系统接入摄像头,要确保监控范围合法,并在界面中做好数据访问控制。默认只保存检测结果和统计信息,不额外留存无关的个人信息。
3. 环境准备与安装部署
3.1 环境检查清单
建议先确认本机环境,避免装到一半才发现版本冲突。推荐使用 Python 3.8 到 3.11,Ultralytics 对 Python 版本有持续适配,使用较新稳定版本问题最少。操作系统方面 Windows 10/11、Ubuntu 20.04/22.04 都可以。GPU 训练需要 NVIDIA 显卡,建议驱动更新到较新版本,并安装匹配的 CUDA 和 cuDNN;没有 GPU 也能跑推理和训练小模型,但速度差距明显。磁盘空间建议预留 15G 以上,YOLOv8 模型文件本身不大,但训练中间结果和数据集会占空间。
打开终端或 Anaconda Prompt,执行以下命令创建虚拟环境并激活:
conda create -n pothole python=3.9 -y conda activate pothole如果你更习惯原生 Python,也可以用python -m venv创建虚拟环境。
3.2 安装依赖库
安装ultralytics、pyqt5、opencv-python、pillow,这几个是核心依赖。建议在一个命令中安装,让 pip 统一处理版本依赖:
pip install ultralytics pyqt5 opencv-python pillow如果你的网络环境下载速度较慢,可以临时使用国内镜像源:
pip install ultralytics pyqt5 opencv-python pillow -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,先验证 YOLOv8 是否能正常导入并下载预训练权重。网络正常情况下,首次运行会自动下载yolov8n.pt。
from ultralytics import YOLO # 会自动下载 yolov8n.pt 到当前目录 model = YOLO("yolov8n.pt") print("YOLOv8 安装成功")再验证 PyQt5 是否可以创建窗口。运行下面这段代码,如果弹出一个空白窗口,说明界面环境正常。
import sys from PyQt5.QtWidgets import QApplication, QWidget app = QApplication(sys.argv) w = QWidget() w.resize(400, 300) w.setWindowTitle("PyQt5 测试窗口") w.show() sys.exit(app.exec_())3.3 验证 GPU 是否可用
训练目标检测模型时,GPU 能明显加速。先确认 PyTorch 是否能识别到显卡:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "未检测到 GPU")如果输出True,说明 PyTorch 正常使用 GPU。如果输出False,需要检查显卡驱动、CUDA 版本,或者重新安装对应 CUDA 版本的 PyTorch。即使没有 GPU,继续往下走也没问题,训练时间会变长,但可以用device="cpu"指定。
4. 数据集准备与 YOLOv8 模型训练
4.1 数据集目录结构
训练自己的坑洼检测模型,数据集通常采用 YOLO 格式目录结构,图片放在images文件夹,标注文本放在labels文件夹,训练集和验证集分开放置。
dataset/ images/ train/ road_001.jpg road_002.jpg val/ road_101.jpg labels/ train/ road_001.txt road_002.txt val/ road_101.txt pothole.yaml在标注阶段,建议使用 LabelImg 或 LabelStudio,标注类别名设置为pothole,导出格式选择 YOLO。每张图片对应一个.txt文件,每行内容为类别编号 cx cy w h,坐标是归一化后的值。
4.2 编写数据集配置文件
pothole.yaml是训练时的数据集配置,里面指定训练集、验证集路径和类别名:
train: dataset/images/train val: dataset/images/val nc: 1 names: ["pothole"]注意train和val路径要写实际路径。如果数据集与代码在同一目录下,建议使用相对路径,方便项目整体迁移。
4.3 数据采集与增强建议
坑洼样本在真实道路中分布不均,晴天、阴天、雨天、顺光、逆光、阴影遮挡等因素都会影响检测效果。采集数据时尽量覆盖不同时段、不同光照、不同道路材质。如果样本量不足,可以利用 YOLOv8 自带的 Mosaic、随机翻转、色彩抖动等增强策略。在train方法中加入augment=True可以默认开启部分增强,也可以根据自己的数据情况调整超参数。
4.4 执行模型训练
训练前先决定模型规格。yolov8n速度最快、显存占用最低,适合快速验证流程;yolov8s是精度和速度比较平衡的选择;yolov8m及以上精度可能更高,但显存占用和推理时间都会增加。对路面检测这种场景,样本特征比较明显,yolov8n或yolov8s通常就够用。
from ultralytics import YOLO # 加载预训练模型作为起点,微调到自己的数据集 model = YOLO("yolov8s.pt") # 训练 100 轮,输入尺寸 640,批量大小 8 model.train( data="dataset/pothole.yaml", epochs=100, imgsz=640, batch=8, device=0, # 0 表示第一块 GPU,CPU 则写 "cpu" workers=4, name="pothole_train" )训练结束后,模型权重位于runs/detect/pothole_train/weights/best.pt。best.pt是验证集上表现最好的权重,后续检测和部署都使用这个文件。如果训练过程中发现 loss 曲线不收敛,可以适当增加轮数或者调整学习率;如果验证集精度一直很低,优先检查数据集标注是否正确、类别是否平衡、图片和标注文件是否一一对应。
4.5 训练效果验证
训练完成后,用验证集图片和 1 张从未见过的测试图片来观察效果。运行下面的命令,会在runs/detect/predict目录下生成带标注框的结果图:
yolo predict model=runs/detect/pothole_train/weights/best.pt source=test_images/road_sample.jpg conf=0.25多测试几张图片,重点观察漏检和误检:坑洼漏检说明模型学到的特征不够或者样本覆盖不足;把普通阴影、路面水渍误判为坑洼说明背景干扰样本不够,需要补充负样本或降低置信度阈值。
5. PyQt5 检测界面开发与功能验证
5.1 界面功能规划
一个实用的 PyQt5 检测界面至少需要以下功能区域:
- 模型路径选择框,默认指向
best.pt。 - 图片选择按钮,支持 JPG、PNG 等常见图片格式。
- 摄像头检测按钮,打开本机摄像头并实时显示检测结果。
- 图片预览区,用
QLabel显示原图和检测结果。 - 检测信息栏,显示当前图片检测到的坑洼数量、置信度和耗时。
5.2 核心代码结构
下面是一个简化版的 PyQt5 检测界面代码,包含图片检测和模型加载,可以直接运行做功能验证:
import sys from pathlib import Path import cv2 from PyQt5.QtCore import Qt from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtWidgets import ( QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QVBoxLayout, QHBoxLayout, QWidget, QTextEdit ) from ultralytics import YOLO class PotholeWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("坑洼路面检测系统 - YOLOv8 + PyQt5") self.setMinimumSize(900, 650) self.model = YOLO("best.pt") # 默认加载训练好的权重 self.init_ui() def init_ui(self): self.image_label = QLabel("请选择图片或打开摄像头") self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet("background-color: #2d2d2d; color: #cccccc;") self.result_text = QTextEdit() self.result_text.setFixedHeight(120) self.result_text.setReadOnly(True) btn_open = QPushButton("选择图片") btn_open.clicked.connect(self.open_image) btn_camera = QPushButton("打开摄像头") btn_camera.clicked.connect(self.open_camera) top_layout = QHBoxLayout() top_layout.addWidget(btn_open) top_layout.addWidget(btn_camera) main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.image_label) main_layout.addWidget(self.result_text) container = QWidget() container.setLayout(main_layout) self.setCentralWidget(container) def open_image(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.jpg *.jpeg *.png *.bmp)" ) if not file_path: return results = self.model.predict(source=file_path, conf=0.25, verbose=False) annotated_img = results[0].plot() # 返回 BGR 图像 # 统计检测到的坑洼数量 boxes = results[0].boxes self.result_text.append(f"检测文件: {Path(file_path).name}") self.result_text.append(f"检测到坑洼数量: {len(boxes)}") # BGR 转 RGB 后在 QLabel 中显示 rgb_img = cv2.cvtColor(annotated_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb_img.shape bytes_per_line = ch * w qt_img = QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_img).scaled( self.image_label.width(), self.image_label.height(), Qt.KeepAspectRatio ) self.image_label.setPixmap(pixmap) def open_camera(self): # 正式的摄像头检测需要放到线程中,避免界面卡死 self.result_text.append("摄像头检测功能需配合 QThread 实现,这里先空实现") pass if __name__ == "__main__": app = QApplication(sys.argv) window = PotholeWindow() window.show() sys.exit(app.exec_())运行这个脚本,点击“选择图片”,程序会在当前文件夹中查找best.pt权重文件,如果找不到需要把训练得到的权重复制到项目目录,或在代码中写绝对路径。
5.3 摄像头实时检测
摄像头实时检测和图片检测的区别在于:视频帧是连续输入的,不能把predict写在界面主线程里,否则画面会卡顿、窗口无响应。更稳妥的方案是用QThread子线程读取摄像头帧并执行推理,检测完成后再通过信号把结果传回界面线程。
关键思路是:
- 在
run()循环中读取cap.read()。 - 对当前帧执行
model.predict(source=frame)。 - 把标注后的帧转成
QImage,通过信号发送给主窗口的QLabel刷新。
如果只是做功能验证,也可以先用cv2.VideoCapture(0)读取摄像头,逐帧调用模型,验证效果,然后再优化到线程方案。
5.4 界面卡死的排查
界面点击按钮后无响应,最可能的原因就是推理任务阻塞了主线程。图片检测耗时一般在几百毫秒到几秒,摄像头逐帧推理耗时更长。如果在open_image里直接执行长时间的模型推理,窗口会被冻结。解决方法是把所有耗时操作放到QThread或QRunnable中,界面线程只负责接收结果并刷新控件。
6. 批量任务与 API 接口扩展
6.1 批量图片检测
路面巡检经常会对一批图片进行统一筛查,比如从巡检视频中按帧导出图片。用 YOLOv8 自带的source参数传入一个目录,可以实现批量推理:
yolo predict model=best.pt source=test_images/ save=True project=results/ conf=0.25也可以在 Python 中遍历目录,灵活控制输出逻辑:
from pathlib import Path from ultralytics import YOLO model = YOLO("best.pt") input_dir = Path("test_images") output_dir = Path("output_images") output_dir.mkdir(exist_ok=True) for img_path in input_dir.glob("*.jpg"): result = model.predict( source=str(img_path), conf=0.25, save=True, project=str(output_dir), name="batch", verbose=False ) box_count = len(result[0].boxes) print(f"{img_path.name}: 检测到 {box_count} 个缺陷")批量任务跑起来后,建议把识别结果同步写入 CSV,方便和人工复检对照。输出目录最好按日期归档,避免后期查找困难。
6.2 封装成 HTTP API 接口
如果不想每次都打开 PyQt5 界面,可以把模型封装成接口,供其他系统调用。这里用 Flask 做一个最小可用的接口示例:
pip install flaskfrom flask import Flask, request, jsonify from ultralytics import YOLO import cv2 import numpy as np app = Flask(__name__) model = YOLO("best.pt") @app.route("/detect", methods=["POST"]) def detect(): if "image" not in request.files: return jsonify({"error": "未上传图片"}), 400 file = request.files["image"] img_bytes = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) result = model.predict(source=img, conf=0.25, verbose=False) boxes = result[0].boxes.xyxy.cpu().numpy().tolist() confs = result[0].boxes.conf.cpu().numpy().tolist() return jsonify({ "count": len(boxes), "boxes": boxes, "confs": confs }) if __name__ == "__main__": app.run(host="127.0.0.1", port=5000)启动后,用下面的 Python 脚本模拟一张图片请求:
import requests url = "http://127.0.0.1:5000/detect" files = {"image": open("test_images/road_sample.jpg", "rb")} response = requests.post(url, files=files, timeout=30) print(response.status_code) print(response.json())接口服务启动后,需要在防火墙或网络策略中控制访问范围。部署到服务器时,建议把host设置为127.0.0.1仅本机访问,或者加一层接口鉴权,避免被外部直接调用。
6.3 批量任务队列设计
做大规模巡检时,单线程批量推理会比较慢,尤其是 CPU 推理场景。如果想提升吞吐量,可以基于多进程或任务队列实现:
- 使用
concurrent.futures.ProcessPoolExecutor并行处理多个图片文件。 - 使用 Redis + RQ 或 Celery 构建异步任务队列。
- 在 GPU 上增大
batch参数,用一次前向推理处理多张图片。
需要注意的是,多进程并行会占用更多内存,GPU 显存也要根据单卡容量控制并发数。
7. 资源占用与性能观察
7.1 如何观察资源占用
训练和推理过程中,可以用系统工具实时观察资源占用。Windows 下打开任务管理器,查看 GPU 显存和 CPU 使用率;Linux 下用:
# 每 1 秒刷新一次 GPU 状态 watch -n 1 nvidia-smiPython 中也可以读取显存信息:
import torch if torch.cuda.is_available(): print(f"显卡名称: {torch.cuda.get_device_name(0)}") print(f"显存总量: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB") print(f"当前已用: {torch.cuda.memory_allocated() / 1024**3:.2f} GB") print(f"当前缓存: {torch.cuda.memory_reserved() / 1024**3:.2f} GB")7.2 CPU 推理与 GPU 推理的差异
没有 NVIDIA 显卡的机器也能跑 YOLOv8 推理,Ultralytics 支持 CPU 后端。CPU 推理的优势是兼容几乎所有电脑,但速度比 GPU 慢很多。以常见的 640 输入尺寸为例,GPU 上单张图片推理可能在几十毫秒到几百毫秒,CPU 上则可能需要 1 到 5 秒甚至更久,具体和 CPU 型号、模型规格都有关系。如果只是做几百张图片的离线批量检测,CPU 完全够用;如果要做摄像头实时检测,建议至少让yolov8n跑在支持 CUDA 的显卡上。
7.3 模型规格与推理参数对性能的影响
影响资源占用和速度的主要因素有三个:
- 模型规格:
n最轻,s中等,m、l、x依次变重。 - 输入分辨率:
imgsz=640是默认值,改成imgsz=1280会显著增加计算量和显存占用,对小目标检测有帮助。 - 批量大小:训练时
batch越大,梯度更新越稳定,但显存占用线性增长;推理时一次处理多张图也能提高吞吐,但别超过显存上限。 - 置信度阈值:
conf=0.25比conf=0.5会保留更多检测框,误检可能增多,但不会明显影响速度。
7.4 降低显存占用的方法
显存不足时,可以按顺序尝试这些手段:
# 1. 换更小的模型 model = YOLO("yolov8n.pt") # 2. 降低输入尺寸 model.train(data="dataset/pothole.yaml", epochs=50, imgsz=512, batch=4) # 3. 减少批量大小 model.train(data="dataset/pothole.yaml", epochs=50, imgsz=640, batch=2) # 4. 训练时开启梯度检查点 model.train(data="dataset/pothole.yaml", epochs=50, imgsz=640, batch=8, amp=True)amp=True表示自动混合精度,能减少显存占用并提升训练速度。如果仍然 OOM,说明当前显卡不太适合训练这个规格的模型,建议换yolov8n或减小imgsz。
7.5 端口冲突与进程残留
启动 Flask 接口时如果遇到端口占用,换一个端口即可:
app.run(host="127.0.0.1", port=5001)如果程序异常退出后 GPU 显存没有释放,检查是不是有残留 Python 进程。Windows 下打开任务管理器结束相关进程,Linux 下用ps -ef | grep python找到进程后kill。训练中断后再次训练,记得清理原有的runs/detect输出目录,避免结果混淆。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
pip install ultralytics失败 | 网络问题或 Python 版本不兼容 | 查看 pip 报错信息 | 使用镜像源,升级到 Python 3.9 以上 |
import ultralytics报错 | 依赖库版本冲突 | 检查pip list中的 torch 版本 | 重装 ultralytics,或升级 PyTorch |
| PyQt5 窗口无法弹出 | PyQt5 安装不完整或显示环境问题 | 运行测试窗口脚本 | 重装pyqt5,检查显示服务 |
加载best.pt报错 | 权重文件路径不对或模型损坏 | 确认文件是否存在、大小是否正常 | 重新训练或从训练输出复制权重 |
| 摄像头打开失败 | 摄像头索引错误、驱动或权限问题 | 用cv2.VideoCapture(0)单独测试 | 尝试索引 1 或 2,检查系统摄像头权限 |
| GPU 推理报 CUDA error | 显卡驱动与 PyTorch CUDA 版本不匹配 | torch.cuda.is_available()验证 | 安装匹配的 CUDA 版本 PyTorch |
| 训练时显存不足 OOM | 模型规格过大或 batch 过大 | 观察nvidia-smi是否接近满显存 | 降低 batch、imgsz,换小模型 |
| 界面点击按钮后无响应 | 推理在主线程执行,阻塞界面 | 观察窗口是否冻结 | 把推理移到 QThread |
| 漏检严重 | 训练样本不足或场景差异大 | 分析错误案例图片 | 补充对应场景数据,增加训练轮数 |
| 误检严重 | 背景干扰、置信度阈值过低 | 查看输出框的置信度分数 | 调高conf,补充负样本 |
| 批量任务卡在某一文件 | 图片损坏或模型推理异常 | 定位具体文件路径 | 跳过损坏文件,添加异常捕获 |
| Flask 接口无法访问 | 服务未启动、端口占用或防火墙拦截 | 检查服务日志和端口 | 换端口,控制 host,检查防火墙 |
9. 最佳实践、合规与下一步
9.1 工程化使用建议
第一次跑通后,建议按以下思路做工程化收尾:
- 保留一套最小可运行配置。项目目录固定为
models/、datasets/、outputs/、ui/四个文件夹,权重文件放在models/下,数据增强脚本和训练脚本分离。 - 批量任务必须加日志。每处理一张图片都记录时间、文件名、检测框数、置信度,方便复检和问题回溯。
- 接口服务要限制访问范围。未做鉴权的接口不要直接暴露到公网,可以通过内网部署、API Token 或 Nginx 反向代理控制访问。
- 发布前做效果复核。让标注人员和现场巡检人员各自抽检一批结果,确认模型漏检率、误检率是否在可接受范围。
- 定期用新采集的巡检图片做回测。道路环境随着季节变化,建议每个月补充一批新样本并重新评估模型,必要时做增量训练。
9.2 隐私与合规提醒
路面检测系统在真实场景中会采集到大量视频和图片,其中可能包含车牌、人脸、建筑物外立面等敏感信息。要明确几个原则:数据采集前确认用途和范围,只保存和业务相关的路面画面;标注、训练、存储流程中做好权限管理;单位内部使用时要遵守网络安全和个人信息保护要求。涉及商用前,要对模型输出结果做人工抽检,避免因误检导致错误开工单。
9.3 后续扩展方向
当前系统已经覆盖了“图片检测 + 界面展示 + 批量推理 + 接口调用”这条主线,后续可以从这几个方向继续扩展:
- 多类别检测:把坑洼、裂缝、龟裂、修补痕迹分开标记,做更细粒度统计。
- 多摄像头接入:PyQt5 界面中增加摄像头切换,或者用多个
QThread同时读取多个视频流。 - 检测结果 GIS 化:把巡检图片的 GPS 信息写到结果中,在地图上标记病害位置。
- 模型轻量化部署:把
best.pt导出为 ONNX 或 TensorRT 格式,在嵌入式设备上部署。 - 自动巡检报告:批量任务结束后自动生成带统计图和表格的巡检报告,直接交付给养护单位。
坑洼路面检测系统的核心价值不在于界面多酷炫,而在于能否把本地化目标检测能力和桌面工具稳定地组合在一起。建议先拿 20 到 50 张路面图片,用yolov8n跑通完整流程,确认每个环节都能工作,再逐步扩大数据集、优化模型。最容易踩的坑是数据集标注不一致、模型路径写错、PyQt5 界面卡死这三类,按上面排查表逐项处理即可。下一步可以从摄像头实时检测和批量巡检报告这两个方向继续做,二者在实际项目中都是高频需求。