简介:一套基于YOLOv8的停车场违规占道车辆检测系统项目,聚焦深度学习目标检测方向,面向计算机相关专业学生的毕业设计、课程设计或初期项目演示,可实时识别停车场内违规占道车辆,配合可视化页面和完整数据集,简单部署即可运行。压缩包为zip格式,共含8个文件,主要包含3个Python源码(覆盖可视化界面、模型训练与视频检测)、3个PyTorch权重文件和2个说明文档,整体大小仅15.91MB,目录结构清晰,便于按模块查阅与调用。目前已有58人学习下载,适合需要快速搭建完整检测流程的初学者或答辩项目。项目附带可视化页面、部署教程和完整数据集,运行后可产出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,为毕设评审或课程展示提供直观数据支撑,同时省去从零搭建环境和反复调参的麻烦,拿来即用的完整方案可信赖。
1. 停车场违规占道检测是什么:一个 YOLOv8 就能撑起的课设项目
值班室里的大屏上,消防通道被一辆 SUV 堵了半个多小时,等安保人员从几十路监控画面里发现时,车已经开走了。这种场景在停车场管理里太常见,也是计算机视觉课程设计里最容易做出成果的题目之一。标题这个项目,本质就是拿 YOLOv8 做目标检测,把画面里的车辆分成两类:正常泊位里的车和违规占道的车,再用一个可视化界面把推理结果框出来。拿到这类压缩包,先别急着点训练按钮,按“环境搭建 → 数据核查 → 权重推理 → 界面部署”四步走,一周内把功能跑通完全来得及。适合毕设,也适合课程设计里需要演示系统的同学。
2. 本地复现:环境搭建、项目结构和一个最小推理命令
2.1 运行环境:CPU 版也能跑,独立环境是省事后悔药
这个项目能不能快速跑起来,环境配置占一半。项目包里的部署教程通常会要求 Python 3.9 左右的版本,搭配 PyTorch 和 ultralytics 包。我的做法是先建一个独立 conda 环境,绝不在 base 环境里直接装,否则后面装 opencv、pandas 时版本冲突能把人逼疯。
conda create -n parking python=3.9 -y conda activate parking pip install ultralytics pip install torch torchvision第一条命令创建一个干净的 Python 3.9 环境,第二条激活它,第三条安装 ultralytics 这个核心依赖,它会顺带把推理所需的组件装好。第四行把 torch 和 torchvision 补齐,注意这里默认装的是 CPU 版本,能跑,只是训练会慢。如果你的机器有 NVIDIA 显卡,需要先确认驱动支持的 CUDA 版本,再重新装对应版本的 PyTorch。不少人在 Ubuntu 20.04 上搭建 YOLOv8 环境时图省事直接一键装,结果 import 阶段就报错,都是因为 torch 和 CUDA 版本对不上。
环境装完先跑一个小脚本,确认 torch 真的看到了显卡。这一步是一次验证,跳过的话训练跑了一半才发现用的是 CPU,白白浪费时间。
import torch print("torch:", torch.__version__) print("cuda available:", torch.cuda.is_available()) print("device name:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")这段脚本就是检查两件事:当前环境的 torch 是什么版本,以及 CUDA 是否可用。如果打印出 cuda available: False,说明你装的是 CPU 版 torch,不是显卡有问题。后续训练时 device 参数写 0 是没有意义的,必须先把 torch 的版本换掉。
2.2 项目结构:从训练脚本到界面程序,先分清四个模块
YOLOv8 停车场检测这类项目压缩包里,文件结构大同小异,通常围绕训练、推理、界面、数据四个部分展开。
parking-detection/ ├── train.py # 训练入口 ├── detect.py # 命令行推理 ├── ui_main.py # 可视化界面 ├── requirements.txt # 依赖清单 ├── weights/ │ └── best.pt # 训练好的模型权重 ├── dataset/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ └── parking.yaml # 数据集配置文件先把这六个部分认清楚,再运行任何代码。train.py 负责重训模型,detect.py 负责对单张图片或视频做推理,ui_main.py 是界面程序,weights/best.pt 是已经训练好的权重,dataset 是标注数据,parking.yaml 告诉 YOLOv8 数据路径和类别名。很多同学一上来就双击 train.py,完全忽略 weights 里已经有个能用的模型,这没必要。项目包既然带了 best.pt,就说明作者已经跑通过一遍,你的第一目标是复现推理效果,不是重新发明轮子。
2.3 最小推理:先用 best.pt 跑通,别把代码当黑匣子
拿到项目先跑一次推理,确认模型路径、图片路径和输出结果都没问题,再往后研究界面和训练。
python detect.py \ --source dataset/images/val/000012.jpg \ --weights weights/best.pt \ --conf-thres 0.30 \ --iou-thres 0.45 \ --device 0这条命令的意思是:对验证集里的某一张图片做检测,置信度阈值 0.30,两个框重叠度超过 0.45 就做 NMS 合并。如果电脑没有 GPU,把 --device 0 改成 --device cpu。跑通之后你会看到输出目录里生成了一张带框图片,违规占道车辆被标出来。
这个阶段的重点是确认流程,不是调指标。conf-thres 设 0.30 只是先看看结果,真实的停车场场景我更建议把它降到 0.15 到 0.25,因为监控视角下车身小、遮挡多,置信度普遍偏低。如果压到 0.10 还是一辆车都不框,那问题不在阈值,而在权重和图片本身,后面避坑章节会专门说。把这一步跑通了,项目代码对你来说就不是黑匣子了。
3. 数据是地基:YOLO 标注格式、类别失衡与坏样本排查
3.1 标签文件长什么样:中心点坐标和类别编号别搞反
处理数据集用于 YOLOv8 训练,第一步是看懂标签格式。YOLO 系列用的是 txt 标注,每一行描述一个目标,五个字段分别是:类别编号、目标中心点 x 坐标、中心点 y 坐标、目标宽度、目标高度。x、y、w、h 都是归一化到 0 到 1 的相对值,不是像素坐标。
# dataset/labels/train/000012.txt 0 0.513 0.426 0.211 0.264 1 0.782 0.315 0.158 0.221第一行的 0 代表类别编号,对应的是正常停放车辆,后面的四个数字表示框的中心点在这个图像的 51.3% 和 42.6% 位置,宽度占全图 21.1%,高度占 26.4%。第二行类别是 1,也就是违规占道车辆。项目里的 parking.yaml 会定义类别的具体名字。
path: dataset train: images/train val: images/val names: 0: normal_parked 1: illegal_occupied这段配置让训练脚本知道训练集和验证集在哪,以及每个编号对应的类别名。names 的顺序非常关键,训练和推理时必须保持一致。项目包里的界面如果显示类别名对不上,优先检查这里是不是被改过。另外,如果数据集最初是用 labelme 这类工具标注的 JSON,需要先转成 YOLO 格式的 txt 再训练。转换时最容易出的问题,是把 JSON 里的类别名顺序和编号映射搞错,转完居然和原图对不上。
3.2 类别失衡先统计再补数据:违规样本往往只有零头
停车场场景里,正常停放车辆的数量会远远大于违规占道车辆。一个停车场摄像头画面里可能二十辆车,只有一辆压在消防通道上,这种天然的类别不平衡,会让模型倾向于把所有车都预测成正常停放。训练之前一定要先数一数各类别到底有多少框。
import os from collections import Counter label_dir = "dataset/labels/train" counter = Counter() total_boxes = 0 for name in os.listdir(label_dir): if not name.endswith(".txt"): continue with open(os.path.join(label_dir, name), "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: cls = int(line.split()[0]) counter[cls] += 1 total_boxes += 1 print("各类别框数:", dict(counter)) print("总标注框数:", total_boxes)这个脚本遍历训练集所有标签文件,统计类别 0 和类别 1 的框数。如果违规占道样本不到正常样本的 20%,模型大概率会翻车。补数据不是只能靠手动标注,常见做法是把包含违规车辆的关键片段每隔十秒抽一帧加入训练集,或者对现有违规样本做亮度、平移、旋转增强。需要注意的是,停车场画面里车位线和车道方向有强先验,水平翻转增强不一定适用,镜像后车辆出现在原本不可能出现的车道位置,反而误导模型学习位置规则,所以增强策略要按场景选。
| 类别占比情况 | 典型后果 | 建议做法 |
|---|---|---|
| 违规框少于正常框的 10% | 模型几乎不预测违规类 | 补充样本或做针对性增强 |
| 占比在 10%~25% 之间 | 能检出但召回率偏低 | 添加类别权重或启用数据增强 |
| 占比超过 30% | 训练曲线相对健康 | 正常训练,关注夜间样本覆盖 |
3.3 坏样本排查:把中心点坐标还原成可视化框
标注文件里的数字对不对,光看 txt 很难发现问题。写一个可视化脚本,把所有标签画回原图,再看一遍,是处理数据集最关键的一步。
import cv2 import os img_dir = "dataset/images/val" label_dir = "dataset/labels/val" out_dir = "check_vis" os.makedirs(out_dir, exist_ok=True) class_names = ["normal_parked", "illegal_occupied"] for name in os.listdir(img_dir): if not name.lower().endswith((".jpg", ".jpeg", ".png")): continue img = cv2.imread(os.path.join(img_dir, name)) h, w = img.shape[:2] txt_path = os.path.join(label_dir, os.path.splitext(name)[0] + ".txt") if not os.path.exists(txt_path): print("[warn] 缺失标签:", name) continue with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: print("[warn] 格式异常:", name, line.strip()) continue cls = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) color = (0, 0, 255) if cls == 1 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls], (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(os.path.join(out_dir, name), img)这段脚本把验证集所有标注框用红色和绿色画回原图。红色是违规车辆,绿色是正常停放。归一化坐标还原回像素坐标时,一定要先乘以宽高,再减半宽半高得到左上角坐标。跑完检查几个容易出错的位置:框是不是刚好包住车身,有没有把旁边车辆框进去,有没有画到墙上或树上。这个步骤花半小时,能省下后面训练返工的好几天。看到标注框歪七扭八,就回头改标签工具或标注规范,不要急着开训练。
4. 重训一套自己的权重:训练命令、参数表与损失曲线判读
4.1 训练命令:从 data.yaml 到 batch、imgsz 参数的确定方法
项目自带的权重通常是在作者的数据集上训练的,换一个停车场环境效果会打折扣。想训练自己的数据集,核心命令就是下面这一条。
yolo detect train \ data=parking.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ device=0 \ patience=20 \ optimizer=AdamWdata=parking.yaml 指向刚才确认好的数据集配置文件,model=yolov8n.pt 表示用 YOLOv8n 作为基础模型,在本项目数据集上继续微调。n 是 nano 版本,占用资源最少,适合 6GB 显存左右的显卡。如果你的显卡是 16GB 显存,可以考虑换 yolov8s.pt 提升精度。batch 参数决定每轮喂给模型的图片数量,显存越小 batch 越小,在 GTX 1660 Ti 这类 6GB 显卡上,batch 16 跑 640 分辨率有点吃力,建议降到 8,或者把 imgsz 改成 480。
imgsz 是输入图像分辨率。停车场摄像头通常是高空俯视角,车辆在画面里偏小,imgsz 从 640 升到 800 能明显提升小目标召回,代价是训练时间增加一半左右。patience 是早停参数,意思是验证集指标连续 20 轮没有进步就停止训练,防止后期浪费时间。AdamW 是优化器,多数场景下比默认的 SGD 收敛更稳,对新手更友好。
训练过程中注意看终端打印的进度条。如果报 CUDA out of memory,第一步把 batch 减半,再不行把 imgsz 降到 480。如果训练一切正常,但损失曲线抖动得很厉害,降低学习率,常见做法是从默认的 0.01 改成 0.005。训练结束后,结果会保存在 runs/detect/train/ 目录下,里面的 weights/best.pt 就是你要用的权重。
4.2 损失曲线:results.csv 画出的四种曲线怎么读
YOLOv8 训练过程中会自动记录每个 epoch 的损失值和评估指标,存在 runs/detect/train/results.csv 里。不要只看训练结束时的打印信息,把曲线画出来才能看清楚模型有没有真的学会。
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df["train/box_loss"], label="train box_loss") axes[0].plot(df["val/box_loss"], label="val box_loss") axes[0].set_title("box loss") axes[0].legend() axes[1].plot(df["metrics/mAP50(B)"], label="mAP50") axes[1].set_title("mAP50") axes[1].legend() plt.tight_layout() plt.savefig("loss_curve.png", dpi=150)这段脚本读取 results.csv,画出边界框损失曲线和 mAP50 曲线。不同的 ultralytics 版本列名略有区别,如果报 KeyError,先打印 df.columns 看实际列名,再改脚本都行。判断训练是否正常,看两点:box_loss 是否整体下行,mAP50 是否逐步上升。如果训练损失降了但验证损失不降,是过拟合,需要增加数据或减小模型复杂度。如果最后几个 epoch 突然掉点,往往是 mosaic 增强在最后一轮才关闭,训练时加一行 close_mosaic=10,给模型最后 10 轮一个稳定的学习窗口,这个细节能救回不少精度。
4.3 小目标与俯视角:提升违规类别的两项增强策略
停车场监控视角有个典型问题:车小、视角固定、密集排列。按默认参数训练完,正常停放车辆往往检得很好,违规占道车辆会漏,因为违规样本数量少,且常常出现在画面边缘或远处。
第一个有效手段是提高推理分辨率。640 分辨率下,远处的车可能只有 30 乘 40 像素,模型很难学。把 imgsz 改成 800 甚至 960,小目标特征保留得更多,mAP 提升往往是肉眼可见的。显存不够就别硬扛,换 YOLOv8n 模型,或者在界面推理时保持训练尺度一致,不要在推理时擅自压缩图片尺寸。
第二个手段是关闭水平翻转。YOLOv8 默认开启 fliplr 增强,但停车场地面有车道线和方向标识,违规占道多半和特定位置强相关。镜像翻转后,车出现在实际不可能出现的车道上,等于给模型喂错误的空间先验。训练参数里加一句 flipud=0.0 fliplr=0.0,通常会有惊喜。还有一个容易忽略的点:项目数据集如果只有几百张图,别急着大改参数。先拿预训练权重在真实监控截图上跑一遍,把漏检错检的图挑出来补充到训练集,比加增强更有效。
5. 部署期高频坑:环境冲突、空框、漏检与 mAP 虚高的排查
5.1 环境与依赖:import 报错和 torch 版本冲突
现象:代码一运行就报 ModuleNotFoundError: No module named 'ultralytics',或者 torch 和 torchvision 版本不匹配导致 import 崩溃。
原因:大多数情况是在 base 环境里随意 pip install,装到一半版本冲突,或者 Python 版本本身太高,比如 3.12 下有些旧版 opencv 编译不过去。极少是代码本身的问题。
解决:彻底删掉原来的环境,重新走一遍第 2 章的环境步骤。强烈建议固定 Python 3.9,先装 torch 再装 ultralytics,最后按 requirements.txt 补剩余依赖。不要用 conda 装一半、pip 装一半,两条包管理器的混用是这个坑的主要来源。
5.2 推理端空框:置信度阈值、压缩尺寸与类别名错位
现象:界面里选了一张包含车辆的图片,结果一个框都不画,或者只有正常车辆框、没有违规车辆框。
原因有三个方向,按出现频率排序:置信度阈值设太高,模型对违规车辆的置信度只有 0.2,阈值设置 0.3 就全被滤掉了;界面把输入图片压缩到很小,比如降到 320 分辨率再推理,小目标直接丢了;类别名映射错乱,界面程序里的标签列表顺序和 parking.yaml 里 names 顺序不一致,模型预测类别 1,界面却显示成类别 0 的颜色和名称。
解决:先把置信度降到 0.10 做一次推理,能框出来就是阈值问题,再按场景逐步调回 0.20 左右。图片尺寸问题,在界面代码里找推理函数入口,确认传给 model 的图片没有经过大比例 resize。类别映射问题,用一张已知违规车辆的图片跑 detect.py,再把界面结果和命令行结果对比,看类别是否一样。
注意:推理环境、训练环境和界面程序的环境最好保持同一套依赖版本,否则容易出现训练时正常、部署时结果不对的怪问题。
5.3 数据侧翻车:重复帧、标注框不一致带来的假性高指标
现象:训练时 mAP50 显示 0.9 以上,觉得模型很完美,一到真实停车场视频里就大量漏检,密集误报,完全不是测试集那个效果。
原因:这是项目数据集最常见的坑。数据采集通常来自同一段监控视频,如果按连续帧截图,训练集和验证集里会出现大量极度相似的画面,模型实际上在记忆场景,而不是学习车辆特征。另一个问题是标注规范不一致,有些人画框包含整个车身包括后视镜,有些人只画车头,模型的回归目标乱了,验证集又恰好和训练集风格一致,指标自然虚高。
解决:重新划分数据集,按时间抽帧而不是按连续帧截图。常见做法是每 10 秒取一帧,再用感知哈希算法去掉相似度高的图像。标注方面统一规范:画车辆外接矩形,包含两侧后视镜,不包含旁边车辆和阴影。做完这两件事再重训一轮,你会发现 mAP 可能降到 0.7 以下,但真实场景的可用性反而明显上升。指标好看不等于模型好用,这是毕设答辩时最值得拿出来讲的认知。
5.4 硬件资源踩坑:显存不够和 CPU 硬跑的识别率差异
现象:训练时直接报 CUDA out of memory,或者被迫一直用 CPU 训练,跑了一整天还没完。
原因:显存规划不合理。6GB 显存硬跑 YOLOv8s 加 640 分辨率加 batch 16,已经超出物理限制。CPU 模式不是不能跑,是训练效率极低,而且部分同学等到答辩前两周才发现,连调整的机会都没有。
解决:先看模型大小再定参数。6GB 显存用 YOLOv8n,batch 8,imgsz 640,稳妥起步。如果显存还是爆,就把 imgsz 降到 480。训练之前用 nvidia-smi 看一次显存占用,确认没有其他程序占着显存再启动。另外,输出目录 runs/detect 下的中间权重文件会占用不少磁盘空间,训练前检查磁盘剩余容量。
5.5 界面程序跑不起来:路径问题和缺失依赖
现象:命令行推理正常,但打开界面就闪退或报错。界面里的图片选择框打开目录后没有预览,部分按钮点了没反应。
原因:项目界面程序通常用 PyQt5 或 Tkinter 实现,路径处理比较脆弱。权重路径、数据集路径可能硬编码成了作者的绝对路径,换台电脑就失效。图片选择框默认路径指向作者电脑上的文件夹,找不到目录就直接崩溃。
解决:先打开界面脚本,搜索 .pt、/home、C:\ 这类绝对路径写法,全部改成相对路径或用一个配置文件统一管理。界面打不开图片预览时,检查所选目录是否包含 jpg、png 文件,以及系统是否有中文字体支持。把命令行推理跑通作为前置条件,界面只是一个壳子,模型部分才是核心,不要花太多时间在美化上。
6. 进阶验证:把模型接进监控视频流,测真实误检率与帧率
6.1 用脚本接管视频流:抽帧、推理与违规统计
停车场检测最终要面对的不是一张张图片,而是连续的视频流。界面程序通常已经内置了视频检测,但拿它做效果评估不够直观。我习惯写一个独立的脚本,直接读取摄像头 RTSP 流或本地视频,把违规目标出现的次数统计出来。
import cv2 import time from ultralytics import YOLO rtsp_url = "rtsp://192.168.1.64/stream1" # 换成实际摄像头地址或本地视频路径 model = YOLO("weights/best.pt") cap = cv2.VideoCapture(rtsp_url) fps = cap.get(cv2.CAP_PROP_FPS) or 25 interval = int(fps * 0.5) # 每 0.5 秒抽一帧 frame_id = 0 illegal_count = 0 total_count = 0 while True: ret, frame = cap.read() if not ret: print("stream interrupted") break if frame_id % interval != 0: frame_id += 1 continue result = model(frame, conf=0.15, verbose=False)[0] boxes = result.boxes illegal = sum(1 for c in boxes.cls if int(c) == 1) illegal_count += illegal total_count += 1 frame_id += 1 print("抽帧次数:", total_count) print("检到违规目标次数:", illegal_count)这段脚本的逻辑是按帧读取视频流,每 0.5 秒选一帧送入模型推理,统计检出的违规目标数量。用时间间隔抽帧而不是逐帧推理,是因为 YOLOv8 在 CPU 上无法保证实时处理 1080p 视频,抽帧评估更务实。interval 按视频帧率计算,保证不管摄像头是 25 帧还是 30 帧,都是每 0.5 秒检测一次。推理时 conf 设 0.15,比训练时更宽松,宁可多一点误报也要保证违规不落网。
6.2 三个评估口径:真实 FPS、无效帧参与夜间场景
测试报告里除了 mAP,还应该记录三项实测数据。第一个是真实 FPS,用当前帧时间戳减去上一帧时间戳,一秒钟能处理几帧就是几帧,不要信模型接口文档里的理论值。第二个是无效帧率,cap.read 偶尔会返回 false,这在无线摄像头流里很常见,无效帧率超过 5% 就需要检查网络。第三个是显存占用,训练和推理阶段分别看一次 nvidia-smi,避免部署时显存泄漏导致运行几小时后卡死。
停车场检测真正的检验场是夜间场景。很多项目训练数据集中在白天,夜间低光照下车辆特征丢失严重,误检率会明显上升。我现在拿到任何模型,习惯先找三段与训练集无关的视频做盲测,一段白天、一段夜间、一段雨天,跑完再看统计数据,不只看训练曲线。评估口径对了,训练参数才有调整依据。这套思路对毕设答辩或者项目验收都很有说服力,希望帮到你。
本文还有配套的精品资源,点击获取