
把 YOLOv8 和 PyQt5 组合在一起做坑洼路面缺陷检测最大的收益是能在一套桌面工具里同时完成图像检测、视频检测、摄像头实时检测和结果保存。这个项目适合两类人一类是刚学完 YOLOv8 基础、想让模型不再是命令行黑窗口的人另一类是正在做道路巡检或路面缺陷数据整理需要把检测结果可视化、给非技术人员使用的人。整套系统不难难点在于把模型推理和 UI 交互串得稳。下面按我实际落地时习惯的顺序拆一遍先确认需求再配置环境接着准备模型最后写 GUI 和排查问题。1. 先说清楚这套系统到底做什么1.1 坑洼检测本质上是一个目标检测任务无论界面做得多复杂核心还是一句给定一张路面图片返回“坑洼pothole”的类别、置信度和矩形框。整体上系统可以拆成两半YOLOv8 负责目标检测PyQt5 负责把检测结果展示成人能直接操作的窗口。YOLOv8 是 Ultralytics 开源的检测模型结构上包含 Backbone、Neck 和 Head官方提供了不同规模的权重从 n 到 x 都有通常我们用 n 或 s 就够了。PyQt5 是 Python 常用的桌面 GUI 框架能做出按钮、下拉框、表格、图片显示区域和日志窗口。先想清楚这一点很重要。因为很多人一开始就把精力花在“把界面做得好看”上结果模型精度和运行速度反而拖后腿。实际做的时候我建议先把模型部分跑通再套界面。1.2 为什么选 YOLOv8 PyQt5 而不是 Web 端可能有人会问用 Flask/FastAPI 做网页展示不是更方便吗网页端的优势是远程访问但道路巡检这种场景经常是离线环境现场一台工控机或笔记本接上 USB 摄像头就得干活。这时候 PyQt5 的优势很明显不依赖浏览器不需要部署 Web 服务双击启动直接显示视频流。YOLOv8 的优势是接口简单训练、验证、预测、导出都有统一命令PyQt5 的成熟度也足够高控件丰富IO 方便。另外一个实际考虑PyQt5 适合做成“单机工具”交付给非技术用户时只要把 Python 环境一起打进去或者用 PyInstaller 打包成 exe对方不需要装深度学习环境。Web 方案遇到的浏览器摄像头权限、跨域、并发连接问题在桌面端基本不存在。这个系统还可以拆成四个模块输入模块、检测模块、显示模块、输出模块。输入模块负责处理图片、视频文件、USB 摄像头检测模块负责加载模型和执行推理显示模块负责刷新检测画面、表格和日志输出模块负责保存标注图、导出检测结果文本和统计数据。这样拆分之后出问题的定位速度会快很多。输入模块有问题就看路径、编码和帧读取检测模块有问题就看模型路径、推理参数和显存显示模块有问题就看图像格式、像素格式和 Qt 刷新输出模块有问题就看目录权限、文件名冲突和写入失败。后面所有排查思路都围绕这四个模块展开。2. 环境和依赖配置2.1 先判断 GPU 还是 CPU很多人会问“YOLOv8 需要用到 GPU 吗”。答案很明确训练建议有 NVIDIA GPU如果只做推理CPU 也能跑只是速度受限制。以常见的 GTX1660Ti 6GB 为例跑 YOLOv8n 的单张 640×640 图片推理速度很快跑 YOLOv8s 稍慢但也可接受。如果换成纯 CPU 环境单张图片可能从几十毫秒涨到几百毫秒甚至一秒钟以上如果还要处理摄像头实时视频帧率会很低。所以配置环境前先摸清你的机器方案硬件条件适合场景预期表现参考CPU 推理任意现代 CPU少量图片检测、学习 Demo单张图片 0.3~2 秒视频卡顿NVIDIA GPU 推理GTX1660Ti 及以上 6GB 显存图片、视频、摄像头YOLOv8n/YOLOv8s 单帧几十毫秒到上百毫秒NVIDIA GPU 训练显存 6GB~8GB 以上自己标注数据集训练需要控制 batch 和 imgszNPU/边缘设备RK3588、Jetson Orin Nano 等部署到巡检小车或边缘盒子需要将模型导出为 ONNX/RKNN 等格式如果你的机器没有 NVIDIA GPU也不代表不能做这个项目。选 YOLOv8n 权重把输入尺寸降到 480 或 416处理单张图片还是可以接受的。但你要清楚低配置能跑不代表适合批量跑更不代表适合做实时视频。批量任务中每帧都累积耗时摄像头实时检测更是对单帧耗时非常敏感。2.2 安装步骤和常见坑建议使用虚拟环境不要把 PyQt5 和 ultralytics 直接装进系统 Python。虚拟环境的好处是隔离依赖以后打包也方便。可以用 conda 或 venvconda create -n pothole python3.9 -y conda activate pothole pip install ultralytics pyqt5 opencv-python numpy注意 Python 版本不要太新。比如 Python 3.12、3.13 下 PyQt5 可能出现跳版本兼容问题。更稳妥的是 3.8 到 3.10 之间。具体版本号在你本地确认不要照抄网上所有安装命令。常见问题装 PyQt5 后程序启动闪退优先检查显卡驱动、Qt 插件路径、Python 位数是否一致。下拉框闪退可能是 QComboBox 的 item 数据问题也可能是对象被提前释放要先把运行环境固定住再查代码。我遇到过一次是 pyqt5-tools 版本和 PyQt5 版本不匹配在无界面环境里用 Qt Designer 后删除多余插件就好了。opencv-python 和 PyQt5 一起使用时不要直接混合cv2.imshow和 QLabel 显示会抢窗口焦点甚至崩溃。统一用 QLabel 显示转换后的 QImage。注意不要把 PyQt5 和 ultralytics 一起装进同一个旧环境里还不做隔离。环境出错时最难排查的不是代码而是依赖互相污染。3. 模型准备从预训练权重到自己的坑洼数据集3.1 先用预训练权重跑通一条完整链路在写 GUI 之前先用官方权重验证环境。执行yolo detect predict modelyolov8n.pt sourcetest.jpg如果这行命令能输出结果就说明 YOLOv8 环境基本正常。之后再用自己的数据训练。在 GUI 里加载的也是同一个 torch 模型。用 ultralytics 的 Python 接口from ultralytics import YOLO model YOLO(yolov8n.pt) results model.predict(test.jpg, conf0.3, imgsz640)这里conf是置信度阈值调低能召回更多目标但误检也会增加调高则相反。实际坑洼检测我一般先用 0.25 到 0.3 观察再根据路面背景复杂度调整。如果画面里出现大量误检就把阈值调到 0.4 以上如果出现漏检就把阈值降低。3.2 准备坑洼数据集和标注格式如果要用自己的数据集需要把图片和标注文件整理成 YOLO 格式。YOLO 标签数据长这样图片image_001.jpg对应标签image_001.txt内容示例0 0.438 0.621 0.221 0.153含义是类别 id、归一化中心 x、中心 y、归一化宽度 w、高度 h。目录结构示例datasets/pothole/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── pothole.yamlpothole.yaml内容train: datasets/pothole/images/train val: datasets/pothole/images/val nc: 1 names: [pothole]收集坑洼图片时常见问题是背景单一、角度固定模型容易过拟合到“黑色区域”而不是“坑洼本身”。所以训练集要尽量包含晴天、阴天、湿润路面、阴影、不同相机高度等场景。标注时不要把整块大面积破损都标成一个框但也不要切得过碎。判断标准是框能不能大致包住一个独立的坑洼区域同时相邻坑洼不混在一起。3.3 训练参数和数据增强数据准备好后命令可以写成yolo detect train datadatasets/pothole/pothole.yaml modelyolov8n.pt epochs100 imgsz640 batch8新手最容易犯的错误是一上来就把 batch 和 imgsz 拉满。batch8在 6GB 显存上跑 YOLOv8n 比较接近安全边界如果显存不足减小 batch 或 imgsz。imgsz640是 YOLOv8 的常用输入尺寸坑洼在画面里偏小时可以尝试imgsz800或 1024但训练和推理速度都会下降。数据增强方面YOLOv8 默认带了 mosaic、翻转、颜色抖动等增强。不要把所有增强参数都开满否则训练集精度和验证集精度会出现较大落差。训练过程中重点看runs/detect/train/exp/results.png里面包含 train/loss、val/loss、mAP 等曲线。判断标准损失曲线总体下降验证集的 mAP 不再上升时可以先停掉如果 val loss 明显回升说明过拟合。如果自己不太会看指标可以先看两个点训练结束后的best.pt能不能在验证集图片上稳定框出坑洼换几张训练时没见过的真实路面照片看漏检率和误检率高不高。不要只盯着训练集表现。3.4 小目标坑洼的改进方向如果坑洼是小目标直接用 YOLOv8n 往往漏检。常见改进方向提高输入分辨率imgsz从 640 提到 1024 或更高。增加小目标检测头在浅层特征图上增加检测分支YOLOv8 原始结构里对小目标不算友好自行改的时候要重新训练。修改特征融合模块例如借鉴 C2f 的变体或类似 CFFM 的注意力融合设计让高低层特征更好融合。使用更重的模型YOLOv8s/m 在小目标上通常比 n 有更好表现但显存占用和速度也要接受。改网络属于进阶内容。如果不是为了发论文或打比赛我建议先按原始 YOLOv8把数据集、输入尺寸和置信度调好再考虑结构改动。3.5 导出模型训练结束后权重路径是runs/detect/train/exp/weights/best.pt。桌面端直接用.pt文件即可。如果后续要部署到 RK3588、Jetson 等边缘设备可以导出 ONNX再根据平台转换成对应格式。导出 ONNXyolo export modelruns/detect/train/exp/weights/best.pt formatonnx imgsz640导出前要注意模型输入尺寸要和训练一致否则推理精度可能下降。4. PyQt5 桌面界面功能编排比画控件更重要4.1 界面模块划分一个基础的坑洼检测界面不需要做得太复杂。建议包括区域控件作用输入区按钮选择图片/选择视频/打开摄像头决定数据来源模型区下拉框模型权重选择切换不同模型 run显示区QLabel显示原图或检测结果结果区QTableWidget显示每个框的类别、置信度、坐标日志区QTextBrowser打印推理耗时、错误信息控制区开始检测/停止检测控制视频和摄像头流程整个界面逻辑可以简化成用户选择输入源按下检测界面把任务交给检测线程检测线程返回结果界面刷新图片和表格。选择图片和打开视频是两个不同入口建议共用同一个检测函数只在读取方式上分开。很多人把图片检测和视频检测写成两套后来改参数要改两遍。更好的方式是把输入统一成 numpy 数组图片读出来是数组视频读出来也是数组检测函数只接收数组和来源标识界面只管显示。4.2 检测必须放到子线程不要在 UI 主线程里直接调用model.predict。原因很简单视频连续帧推理可能耗时几百毫秒主线程一旦阻塞窗口会无响应拖动、关闭都会卡死。正确做法是把检测封装到 QThread 或 QRunnable 中使用信号把结果传回主线程。示例from PyQt5.QtCore import QThread, pyqtSignal class DetectThread(QThread): result_ready pyqtSignal(object, object) # frame, boxes def run(self): results self.model.predict(self.source, conf0.3, imgsz640) # 解析结果并发送回主线程这里信号参数可以根据需要调整。只要检测在子线程界面刷新和停止按钮就不会假死。4.3 下拉框闪退和超链接点击问题在 PyQt5 使用中经常遇到“下拉框闪退”。遇到这类问题不要先怀疑 YOLO先做最小复现新建一个空窗口只放 QComboBox能稳定闪退就是 Qt 运行环境问题稳定不闪退再往项目里加代码。常见原因包括QComboBox 的 item 对象被提前释放、信号触发槽函数里操作了已关闭的窗口、PyQt5 和 Qt 的 DLL 版本不匹配。另外PyQt5 的 QTextBrowser 默认可以显示 HTML。如果要把“跳转到桌面端某个目录”或“点击后执行自定义操作”不要依赖默认链接跳转。可以这样做self.log_browser.setOpenLinks(False) self.log_browser.linkActivated.connect(self.handle_link) def handle_link(self, link): # 这里写自定义逻辑比如打开日志文件夹 import os os.startfile(link) # Windows 示例这样可以避免点链接时跳到没意义的浏览器页面也能让“查看检测结果目录”这种操作从日志区直接触达。4.4 图像显示时的关键坑在 PyQt5 中显示 OpenCV 图像时最常见的坑是不做BGR - RGB转换导致画面颜色偏蓝偏暗。另一个坑是 QImage 生命周期问题。用rgb_image.data创建 QImage 时如果原始数组在显示函数结束后被回收画面可能出现花屏或黑线。稳妥做法是显示时把数据复制一份或者把rgb_image保存为窗口类的成员变量。rgb_image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape qimage QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) self.label_image.setPixmap(QPixmap.fromImage(qimage))如果发现显示区域大小不合适可以用setScaledContents(True)让图片缩放填满 QLabel。注意这会影响画框坐标对应的显示位置所以不要把缩放后的显示图直接拿去保存检测结果。保存结果时应该基于原始分辨率重新绘制。5. 核心代码实现与跑通顺序5.1 先做一个无界面的图片检测脚本先实现最简单的图片检测整条链路跑通再写 GUI能节省大量调试时间。图片检测核心代码如下import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/exp/weights/best.pt) image cv2.imread(test.jpg) results model.predict(image, conf0.3, imgsz640) for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf box.conf[0].item() cls int(box.cls[0].item()) label fpothole {conf:.2f} cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(image, label, (int(x1), int(y1) - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)这段代码的逻辑很简单读取图片推理拿到坐标框画到图上。判断成功标准是能看到框位置合理、标签和置信度正常。我这里刻意没有写置信度过滤的二次判断因为model.predict(conf0.3)已经过滤过了。如果你在代码里手动拿到box.conf再过滤也不要重复赋一个不同的阈值否则结果会让人困惑。5.2 视频文件和摄像头实时检测视频检测比图片检测多了一个“循环读帧”的过程。可以把读取帧放在子线程里也可以用 QTimer 驱动。QTimer 的好处是不需要自己处理 while 循环界面更稳定。一个建议的流程打开 VideoCaptureQTimer 每隔 30~50 毫秒读取一帧将帧交给检测函数在界面上刷新结果。“摄像头只识别一次”这个问题通常不是模型的问题而是摄像头读取逻辑写错了。比如有些代码在打开摄像头后只read了一次然后把结果放到了循环外面或者每帧都重新VideoCapture(0)又关闭导致第二帧读不到。正确做法是只打开一次摄像头循环readread失败时打印日志而不是静默跳过。另一个与视频相关的问题是处理速度跟不上摄像头帧率。普通 USB 摄像头是 30 FPS如果处理一帧需要 0.2 秒实际显示不到 5 FPS画面看起来会卡。这不是 bug而是计算瓶颈。可以先降低检测分辨率比如把帧缩到 640 再推理或者隔帧检测。隔帧检测的含义是画面显示始终要流畅检测结果可以每 2 帧或 3 帧更新一次视觉上更舒服。如果摄像头画面只识别一次不要先怀疑模型先检查帧读取循环。5.3 批量图片检测和设备无关的结构批量检测时不要用循环里硬编码文件名。建议用glob获取图片列表在输出目录里按原文件名加_detected后缀保存。每处理完一张图就写一行日志处理失败时打印路径并继续而不是让整个程序中断。要统计成功数、失败数最后输出一个汇总文本。这些看起来很简单但真正用起来才知道有多重要。import glob import os image_paths glob.glob(samples/*.jpg) os.makedirs(output, exist_okTrue) for path in image_paths: try: image cv2.imread(path) results model.predict(image, conf0.3, imgsz640) # 绘制后保存到 output/xxx_detected.jpg print([OK], path) except Exception as e: print([FAIL], path, e)在写 GUI 时也是同样的思路检测函数保持“输入一个 numpy 数组输出绘制结果和框信息”不关心来源是图片、视频还是摄像头。这样后续接口化、多线程化都容易。6. 真实硬件上能跑成什么样6.1 GTX1660Ti 推理参考很多人在问 GTX1660Ti 跑 YOLOv8 需要 GPU 吗。直接说结论GTX1660Ti 有 6GB 显存跑 YOLOv8n 推理完全没问题跑 YOLOv8s 推理也可以接受如果要训练batch 就要控制。不要在 6GB 显卡上尝试 YOLOv8m 以上的大模型显存很容易不够。低配置能跑不代表适合批量跑批量任务中每帧都累积耗时必须看吞吐量和队列长度。给一个判断方法先跑 100 张代表图片记录总耗时得到平均单帧时间。如果你的摄像头是 30 FPS单帧推理必须在 33ms 以内如果平均在 100ms那只能做到约 10 FPS。如果用 CPU实测某些机器单帧超过 300ms只能用于图片巡检不适合实时视频。6.2 批量任务和多摄像头并发架构批量图片检测比较简单按顺序处理即可。但如果要处理大量视频或接入多个摄像头就要设计并发。最容易出问题的做法是为每个摄像头启动一个无限循环线程每个线程里面做一次完整推理。这样线程数一多CPU/GPU 切换开销很大还可能出现同一模型在不同线程同时推理导致的竞争。更稳妥的做法是摄像头读取线程只负责抓帧把帧放到队列一个或两个推理 Worker 从队列取帧检测完的结果再发回主界面。并发数不要拍脑袋。先用 1 路摄像头测再用 2 路、3 路测观察显存、内存和 CPU 占用。如果显存接近上限就降低输入尺寸或增大队列的消费间隔。批量任务还要考虑失败重试某个文件损坏时要能跳过并记录而不是卡在循环里。6.3 边缘设备部署思路热搜里也常常看到 RK3588 部署 YOLOv8、Jetson Orin Nano 部署 YOLOv8。如果你要把这套检测能力放到巡检小车上PyQt5 桌面端只能作为调试工具真正的推理建议放在边缘设备上。一般流程是先在电脑上训练得到 best.pt导出 ONNX再在边缘设备上转换成对应格式比如 RKNN、TensorRT 等。边缘设备上不一定要带 GUI可以只出检测结果通过协议发给上位机显示。7. 常见报错排查链路7.1 模型加载失败现象代码报FileNotFoundError或“.pt is not a YOLO model”。排查顺序先看错误文本是路径不存在还是文件内容不对。确认路径没有中文Windows 下尤其容易出问题。确认权重文件确实是 YOLOv8 训练出来的不是只复制了名字。检查 ultralytics 版本和训练时版本是否一致。如果是在 GUI 里加载失败先把加载代码放到独立脚本里跑一遍排除 UI 环境的影响。7.2 PyQt5 黑屏和闪退现象程序启动后窗口黑屏、闪退或者点击按钮后崩溃。排查顺序在纯 PyQt5 环境里做一个空窗口先排除环境问题。把 OpenCV 的cv2.imshow全部注释掉确保窗口焦点不让给 OpenCV。检查图像转换时数组是否被过早释放。保留完整 traceback而不是只看“段错误”或“崩溃”。很多时候闪退问题不是 PyQt5 代码写错而是 Qt 运行库冲突。这时候优先检查是否安装了多个 Qt 版本或者 PyQt5 相关包版本不一致。7.3 摄像头无画面或只识别一次现象点击打开摄像头后能看到画面但检测只在第一帧执行一次后续不再更新。排查顺序确认 VideoCapture 是否只 open 一次不要每帧都打开。确认读取循环里是否调用read()。确认read()返回值第一项是否为 True。确认处理完一帧后 QTimer 是否继续触发。不要每次循环都新建 YOLO 模型对象模型只需要初始化一次。原因表现处理模型只在初始化时加载正常保持这样模型在循环里重复加载极度卡顿移到初始化阶段read() 失败画面停顿/黑屏打印系统日志检查摄像头占用QTimer 被 stop无后续帧检查停止按钮逻辑7.4 其他常见问题“下拉框闪退”先最小复现再查运行环境最后查代码。“文本框超链接点击后无反应”setOpenLinks(False) 连接linkActivated。“训练时内存不足”降低 batch、imgsz关闭数据加载的多个 worker。“推理速度突然变慢”先看是否有多个程序占用 GPU 显存再看输入分辨率是不是被无意调大。8. 项目落地建议8.1 建议跑通顺序我建议按这个顺序推进用预训练权重跑通单张图片检测。准备自己的坑洼数据集训练并导出 best.pt。写一个无 GUI 的 Python 脚本完成图片、视频、摄像头检测。把检测结果封装成函数接到 PyQt5 界面上。处理批量任务和多路输入。打包交付。不要一上来就做多线程和多摄像头。功能越复杂排查问题越难。先把单任务跑稳再扩展。尤其是把“摄像头实时检测”和“多摄像头并发”分开摄像头单路能跑通才有资格考虑并发。8.2 可以扩展的方向这个项目后续可以扩展的方向很多缺陷分类坑洼、裂缝、修补块、井盖下沉等多类别。结果统计输出每个坑洼的面积估算、中心点坐标方便后续道路养护。报警当检测到较大坑洼时界面弹窗或生成报告。数据回传将检测结果写入数据库便于巡检记录查询。模型改进小目标检测头、注意力模块、更轻量的主干网络。对于大部分实际需求建议先做“多类别分类”和“结果统计”这两项对道路巡检的价值最直接。面积估算可以用像素面积加简单比例换算不一定需要深度相机。8.3 长期维护的关键这类系统最容易出的问题不是模型精度不够而是运行环境变更后无人维护。建议把requirements.txt固定下来记录你的 Python 版本和关键依赖版本。把模型文件放在项目内相对路径下不要依赖系统盘某个绝对路径。每个功能都保留控制台日志方便远程排错。给 GUI 增加“模型选择”下拉框这样以后换模型不用改代码。踩过几次之后我能确定一点坑洼检测系统真正落地不是看模型 mAP 是 80 还是 90而是看你能不能把输入、推理、显示、保存四个环节串得稳。不管你是用 GTX1660Ti 做轻量训练还是准备在 RK3588、Jetson 上边缘部署第一步都应该先跑通最小闭环。先把一张图片检测出来然后慢慢加视频、加摄像头、加并发最后你会发现最难的部分不是 YOLOv8而是环境、路径和线程。