ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv8的停车场空位车辆检测:从训练到部署全流程解析

基于YOLOv8的停车场空位车辆检测:从训练到部署全流程解析 简介这是一份面向计算机视觉方向毕业设计、课程设计及入门实战的停车场空位与车辆检测项目基于YOLOv8目标检测框架实现。资源定位清晰适合高校学生、教师以及具备一定Python基础的初学者直接使用或二次开发可快速完成从模型训练、验证评估到可视化界面展示的完整流程。压缩包共8个文件包含3个Python脚本、3个模型权重文件及2个说明文档整体大小约15.91MB。脚本分别承担模型训练、视频检测与可视化界面展示等任务权重文件提供不同规模的预训练模型说明文档则给出部署与重跑建议结构精简便于上手。资源集成完整数据集与部署教程运行后可产出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图等能满足毕业设计答辩评审时对实验数据展示的要求。项目目前已有28人浏览学习适合需要快速搭建完整目标检测演示系统并追求可靠运行结果的用户下载。1. 停车场空位检测为什么绕不开YOLOv8一个 200 车位的园区停车场管理方最想知道的是「哪片区域还剩几个空位」。靠人盯监控必然漏报更实际的做法是让程序先在画面里找到每一辆车再用车辆框去匹配固定不变的车位区域——命中就标占用否则标空位。这套基于 YOLOv8 的停车场空位车辆检测资源正是这个思路用 YOLOv8 检测画面中的车辆通过车辆框与车位坐标的空间关系判定空位状态并附带了可视化界面、完整数据集和部署说明。即使不重新训练用自带的best.pt权重跑Detection_video.py也能直接出效果想换成自己停车场的数据按README.txt跑一遍train_mode.py即可。对毕设、课设或刚接触深度学习目标检测的从业者来说这是一条能快速落地的完整链路。2. 车位检测任务拆解与YOLOv8结构要点2.1 Anchor-Free设计让部分遮挡车辆更容易被检出停车场的监控视角通常是斜俯拍车辆之间互相遮挡、车尾与车头交错是常态。YOLOv8 相比 YOLOv5 最重要的一点是彻底转向 Anchor-Free 检测头不再为每个特征图位置预设多组不同宽高比的 Anchor而是直接回归目标中心点到四条边的距离。这个设计对停车场景很友好——当一辆车只有 60% 的车身露在外面时Anchor-Based 方法需要靠预设框去“猜”完整尺寸而 Anchor-Free 方法只需要中心点可见就能稳定回归出物体边界。import torch from ultralytics import YOLO model YOLO(yolov8n.pt) x torch.randn(1, 3, 640, 640) with torch.no_grad(): preds model.model(x) for i, p in enumerate(preds): print(i, p.shape)这段代码把一张随机 640×640 的图喂进模型打印的是三个检测头的输出张量。YOLOv8 沿用多尺度检测策略分别在 8 倍、16 倍、32 倍下采样特征图上做预测所以看到三个不同尺寸的输出。preds里的计算结果直接对应小目标、中目标、大目标三个分支车位里的车离摄像头远时只有几十像素宽靠大特征图那个分支负责召回。2.2 C2f模块与解耦头读网络结构图时要盯的两个地方如果打开 YOLOv8 的网络结构图最显眼的变化是 Backbone 里的 C2f 模块取代了旧版的 C3。C2f 将输入在通道维度上切分后经过多个 Bottleneck 分支再拼接回主梯度流相当于给浅层特征保留了更长的梯度通路。在训练集中车辆样本不多、类别只有一类的情况下C2f 的这个特性直接降低了过拟合风险因为梯度不再集中流经单一卷积分支。检测头部分换成了解耦结构分类分支和回归分支各自走独立的卷积层最后再合并输出。对停车场这种单一类别场景解耦头的直观收益是分类和定位的损失互不干扰模型不会为了“把车找全”而牺牲框的贴合度。模块作用在停车场景的实际影响Anchor-Free 检测头回归中心点与宽高部分遮挡车辆仍能出框C2f 特征融合多分支梯度分流单类小数据集更稳不容易在浅层丢失语义解耦头分类/回归独立卷积车辆类别置信度与框回归质量解绑三个尺度的输出8/16/32 倍下采样远景小车与近景大车都能覆盖需要提醒的是网络结构图里 Backbone 最后一个 SPPF 模块后面接的是 PAN-FPN 结构也就是自顶向下和自底向上两路特征融合。车位检测中经常出现“近处一辆大车挡住了后面两辆小车”的情况两辆车在相邻尺度特征图上的响应强度相差很大PAN 的双向融合就是用来缓解这类尺度差异的。2.3 判断空位直接分类还是车辆框后处理拿到车辆检测结果后有两种方式得到空位信息。第一种是训练两个类别——“占用车位”和“空闲车位”让模型直接输出空位第二种是只训练“车辆”这一类再用车辆框与固定车位多边形做几何匹配框压到哪个车位哪个车位就是占用状态。这套资源走的是后一条路。第二种方案的理由很实际车位是固定的而车辆会动、会遮挡。训练一个“空位”类反而会被影子、树叶、地面反光干扰模型学到的可能是“这块区域颜色和周围不一样”而不是“这个位置没有车”。只检测车辆再拿结果去匹配预先标好的车位坐标逻辑上更干净换一个停车场也只需要更新车位坐标文件。# 用中心点匹配车位多边形的快速原型 def match_spot_by_center(cx, cy, spots): hits [] for spot_id, polygon in spots.items(): if cv2.pointPolygonTest(polygon, (cx, cy), False) 0: hits.append(spot_id) return hits这段代码是中心点判定的最小实现pointPolygonTest返回 1 表示点在多边形内0 表示在边上-1 表示在外。中心点判定实现最快但车跨两个车位时会漏判或双判后面第 5 章会给出按 IoU 匹配的更稳做法。3. 数据集整理与训练配置从目录结构到train_mode.py3.1 数据集目录结构与data.yaml编写拿到资源后先不要急着开训第一步是核对数据集目录是否满足 Ultralytics 的约定。YOLOv8 训练时要求图片和标签按 train/val 分开存放且图片目录与标签目录的名称可以自定义但图片文件名必须与标签文件名一一对应例如IMG_0421.jpg对应IMG_0421.txt。这套资源的数据集目录一般是这种形态dataset/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── IMG_0001.jpg │ │ └── IMG_0002.jpg │ └── val/ │ ├── IMG_0101.jpg │ └── IMG_0102.jpg └── labels/ ├── train/ │ ├── IMG_0001.txt │ └── IMG_0002.txt └── val/ ├── IMG_0101.txt └── IMG_0102.txt标签文件里每一行是class x_center y_center width height四个坐标全部归一化到 01。如果只有一个车辆类别所有行的开头都是 0。要快速验证标注是否画在目标上可以写一个几行的 Python 脚本读取标签文件并在图上画框这是排查标注错位最直接的手段。data.yaml是训练入口的配置文件内容类似train: dataset/images/train val: dataset/images/val nc: 1 names: 0: vehicletrain和val的路径是相对执行命令时的工作目录写的建议把训练命令统一放在项目根目录下执行避免路径对不上。nc为 1 表示只有一个类别names里 0 对应vehicle。如果后续想把“公交车、轿车、卡车”分开统计需要重新整理标签并把nc改成对应数量。3.2 train_mode.py的训练参数与关键开关train_mode.py的本质是封装了一组 Ultralytics 训练参数。常见做法是把训练参数全部显式写在model.train()里方便不同显存容量的机器直接改数字。核心部分如下from ultralytics import YOLO if __name__ __main__: model YOLO(yolov8n.pt) model.train( datadataset/data.yaml, epochs120, imgsz640, batch16, lr00.01, patience20, device0, projectruns/detect, nameparking, )第一个YOLO(yolov8n.pt)是加载 COCO 预训练权重。这样做的意义是模型初始就认得车辆、人、交通标志等常见目标在停车场数据集上只需微调而不是从零学。各参数的实际影响epochs120是训练轮数的上限配合patience20表示连续 20 个 epoch 验证集的 mAP 没有提升就自动早停。batch16是批大小显存只有 6GB 的 GTX 1660 Ti 这类卡建议降到batch8否则会直接 CUDA out of memory。imgsz640是训练输入分辨率源数据本身像素高的话可以提到 960 换取小目标召回率但显存占用按二次方上涨。lr00.01是初始学习率YOLOv8 内部会配合余弦退火调度器自动调整新手不建议改动这个值。# 如果训练意外中断支持断点续训 python train_mode.py --resume runs/detect/parking/weights/last.pt训练过程中不要频繁去看 loss 曲线是否下降正确做法是盯验证集 mAP50。常见误区是一看到 loss 波动就调学习率实际训练前 30 个 epoch 的 mAP 从 0 快速拉升、后期在 0.85 附近波动都属于正常范围。训练结束后runs/detect/parking/下会出现weights/best.pt和weights/last.pt前者是验证集表现最好的权重后续推理一律用best.pt。3.3 训练卡壳的常见位置最常翻车的地方不是模型结构而是数据路径和显存。训练启动后如果提示AssertionError: train dataset not found检查data.yaml里的路径是否相对于当前工作目录以及images/train下是否真的有图片。如果出现CUDA out of memory先把batch降到 8 或 4再考虑把imgsz从 640 降到 512。资源里的README.txt一般都会写明运行顺序先把环境变量和路径按说明配好再跑训练比盲目改参数省时间。提示本资源仅供学习参考避免用于商业用途。毕设答辩时直接展示训练曲线和测试视频是足够的不需要刻意对评审隐藏实验细节。4. 训练产物解读混淆矩阵、PR曲线与F1曲线4.1 runs/detect/parking目录下每张图对应什么训练跑完后结果目录里会有十来张 PNG 图片。毕设答辩时这些图是“模型效果可信”的直接证据但很多同学只把图贴上去说不清每张图的含义。读懂这些文件才能在答辩时言之有据。各产物的对应关系如下文件内容主要用途results.pngtrain/loss、val/loss、mAP50、mAP50-95 等曲线看整体收敛趋势与是否过拟合confusion_matrix.png实际类别与预测类别的混淆矩阵看漏检和误报方向F1_curve.png不同置信度阈值下的 F1 分数确定最终推理阈值PR_curve.png精确率-召回率曲线看模型在召回率和误报之间的平衡labels.jpg训练集所有 GT 框叠加图检查标注框与目标是否吻合val_batch*_pred.jpg验证集预测结果可视化随机抽几批图的预测框效果results.png是六宫格图里面包含了训练集和验证集的 box_loss、cls_loss、dfl_loss以及 mAP50 和 mAP50-95 两条评价曲线。这张图最容易被评审拿放大镜看如果 mAP50 最终在 0.9 以上但 mAP50-95 低于 0.6说明车辆框定位不错、但对精确边框回归还有提升空间。4.2 混淆矩阵怎么读confusion_matrix.png是一个 N1 乘 N1 的方形矩阵行列都是类别加背景。对角线上的数字越大越好代表该类别被正确分类的比例。停车场这个项目里只有一类 vehicle你最需要盯的是「vehicle 被预测成 background」这一格——这一格的值越大说明漏检越严重通常发生在远距离小目标或者严重遮挡的车辆上。另一个值得看的位置是「background 被预测成 vehicle」对应的行。如果这项数值偏高说明模型把地面文字、轮胎阴影、绿化带误判成了车。出现这种情况时优先增加训练集中含阴影和地面干扰的负样本图而不是盲目加 epoch。只调训练时长不会消除误检数据里没见过这类反例模型就永远学不会区分。4.3 用F1_curve选出最终置信度阈值置信度阈值是推理阶段最容易被忽略的超参数。F1_curve.png横轴是置信度阈值纵轴是 F1 分数。每一类是一条曲线曲线最高点对应的横坐标就是这类的最优阈值。毕设里推理代码写conf0.25当然能跑但往往不是最优选择。from ultralytics import YOLO model YOLO(runs/detect/parking/weights/best.pt) metrics model.val(datadataset/data.yaml, splitval, conf0.25, iou0.5) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map)这段代码用model.val()在验证集上重新评测一轮splitval指定评测集合conf0.25是置信度过滤iou0.5是 NMS 的 IoU 阈值。输出项metrics.box.map50对应当前阈值下的 mAP50。如果想更精确选阈值可以把conf依次设成 0.3、0.4、0.5对比哪个值下 mAP50 最高。注意model.val()返回的指标是整个验证集上的测试集效果还取决于监控视角与训练集视角的相似度。5. Detection_video.py实战视频推理与车位占用后处理5.1 权重加载与推理参数选择Detection_video.py承担的是视频流推理任务。模型加载只有一行YOLO(best.pt)但推理参数需要根据视频分辨率来调。视频帧比训练图更大时模型内部会按imgsz参数缩放后再推理imgsz太大会拖慢每帧速度太小又会丢掉小目标。对 1080p 的停车场监控视频imgsz640是速度和召回率的平衡点如果用的是 4K 画面且只想检测近处车辆可以降到 480 提速。from ultralytics import YOLO import cv2 model YOLO(runs/detect/parking/weights/best.pt) cap cv2.VideoCapture(parking_lot.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model.predict(frame, imgsz640, conf0.4, iou0.45, verboseFalse)[0] boxes results.boxes.xyxy.cpu().numpy() clss results.boxes.cls.cpu().numpy() confs results.boxes.conf.cpu().numpy()results.boxes.xyxy是每个框的左上角和右下角坐标格式为[x1, y1, x2, y2]。clss是类别索引单类模型里全部是 0。拿到这三组数据后车辆检测阶段就算完成剩下的工作属于业务逻辑层面。verboseFalse是关掉逐帧的打印日志避免控制台被刷爆。5.2 车辆框与固定车位区域的匹配逻辑匹配是整套系统能否准确报空位的核心。车位坐标一般通过提前标定得到常见落地方式是每个车位存一个四边形的四个角点放在 JSON 或 Python 配置里。最稳妥的匹配算法不是看中心点而是计算车辆框与车位多边形的 IoU一辆车同时压到两个车位时只取 IoU 最大的那个车位。import numpy as np import json with open(spots.json, r, encodingutf-8) as f: spots json.load(f) def polygon_area(poly): pts np.array(poly, dtypenp.int32).reshape(-1, 2) return cv2.contourArea(pts) def match_spots(car_box, spots, overlap_thresh0.15): car_poly np.array([[car_box[0], car_box[1]], [car_box[2], car_box[1]], [car_box[2], car_box[3]], [car_box[0], car_box[3]]], dtypenp.int32) car_area polygon_area(car_poly) occupied [] best_iou overlap_thresh best_spot None for spot_id, spot_poly in spots.items(): spot_poly np.array(spot_poly, dtypenp.int32).reshape(-1, 2) inter, _ cv2.intersectConvexConvex(car_poly, spot_poly) inter_area polygon_area(inter) if inter is not None else 0 union_area car_area polygon_area(spot_poly) - inter_area iou inter_area / union_area if union_area 0 else 0 if iou best_iou: best_iou iou best_spot spot_id if best_spot is not None: occupied.append(best_spot) return occupiedcv2.intersectConvexConvex只支持凸多边形停车位的四边形恰好是凸的所以可以直接用。overlap_thresh0.15表示车辆与车位重叠面积占比超过 15% 才认为该车位被占。这个阈值不是拍脑袋定的调到 0.3 以上时斜停的车辆有一半车身在车位外会被漏判调到 0.05 以下时车尾扫过相邻车位会多报占用。5.3 空位状态绘制与视频输出匹配完成后把车位状态画回视频帧才算一个完整的上层应用。空闲车位用绿色、占用车位用红色绘制车位上显示车辆置信度数字供人工核对。绘制代码可以继续复用的 OpenCV 原语for spot_id, polygon in spots.items(): poly np.array(polygon, dtypenp.int32).reshape(-1, 2) color (0, 0, 255) if spot_id in occupied_set else (0, 255, 0) cv2.polylines(frame, [poly], isClosedTrue, colorcolor, thickness2) cv2.putText(frame, spot_id, tuple(poly[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) out.write(frame)occupied_set是由前面匹配函数累加出来的被占车位 ID 集合。这里有个容易忽略的细节cv2.putText的坐标是 Go 的整型元组poly[0] 恰好是车位第一个角点所以车位标签会出现在四边形顶点上。如果想在车位面积较大时把标签放在中心点用cv2.moments(poly)计算质心。最后out.write(frame)把绘制完的帧写入VideoWriter输出文件建议用 MP4 编码。6. Visual_interface.py界面整合与轻量部署优化6.1 界面线程模型与帧更新逻辑资源里的Visual_interface.py通常承担的是把视频推理封装成可视化页面的任务。无论底层用的是 PyQt5 还是 Web 前端核心问题只有一个推理循环不能阻塞界面刷新。PyQt5 的常见做法是让视频推理跑在QThread子线程里子线程把推理完成的帧通过信号发给主线程主线程只负责把 QImage 显示到控件上。class DetectThread(QThread): frame_ready pyqtSignal(QImage) def run(self): cap cv2.VideoCapture(parking_lot.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results self.model.predict(frame, conf0.4, verboseFalse)[0] annotated results[0].plot() rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape img QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(img)frame_ready信号把每一帧打包成QImage交给主界面主界面的槽函数只需执行label.setPixmap(QPixmap.fromImage(img))。这里有一个关键点self.model.predict()在循环里重复调用没问题但不要让模型对象在每次循环里被重新加载否则加载权重的时间会占掉一大半帧率。results[0].plot()是 Ultralytics 自带的可视化接口会把检测框、类别名和置信度直接画在帧上省去手工绘制。如果想要定制车位状态颜色就丢掉plot()改用第 5 章的匹配逻辑自行拼接画面。6.2 ONNX导出与跨平台部署验证毕设答辩只需要在演示机本地运行但如果你想把检测结果部署到 Jetson、RK3588 这类边缘设备上或者脱离 Python 环境单独发布ONNX 导出是成本最低的一步。这种资源里通常不直接提供 ONNX 文件需要自己用官方导出命令生成。yolo export modelruns/detect/parking/weights/best.pt formatonnx dynanmicTrue imgsz640导出成功后用 onnxruntime 快速验证输出张量的尺寸是否符合预期import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) blob np.random.rand(1, 3, 640, 640).astype(np.float32) outputs sess.run(None, {sess.get_inputs()[0].name: blob}) print([o.shape for o in outputs])YOLOv8 导出的 ONNX 输出通常是一个[1, 84, 8400]的张量其中 84 来自 4 个坐标值加 80 类 COCO 类别数8400 是三个尺度特征图展平后的候选框总数。用这个导出模型在 RK3588 或 CPU 上跑前记得确认dynanmic参数是否允许输入分辨率变化如果后续要换分辨率推理把dynanmicTrue保留下来否则单次导出的模型只能接受固定尺寸输入。验证输出尺寸这一步能过滤掉绝大多数部署期的 shape 报错。本文还有配套的精品资源点击获取
返回列表