ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv8和PyQt5的自行车违规停放检测告警系统设计

基于YOLOv8和PyQt5的自行车违规停放检测告警系统设计 简介这是一套基于YOLOv8与PyQt5的自行车违规停放检测告警项目源码面向需要完成课设、毕设或算法竞赛的在校学生以及希望快速搭建检测系统的开发者。包内共805个文件压缩后约431.65MB涵盖276张jpg图像、247个txt标注、80个py脚本、46个yaml配置、6个pt权重模型以及部署所需的Dockerfile、GUI界面代码和mp4演示视频从数据集划分到模型训练、评估与推理均有覆盖。自行车数据集与训练好的yolov8模型可直接投入使用准确率达98%配套评估曲线和超详细部署文档便于按步骤复现。已有1855人学习下载适合用于课程设计、毕业设计或共享自行车停放管理项目的前期验证与二次开发。1. 这个自行车违规停放检测告警毕设项目到底在解决什么想必你见过小区楼道口、地铁站出口、写字楼消防通道里横七竖八的自行车和电动车。物业管理靠人巡逻保安不可能二十四小时盯着监控画面。如果有一条视频流进来程序自动把自行车检测出来再判断它是不是停进了不该停的区域一旦停留超过阈值就弹窗告警这就是基于YOLOv8和PyQt5的自行车违规停放检测告警项目要做的事。它本质上是目标检测加业务规则判定的组合YOLOv8负责在画面里找出“自行车”这类目标PyQt5负责把检测结果变成一个人能看的界面并用连续帧判定、ROI区域过滤等手段把单纯的检测结果转成“是否违规停放”的告警事件。项目源码里通常已经包含数据集、训练好的模型、一个简易的GUI界面和部署教程适合拿来做毕业设计也适合想快速搭一个视觉告警demo的开发者。我不建议你拿到源码先跑训练——那样很容易被环境安装、数据集格式、参数调优一堆问题劝退。文章会按“场景定义 → 数据准备 → 模型训练 → GUI联调 → 排错 → 验证进阶”的顺序把每一步的关键决策和坑点讲清楚。下面全程基于常见做法和我自己的经验你可以直接照着改。2. 从监控画面到训练样本违规怎么定义、数据集怎么攒这一章必须先回答一个问题YOLOv8检测出来的是“自行车”但“违规停放”不是目标检测能直接输出的概念。目标检测模型只会给你每个目标的类别、置信度和框坐标它不知道消防通道在哪也不知道这辆车停了几分钟。所以做系统的第一步是把业务规则转化成代码逻辑。2.1 违规判定不能只靠一个检测框先定义“违规”的边界常见做法是引入“禁停区域ROI”和“停留时长”两个条件。比如在GUI左侧画面上用鼠标画一块多边形区域覆盖消防通道程序每秒从视频流取一帧只要检测到自行车且其检测框中心点落在ROI内就给这个目标累加停留计数当计数连续达到阈值比如检测到目标在ROI内持续出现10秒以上触发告警。这里有个细节容易被新手忽略检测是无状态的YOLOv8这一帧检测到自行车下一帧可能因为遮挡或者模糊就丢掉了。如果直接用单帧判断会产生大量误告警。所以你需要在代码里维护一个目标队列用IoU交并比做跨帧匹配。当前帧某个检测框和上一帧某个框的IoU大于0.5就认为是同一辆车把停留时间延续下来没有匹配上的新框重新计时连续若干帧都匹配不上的直接从队列里移除。这条逻辑比模型本身还容易翻车。很多毕设源码只做了“检测到自行车就告警”没有区域判定也没有去抖结果演示时任何骑车路过的人都会触发告警评委一质疑就不敢说这是“违规检测”。如果你手里的源码是这种半成品在做训练前先花半小时把ROI判定和跨帧匹配补上这是整个项目最值钱的部分。2.2 数据集从哪来用公开数据还是自己标注YOLOv8训练需要的是VOC或YOLO格式的标注文件一张图片对应一个txt每行内容为类别id 中心x 中心y 宽度w 高度h全部归一化到0-1。对自行车检测这个任务类别可以只保留一个“bicycle”也可以拆成“bicycle”和“electric_bicycle”如果场景里电动车很多。通常毕设只做自行车类别id设0即可。数据来源我一般推荐“公开数据集起步 自己标注补差”。常见做法是去Roboflow、Kaggle上找bicycle detection数据集或者用COCO数据集里所有标注为bicycle的图片过滤出来。COCO的类别文件里bicycle是第2类id2如果你用COCO预训练权重继续训练最好沿用它的类别映射如果你只用yolov8n.pt从头训那data.yaml里自己定义类别就行。最省力的方案是直接用Ultralytics生态里的coco128、coco8这种小数据集先跑通流程再换到自己的完整数据集。因为训练脚本本身不区分数据集是谁标注的只要data.yaml路径正确、txt文件数量和图片一一对应就能开始训练。深坑在后面很多人下载了某个GitHub仓库的数据集但图片文件名和标注文件名大小写不一致或annotation里混着VOC格式的xml导致训练时检出的目标数为0下面3.2会专门讲怎么排查。2.3 用 labelme 打标注时先把 pyqt5 依赖的坑填平自己标注的场景开源的标注工具有两个方向labelImg和labelme。labelImg直接输出YOLO格式最省事labelme输出的是json还得转一道。但很多毕设教程选用labelme因为它在画多边形和多类别时更顺手网上相关教程也多。需要提醒的是labelme是基于PyQt5开发的GUI工具你在装labelme时常常会连带装进一个PyQt5之后再做GUI开发又得手动装一遍PyQt5两个版本冲突概率极高。更常见的问题是python 3.8以上环境里执行pip install labelme可能会因为Qt平台插件缺失出现qt.qpa.plugin: could not load the Qt platform plugin xcb。现象是点击图标没反应或直接闪退原因多数是PyQt5安装不完整或OpenCV的Qt依赖被一起引入导致库冲突。解决顺序卸载重装PyQt5安装libxcb相关的系统依赖再给PyQt5指定C运行时库目录。给想快速开工的人一个最小步骤用labelImg做矩形框标注直接生成YOLO格式# 创建虚拟环境避免系统Python环境被搞乱 conda create -n label python3.8 -y conda activate label pip install labelImg # 启动标注工具 labelImg启动后先在“Change Save Dir”里选保存标注的目录再点“Open Dir”选图片目录左下角“YOLO”按钮确保是YOLO模式而非PascalVOC模式。画完框后按CtrlS保存右边能看到自动生成的classes.txt和每张图片对应的txt文件。这个步骤没有太多可调参数但有一个点很关键图片目录里的文件名不要带中文也不要带空格否则labelImg保存路径解析会出错训练时读不到文件。关于标注密度我的经验是单类自行车目标一张图里小目标多、遮挡多一个目标一个框尽量让框完整包住车身不要把前后轮拆成两个框。数据集规模上做毕设至少准备800到1500张图其中至少100张是俯拍角度、夜间的不然训练出来的模型在真实监控画面里泛化很差。最后用下面的脚本把图片和txt按8:1:1划分成train、val、test三个目录这是YOLOv8训练最常见的数据目录结构。import os, random, shutil dataset_root bicycle_data images [f for f in os.listdir(os.path.join(dataset_root, images_all)) if f.endswith(.jpg)] random.seed(42) train, val, test [], [], [] for i, f in enumerate(images): if i % 10 9: test.append(f) elif i % 10 8: val.append(f) else: train.append(f) for split, files in [(train, train), (val, val), (test, test)]: img_dir os.path.join(dataset_root, split, images) lbl_dir os.path.join(dataset_root, split, labels) os.makedirs(img_dir, exist_okTrue) os.makedirs(lbl_dir, exist_okTrue) for f in files: shutil.copy(os.path.join(dataset_root, images_all, f), img_dir) shutil.copy(os.path.join(dataset_root, labels_all, f.replace(.jpg, .txt)), lbl_dir)脚本逻辑把images_all里的所有图片按索引取模分配到train、val、test三个集合对应的同名txt文件也被拷过去。这里用8:1:1的比例一共分成三个目录。注意两个容易出错的地方一是labels_all和images_all的目录必须真实存在二是jpg和txt文件名必须完全一致如果你之前存的是jpeg或png要先把后缀统一成jpg。先跑通再改进用labelImg标注100张图训练一个模型看能不能检出然后决定要不要补数据。一上来标2000张图再训练发现标注格式有错返工成本太高。验证标注格式是否正确用一行代码读一个txt看看内容第一行应该是0 0.5 0.5 1 1这种归一化坐标如果出现大于1或者负值说明标注工具设置有问题。再补充一条更快的数据检查方法用OpenCV把标注框画回图像肉眼确认框没有偏。下面的脚本能在一张图上画出所有框import cv2 img cv2.imread(/path/to/image.jpg) h, w img.shape[:2] with open(/path/to/image.txt) as f: for line in f: cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(/tmp/check.jpg, img)这里把归一化坐标还原成像素坐标画出来是绿色框。如果发现框跑偏、宽高为0基本就是标注文件损坏需要回到labelImg里重新标注。这一步虽然笨但能避免你后面花了几个小时训练出一个永远检不出目标的模型。3. YOLOv8 训练自己的数据集环境到参数一条命令跑通数据准备好了接下来是训练。这一章分成环境、配置、结果判读三块。很多人卡在环境上尤其是用CPU训练的所以我先把环境搭建讲透。3.1 从零搭 YOLOv8 训练环境CPU版和GPU版都怎么装Ultralytics的YOLOv8代码包可以用pip直接安装也可以git clone源码。做毕设最省事的路线是pip安装因为训练、验证、导出命令都集成在ultralytics命令行里。CPU环境下的安装流程# 建议在conda虚拟环境里装避免和系统Python冲突 conda create -n yolov8 python3.10 -y conda activate yolov8 # 装CPU版pytorch注意不要直接pip install torch这样会拖4GB以上的包 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics安装后验证yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg如果这条命令能输出一张带框的bus.jpg预测图说明环境OK。这里有个常见坑ultralytics安装时会自动装依赖opencv-python而在Ubuntu20.04上如果之前装过libopencv两个OpenCV同时存在导入cv2时会段错误Segmentation fault。解决办法是卸载libopencv系统库只用pip的opencv-python。至于GPU版先查显卡驱动支持然后安装对应CUDA版本的PyTorch。常见的坑是GTX 1660 Ti这种老卡在更高CUDA版本下算不动建议CUDA 11.8以下配PyTorch 2.0。显存不够6GB的直接用yolov8n模型不要碰yolov8l。3.2 写 data.yaml 和训练命令imgsz、epochs、batch 三个参数最影响结果YOLOv8训练需要data.yaml内容很简单train: /home/user/bicycle_data/train/images val: /home/user/bicycle_data/val/images nc: 1 names: [bicycle]训练命令yolo train data./bicycle_data/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ devicecpu \ patience20如果是GPU把devicecpu改成device0batch可以提到32。参数说明modelyolov8n.pt表示加载COCO预训练权重做迁移学习虽然COCO里有bicycle但它不等于你已经有了自行车数据集只表示底层的特征提取网络已经见过大量真实图像训练收敛会更快。imgsz640是输入分辨率监控画面通常1280x720或1920x1080这里有个技巧测试时用imgsz640训练、imgsz1280推理能明显提升小目标召回但推理速度会降低GUI里要权衡。epochs100在数据集小于2000张时足够别设300纯属浪费时间patience20表示连续20个epoch损失没下降就自动停止能省大量训练时间。batch16是在CPU上比较稳妥的值GPU显存8GB以上能用batch32。训练过程中注意观察终端的三个指标box_loss、cls_loss、dfl_loss它们分别表示回归框损失、分类损失、分布焦点损失。正常情况会随着epoch下降然后趋平。如果loss曲线在训练后期开始回升那是过拟合减少epoch或者加数据增强。一个容易被忽略的点训练前检查data.yaml路径。很多人用相对路径比如train: dataset/train/images但启动命令时当前目录和数据目录不在同一个位置导致报错“Dataset ‘bicycle_data’ images not found”。解决办法是全部用绝对路径或者把data.yaml放在数据集根目录并保证命令从数据集根目录下执行。另外数据目录里图片数量和label数量必须一致差一个都会让train中途卡住。我们用一个bash脚本快速检查#!/bin/bash for split in train val test; do img_count$(ls -l $split/images/*.jpg 2/dev/null | wc -l) txt_count$(ls -l $split/labels/*.txt 2/dev/null | wc -l) echo $split images$img_count labels$txt_count done如果images和labels数量不一致找出多余文件删掉。这种数量不匹配往往是标注过程中某张图保存了但忘记生成txt导致。3.3 训练完看什么weights、results.csv、损失曲线怎么读训练结束后在runs/detect/train/目录下会生成weights/last.pt和weights/best.pt以及results.png和results.csv。这里最容易让新手困惑的是last.pt是最后一个epoch的权重best.pt是根据验证集上mAP最高保存的权重两者的选择直接影响GUI推理效果。我建议GUI里边默认加载best.pt不要贪方便加载last.pt因为last.pt有可能过拟合已经回来了验证指标不如best。results.csv是逐epoch的指标记录用Excel或pandas能打开里面包含train/box_loss、val/box_loss、metrics/precision、metrics/recall、metrics/mAP50(B)、metrics/mAP50-95(B)。画损失函数曲线图用以下脚本可以直接读csv出图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df df.rename(columnslambda x: x.strip()) plt.figure(figsize(8, 5)) plt.plot(df[epoch], df[train/box_loss], labeltrain box_loss) plt.plot(df[epoch], df[val/box_loss], labelval box_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss_curve.png, dpi150)从损失曲线图能看出三类问题一是val/box_loss持续不降说明模型容量不够或数据标注噪声过大二是train/box_loss下降但val/box_loss回升过拟合了三是loss曲线早期就趋平说明数据和类别太简单需要把imgsz调大或加更多难例。对自行车这种刚性物体box_loss通常降到0.03以下就算不错mAP50能到0.85以上就能用于告警演示。如果mAP50只有0.6说明框偏得厉害先检查标注框是否贴边。4. 用PyQt5把检测变成告警GUI线程模型和告警去重逻辑模型训练好只是第一步毕设验收时评委要看的是你那个界面里能不能实时框出自行车并弹告警。PyQt5在这里的作用是把检测脚本变成一个能交互的工具。很多源码的GUI写得很基础一个QLabel显示画面一个按钮开始检测一个文本框输出检测结果。但只要你点开始按钮后画面一卡一卡的评委就会觉得项目不行。所以线程模型必须从一开始就设计对。4.1 GUI的三层结构主线程、检测线程、业务逻辑怎么分工PyQt5的GUI事件循环必须在主线程所有界面控件的刷新也必须在主线程。如果摄像头的读取、YOLOv8推理放在主线程每一帧推理耗时一旦超过300ms整个界面就会冻结窗口拖不动、按钮没反应。更麻烦的是QTimer回调里不能做耗时操作。所以要做三件事用QThread放视频读取和推理用信号把处理好的帧传回主线程用mutex或queue保护帧缓存。一个最小可跑的检测线程骨架import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectThread(QThread): frame_ready pyqtSignal(np.ndarray) result_ready pyqtSignal(list) def __init__(self, model_path, source): super().__init__() self.model YOLO(model_path) self.cap cv2.VideoCapture(source) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: break res self.model(frame, verboseFalse)[0] boxes res.boxes.xyxy.cpu().numpy() confs res.boxes.conf.cpu().numpy() cls_ids res.boxes.cls.cpu().numpy() # 把框画在帧上 for box, conf, cls_id in zip(boxes, confs, cls_ids): x1, y1, x2, y2 map(int, box) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fbicycle {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) self.frame_ready.emit(frame) self.result_ready.emit(boxes.tolist()) self.msleep(20) # 控制帧率 def stop(self): self.running False self.cap.release() self.wait()这里的关键点model推理放在QThread.run()内部循环里frame_ready和result_ready两个信号把绘制好的帧和结构化结果抛给主线程。主线程里只需连接信号self.detect_thread DetectThread(best.pt, 0) self.detect_thread.frame_ready.connect(self.show_frame) self.detect_thread.start()然后show_frame里直接setPixmap。检测结果的列表可以送到业务逻辑层做违规判定不要在run()里把业务逻辑写死否则改ROI又得改线程代码。注意pyqtSignal传递numpy.ndarray会自动转成QVariant但格式是普通的ndarray没问题res.boxes.conf这种对象要先用.cpu().numpy()转成numpy数组否则信号里传的是torch.Tensor在主线程dereference会报错。4.2 告警去重与误报抑制跨帧匹配、冷却时间、停留阈值告警逻辑我建议单独做成一个类不掺在GUI和检测线程里。它接收检测结果维护一个目标队列输出告警事件。核心参数有三个进入ROI判定时间阈值默认5秒、连续帧数阈值默认10帧、告警冷却时间默认30秒。这三个值在GUI界面上做成可调的比写死在代码里更能加分。跨帧匹配用IoU。下面的函数判断当前目标是否和之前某个目标相同def calculate_iou(box1, box2): x1, y1, x2, y2 box1 xa, ya, xb, yb box2 inter_x1, inter_y1 max(x1, xa), max(y1, ya) inter_x2, inter_y2 min(x2, xb), min(y2, yb) inter_w max(0, inter_x2 - inter_x1) inter_h max(0, inter_y2 - inter_y1) inter_area inter_w * inter_h area1 (x2 - x1) * (y2 - y1) area2 (xb - xa) * (yb - ya) return inter_area / (area1 area2 - inter_area 1e-6)匹配策略对当前帧每个新框和队列所有目标计算IoU找最大IoU的那个如果IoU0.3就把该目标的时间戳更新为当前帧如果中心点在ROI内就在停留计数上1。超过阈值的触发告警同时记录告警时间在冷却时间内不再重复报警。这里最容易被忽略的是误报抑制检测器经常把远处的行人当成自行车或把停在墙边的车辆框飘来飘去。所以要同时要求“目标连续N帧都在ROI内”和“累计停留超过T秒”两者都满足才告警。还有一个细节是帧率和时间换算检测线程可能因为CPU繁忙导致每帧耗时波动不要用frame_count当时间单位要用time.time()算真实秒数否则卡顿一下就会跳秒。一个简化但有效的规则是如果同一目标在ROI内停留超过5秒且这5秒内该目标至少出现在80%的帧里就触发一次告警然后对该目标标记done不再重复触发。写代码时用字典存每个目标的start_time用集合标记done。4.3 把模型接进界面的线程安全写法在PyQt5里另一个翻车点是信号传出大尺寸帧或列表时主线程来不及处理导致信号队列积压内存一路涨。解决办法是给frame_ready信号加节流检测线程每处理一帧就emit但主线程在setPixmap时先判断一下上一帧是否已经显示完或者干脆把帧率限制在15fps以下。我一般会在检测线程里维护一个last_emit_time距离上次emission超过33ms才发新帧这样界面就不会越刷越卡。还见过不少源码在QLabel上直接叠加绘制框但QLabel的pixmap更新其实是全量重绘如果视频分辨率是1920x1080每次setPixmap都会消耗主线程两三毫秒多路视频直接卡死。建议在展示前把推理后的帧缩放成800像素宽度再转QImage。还可以用QLabel.setScaledContents(True)配合固定尺寸显示省掉手动缩放但这样画面会变形。更稳的做法是保持宽高比缩放def resize_frame(frame, width800): h, w frame.shape[:2] scale width / w new_w, new_h int(w * scale), int(h * scale) return cv2.resize(frame, (new_w, new_h))然后frame转QImage注意cv2默认通道是BGR要转成RGB再交给Qtrgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qimage QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimage).copy())那段QImage(rgb_image.data)要求rgb_image的生命周期一直存活到setPixmap完成否则内存会被释放导致画面花屏。所以最好在函数内先转换为QPixmap再返回或者把rgb_image作为成员变量保存到下一次更换。这是一个只看书上写不出来的细节。5. 部署与常见问题排查环境、速度、GUI卡死的五个真实案例这部分是血泪经验汇总。前面每一步都可能埋雷我把最常遇到的五个问题按现象、原因、解决三段写。排查这些东西时别闷头改代码先用最简命令复现再逐层缩小范围。5.1 现象labelme 装完就打不开报 Qt platform plugin xcb发生在我同事的Ubuntu20.04上pip install labelme后双击图标没有任何反应终端启动报qt.qpa.plugin: could not load the Qt platform plugin “xcb”。原因PyQt5的Qt库依赖系统里的xcb插件而系统缺少libxcb-cursor0或libxcb-icccf4等运行时库也可能是系统里有Anaconda自带Qt库和系统Qt库冲突。解决先删掉当前环境里的PyQt5重新安装最新版然后执行sudo apt install libxcb-cursor0 libxcb-icccf4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev。如果还不行检查LD_LIBRARY_PATH里有没有别的Qt目录导出有就清掉。这个坑在服务器上用conda环境尤其常见因为你也没法重装系统只能一点点补库。5.2 现象CPU 跑 YOLOv8 慢到没法实时开完GUI检测线程15秒才能刷新一帧妥妥的幻灯片。原因CPU版torch本身没有优化加速再加上YOLOv8默认推理参数是imgsz640对CPU来说运算量很大。另外如果用了yolov8s或者yolov8l模型那就更慢了。很多毕设源码默认加载yolov8s.pt从没意识到要换成nano。解决把模型换成yolov8n.pt这能把推理时间缩短一半以上推理时显式指定imgsz480或416会牺牲一点小目标检测但换来了可用性。如果你用的是OpenCV的DNN后端而不是PyTorch推理效果也差不多。另外线程里用model.predict(sourceframe)和model(frame)差别不小后者更省时间再把verboseFalse关掉能省掉打印日志的开销。别忘了一个关键如果你的CPU支持AVX2要在运行时设置OMP_NUM_THREADS4或者6PyTorch默认把线程全拉满反而导致上下文切换开销。CPU上经验数据是yolov8n、416分辨率、单线程推理大概0.8到1.2秒多线程并行能到0.5秒左右够做演示但不够监控级实时。真要实时去用TensorRT或OpenVINO下面第6章给思路。5.3 现象GUI 打开摄像头后界面卡死点“开始检测”界面直接失去响应Windows任务管理器显示程序未响应。原因前面4.1说了主线程里做了摄像头读取和推理或者QTimer使用了过短的间隔比如5ms主线程被回调阻塞。还一种情况QLabel显示帧时用了setPixmap但帧分辨率太大加上转QImage的方式不对导致每帧copy开销过高。解决把读取和推理从主线程迁到前面给的DetectThread里确保start后主线程能立刻返回。如果你已经迁了线程还卡检查是不是在slot函数里做了cv2.imshow或者在槽函数里再调用了model推理。把槽函数只做ui更新和列表记录告警逻辑放到单独线程。实在排查不出来就在槽函数入口打印一条日志确认槽函数是否被高频调用且耗时过长。5.4 现象训练时检测出来一堆空标签或者 loss 下降很慢训练开始后终端输出中有很多图片的labels为空最终训练的mAP一直是0。原因data.yaml里train/val路径指错了或者标注文件里类别id超过了data.yaml的nc。例如你用labelImg画了多个类别但data.yaml只写了nc: 1 names: [bicycle]如果标注文件里出现类别id1YOLOv8会当成无效类别忽略。另一个常见原因是train/val里图片和labels目录名称必须遵循规范Ultralytics要求labels目录名是labels前缀加分割名比如train/labels。解决先用前面的bash脚本检查每个分割的labels数量再用脚本画框验证。最有效的是一条命令直接检查标注内容分布awk {print $1} train/labels/*.txt | sort | uniq -c如果输出只有0说明全0类正常如果出现大于等于1的数字立刻核对类别定义。另外把data.yaml里的names和标注类别顺序严格对应上如果是COCO预训练模型迁移最好保留原来COCO的类别顺序否则加上预训练权重会导致feature map输出维度不一致直接报错。5.5 现象打包成 exe 后模型路径找不到用PyInstaller打包GUI后点运行报错FileNotFoundError: best.pt。原因PyInstaller会把代码里相对路径的资源打包到_MEIPASS临时目录但你的代码直接用“best.pt”这种相对路径运行时当前目录不是exe所在目录模型文件根本没被拷进去。解决把模型文件放在exe同级目录用sys.executable的前路径合成绝对路径或者把模型作为data文件加进PyInstaller的--add-data参数。我习惯先做一个resource_path函数import sys import os def resource_path(relative_path): if hasattr(sys, _MEIPASS): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path) model_path resource_path(weights/best.pt)打包命令pyinstaller -w -F main.py --add-data weights/best.pt;weights注意在Windows上参数分隔符是分号Linux上是冒号使用--add-data时要区分。打包后还要验证exe是否能访问到数据集路径如果你把data.yaml写死了绝对路径别人电脑上就会崩最好把训练和GUI的路径都改成可配置。6. 把毕设变成完整演示批量验证、告警录像和快速部署技巧当你的模型能在GUI里跑起来下一个问题是演示的时候不能只靠运气。你需要一套可复现的验证流程让评委看到“检测成功”不只在某一帧出现。这里分享三个我常用的收尾技巧。第一个是批量验证脚本。准备好一段包含多辆自行车、且其中至少一辆停在ROI区域的视频让它跑一遍统计总帧数、检测到自行车的帧数、真正触发告警的次数。这个指标可以打印在GUI界面上比口头说“能检测”更有说服力。用下面的脚本可以离线统计检测帧率from ultralytics import YOLO import cv2 model YOLO(weights/best.pt) cap cv2.VideoCapture(test.mp4) frame_id 0 detected_frames 0 while True: ret, frame cap.read() if not ret: break result model(frame, verboseFalse)[0] if len(result.boxes) 0: detected_frames 1 frame_id 1 print(fdetected {detected_frames}/{frame_id} frames)这种做法还能顺便验证你的ROI逻辑如果一段视频里始终没有目标停在ROI内GUI不应触发告警反之如果视频里有一辆车停着超过5秒必须触发告警。把“该告警时告警、不该告警时不告警”两个方向都测一遍才算完成一个最小闭环。第二个技巧是把告警事件保存下来。GUI触发告警时把当前帧和检测框信息以时间戳命名保存一张jpg同时将告警时间、类别、置信度写入csv。这样既方便调试又能在答辩时展示“系统有记录”。import csv from datetime import datetime def save_alert(frame, box, conf): ts datetime.now().strftime(%Y%m%d_%H%M%S) cv2.imwrite(falerts/{ts}.jpg, frame) row [ts, box[0], box[1], box[2], box[3], conf] with open(alerts/log.csv, a, newline) as f: csv.writer(f).writerow(row)第三个技巧是模型加速如果演示现场机器性能不够把YOLOv8模型导出为ONNX并用OpenCV推理通常能比PyTorch CPU快两倍。导出命令一句话yolo export modelweights/best.pt formatonnx imgsz640然后GUI里加载onnx文件用cv2.dnn.readNetFromONNX推理输出后处理要自己写这会增加工作量。但如果你答辩机器是台性能很差的笔记本这个加速往往是唯一出路。真要部署到RK3588这类板子上就用瑞芯微的rknn模型转换流程是pt转onnxonnx转rknn再板端推理。这一步不是必要但能让你从“毕设demo”走向“能落地的产品”。我自己的习惯是做毕设别一开始就想着把全部模块都写完。先跑通一个最小可用版本模型能检测、GUI能显示、告警能弹出来然后往里面加批量验证、告警记录、导出onnx。这样周末两天能出第一版剩下的时间全用来打磨演示效果。说得更直接些评委一般不会问你用了什么高端网络结构而是会问“你的告警会不会误报”“误报怎么办”所以花在ROI和去重上的时间比调模型结构更值钱。希望这些经验对你有帮助希望你的毕设答辩顺利。本文还有配套的精品资源点击获取
返回列表