简介一份基于YOLOv8的停车场车辆占道违停检测完整工程资源面向计算机视觉、人工智能相关专业的毕设或课程设计场景可快速完成模型训练与可视化检测。资源包共含8个文件包括3个Python脚本、3个模型权重文件和2个说明文档其中源码负责可视化界面、视频检测与模型训练权重文件支持直接推理README与说明文档提供详细部署指引整体压缩包仅15.91MB便于下载与分发。当前已有62人学习使用。项目内置完整数据集与可视化页面运行后可产出核心指标曲线、混淆矩阵、F1分数曲线、PR曲线、验证集预测结果及标签分布图既能直观展示训练效果也方便答辩时呈现实验过程。项目代码已经个人测试通过功能稳定适合作为毕业设计或课程设计的基础工程也支持在此基础上扩展其他检测功能。1. 从“有没有车占道”到“马上推送告警”这个项目到底解决了什么不少做毕设或课程设计的同学第一次看到“停车场车辆占道违停检测”这个标题第一反应是“这不就是个目标检测嘛YOLOv8 一跑把车框出来不就完事了”。真做起来才知道检测只是地基真正磨人的是把检测结果变成“哪辆车、在哪个车道、违停了多久、要不要告警”这一串业务逻辑。停车场里车道、车位、禁停区往往是交织的一辆车停在车位线内是合规压线半个车身就是违停这里面的判断阈值和规则设计才是项目灵魂。这个基于 YOLOv8 的完整方案正好把从数据集标注、模型训练到可视化界面告警的整条链路串了下来。它适合两类人一类是准备拿它做毕设或课程设计的在校生需要的是能开箱跑通的完整工程和清晰可讲的创新点另一类是刚入门目标检测、想看看“训练完的模型如何接到实际业务界面里”的开发新手。接下来我会从模型选型、数据集处理、训练参数、部署方式四个环节拆解把每一步的可行做法和踩过的坑一并讲清楚。2. YOLOv8 为什么适合停车场场景检测精度之外的三个理由2.1 场景里的小目标与遮挡mAP 之外要看的指标停车场监控通常架设在立杆或建筑外墙高处视角往下俯拍车辆在画面里往往只占几十到上百像素。YOLOv8 的 C2f 结构替换了老版 C3在同样算力下对中小目标特征提取更充分这一点在车辆这类长宽比固定、纹理清晰的物体上尤其占优。但选型时不能只看公开数据集的 mAP更要看实际俯拍场景下的表现。我一般会额外统计两个指标一是检测框中心点落在真实框内的比例定位稳定性二是连续帧之间 ID 切换次数。停车场场景下车辆长时间静止如果模型每几帧就把同一辆车识别成新目标后面做“违停时长累计”就完全没法用。YOLOv8 默认的 NMS 参数对密集停放的车辆有时会误删相邻框后面排错章节会专门讲这个调整点。选型上还有一条理由很实际YOLOv8 有官方提供且仍在维护的 Python 包后续无论做界面集成还是部署到 Jetson 这类边缘设备资料都最全遇到问题最容易搜到解法。2.2 从检测框到违停判定IOU 与车道区域的配合模型输出的只是“这有个 car置信度 0.87框在 (x1,y1,x2,y2)”违停判定完全是后处理逻辑。常见做法是预先在图像坐标系里划定违停区域——可以是车位线外的道路区域、消防通道或者直接用车道线构成的凸多边形——然后用检测框中心点与这些区域做包含关系判断。具体判定规则我常用三条车辆中心点落在违停区域内判定为候选违停连续 N 帧比如 15 帧持续满足条件才正式判定违停避免车辆临时借道车辆进入违停区域后启动计时离开或驶入合规区域则复位。这些规则里 IOU 是另一个关键如果画线区域和车辆框的相交面积过小判定会抖动我一般用中心点法配合最小 IOU 阈值双条件中心点在区域内且 IOU 大于 0.3 才触发。2.3 可视化界面在项目里的真实作用不只是一个“展示壳”很多开源项目把 GUI 当成锦上添花但这个停车场违停检测项目里界面承担了规则配置和告警闭环两个功能。你可以在界面上开关不同违停区域的检测、调节置信度阈值和触发帧数而不是每次改参数都去改代码重启。对毕设答辩来说能在界面上现场演示“调整帧数后告警延迟变化”比只放一段模型跑视频的效果扎实得多。界面实现一般有两种路线PyQt5 或 Tkinter 直接绘制视频帧并叠加检测框或者用 Streamlit/Gradio 做成网页版。前者适合本地单机演示后者适合想顺带展示前后端能力的场景。项目中默认的桌面端方案通常包含视频源选择、实时检测画面、告警列表、参数调节侧栏四个模块这四块也正好对应检测、跟踪、判定、记录四条数据流。3. 训练自己的停车场数据集从采集、标注到格式转换3.1 公开数据集做底料自采数据补场景缺口标题里配套的完整数据集一般覆盖了白天、夜间、晴雨等常见情况但如果你要用在自己的校园或园区停车场强烈建议补拍一段自己场景的视频抽帧后加入训练集。俯拍视角下不同停车场的车道宽度、路面纹理、光照色温差异明显只用公开数据训练出的模型在新场景容易出现漏检。常见的公开底料是 UA-DETRAC 和停车场场景相关的车辆检测数据集前者主要是行车视角后者俯拍多两者混合能提升视角泛化性。补拍时注意几点用 1080p 及以上分辨率录制抽帧间隔按车辆运动速度调快速行车场景每 5 到 10 帧抽一张静态场景每 30 帧抽一张每张图里保证至少有 5 到 10 辆不同距离和姿态的车夜间样本至少要占总量的 20%否则夜间检测会明显崩。3.2 Labelme 标注到 YOLO 格式坐标归一化的细节标注工具用 Labelme 最常见但 YOLOv8 训练需要的是 txt 格式的归一化坐标每个标注框一行类别序号 cx cy w h。这里有个反复踩的坑Labelme 导出的是左上右下角点坐标而 YOLO 需要中心点和宽高而且必须除以图像宽高做归一化。转换脚本我一般这样写import json import os def convert_labelme_to_yolo(json_path, txt_path, class_map, img_w, img_h): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue # Labelme 存的是多边形点集取外接矩形 xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) # 转 YOLO 格式中心点 宽高全部归一化到 0~1 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 防止边界像素溢出导致训练报错 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: class_map {car: 0, bus: 1, truck: 2} # 实际使用中按你的数据集类别调整 convert_labelme_to_yolo(annotations/001.json, labels/001.txt, class_map, 1920, 1080)这个脚本里有两个容易被忽略的点一是多边形标注要用外接矩形而不是直接取前两个点因为 Labelme 手动标注的框往往不是严格矩形二是坐标边界裁剪的作用是防止标注时鼠标拖出画面边缘产生的负值或大于 1.0 的值这类脏数据会让 YOLOv8 训练直接报“AssertionError: box coords must in [0,1]”。如果你用的是矩形标注工具就取 point 列表的首尾两点即可但如果混用了多边形标注就必须按上面的方式遍历所有点求极值。3.3 数据集划分与目录组织YOLOv8 期望的文件夹结构YOLOv8 的 dataset.yaml 对目录结构有固定约定最省事的方式是 images 和 labels 分目录存放每个目录下再分 train 和 val。划分比例我一般用 8:1:1但测试集不单独建目录直接从 val 里抽因为训练阶段用不到测试集。这里有个关键点划分时按视频片段划分而不是按单帧随机划分否则同一段视频的连续帧会同时出现在训练集和验证集里验证指标虚高换到真实场景立刻打回原形。划分工具可以用 sklearn 的 train_test_split但更稳的做法是先把所有图片按视频名分组再对组做划分。另外建议在数据集根目录放一个 dataset.yaml内容指向绝对路径或相对路径并在训练命令里用 data 参数指定它。路径最好用绝对路径因为 YOLOv8 在 Windows 上跑时相对路径偶尔会串目录。4. 训练参数与损失曲线别让模型“看起来收敛实则过拟合”4.1 预训练权重选择与 batch size 的匹配逻辑YOLOv8 家族里n/s/m/l/x 五个尺寸对应从边缘设备到高性能服务器的算力范围。停车场场景用 v8s 或 v8m 性价比最高v8n 虽然轻快但俯拍小目标容易漏v8l 以上在 GTX 1660 Ti 这类显卡上训练速度会非常难受。热词检索里大量人问“GTX1660Ti 跑 YOLOv8 是什么体验”我的经验是6GB 显存跑 v8s、batch size 8 勉强流畅跑 v8m 就得降到 batch size 4再大就爆显存。预训练权重用官方 yolov8s.pt 起步比从零训练收敛快得多。有两点要注意一是下载的预训练权重包含 COCO 80 类如果你把类别映射改成只看车类加载时它会自动丢弃不匹配的类别输出层二是冻结 backbone 前三层做迁移学习只适合数据量极少的情况数据集超过 5000 张时我没觉得有明显收益反而拖慢收敛。4.2 必须手动盯的三个超参与两个训练陷阱epochs 我一般设 100 到 150配合早停 patience20。初始学习率 lr0 默认 0.01但俯拍场景数据集通常不大0.005 更稳避免前几个 epoch 梯度震荡。imgsz 默认 640如果你的监控画面是 1920×1080建议开到 960 或 1280——这对小目标检测的提升是肉眼可见的代价是训练时间和显存翻倍。如果你用的是 8GB 以下显存先用 640 跑通再试大尺寸。训练时最容易踩的两个陷阱一是 Mosaic 增强默认开启它对小目标有一定提升但当你训练集里车辆已经很小且密集时Mosaic 会把多张图拼在一起让目标变得更小这时建议打开 close_mosaic10即最后 10 个 epoch 关闭 Mosaic让模型在正常尺度上精调二是验证集里如果混入了标注质量差的数据loss 曲线会出现周期性抖动很多人误以为是学习率问题实际是标注框和真实车辆边缘差出十几个像素。4.3 损失函数曲线图怎么判断训练真的收敛了YOLOv8 训练日志里会输出 box_loss、cls_loss、dfl_loss 三条曲线val 版本的曲线才是判断依据。判断标准有三个train 和 val 的 box_loss 最终差值小于 20%val loss 曲线尾部没有明显上扬上扬就是过拟合信号precision 和 recall 曲线趋于平缓、不再随 epoch 增加而大幅跳动。画曲线的常用方式是从 runs/detect/train 目录下的 results.csv 读取数据用 matplotlib 绘制。这里给一个能直接用的小脚本import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) # YOLOv8 的列名是带空格的要去除首尾空格后取数 df.columns df.columns.str.strip() epochs df[epoch] fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(epochs, df[train/box_loss], labeltrain_box) axes[0, 0].plot(epochs, df[val/box_loss], labelval_box) axes[0, 0].set_title(Box Loss) axes[0, 1].plot(epochs, df[train/cls_loss], labeltrain_cls) axes[0, 1].plot(epochs, df[val/cls_loss], labelval_cls) axes[0, 1].set_title(Cls Loss) axes[1, 0].plot(epochs, df[metrics/precision(B)], labelprecision) axes[1, 0].plot(epochs, df[metrics/recall(B)], labelrecall) axes[1, 0].set_title(PR Curve) axes[1, 1].plot(epochs, df[metrics/mAP50(B)], labelmAP50) axes[1, 1].plot(epochs, df[metrics/mAP50-95(B)], labelmAP50-95) axes[1, 1].set_title(mAP) for ax in axes.flat: ax.legend() ax.grid(True) plt.tight_layout() plt.savefig(loss_curves.png, dpi150)这段脚本最实用的地方是同时看了 loss 和 mAP很多新手只看 loss 下降就觉得完事了实际 mAP50 可能还在 0.6 左右没到可用水平。如果 val/box_loss 下降但 mAP 不涨优先怀疑标注框质量而不是继续堆 epoch。5. 从训练好的权重到可视化界面部署的完整路径5.1 本地推理脚本用最小代码验证模型可用性训练完成后先跑一段最小推理脚本确认模型在监控画面上的实际效果再去做界面。这里用 YOLOv8 的 Python 包最快from ultralytics import YOLO # 加载训练好的权重优先用 best.pt 而不是 last.pt model YOLO(runs/detect/train/weights/best.pt) # 单张图片推理 results model.predict( sourcetest_01.jpg, conf0.35, # 置信度阈值停车场场景建议 0.3~0.4 imgsz960, # 与训练时保持一致效果最好 devicecpu, # 没 GPU 或显存不足时切 CPU速度会慢但能跑 saveTrue, projectinference_output, namedemo ) # 打印每个检测框的类别、置信度和坐标 for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f类别 {cls_id} 置信度 {conf:.3f} 坐标 {xyxy})这段代码里有三个值得展开的参数。conf0.35 是实际项目里反复调出来的经验值调太低会出现大量误检框调太高夜间车辆轮廓模糊时又会漏检。imgsz960 要与训练时一致如果训练用 640 推理用 1280模型会多一次额外缩放检测框位置会有几个像素的偏移。devicecpu 这行值得单独说——很多人的电脑没有可用 GPUYOLOv8 在 CPU 上跑 640 分辨率一帧大约需要 200 到 500 毫秒做视频实时检测不够顺畅但做单张验证完全没问题。真实开发时核心帧处理循环要单独写不能靠 predict 接口因为每次调用它都会重新加载模型。5.2 在 Ubuntu 20.04 上搭建 CPU 推理环境一次装到位的流程热词里“ubuntu20.04搭建yolov8环境cpu版本”出现频率很高这里给一套我在无 GPU 机器上验证过的流程。YOLOv8 的 CPU 推理不依赖 CUDA核心依赖只有 PyTorch CPU 版和 ultralytics 包。先确认 Python 版本是 3.8 到 3.11接着依次执行# 创建独立虚拟环境避免污染系统 Python python3 -m venv yolov8_cpu_env source yolov8_cpu_env/bin/activate # 安装 PyTorch CPU 版本务必走官方源 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics 及依赖 pip install ultralytics # 验证安装 python -c from ultralytics import YOLO; print(ok)这套流程里的关键点一个都不能省。用虚拟环境是必须的Ubuntu 20.04 自带的 Python 3.8 装了系统级 OpenCV直接 pip install ultralytics 会把 OpenCV 覆盖成 4.x可能导致系统里其他 Python 程序起不来。PyTorch 必须从官方 CPU 源装如果直接 pip install torch 会默认拉 CUDA 版本体积多出 2GB 且没有 GPU 也跑不起来。验证那步看似多余实际能提前暴露 OpenCV 的 libGL.so.1 缺失问题——头一次在新环境跑 YOLOv8 的人大概率会遇到这个报错sudo apt install libgl1 就能解掉。5.3 可视化界面里的违停逻辑实现核心代码片段界面端拿到检测框之后下一步是把框和预设违停区域做判定。下面这块代码是界面程序里做违停判定的核心逻辑可以用 PyQt5 的定时器每 100 毫秒调用一次import cv2 import numpy as np from shapely.geometry import Point, Polygon class IllegalParkingDetector: def __init__(self, polygon_points, max_distance_from_lane60): # polygon_points 是违停区域的多边形顶点格式为 [(x1,y1), (x2,y2), ...] self.parking_zone Polygon(polygon_points) self.max_distance max_distance_from_lane self.vehicle_stay_frames {} # 记录每个目标连续停留帧数 def update(self, detections, vehicle_id, frame_count): detections: YOLOv8 输出的检测结果 vehicle_id: 跟踪器赋予的车辆 ID frame_count: 当前帧序号 for det in detections: # 检测框中心点 x1, y1, x2, y2 det[bbox] center Point((x1 x2) / 2, (y1 y2) / 2) # 中心点落在违停区域内记为候选 if self.parking_zone.contains(center): if vehicle_id not in self.vehicle_stay_frames: self.vehicle_stay_frames[vehicle_id] frame_count else: stay_frames frame_count - self.vehicle_stay_frames[vehicle_id] # 超过 15 帧约 1.5 秒判定为违停 if stay_frames 15: self.trigger_alarm(vehicle_id, det[bbox], stay_frames) else: # 车辆离开区域复位计时 self.vehicle_stay_frames.pop(vehicle_id, None) def trigger_alarm(self, vehicle_id, bbox, stay_frames): # 这里接到界面告警列表记录时间、坐标和截图 print(f违停告警: 车辆ID {vehicle_id} 持续停留 {stay_frames} 帧, 位置 {bbox})这段逻辑里有三个真实场景才体会得到的细节。一是用 shapely 的 Polygon.contains 而不是自己写射线法判断点在多边形内简洁且不会写出边界 bug。二是 vehicle_stay_frames 按 ID 存储是关键边界停车场里多辆车进入同一区域每辆车的停留时长必须独立记录如果用单个变量存后进来的车会把先来的车覆盖掉。三是 15 帧这个阈值不是固定的——帧率 30fps 下 15 帧是 0.5 秒你觉得合理但如果是抽帧处理、每 5 帧才处理一次15 帧实际是 2.5 秒要按实际处理帧率换算。5.4 告警推送与记录保存界面端不仅要显示告警列表还得能回溯。常见做法是每触发一次违停就保存三样东西当前帧截图、违规时间戳、车辆检测框坐标加 ID。截图存在 run 目录下的 alarm_capture 文件夹文件名按时间戳生成方便后续按时间检索。如果想做推送告警国内环境用企业微信机器人或钉钉机器人最省事——把告警消息 POST 到 webhook 地址就行几十行代码不用买短信服务。6. 避坑与常见问题5 个让部署翻车的真实案例6.1 现象模型训练完用 best.pt 做界面推理类名全是乱码原因YOLOv8 在训练时自动把标签索引映射到 names如果你的 dataset.yaml 里 names 顺序和训练集 class_map 不一致比如训练时 car 是 0 但界面推理时加载的权重内置 names 是 40 类 COCO检测结果就会错乱。解决唯一可靠的做法是在推理脚本里显式指定 class namesmodel YOLO(best.pt)之后设置model.names {0: car, 1: bus, 2: truck}不要相信权重文件里内置的名字。这个坑在换设备部署时最容易出现你在自己电脑上训练并推理没问题拷到别的机器重新装环境后莫名出问题多半就是这里。6.2 现象夜间检测漏检率翻倍白天好好的原因数据集里夜间样本太少模型没有见过足够的暗光特征另外监控夜间画质普遍差车辆和路面灰度接近检测框置信度整体下降。解决给夜间用单独的预处理分支先做自适应直方图均衡化 CLAHE 再送入模型。我实测过同一模型加了 CLAHE 后夜间 mAP50 从 0.61 提升到 0.74。具体参数用 clipLimit2.0, tileGridSize(8,8)。这个操作不改变模型权重但能显著改善输入图像对比度属于“不改模型只改输入端”的典型优化。6.3 现象GPU 显存够但训练中途报错“CUDA out of memory”原因最隐蔽的不是 batch size 太大而是 workers 数量太多导致内存溢出的连锁反应。很多人只调 batch size不动 workers结果系统内存不够PyTorch 报错却指向 CUDA。解决把 workers 从默认 8 降到 4同时把 batch size 降到上一个档位。6GB 显存跑 v8s 用 batch 8、workers 4 是稳的。如果还想再省显存把 imgsz 从 960 降到 640代价是检测精度掉一截但至少训练不断。这里有个玄学规律同一个 batch sizeworkers 从 8 降到 4 有时反而能提速因为减少了 CPU 端数据加载的排队冲突。6.4 现象界面实时检测 FPS 极低卡成幻灯片原因视觉上最容易被忽视的元凶是画面上同时跑着检测、画框、写日志、截图四个操作还都在主线程里同步执行。检测一帧 150ms画框和截图再加 50ms主线程就被堵死了。解决把检测和界面刷新拆到两个线程检测线程用队列把结果传给主线程画框只在检测出结果的帧上做没有检测结果的帧直接显示原图截图的 IO 操作异步写。另一个常见优化是把降分辨率渲染和检测分辨率分开——界面显示用 960 宽检测用 640这样视觉流畅度提升但检测精度不掉。还有一条实际操作建议视频流别用 OpenCV 的默认 VideoCapture 读 RTSP 流容易卡改用 ffmpeg 拉流后通过管道给 OpenCV。6.5 现象车辆驶入违停区域立即告警误报严重原因没有做“稳定帧数”滤波车只是借道转弯压到区域边缘就触发了而且区域边界画得离正常车道太近。解决两条路一起走。一是把触发帧数从 0 改成 15 到 30 帧让车辆在区域内停留超过 1 秒再触发二是把违停区域缩进几个像素不要直接用车道线边缘作为判定边界——车辆压线但车身大部分在车道内时中心点通常不在区域里误报能去掉一半以上。这个调参本质上是在误报率和漏报率之间找平衡我的习惯是优先保漏报率低——漏掉一次真违停比误报一次让用户体验更差。7. 进阶收尾用 video_mode 自检整套流程再做一个“置信度回看”小功能当你把训练、部署、界面串起来之后下一步不是加功能而是做一轮系统性的自检确保每一步在不同环境都能复现。我最常用也最推荐的方法是“video_mode 回放验证”选一段包含违停车辆的视频切成三段——一段白天正常停放、一段白天穿插违停、一段夜间场景——分别在界面里跑完记录每一段的准确率、误报数和漏报数。这个验证比看单张图片的结果可靠得多因为违停判定本质是时间维度上的逻辑单帧无法验证帧数阈值设置是否合理。做这个验证时我会顺手做一个“置信度回看”功能界面上点击任意一个历史告警记录自动跳转到该时段附近的最低置信度帧列表标注模型最小的把握到底低到什么程度。这个功能对毕设答辩特别有用——答辩老师问“模型在什么情况下会失效”你可以直接调出这些低置信度帧解释是光照还是遮挡导致的问题比口头描述有说服力得多。实现很简单把告警截图时同时保存模型的 conf 值和一个 3×3 的检测框缩略图存成一行 JSON 记录在 alarm_log 文件里就行。还有一个容易被忽视的习惯是保存每次训练的完整配置快照包括 dataset.yaml 内容、超参数、训练命令、随机种子。YOLOv8 会自动存 results.csv 和 args.yaml但 dataset.yaml 里的类别映射不会自动存我踩过一次“复现训练时搞不清 0 和 1 对应什么类别”的教训后现在每次训练前都会把 dataset.yaml 复制到 runs 目录下存档。做毕设的同学答辩时把这个目录展示出来比嘴上说“我调了很多次模型”可信得多。这个方向值不值得投入如果你是要做毕设或课程设计它覆盖了深度学习落地最完整的链路——数据、训练、调参、部署、界面、业务规则——每个环节都有可讲的技术点和可优化的空间性价比很高如果你是工作中要真做停车场监控系统那核心成本不在模型训练而在数据采集和规则调试上模型反而不需要自己从头训练用预训练权重微调就够用。希望这篇拆解能帮你把这个题目的潜力榨出来少走几步我当时走过的弯路。本文还有配套的精品资源点击获取