☰
YOLOv8停车场违规占道检测系统毕设实战:从数据集标注到界面部署全流程
2026/10/2 14:11:16 网站建设 项目流程

简介:这份资源是一套基于YOLOv8的停车场违规占道车辆检测系统,面向计算机视觉、人工智能方向的学生及开发者,适用于毕设、课程设计或初期项目演示。系统可对停车场内违规占道车辆进行实时检测与识别,覆盖从模型训练到可视化交互的完整流程。资源包共8个文件,以3个Python脚本、3个模型权重文件及2个说明文档为主,其中脚本分别承担模型训练、视频检测和可视化界面展示功能,压缩包整体约15.91MB,结构清晰、部署门槛低。目前已有58人学习下载。作者在备赛与答辩基础上对代码进行了完整测试,包含数据集、可视化页面与部署说明,运行后可直接产出混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图等关键评估指标,便于论文配图与答辩展示。对希望快速落地目标检测项目或完善毕设成果的读者而言,这是一份拿来即用的完整参考方案。

1. 毕设里那种“能运行就行”的YOLOv8项目,到底该怎么落地

很多同学拿到这个标题的第一反应是:又是一个打包好的“毕设全家桶”,解压、跑通、截图、写论文,四步走完。但真正动手时你会发现,能双击运行的demo和能通过答辩、能讲清楚原理的系统,中间隔着一条巨大的沟。这个项目标题里的关键词——YOLOv8、停车场、违规占道、可视化界面、完整数据集、部署教程——每一个都是可以单独写一篇文章的点,合在一起就是一个完整的计算机视觉工程闭环。

我的建议是:不要把这份资料当成“答案”,而是当成“骨架”。你需要在它的基础上,自己把数据、训练、推理、界面、部署这条链路走一遍。这篇文章我会按我实际做这类系统的顺序,从数据集处理讲到界面打包,把每一步的参数、命令和坑位都铺开。哪怕你手头没有这份zip,照着下面的方法也能从零搭出一套可用的停车场占道检测系统——因为核心不是那个压缩包,而是YOLOv8这条技术链路本身。

2. 从原始视频到训练数据集:停车场场景的标注与格式转换

2.1 停车场违规占道检测到底检测什么

先想清楚业务定义。停车场里的“违规占道”通常指三类:车辆压线停放、车辆停在消防通道或禁停区、车辆逆向驶入或堵塞通道。但在目标检测层面,你不需要让模型直接理解“违规”这个词——模型只负责输出“车在哪里”,违规判定交给后端的几何规则。

所以数据集的标注类别不需要太多。常见做法是两类:一类是car(所有机动车),另一类是专门标注出来的“禁停区域”或者“占道状态”。但实际项目中,我一般只标注car,然后手动在代码里画出禁停区多边形,检测到car的中心点或IOU落在禁停区里,就判定为违规占道。这样做的原因很实在:标注“违规状态”这个属性太主观,不同人标的边界不一致,模型很难学;而只标车,标注一致性高,模型精度容易做上去。

如果你手头的项目数据集里已经带了“违规”这个类别,那训练时要注意样本均衡。停车场视频里正常停车的画面远超违规画面,一个epoch里模型看到100个正常样本才看到2个违规样本,损失函数会被带偏。解决办法是用focal loss或者在数据加载时对违规样本做过采样。YOLOv8里没有内置的过采样参数,我一般是在Dataset类里做,让违规帧的采样概率乘一个系数,比如2到3。

2.2 用Labelme标注车辆:JSON格式怎么转成YOLO的txt

不管数据集是从网上下载的还是自己拍的,最终进YOLOv8训练之前,标签格式必须统一。YOLO系列要的是每个图像对应一个txt文件,每行是class_id x_center y_center width height,坐标全部归一化到0到1之间。

如果你用的是Labelme标注,标出来的是JSON文件,里面存的是多边形点坐标,需要自己写转换脚本。这里给一个我常用的转换脚本,支持多边形转矩形框:

import json import os from PIL import Image def labelme_to_yolo(json_path, img_path, output_txt_path, class_names): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img = Image.open(img_path) img_w, img_h = img.size lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_names: continue class_id = class_names.index(label) points = shape['points'] # 多边形顶点列表 # 取多边形外接矩形 xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 转YOLO格式 x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h box_w = (x_max - x_min) / img_w box_h = (y_max - y_min) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(output_txt_path, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': class_names = ['car'] # 按你的类别顺序写 json_dir = 'labels_json' img_dir = 'images' out_dir = 'labels_txt' for jf in os.listdir(json_dir): if not jf.endswith('.json'): continue base = jf.replace('.json', '') img_file = os.path.join(img_dir, base + '.jpg') if not os.path.exists(img_file): print(f"跳过 {base},找不到对应图片") continue labelme_to_yolo( os.path.join(json_dir, jf), img_file, os.path.join(out_dir, base + '.txt'), class_names )

这段脚本的逻辑分三步:先读JSON拿到多边形顶点,再算外接矩形,最后用图片宽高归一化。注意class_names的顺序必须和训练时的data.yaml完全一致,否则类别就错位了。我踩过这个坑——换了类别顺序忘了改data.yaml,模型训练完检测结果全错,排查了半天才发现是索引对不上。

2.3 要多少张图才够训——停车场场景的最小样本量

停车场场景有一个优势:场景结构高度固定。摄像头位置不变,光照变化有限,背景几乎静止。这种场景下,YOLOv8n从头训练大约需要600到800张标注图就能跑到一个可用的mAP(0.85以上)。但如果你的数据是从公开数据集凑的,比如用COCO里的car类图片拼,那场景泛化能力会差——停车场的地面纹理、车位线、禁停标志这些特征,COCO里没有。

我的建议是:优先自己从监控视频抽帧。抽帧不要按时间均等抽,要按“画面变化”抽——停车场一天里光照变化剧烈,早晨逆光、中午强光、晚上灯下,每个时段都要有。一个简单的做法是每5秒抽一帧,然后人工删掉连续重复帧。1000张图的工作量,用Labelme标注,熟练的话一天半能完成。不要觉得1000张多,这比训练完发现场景不对再回头补数据要省太多时间。

如果你拿到的项目自带数据集,也要先做清洗。打开每张图看一眼,删掉模糊的、目标被严重遮挡的、以及标注框和物体明显对不上的。数据清洗是这类项目里回报最高的一步,比调任何训练参数都管用。

3. 训练前的环境与配置:YOLOv8的CPU/GPU环境搭建与参数选择

3.1 环境搭建的两种典型路径

YOLOv8跑起来的前提是环境干净。这里的环境指三件事:Python版本、PyTorch版本、CUDA版本。三者必须对齐,否则会出现CUDA error: no kernel image is available或者装上torch但torch.cuda.is_available()返回False。这种问题占了毕设排障的七成,都是环境不一致导致的。

如果你手头没有NVIDIA显卡,用CPU跑也不是不行。Ultralytics官方对CPU的支持做得很完整,YOLOv8n在CPU上推理一张640x640的图大约需要300到500毫秒,训练的话就慢了——1000张图、100个epoch,i5处理器大概要跑10个小时以上。CPU版本安装时注意不要装CUDA版的torch,直接pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu即可。GPU环境则要先确认驱动版本和CUDA版本,我的习惯是用nvidia-smi看驱动支持的CUDA版本,再装对应版本的PyTorch,不要盲目装最新版。

3.2 训练前的配置文件:data.yaml与模型选择

YOLOv8训练前要准备一个data.yaml,指向你的数据集和类别信息。这个文件看似简单,但路径写错是常见问题——明明数据集在本地,训练却报File not found。我的建议是绝对路径,不要用相对路径,因为YOLOv8的相对路径基准跟你的工作目录有关,换目录就翻车。

data.yaml的写法如下:

# 数据集绝对路径 path: /home/user/parking_dataset train: images/train val: images/val # 类别名称(顺序必须与标注txt中的class_id对应) names: 0: car

模型选型上,停车场这类单一类别任务,YOLOv8n就足够了。n模型11M参数,训练速度快,推理速度快,精度在单一类别场景下不会比s或m差太多。如果你要写论文拿更漂亮的mAP曲线,可以选YOLOv8s,代价是显存从4G涨到8G左右。我做过对比:同样的停车场数据,n和s的mAP差距通常在0.02以内,但s的推理时间翻倍。考虑到毕设演示时可能用CPU跑,选n会更稳妥。

3.3 训练命令与关键参数:从epoch到batch size的取舍

训练命令本身不复杂,但参数背后的含义要清楚。这里给出一个我常用的训练命令:

yolo detect train \ --model yolov8n.pt \ --data /home/user/parking_dataset/data.yaml \ --epochs 100 \ --batch 16 \ --imgsz 640 \ --device 0 \ --optimizer AdamW \ --lr0 0.001 \ --patience 15

几个参数的取舍逻辑:

  • batch由显存决定。6G显存跑batch 16没问题,4G显存降到8。batch越大收敛越稳定,但显存不够会OOM报错。
  • patience 15是早停参数——15个epoch内验证集mAP没有提升就自动停止。不要设太大,防止过拟合。
  • imgsz用640。停车场场景车辆较大,不需要1280分辨率去捕捉小目标,640省显存且够用。
  • lr0的默认值就是0.01,但我习惯降到0.001,尤其是自定义小数据集上,过大的学习率容易让loss曲线在前10个epoch震荡。

训练过程中要盯两个东西:一个是results.csv里的loss曲线,另一个是验证集上的confusion_matrix。如果val loss出现拐点往上走,说明开始过拟合,早停会自动触发。如果训练结束后mAP很低,先检查数据集,别急着调参——多数时候是标注框有问题,比如框没贴紧车身、类别标错这种低级错误。

3.4 损失函数曲线图怎么画,以及它说明了什么

很多同学跑完训练直接拿results.png里的曲线放进论文,但自己看不懂。其实YOLOv8训练完成后,autosave目录下会生成results.csv,你可以自己画更精细的曲线。这里给一个用matplotlib画loss曲线的脚本:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/detect/train/results.csv') # 列名中train/box_loss是边界框回归损失,val/box_loss是验证集损失 plt.figure(figsize=(10, 5)) plt.plot(df['epoch'], df['train/box_loss'], label='train box_loss') plt.plot(df['epoch'], df['val/box_loss'], label='val box_loss') plt.xlabel('epoch') plt.ylabel('loss') plt.legend() plt.grid(True) plt.savefig('box_loss_curve.png', dpi=200)

判断标准:train/box_loss和val/box_loss都在下降,且差距不大,说明训练健康。如果train loss降但val loss不降,是过拟合;如果两个都不降,先查学习率,再查数据标注质量。这两个损失曲线是答辩时老师必问的点,你要能指着曲线说清楚每一段的变化原因。

4. 违规占道判定的核心逻辑:用检测结果做业务规则

4.1 从检测框到“是否违规”的几何判定

训练好的模型只告诉你“这里有辆车、框在哪里”,不会告诉你“这辆车违不违规”。判定逻辑要自己写。停车场最常见的违规是压线停车——车框和车位线或禁停区有交集。

常见做法是预先在代码里定义禁停区多边形,然后计算检测框和禁停区的交并比(IOU)。如果IOU超过一个阈值(我一般设0.3),判定为违规。这个阈值是有讲究的:设太高,车辆只压一点线会被漏掉;设太低,正常停在旁边的车会因为边缘触碰被误判。0.3这个值是针对“车辆中心或三分之一车身进入禁停区”经验性调出来的,如果实际场景误报多,可以往上调到0.4。

4.2 一个可直接改的违规判定模块

下面这段代码是我经常用在停车场项目里的判定模块,可以直接嵌到推理脚本里:

import cv2 import numpy as np def polygon_contains_box(polygon, box): """ 判断检测框和禁停区多边形是否重叠 polygon: [(x1,y1), (x2,y2), ...] 禁停区顶点 box: [x1, y1, x2, y2] 检测框 """ # 检测框四个角 corners = [ (box[0], box[1]), (box[2], box[1]), (box[2], box[3]), (box[0], box[3]) ] poly = np.array(polygon, dtype=np.float32) # 统计落在多边形内的角点数 inside_count = 0 for pt in corners: res = cv2.pointPolygonTest(poly, pt, False) if res >= 0: inside_count += 1 # 有两个以上角落在禁停区里,判定为占道 return inside_count >= 2 def judge_violation(detections, forbidden_zones): """ detections: [[x1, y1, x2, y2, conf, cls], ...] forbidden_zones: [polygon1, polygon2, ...] return: 违规检测框列表 """ violations = [] for det in detections: x1, y1, x2, y2 = det[:4] for zone in forbidden_zones: if polygon_contains_box(zone, [x1, y1, x2, y2]): violations.append(det) break return violations

这里的判断逻辑是“检测框两个以上角点落在禁停区多边形内”,比起IOU计算更接近人对“压线”的直觉判断。注意pointPolygonTest传入的坐标是图像像素坐标,所以检测框要乘回原图尺寸——YOLOv8默认输出的是640x640坐标系下的结果,你需要用results[0].plot()拿到的还是带框的图,但如果要自己画多边形,必须做坐标缩放。

4.3 为什么直接用模型输出“违规”类别是条弯路

我之前提过数据集里标“违规”类别的做法,但最后不建议走那条路。原因在于:YOLOv8的检测框是学习“外观特征”的,“车辆压线”是一个位置关系,不是外观特征。模型能学到的是“这辆车旁边有黄线”这种伪特征,而不是真正理解“压到了线”。换一个停车场、换一种地面颜色,模型就失灵。

用“检测车+几何规则”的方案,模型只负责它擅长的事(找车),违规判定交给确定性的几何计算。这样模型和规则可以分别迭代——模型精度不够就补数据重训,规则误报多就调多边形的精确程度。解耦之后,系统的可维护性和答辩时的可讲性都强很多。这个思路本身,就是你在答辩时展示与现实接轨的加分点。

5. 可视化界面的实现逻辑:PyQt5做实时检测面板的思路

5.1 界面该有哪些功能

毕设系统的可视化界面,不需要做得像商业软件那样花哨,但三个核心功能必须有:实时画面显示(视频文件或摄像头)、检测结果叠加(框和标签)、违规事件记录(时间+截图)。再加一个阈值滑块(置信度阈值)就足够撑起“功能完善”的门面了。很多项目的界面问题不是功能少,而是按钮堆了一堆没有一个能真正响应。从评审角度看,功能闭环比功能数量重要得多。

我用PyQt5比较多,原因很简单:OpenCV的图像格式可以直接转成QImage显示,不需要额外学图形框架。YOLOv8的推理封装成一个类,界面调用这个类的方法,两边隔离——界面只负责显示和交互,推理类只管返回结果。

5.2 界面调用YOLOv8推理的核心代码

下面这段是界面和YOLOv8推理之间的关键桥接代码,它解决的是“视频每一帧怎么送进模型、结果怎么画回画面”:

import cv2 from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class Detector: def __init__(self, weights_path, conf_thres=0.4): self.model = YOLO(weights_path) self.conf_thres = conf_thres def inference(self, frame): """输入BGR帧,返回标注后的画布和检测框列表""" # 保原始帧尺寸,YOLOv8内部自己会做letterbox results = self.model(frame, conf=self.conf_thres, verbose=False) boxes = results[0].boxes.xyxy.cpu().numpy() confs = results[0].boxes.conf.cpu().numpy() # 绘制检测框 for box, conf in zip(boxes, confs): x1, y1, x2, y2 = box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f'car {conf:.2f}', (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return frame, boxes def cv2_to_qimage(frame): """OpenCV的BGR帧转成Qt能显示的QImage""" rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape bytes_per_line = ch * w return QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) # 使用示例(在Qt的update槽里调用) # detector = Detector('best.pt') # ret, frame = cap.read() # frame_with_boxes, dets = detector.inference(frame) # qimg = cv2_to_qimage(frame_with_boxes) # label.setPixmap(QPixmap.fromImage(qimg))

这段逻辑里有几个容易出问题的地方。一是results[0].boxes.xyxy是归一化前的像素坐标,它和原图分辨率一致,不需要缩放,直接用就行——因为推理时传进去的就是原图,YOLOv8返回的坐标也是该分辨率下的。二是cv2_to_qimage里用的Format_RGB888,如果转出来画面色彩发蓝或发红,说明BGR和RGB顺序搞反了,检查一下这行。

5.3 界面产品的“操作简单”体现在哪

“操作简单”不是说按钮少,而是让使用者不用看说明也能上手。我的一般做法:主界面打开后自动加载模型和默认视频源,点“开始检测”就进入实时推理;右侧用表格实时刷新违规记录,双击记录能跳转到对应帧的截图;阈值滑块在运行时拖动,立刻生效,不用重启程序。这三件事做到位,基本就是一个能上台演示的系统了。

有一个细节值得强调:模型加载放在程序启动时做一次,不要放在视频播放的回调里。如果你的代码把model(frame)写在每一帧的循环里结果还慢得离谱,多半是误把模型加载也写进了循环体——每帧重新读一次权重文件,能不卡吗。YOLOv8的模型加载耗时约1秒,初始化一次就够用了。

6. 部署与打包阶段:模型还是带不动?五个必查的坑

6.1 电脑没有NVIDIA显卡,报错CUDA不可用怎么办

现象:训练或推理时报错AssertionError: CUDA unavailable。

原因:PyTorch默认安装的是CUDA版本,没有NVIDIA显卡时就会触发这个错误。另一个可能是装了GPU版torch但驱动太老、显存不足以分配batch。

解决:先看有没有独立显卡。有的话用nvidia-smi确认驱动版本是能支持的CUDA版本,再装对应的PyTorch。没有显卡的话直接卸载torch重装CPU版本:pip uninstall torch torchvision,然后pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu。重装完在Python里执行import torch; print(torch.cuda.is_available()),输出False是正常的,CPU模式也能跑的。YOLOv8在CPU上的推理速度大约每帧0.4秒,演示时用视频文件而不是实时摄像头,体验不会太差。

6.2 训练时显存OOM:batch size和图片尺寸的取舍

现象:训练开始几秒后报RuntimeError: CUDA out of memory。

原因:batch size设太大,或者显卡显存本身只有4G到6G,装不下整个batch的数据。

解决:优先把batch调小,比如从16降到8,甚至降到4。如果batch已经调到4还是OOM,把--imgsz从640降到480。这两步是正交的,可以先固定batch调imgsz。我很少为了降低显存去改模型结构——YOLOv8n已经是轻量级了,显存不足几乎都是batch和imgsz的锅。

6.3 训练时loss为nan:学习率过大或数据里有脏值

现象:训练日志里box_loss显示nan,loss曲线断崖式跳到负值。

原因:学习率过大,梯度更新越过最优解直接发散;也可能是标注框坐标数据里有脏值——比如标注文件里出现了0宽度的框、坐标超出图片边界。

解决:第一件事把--lr0从0.01降到0.001,重启训练。如果loss还是nan,去检查标注txt——写一个检查脚本,解析所有txt文件,过滤出w或h小于等于0的行。标错的数据清理掉,问题自然解决。这个脚本我每次训练前都会跑一遍,当作数据清洗的固定步骤。

6.4 换了电脑运行报错:权重文件路径和Python环境迁移

现象:项目在自己电脑跑得好好的,拷到另一台电脑上运行报ModuleNotFoundError: No module named 'ultralytics'。

原因:YOLOv8的环境没有完整带过去,另一台电脑缺少依赖包。

解决:不要手动复制site-packages,用requirements.txt的方式装环境。在项目根目录执行pip freeze > requirements.txt,到了新机器上先建虚拟环境,再pip install -r requirements.txt。如果对方电脑没有GPU,需要在安装时把requirements里的torch替换成CPU版再装。这个操作不复杂,但能省掉一晚上排障时间。

6.5 中文路径导致的读取失败

现象:数据集或模型权重路径里带中文,运行时抓狂地报错FileNotFoundError或者OpenCV读取不到图片。

原因:OpenCV的imread和PyTorch的某些文件读取函数对中文路径支持不好,路径解析在上面就断了。

解决:根治办法是项目路径下不要出现任何中文——文件夹名用英文,数据集也放英文路径。如果实在避免不了中文路径,用cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), cv2.IMREAD_COLOR)替代cv2.imread来读图片,这个接口能正确处理中文路径。但YOLOv8训练时数据集加载是内部处理的,所以最保险的还是在数据集和权重文件上彻底避开中文路径。

7. 从能跑到能答辩:验证系统的三个隐藏指标

跑通demo只是起点,答辩时老师问得最深的往往是“你怎么证明它有效”。除了mAP,我建议你再准备三个数据,它们才是区别“调包侠”和“真做过”的分水岭。

第一个是误报率。从测试视频里统计1000帧画面,人工数出模型误报了多少次违规。误报的常见来源是树影、车辆倒影、行人和禁停区边缘的抖动。把数据统计出来,能说明你的违规阈值是否合理。第二个是单帧推理耗时。用time模块包住model(frame)调一次的时间,在CPU和GPU上分别跑三组,取平均值——这个数据回答“能否实时”的问题。第三个是夜间或雨天场景的单独表现。停车场监控最怕晚上,灯光下的车辆检测和白天差异很大。如果测试集里有夜间帧,单独统计这一组的precision和recall。

如果你想在这个方向上继续往下做,有两个小的进阶方向比较可行:一个是把违规车辆的车牌用OCR接进来,在界面里显示“车牌号+违规时间+截图”的完整记录,这能让系统的完整度上一个档次;另一个是把训练好的.pt模型用onnx导出,加速推理速度。导出命令是:

from ultralytics import YOLO model = YOLO('best.pt') model.export(format='onnx', imgsz=640)

导出后替换推理方式,注意ONNX模型输入是RGB格式而不是BGR,颜色通道顺序弄反会导致检测准确率骤降,这是导出后最常见的翻车点。加速收益在CPU上通常是1.5到2倍——从0.4秒一帧提到0.2秒左右,对实时演示意义很大。

做这类毕设项目,我的习惯是每改完一个环节就完整跑一遍全流程,不要攒到最后一起验收。否则data.yaml改了一次、标注脚本改了一次、界面又改了一次,最后合在一起跑不通,根本不知道问题出在哪一环。这个习惯帮我省了很多通宵排障的时间,希望也能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询