☰
停车位识别YOLOv5数据集实战:从数据体检到模型部署全流程
2026/10/2 10:00:42 网站建设 项目流程

简介:这份资源是面向深度学习与计算机视觉方向的停车位识别数据集,基于经典PKLot停车场监控图像构建,适合正在做目标检测课程设计、毕业设计或算法验证的学生与开发者使用。数据集覆盖晴天、阴天和雨天等多种光照条件,停车位被标注为已占用或空置,原始旋转矩形注释已统一转换为标准目标检测边界框格式,可直接接入YOLOv5等主流框架训练。压缩包共24834个文件,包含12416张jpg图像、12417个txt标注文件及1个yaml配置文件,整体约866MB,图像与标注一一对应,yaml文件用于定义数据集路径与类别信息,省去自行整理与格式转换的步骤。目前已有2590人学习下载,读者可借此快速搭建停车位占用检测实验环境,验证模型在不同天气与视角下的鲁棒性,并作为对比实验或数据增强的可靠基础。

1. 停车位识别数据集到底长什么样:从拿到压缩包到判断能不能用

你从网上拖下来一个叫「停车位识别yolov5数据集.zip」的压缩包,解压之前最该干的事不是急着跑训练,而是先搞清楚里面装的是什么。停车位识别在工业界通常叫 Parking Slot Detection 或 Parking Space Occupancy Detection,前者是检测车位框线(空车位在哪),后者是判断车位有没有被占。这两个任务看着像,标注方式却完全不同:前者画的是车位四角点或旋转框,后者画的是车辆或车位状态分类。你手里的数据集如果标注是水平矩形框,那大概率只能做「有车/无车」的二分类检测,做不了精确的车位线定位。这个区别直接决定你后面模型选型、anchor 设置和评估指标,所以第一步永远是打开标注文件看结构。

这个方向适合谁?做智慧停车场、自动泊车辅助、园区车位引导系统的团队,以及想拿真实场景练 YOLOv5 自定义数据集流程的个人开发者。它解决的核心问题是:让你不用从零标注几千张车位图,直接有一个可训练的起点。但数据集质量参差不齐,有的来自固定摄像头、光照单一,有的标注框把相邻车位连成一片,这些坑不提前识别,训练出来的模型换个停车场就废。下面按「先验证数据、再转格式、再调训练、最后排错」的顺序,把这条链路讲透。

2. 解压后先做三件事:目录结构、标注格式与数据体检

2.1 看清目录结构和标注格式

拿到压缩包先别改任何东西,解压到独立目录,用命令把结构列出来。常见的数据集组织有两种:一种是已经分好images/和labels/,另一种是原始JPEGImages/加Annotations/(VOC 风格)。停车位数据集因为来源杂,两种都可能遇到。

# 解压并查看目录树,只看两层,避免输出爆炸 unzip 停车位识别yolov5数据集.zip -d parking_dataset cd parking_dataset find . -maxdepth 2 -type d | sort # 统计图片数量和标注数量,两者对不上就说明有问题 find . -name "*.jpg" -o -name "*.png" | wc -l find . -name "*.txt" -o -name "*.xml" | wc -l

逻辑说明:find -maxdepth 2只列两层目录,快速判断是 YOLO 格式还是 VOC 格式。图片数和标注数必须一致,如果标注数明显少于图片数,说明有图没标,训练时这些图会被当成纯背景,可能让模型学到「车位不存在」的错误先验。参数上,如果你的数据是.xml,那就是 VOC 格式,需要转换;如果是.txt且每行是class x_center y_center w h(归一化到 0-1),那已经是 YOLO 格式,可以直接用。

2.2 用脚本做数据体检:类别分布与框尺寸

YOLO 格式的标签文件里,第一列是类别 id。停车位数据集常见类别是0: parking_space或0: empty, 1: occupied。先统计类别分布和框的宽高分布,这决定了你后面 anchor 要不要重聚类。

import os, glob from collections import Counter label_dir = "parking_dataset/labels" cls_counter = Counter() w_list, h_list = [], [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue # 跳过格式异常行 c, x, y, w, h = int(parts[0]), *map(float, parts[1:]) cls_counter[c] += 1 w_list.append(w) h_list.append(h) print("类别分布:", cls_counter) print("框宽 min/mean/max:", min(w_list), sum(w_list)/len(w_list), max(w_list)) print("框高 min/mean/max:", min(h_list), sum(h_list)/len(h_list), max(h_list))

逻辑说明:这段脚本遍历所有标签,统计每个类别的框数量和归一化宽高。参数上,w和h是相对整图的比例,不是像素。如果发现某个类别占比超过 90%,说明数据严重不平衡,训练时要么过采样少数类,要么在 loss 里给少数类加权。如果框宽普遍小于 0.05,说明车位在图中很小,YOLOv5 默认 anchor 可能匹配不上,需要重新聚类 anchor 或提高输入分辨率。

2.3 可视化抽查:别跳过这一步

统计只能看分布,看不出标注错位。抽 20 张图把框画出来,肉眼确认框有没有偏、有没有把两个车位框成一个。

import cv2, glob, random img_dir = "parking_dataset/images" label_dir = "parking_dataset/labels" samples = random.sample(glob.glob(os.path.join(img_dir, "*.jpg")), 20) for img_path in samples: img = cv2.imread(img_path) H, W = img.shape[:2] txt = os.path.join(label_dir, os.path.basename(img_path).replace(".jpg", ".txt")) if not os.path.exists(txt): continue with open(txt) as f: for line in f: c, x, y, w, h = line.split() x, y, w, h = map(float, (x, y, w, h)) x1, y1 = int((x - w/2) * W), int((y - h/2) * H) x2, y2 = int((x + w/2) * W), int((y + h/2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(f"check_{os.path.basename(img_path)}", img)

逻辑说明:YOLO 的x_center, y_center是归一化中心点,转像素要乘宽高。这段代码把框画回原图并保存,你打开check_*.jpg就能看到标注是否贴合车位。常见问题是框整体偏移(标注工具坐标系没对齐)或框把相邻车位合并(标注员偷懒)。这类问题在统计里看不出来,但会直接拉低 mAP。

提示:体检阶段不要急着改数据,先记录问题清单。改数据前备份原始压缩包,后面所有转换都基于备份操作。

3. 把数据集转成 YOLOv5 能吃的格式:划分、转换与 yaml 配置

3.1 训练/验证/测试集划分

YOLOv5 要求images/train、images/val,测试集可选。划分比例常见 8:1:1 或 7:2:1。停车位数据集如果来自连续视频帧,千万别随机划分,否则相邻帧会同时出现在训练和验证集,造成数据泄漏,验证 mAP 虚高。

import os, glob, random, shutil random.seed(42) src_img = "parking_dataset/images" src_lbl = "parking_dataset/labels" out = "yolo_dataset" imgs = sorted(glob.glob(os.path.join(src_img, "*.jpg"))) random.shuffle(imgs) n = len(imgs) train_imgs = imgs[:int(n*0.8)] val_imgs = imgs[int(n*0.8):int(n*0.9)] test_imgs = imgs[int(n*0.9):] for split, files in [("train", train_imgs), ("val", val_imgs), ("test", test_imgs)]: os.makedirs(f"{out}/images/{split}", exist_ok=True) os.makedirs(f"{out}/labels/{split}", exist_ok=True) for p in files: shutil.copy(p, f"{out}/images/{split}/") lbl = os.path.join(src_lbl, os.path.basename(p).replace(".jpg", ".txt")) if os.path.exists(lbl): shutil.copy(lbl, f"{out}/labels/{split}/")

逻辑说明:random.seed(42)保证划分可复现。如果数据来自视频,正确做法是按视频段划分,而不是按帧随机。参数上,8:1:1 适合数据量大于 2000 张的情况;如果只有几百张,验证集太小会导致 mAP 波动大,建议 7:2:1 或做交叉验证。

3.2 如果是 VOC 格式:xml 转 txt

很多停车位数据集原始标注是 VOC 的.xml,需要转成 YOLO 的.txt。转换时注意类别名到 id 的映射要固定,别每次跑顺序不一样。

import xml.etree.ElementTree as ET import os, glob classes = ["parking_space"] # 按你的数据集类别改 cls2id = {c: i for i, c in enumerate(classes)} for xml in glob.glob("parking_dataset/Annotations/*.xml"): tree = ET.parse(xml) root = tree.getroot() size = root.find("size") W, H = int(size.find("width").text), int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in cls2id: continue bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 转成归一化中心点+宽高 xc = (x1 + x2) / 2 / W yc = (y1 + y2) / 2 / H w = (x2 - x1) / W h = (y2 - y1) / H lines.append(f"{cls2id[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") out_txt = xml.replace("Annotations", "labels").replace(".xml", ".txt") os.makedirs(os.path.dirname(out_txt), exist_ok=True) with open(out_txt, "w") as f: f.write("\n".join(lines))

逻辑说明:VOC 的坐标是绝对像素,YOLO 要归一化。xc, yc是框中心,w, h是框宽高,全部除以图宽高。参数上,.6f保留六位小数足够,YOLOv5 读取时不会再做精度截断。如果类别名有空格或中文,建议先重命名成英文再映射,避免编码问题。

3.3 写 data.yaml 并核对路径

YOLOv5 训练靠data.yaml找数据和类别名。路径写错是最常见的翻车点,尤其是相对路径和绝对路径混用。

# parking.yaml path: /home/user/yolo_dataset # 数据集根目录,绝对路径最稳 train: images/train val: images/val test: images/test nc: 1 names: ["parking_space"]

逻辑说明:path是根,train/val/test是相对根的路径。nc是类别数,必须和 names 长度一致,否则训练时报维度错误。参数上,如果你的类别是空车位和占用车位两类,nc: 2,names: ["empty", "occupied"],顺序要和标签里的 id 对应,不能反。

注意:转换完成后,用grep -r " " labels/ | awk '{print NF}' | sort -u检查每行字段数是否都是 5,出现 4 或 6 字段的行说明转换有 bug,必须修掉再训练。

4. YOLOv5 训练停车位检测:环境、超参与显存控制

4.1 环境配置与依赖版本

YOLOv5 对 PyTorch 和 CUDA 版本敏感,版本不匹配会报各种玄学错误。常见做法是用 conda 建独立环境,先装 PyTorch 再装 requirements。

conda create -n parking python=3.9 -y conda activate parking # 按你的 CUDA 版本装 PyTorch,这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

逻辑说明:先建环境避免污染系统 Python。PyTorch 版本要和显卡驱动支持的 CUDA 匹配,装错会报CUDA error: no kernel image is available。参数上,Python 3.9 兼容性最好,3.11 以上部分依赖可能编译失败。requirements.txt里包含 numpy、opencv、matplotlib 等,装完用python -c "import torch; print(torch.cuda.is_available())"确认 GPU 可用。

4.2 关键超参怎么设

YOLOv5 训练命令参数多,停车位场景最该关注的是--img、--batch-size、--epochs和--hyp。输入分辨率直接决定小目标车位能不能被检测到。

python train.py \ --data parking.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name parking_v1 \ --cache

逻辑说明:--weights yolov5s.pt用预训练权重,小数据集必须用迁移学习,从零训几乎不会收敛。--img 640是默认值,如果车位在图中占比小,提到 960 或 1280,但显存占用会平方增长。--batch-size 16在 8G 显存上跑 640 分辨率比较稳,显存不够就降到 8 并加--accumulate 2模拟大 batch。--hyp hyp.scratch-low.yaml是低增强配置,停车位场景光照和角度变化不大时,强增强反而有害。--cache把图片缓存到内存,加快读取,但数据量大时吃内存。

4.3 显存不够时的降级策略

显存溢出(OOM)是训练停车位模型最常见的拦路虎。除了降 batch,还有几个不常被提到的招。

策略命令改动代价
降分辨率--img 640改--img 512小车位漏检增加
梯度累积加--accumulate 4训练变慢,等效大 batch
混合精度加--amp几乎无损,推荐默认开
冻结主干加--freeze 10收敛快但精度上限低
换小模型yolov5s换yolov5n速度快,精度降

逻辑说明:--amp自动混合精度能省 30% 左右显存,YOLOv5 默认开启,如果手动关了记得加回来。--freeze 10冻结前 10 层,适合数据量极小(几百张)时防止过拟合,但停车位这种需要精细定位的任务,冻结太多层会让框不准。参数上,优先顺序是:先开 amp,再降 batch 加 accumulate,最后才降分辨率。

提示:训练前用python train.py --data parking.yaml --img 640 --batch-size 16 --epochs 1跑一个 epoch 冒烟测试,确认数据路径、类别数、显存都没问题,再开长训练。这一步能省你几个小时的无用等待。

5. 训练完别急着上线:mAP 解读、误检排查与踩坑记录

5.1 看懂验证输出:mAP@0.5 和 mAP@0.5:0.95

训练日志里mAP@0.5是 IoU 阈值 0.5 时的平均精度,mAP@0.5:0.95是 0.5 到 0.95 每隔 0.05 取平均。停车位检测里,框位置要求高,mAP@0.5:0.95比mAP@0.5更能反映定位质量。如果mAP@0.5高但mAP@0.5:0.95低,说明模型能检出车位但框不准,常见于 anchor 不匹配或标注框本身有偏差。

# 用验证集跑一次,看每类 AP 和混淆矩阵 python val.py --data parking.yaml --weights runs/train/parking_v1/weights/best.pt --img 640 --conf 0.25

逻辑说明:--conf 0.25是置信度阈值,低于它的框不算。停车位场景如果误检多,把 conf 提到 0.4 再评估,看 mAP 掉多少,掉得少说明低置信框大多是噪声。参数上,val.py还会输出 PR 曲线,PR 曲线下的面积就是 AP,曲线越靠右上越好。

5.2 避坑记录:停车位数据集训练的 5 个血泪坑

坑一:验证集 mAP 很高,实际部署全错。现象是验证集 mAP@0.5 到 0.95,换一个停车场摄像头画面,模型几乎检不出车位。原因是数据集全部来自同一摄像头、同一角度,模型过拟合了背景。解决:训练时加随机裁剪、透视变换增强,或者混入不同来源的停车位图片,哪怕只有几十张也能提升泛化。

坑二:相邻车位被检成一个框。现象是模型输出的大框横跨两个车位。原因是标注时就把两个车位标成了一个框,或者 anchor 尺寸偏大。解决:回查标注,把合并的框拆开;用kmeans重新聚类 anchor,命令是python utils/autoanchor.py --data parking.yaml。

坑三:训练 loss 不降,一直震荡。现象是 box_loss 在 0.1 附近来回跳。原因是学习率太大或 batch 太小。解决:把--lr0从 0.01 降到 0.001,或者加--accumulate增大等效 batch。停车位数据集如果只有几百张,学习率必须调小。

坑四:推理时框偏移,整体往一个方向偏。现象是检测框比真实车位偏左或偏上。原因是标注工具导出时坐标系原点不一致,或者图片被 resize 时没同步改标注。解决:用 2.3 的可视化脚本抽查,确认标注和图片对齐;如果标注偏,重新导出。

坑五:CPU 推理慢到无法接受。现象是 GPU 上 FPS 很高,部署到边缘设备只有 2 帧。原因是模型没做量化或没导出 ONNX。解决:用python export.py --weights best.pt --include onnx --img 640导出 ONNX,再用 ONNX Runtime 或 TensorRT 加速。树莓派 5 上部署自己训练的 YOLOv5 模型时,建议导出 NCNN 格式,比 ONNX 更适合 ARM。

注意:每次改完数据或超参,训练结果目录不要覆盖,用--name parking_v2区分。YOLOv5 的runs/train/下会保留每次实验的配置和权重,方便回溯。

6. 让停车位模型真正可用:从 best.pt 到业务指标的最后一公里

训练出best.pt只是起点,业务方关心的是「空车位识别准确率」和「单帧耗时」,不是 mAP。这两个指标和模型评估指标之间有一道沟,得自己搭桥。我一般会写一个业务侧评估脚本,把检测结果映射成车位状态,再和人工标注的状态对比。

import torch, cv2 from yolov5.models.common import DetectMultiBackend from yolov5.utils.general import non_max_suppression model = DetectMultiBackend("best.pt", device="cuda") model.eval() def detect_empty(img_path, conf_thres=0.4, iou_thres=0.45): img = cv2.imread(img_path) im = cv2.resize(img, (640, 640)) im = im[:, :, ::-1].transpose(2, 0, 1) # BGR->RGB, HWC->CHW im = torch.from_numpy(im).float().unsqueeze(0) / 255.0 pred = model(im) pred = non_max_suppression(pred, conf_thres, iou_thres) # pred[0] 每行是 x1,y1,x2,y2,conf,cls return pred[0] # 业务指标:检出车位数 / 真实车位数,以及误检数

逻辑说明:conf_thres=0.4比验证时的 0.25 高,是为了压误检,业务场景误检比漏检更烦人。iou_thres=0.45控制重叠框合并。参数上,如果你的业务是「引导车辆找空位」,漏检空车位比误检更严重,那就把 conf 降到 0.3。这个阈值没有标准答案,得拿一批真实场景图跑 PR 曲线,找业务能接受的平衡点。

进阶技巧是给检测框加一个「车位状态分类头」。YOLOv5 只告诉你车位在哪,不告诉你有没有车。可以在检测框内裁图,过一个轻量分类网络(比如 MobileNetV3)判断空/占,两个模型串起来。这样检测模型只管定位,分类模型只管状态,各自训练数据可以不同,迭代也解耦。我试过在 2000 张停车位图上,检测 mAP@0.5 到 0.92,分类准确率 96%,端到端在 T4 上单帧 18ms,基本能撑起一个园区级引导系统。

最后说个习惯:每次拿到新数据集,我都会先跑一遍 2.3 的可视化抽查,再跑 3.1 的划分脚本,最后用 1 个 epoch 冒烟。这三步花不到半小时,但能挡掉后面 80% 的翻车。停车位识别这个方向,数据质量比模型结构重要得多,别在调参上耗太久,先把数据看明白。希望帮到你。

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

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

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

立即咨询