简介本资源面向计算机、人工智能、自动化等专业的在校学生与开发者提供一套基于YOLOv8与PyQt5的自行车违规停放检测告警项目源码可用于毕业设计、课程设计、学科竞赛或项目立项演示。项目包含自行车数据集、已训练好的YOLOv8模型及评估曲线、简易GUI界面和超详细部署教程模型准确率约98%按步骤操作即可运行也支持自行训练其他模型。压缩包共805个文件约431.65MB涵盖276张jpg图像、247个txt标注、80个py脚本、46个yaml配置、33个md文档及6个pt权重文件另含Dockerfile、cpp推理示例与csv评估结果结构完整。目前已有1855人学习下载。读者可获得从数据标注、模型训练到界面交互的完整方案并借助排错思路与目录组织快速复现适合在此基础上二次开发或扩展新功能。1. 从一张违停自行车照片说起这套 YOLOv8PyQt5 源码到底能跑出什么自行车乱停乱放这件事物业和城管都头疼。人工盯监控不现实一个摄像头一天产生几万帧靠人眼筛违规停放成本高还容易漏。这套源码解决的正是这个场景用 YOLOv8 做自行车目标检测配合 PyQt5 搭一个桌面端 GUI把检测结果实时显示出来检测到违规停放就告警。它不是一个空壳 demo而是带了数据集、训练好的模型权重、可运行的界面代码和部署文档的完整包拿来就能跑改改就能用。适合谁做毕设的学生可以直接在此基础上改需求省掉从零搭环境、找数据、调界面的时间做安防或园区管理的工程师可以把它当成一个原型验证工具先跑通检测逻辑再考虑上边缘设备。需要说明的是这套东西的核心价值在于「完整链路」——从数据标注格式、模型训练配置、推理脚本到 GUI 信号槽联动每一环都有可复现的文件而不是只给一个模型文件让你自己猜怎么用。2. 拆开这个包数据集、模型权重和 PyQt5 界面各自负责什么2.1 数据集的组织方式与标注格式拿到一个目标检测项目第一件事不是急着跑训练而是看数据集怎么组织的。这套源码里的数据集按 YOLO 标准格式存放目录结构通常是这样的dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ # 训练集标注 txt │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件每张图片对应一个同名的.txt标注文件每行格式为class_id x_center y_center width height坐标都是归一化到 0~1 的值。自行车违规停放检测通常只涉及一个类别所以class_id基本都是 0。data.yaml里写清楚训练集、验证集路径和类别名称# data.yaml - 数据集配置 path: ./dataset # 数据集根目录 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 test: images/test # 测试集相对路径 nc: 1 # 类别数量自行车违停只有一类 names: [bicycle] # 类别名称列表这里有个容易忽略的点path写相对路径时YOLOv8 是相对于你执行训练命令时的工作目录来解析的不是相对于data.yaml文件本身。我一般会写成绝对路径或者确认工作目录后再跑否则训练启动时报「No labels found」大概率就是路径没对上。2.2 模型权重与训练配置源码包里带的模型权重一般是best.pt这是训练过程中验证集指标最好的那一版。YOLOv8 的权重文件不大n 版本大概 6MB 左右s 版本 22MB 左右具体用哪个版本取决于源码作者的训练配置。如果你要自己重新训练基础模型可以从官方预训练权重开始# 从头训练基于 yolov8n 预训练权重微调 yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/bicycle \ nameexp1参数逐个说清楚data指向数据集配置文件model指定基础权重用yolov8n.pt是因为自行车检测场景相对简单n 版本速度快、显存占用低GTX 1660 Ti 这个级别的卡就能跑epochs100是常见起点数据集小的话 50 也能收敛imgsz640是 YOLOv8 的默认输入尺寸如果你的图片里自行车目标很小可以提到 1280但显存和推理时间都会涨batch16根据显存调整8G 显存跑 n 版本 640 尺寸16 基本是上限device0指定第一块 GPU没有 GPU 就写cpu但训练速度会慢很多。训练完成后runs/bicycle/exp1/weights/目录下会有best.pt和last.pt。best.pt用于推理last.pt是最后一轮的权重一般不用。如果你想把训练过程的损失曲线画出来YOLOv8 训练结束后会自动在exp1目录下生成results.csv用 pandas 读出来画就行import pandas as pd import matplotlib.pyplot as plt # 读取训练日志 df pd.read_csv(runs/bicycle/exp1/results.csv) df.columns df.columns.str.strip() # 列名可能有空格先清理 fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain box loss) axes[0].plot(df[epoch], df[val/box_loss], labelval box loss) axes[0].set_xlabel(epoch) axes[0].set_ylabel(loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].set_xlabel(epoch) axes[1].set_ylabel(mAP50) axes[1].legend() plt.tight_layout() plt.savefig(training_curve.png, dpi150)这段代码的逻辑很直接把results.csv读进 DataFrame取train/box_loss、val/box_loss和metrics/mAP50(B)三列画图。注意列名里可能有前导空格所以先做strip()。mAP50 是 IoU 阈值 0.5 时的平均精度自行车检测一般能到 0.85 以上就算不错了。如果验证集 loss 一直不降大概率是标注有问题或者学习率太大。2.3 PyQt5 界面与推理线程的联动PyQt5 这边最核心的问题是推理是耗时操作不能放在主线程里否则界面直接卡死。这套源码的 GUI 部分通常会把推理逻辑放在QThread子类里通过信号槽把检测结果传回主线程更新界面。一个典型的推理线程长这样from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO import cv2 class DetectThread(QThread): # 定义信号传递检测后的帧和告警状态 frame_signal pyqtSignal(object) alarm_signal pyqtSignal(bool) def __init__(self, model_path, source0): super().__init__() self.model YOLO(model_path) # 加载模型权重 self.source source # 0 表示默认摄像头 self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break # 推理conf 阈值设 0.5低于这个值不认为是自行车 results self.model(frame, conf0.5, verboseFalse) annotated results[0].plot() # 把检测框画到帧上 # 判断是否有检测结果有则触发告警 has_detection len(results[0].boxes) 0 self.alarm_signal.emit(has_detection) self.frame_signal.emit(annotated) cap.release() def stop(self): self.running False self.wait()逻辑说明DetectThread继承QThread在run()里打开摄像头逐帧读取每帧送入 YOLO 模型推理。conf0.5是置信度阈值低于 0.5 的检测框会被过滤掉这个值可以根据实际场景调误检多就调高漏检多就调低。results[0].plot()是 Ultralytics 提供的可视化方法直接把框和类别画在帧上返回 numpy 数组。alarm_signal在检测到目标时发出True主线程收到后可以触发界面上的告警提示或者记录日志。主线程这边接收信号并更新界面class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label QLabel(self) # 用于显示视频帧 self.setCentralWidget(self.label) self.thread DetectThread(best.pt, source0) self.thread.frame_signal.connect(self.update_frame) self.thread.alarm_signal.connect(self.handle_alarm) self.thread.start() def update_frame(self, frame): # 把 OpenCV 的 BGR 帧转成 Qt 能显示的 RGB 格式 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) def handle_alarm(self, active): if active: self.statusBar().showMessage(检测到自行车请注意) else: self.statusBar().showMessage(正常)这里的关键点是cv2.cvtColor把 BGR 转 RGB因为 OpenCV 读进来是 BGR 顺序Qt 显示需要 RGB不转的话颜色会偏。QImage构造时ch * w是每行字节数这个参数写错会导致图像错位。信号槽的机制保证了推理线程和界面线程各干各的不会互相阻塞。3. 从零跑通这套源码环境配置、推理和界面启动的完整步骤3.1 环境配置与依赖安装这套源码依赖的东西不多但版本对不上就容易出玄学问题。我一般会先建一个干净的虚拟环境避免和系统里的包打架# 创建虚拟环境Python 版本建议 3.8~3.10 conda create -n bicycle_detect python3.9 -y conda activate bicycle_detect # 安装 PyTorch根据你的 CUDA 版本选对应命令 # CUDA 11.8 的情况 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics 和 PyQt5 pip install ultralytics pyqt5 opencv-pythonPyTorch 的安装命令一定要去官网查对应你 CUDA 版本的装错了要么用不了 GPU要么直接报错。ultralytics包会自动带上 YOLOv8 的推理和训练接口不需要单独装yolov8包。PyQt5 用 pip 装就行版本一般用 5.15.x 比较稳。验证环境是否正常import torch print(torch.__version__) print(torch.cuda.is_available()) # 有 GPU 且驱动正常应返回 True from ultralytics import YOLO model YOLO(yolov8n.pt) # 会自动下载预训练权重 print(YOLO loaded ok) from PyQt5.QtWidgets import QApplication print(PyQt5 ok)如果torch.cuda.is_available()返回False先检查显卡驱动版本和 CUDA 版本是否匹配再检查装的是不是 GPU 版的 torch。CPU 也能跑推理只是帧率会低不少。3.2 用训练好的权重做单张图片推理在启动 GUI 之前建议先用命令行验证模型权重能不能正常出结果# 单张图片推理保存结果 yolo detect predict \ modelbest.pt \ sourcetest_image.jpg \ conf0.5 \ saveTrue \ projectruns/predict \ nametestsource可以换成图片目录、视频文件或者0摄像头。saveTrue会把画了框的结果图存到runs/predict/test/下。跑完去看一眼结果图如果框的位置明显不对说明模型权重和数据集不匹配或者标注格式有问题。这一步是后面 GUI 能正常显示的前提命令行都跑不通界面里更跑不通。3.3 启动 PyQt5 界面并接入摄像头确认模型没问题后启动主界面程序。源码包里一般会有一个main.py或者gui.py作为入口python main.py启动后界面通常包含几个区域视频显示区、开始/停止按钮、置信度调节滑块、告警状态栏。点击「开始检测」后程序会打开摄像头推理线程开始工作检测到的自行车会被画框显示同时状态栏提示告警。如果你想改成读取本地视频文件而不是摄像头把DetectThread初始化时的source参数从0改成视频文件路径就行# 读取本地视频 self.thread DetectThread(best.pt, sourcetest_video.mp4)这里有个实际部署时经常碰到的问题摄像头分辨率太高会导致推理帧率下降。常见做法是在cap.read()之后加一步缩放ret, frame cap.read() if not ret: break # 缩放到宽度 640保持宽高比 h, w frame.shape[:2] scale 640 / w frame cv2.resize(frame, (640, int(h * scale)))缩放到 640 宽之后推理速度会明显提升显示效果也够用。如果要做告警记录可以在handle_alarm里加保存截图和时间的逻辑方便后续查证。4. 避坑与排查跑这套源码时最容易翻车的五个地方4.1 现象训练时 loss 不降mAP 一直是 0原因最常见的是标注文件路径不对或者标注格式错误。YOLO 要求每张图片对应一个同名 txt且 txt 里每行是归一化坐标。如果标注是 VOC 的 XML 格式没转过来或者坐标没归一化模型学不到任何东西。解决先检查labels/train/下有没有和images/train/同名的 txt 文件。然后随便打开一个 txt确认每行是 5 个 0~1 之间的数。如果是从其他格式转过来的用脚本重新转一遍转换后抽查几张图的可视化结果。4.2 现象PyQt5 界面启动后点「开始检测」直接卡死原因推理逻辑写在了主线程里model(frame)是阻塞调用主线程被占住界面无法响应。解决确认推理代码在QThread子类的run()方法里而不是直接写在按钮的槽函数里。如果已经用了线程但还是卡检查run()里有没有死循环没有退出条件while self.running的running标志要在stop()里能置为False。4.3 现象摄像头画面颜色发蓝或发红原因OpenCV 读进来是 BGR 顺序Qt 的QImage默认按 RGB 解析不转换就会偏色。解决在update_frame里加cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)。如果已经加了还偏色检查QImage构造时的bytesPerLine参数是不是ch * w写错了会导致像素错位。4.4 现象GPU 显存不够训练报 CUDA out of memory原因batch设太大或者imgsz设太高。YOLOv8n 在 640 尺寸下batch16 大概占 4~5G 显存如果同时开了其他占显存的程序就容易爆。解决先把batch降到 8 或 4再不行就把imgsz降到 416。另外可以在训练命令里加ampTrue开启混合精度能省一部分显存。如果卡本身显存就小比如 4G建议直接用yolov8n并且batch4。4.5 现象换了自己的数据集后检测框位置偏移严重原因新数据集的图片尺寸和训练时用的imgsz差异太大或者标注时用的工具输出的坐标格式不是 YOLO 格式。解决统一把训练和推理的imgsz设成同一个值。标注格式用labelImg或roboflow导出时选 YOLO 格式导出后抽查几张图的标注框是否贴合目标。如果图片长宽比差异大可以在data.yaml同级目录加一个rectTrue参数让训练时按矩形推理减少 padding 带来的偏移。5. 进阶玩法把检测结果落成告警日志和可视化统计跑通基础检测之后这套源码还能往上加东西。最实用的一个方向是把告警事件落成日志方便后续统计哪个时间段违停最多、哪个区域最频繁。在handle_alarm里加一段写文件的逻辑就行import csv from datetime import datetime class MainWindow(QMainWindow): def __init__(self): # ... 前面的初始化代码 ... self.log_file alarm_log.csv # 写入表头如果文件不存在 with open(self.log_file, a, newline) as f: writer csv.writer(f) if f.tell() 0: writer.writerow([timestamp, event, confidence]) def handle_alarm(self, active, conf0.0): if active: self.statusBar().showMessage(检测到自行车请注意) # 记录告警事件 with open(self.log_file, a, newline) as f: writer csv.writer(f) writer.writerow([ datetime.now().strftime(%Y-%m-%d %H:%M:%S), bicycle_detected, round(conf, 3) ]) else: self.statusBar().showMessage(正常)这段代码在每次检测到自行车时往alarm_log.csv追加一行记录包含时间戳、事件类型和置信度。f.tell() 0用来判断文件是不是空的空的话先写表头。有了这个日志你可以用 pandas 按小时或按天聚合画出违停事件的时间分布图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(alarm_log.csv, parse_dates[timestamp]) df[hour] df[timestamp].dt.hour hourly df.groupby(hour).size() hourly.plot(kindbar, figsize(10, 4)) plt.xlabel(Hour of day) plt.ylabel(Alarm count) plt.title(Bicycle violation alarms by hour) plt.tight_layout() plt.savefig(alarm_by_hour.png, dpi150)这个统计图能直接放进毕设论文里比单纯截几张检测效果图有说服力得多。另外如果你想把告警信号接到外部设备上比如声光报警器可以在handle_alarm里通过串口发指令常见做法是用pyserial往指定串口写一个字节硬件端收到就触发报警。这块我没有在源码里看到现成的实现但接口留得很清楚加进去不难。还有一个值得试的方向是调整置信度阈值和 NMS 的 IoU 阈值来平衡误检和漏检。YOLOv8 推理时可以通过model(frame, conf0.5, iou0.45)来同时指定两个参数。conf控制低于多少分的框被丢弃iou控制重叠框合并的阈值。自行车违停场景下如果画面里自行车密集iou可以适当调低到 0.4 左右避免相邻车辆被合并成一个框。从那以后我每次拿到一个新的检测项目都会先用命令行跑单张图确认模型没问题再启动 GUI最后才去调界面细节。这个顺序能省掉大量在界面和模型之间来回排查的时间。希望帮到你。本文还有配套的精品资源点击获取