十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

自建数据集与YOLOv8训练:停车位检测实战全流程解析

自建数据集与YOLOv8训练:停车位检测实战全流程解析 简介从目标检测的基础概念出发探讨垂直场景下数据集的构建价值与模型训练方法。在智慧停车应用中通用预训练模型因场景差异大、车位线特征细微而难以稳定工作自建高质量数据集成为提升模型效果的关键。结合YOLOv8的Anchor-Free结构、C2f特征融合等特性阐述数据采集、标注规范、格式转换到训练调参的完整链路。通过实际案例展示如何解决车位重叠、误检、小目标检测等工程问题并介绍模型导出与部署的要点。文章强调数据集质量决定模型上限为停车位检测及类似垂直场景的目标检测任务提供可复用的实践思路。1. 停车位检测任务为什么值得单独做数据集停车位检测这个任务乍一看好像只是目标检测里的一个小分支但真正上手后你会发现它比常规的行人检测、车辆检测要难不少。车位线通常是比较细的矩形框颜色和地面接近再加上光照变化、树影遮挡、磨损褪色模型很容易误检或漏检。很多做智慧停车、停车场道闸、园区车位引导的开发者最后都会卡在“通用预训练模型根本压不住场景”这一关上。我第一次做这个项目时直接用COCO预训练权重去跑视频流里的车位检测结果相当惨烈——晴天勉强能用一到傍晚影子拉长检测框就开始乱跳停车场地面上的箭头、减速带、排水沟盖板全被当成车位。后来我意识到问题的核心不在模型结构而在数据集停车位检测是一个强场景依赖的任务通用数据解决不了专用场景的问题。所以这个项目的基本思路是自建一套针对停车位场景的数据集配合YOLOv8进行训练和迭代最终得到一个能在真实停车场环境下稳定工作的检测模型。这里的关键词是“场景”——车位线在不同停车场有不同的画法、颜色、磨损程度数据集的多样性直接决定了落地时模型的下限。整套方案适合正在做智慧停车、车位引导、停车场监控相关项目的开发者也适合想学YOLOv8训练全流程的朋友。本文会把从数据采集、标注规范、格式转换、训练调参到模型部署的完整链路拆开来讲重点分享我在实际踩坑中总结的经验而不是复述文档。2. 方案设计自建数据集还是改造公开数据集2.1 先搞清楚一个问题你检测的是“车位”还是“车位线”很多人在任务定义上就搞混了。停车位检测从视觉形态上可以分两类检测“空车位/占用车位”目标是车位里的车判断车位状态本质是车辆检测加车位划分。检测“车位线/车位框”目标是地面上的白色或黄色矩形标线输出车位的几何位置。两者在标注方式、检测目标和后处理逻辑上完全不一样。如果你的系统最终要做的是“引导车辆停入指定车位”那么检测车位线是更直接的方案因为车位的位置不依赖车是否在场。如果只是统计“还有几个空位”检测车辆反而更省事——车位上没车就是空位。我做的这个项目选择的是检测“车位框”也就是把车位本身当作目标类别。这么选的原因有几个车位框是静态目标不会跑做跟踪和多帧融合更稳定检测到车位框之后可以结合车辆检测判断占用状态扩展性更好车位框的类别相对单一数据标注的成本比多类别检测低。2.2 公开数据集为何不够用自建数据要覆盖什么市面上有一些公开的停车位数据集比如CNRPark、PKLot但这些数据集的拍摄角度、车位类型和国内停车场差异较大。CNRPark的很多场景是国外那种大型露天停车场车位线清晰、宽度统一而国内地下车库的车位线经常磨损、被柱子遮挡、灯光昏暗直接迁移过来效果并不好。自建数据集时要重点覆盖这些变化室内车库和露天停车场的不同光照条件车位线是完好的、磨损的还是部分褪色的不同颜色车位线白色、黄色为主偶尔有蓝线俯视角、平视角、斜视角的拍摄位置差异车位里有车、没车、有杂物、被挡车器遮挡等情况。采集数据时不要只在一个停车场拍尽量找三五个不同类型的地点覆盖工作日、周末、白天、晚上、晴天、阴天。我自己的经验是如果条件有限至少要保证有两个不同停车场的白天和夜间数据否则训练出来的模型换一个停车场就崩。2.3 YOLOv8在这个任务上的选型优势目标检测框架很多Faster R-CNN稳定但速度不行SSD速度尚可但小目标检测弱YOLOv5成熟但已经被v8甩开一段距离。YOLOv8在停车位检测这个场景上有几个非常实用的特性Anchor-Free设计减少了对先验框的依赖对于车位这类宽高比变化较大的目标省去了聚类anchor的步骤。C2f模块的梯度流动更顺畅小目标检测能力比之前的版本有明显提升。车位线在高空俯视画面里经常只有几十个像素宽这个提升很关键。官方仓库自带的训练、验证、导出管线很完整从PyTorch训练到ONNX/TensorRT导出一路打通适合快速迭代。如果你用的是RTX 3060、GTX 1660 Ti这档显卡YOLOv8s或YOLOv8n的体量完全跑得动训练时间也完全可以接受。关于GTX 1660Ti跑YOLOv8我实测下来用YOLOv8s、输入640、batch 16显存勉强够用时间大概是一轮三四十分钟后面会在训练章节详细说。3. 数据准备细节从拍照到符合YOLO格式3.1 数据采集的几个实用建议采集图像不要想着一口气拍完分多次、分时段、分天气去拍效果远好于一个时间点批量拍。拍照设备用手机就行重点是把车位线拍清楚分辨率别太低建议至少1920x1080能上4K更好因为后面缩放成640训练时高分辨率原图的信息量更大。拍摄角度方面我建议尽量模拟实际部署时的机位。摄像头装在高处往下看的就手持设备在高处俯拍摄像头装在墙边的就斜着拍。训练集和部署场景的视角差异越小模型效果越好这是一个经常被忽略的点。采集之后要做数据清洗把模糊的、过曝的、几乎看不清车位线的图片删掉。清洗的标准是人眼能明确分辨车位框位置和边界的图才留下。这个步骤别偷懒一张模糊图混进去可能让模型学会一些奇怪的特征。3.2 标注工具选择与标注规范标注工具我用的是LabelImg和X-AnyLabeling。LabelImg是老牌工具简单稳定但功能性弱一些适合快速拉框X-AnyLabeling支持yolo格式导出还能用预训练模型做辅助标注能省不少时间。标注规范是整个数据集质量的关键我在这个项目里定了几条规矩车位框按四角标注不要只框里面的车或挡车器框要贴合车位线内沿不要留太大边距也不要切掉车位线被车挡住的车位框能推断出完整边界的就按完整边界标看不清的坚决不标同一个车位出现在不同画面里每次都要标不要因为“之前标过了”就不标因为不同帧里车位的形变和遮挡不同类别统一命名为vacant或parking_space不要一个数据集里一会儿叫spot一会儿叫space。这里特别说下“框要贴合车位线内沿”这条。很多人标注喜欢把白色线本身也框进去导致框比真实车位大一圈。别小看这一圈训练出来的模型预测框会普遍偏大后期做车位坐标计算时误差非常麻烦。3.3 如何把标注数据转成YOLOv8要的格式YOLOv8训练的标注格式是每张图片对应一个同名的txt文件每行代表一个目标。格式是class_id x_center y_center width height注意这里的x_center、y_center、width、height都是归一化到[0,1]的值不是像素坐标。class_id从0开始编号。LabelImg直接能导出YOLO格式X-AnyLabeling也支持但如果用的是其他工具导出的VOC XML或COCO JSON就需要写个转换脚本。下面是一个把COCO JSON转成YOLO txt格式的简单脚本我项目里一直用它稍微改改就能适配自己的数据import json import os def convert_coco_to_yolo(coco_json, output_dir): with open(coco_json, r) as f: data json.load(f) # 建立类别id到yolo类别的映射这里假设只有一个类 cat_id_map {cat[id]: i for i, cat in enumerate(data[categories])} os.makedirs(output_dir, exist_okTrue) for img in data[images]: img_id img[id] img_w img[width] img_h img[height] base_name os.path.splitext(img[file_name])[0] out_path os.path.join(output_dir, base_name .txt) lines [] for ann in data[annotations]: if ann[image_id] ! img_id: continue x, y, w, h ann[bbox] # COCO格式是[x, y, width, height] x_center (x w / 2) / img_w y_center (y h / 2) / img_h w_norm w / img_w h_norm h / img_h cat_id ann[category_id] lines.append(f{cat_id_map[cat_id]} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) if lines: with open(out_path, w) as f: f.write(\n.join(lines)) print(f转换完成共处理 {len(data[images])} 张图片输出到 {output_dir})转换完之后一定要做一遍校验。最快的方式是写个可视化脚本把标注框画回图片上人眼看一遍。这一步能发现坐标归一化算错、类别id错位、txt文件名对不上等问题我自己至少两次因为文件名前缀不一致导致有的图片没有对应标注训练时那些图片直接被跳过数据集量白白缩水。4. YOLOv8训练与调参的实操记录4.1 环境配置与目录结构环境这块YOLOv8的安装很简单pip一条命令就行。需要注意的是PyTorch版本我当时用的PyTorch 2.1测试下来和YOLOv8的配合没有问题。如果你用PyTorch 2.13这种较新版本建议先跑一次推理测试确认兼容性不同小版本之间有些算子兼容性问题踩到会浪费时间。把YOLOv8跑起来项目目录结构建议这么组织parking_slot_detection/ ├── data/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── parking.yaml ├── runs/ │ └── detect/ └── weights/图片和标注文件分开放图片放images标注放labels同名文件对应。这是YOLO系训练的标准结构不要搞成图片和标签混在一起后面处理起来很乱。4.2 数据集配置文件的写法数据集的配置文件是个YAML文件内容很简单但要注意路径写绝对路径还是相对路径。为了避免在不同机器间换环境时路径失效我习惯在配置里用绝对路径# parking.yaml path: /home/user/parking_slot_detection/data train: images/train val: images/val nc: 1 names: [parking_space]nc表示类别数这里只有车位一个类别。names的列表顺序要和标注文件里的class_id对应如果class_id是0那names[0]就是这个类别的名字别搞反了。4.3 划分训练集和验证集数据划分是个容易忽略的细节。要特别注意避免同一场景的连续帧同时出现在训练集和验证集里否则验证集的指标会虚高。我见过有人从视频里抽帧做数据直接前80%帧归训练、后20%帧归验证结果模型其实“见过”了验证集里的绝大多数场景loss曲线和mAP都好看一到新环境立刻现原形。正确的做法是以拍摄场景为单位划分比如A停车场和B停车场的数据进训练集C停车场的数据进验证集。这样才能验证模型跨场景的泛化能力。如果数据量少可以用K折交叉验证来评估稳定性但那样训练成本会翻几倍建议在数据量超过1000张时再考虑。4.4 模型选择和训练参数设置YOLOv8有n/s/m/l/x五个尺寸对于停车位检测这种单类别任务从YOLOv8n或YOLOv8s起步就够了。我在1660Ti上实测模型输入尺寸batch size显存占用训练速度每轮最终mAP0.5YOLOv8n64032约4GB约20分钟0.92YOLOv8s64016约6GB约40分钟0.95YOLOv8m6408约8GB约70分钟0.96以上是我们用了约3000张训练图、100个epoch的实测数据仅供参考。如果你手里只有几百张图直接上YOLOv8m反而容易过拟合用n或s体量更稳。训练命令是这个样子的yolo detect train \ modelyolov8s.pt \ dataparking.yaml \ imgsz640 \ batch16 \ epochs100 \ patience20 \ projectruns/detect \ nameparking_exp1几个关键参数说明一下imgsz640多数场景下性价比最高的尺寸。如果车位线特别细、图像特别大可以试试768或1024mAP会有提升但训练和推理时间也相应增加。patience20早停参数连续20轮验证集指标没有提升就停止训练。这个参数防止训练时间被白白浪费。pretrained权重用yolov8s.pt作为起点不是从零训练。迁移学习在目标检测里非常有效尤其是数据量不大的时候。还有一个实用技巧如果想让模型更关注车位的边角特征可以调整数据增强参数把mosaic和mixup的开关打开。默认配置下mosaic是开启的它把四张图拼在一起训练对小目标检测很有帮助。但要注意如果数据里有大量空车位照片mosaic拼图可能会产生不自然的车位拼接导致模型学到错误的上下文这时候可以适当调低mosaic概率。4.5 训练过程中的指标监控怎么看训练时要关注两组指标训练集上的box_loss和cls_loss以及验证集上的mAP50和mAP50-95。loss曲线持续下降说明模型在学习mAP50达到0.9以上才能算基本可用。有一点我要特别强调mAP50-95和mAP50的差距能反映检测框的精度。如果mAP50很高但mAP50-95只有零点几说明模型虽然能大概找到车位位置但框不够贴合。这时候可以检查是不是标注框本身就不齐或者输入分辨率不够导致边缘预测不准。训练结束后的权重文件保存在runs/detect/parking_exp1/weights/下best.pt是验证集指标最好的模型last.pt是最后一轮的模型。默认用best.pt做推理几乎不会出错。4.6 调试一个关键问题车位重叠和大尺度变化停车位检测里两个经典难题是相邻车位框重叠和大尺度差异。相邻车位框在俯视画面里几乎紧挨着模型很容易把两个车位合并成一个框预测或者一个车位被旁边的框带偏。YOLOv8的NMS后处理阈值在这种情况下要调低一些比如从默认的0.45降到0.3能减少重叠框合并的概率。大尺度差异是指近处的车位框很大、远处的车位框很小这种场景下单一输入分辨率难以兼顾。实践中可以先用imgsz640训练推理时用imgsz800或更高效果比单纯调模型参数更立竿见影。YOLOv8推理时imgsz可以独立设置不一定跟训练时一致。5. 完整实操演示用一套小数据集跑通全流程5.1 准备一个最小可运行的示例空谈经验不如直接跑一遍下面用一个最小示例串起整个流程。假设我们已经有了一批图片每张图里都有清晰的停车位框数据量不大大约200张训练图加50张验证图。这足够跑通流程但实际项目里建议数据量至少上千张。首先把图片分为train和val两个目录对应的标注txt也分好。目录结构参照前面说的标准结构。然后写parking.yaml配置文件train和val路径指向对应目录。接着执行训练yolo detect train \ modelyolov8n.pt \ data/path/to/parking.yaml \ imgsz640 \ batch16 \ epochs100 \ patience20 \ project./runs \ namedemo_parking训练过程中控制台会滚动输出每一轮的loss、mAP等信息正常跑的话前几轮loss会快速下降后面逐渐趋缓。一轮大概几十秒到几分钟不等取决于显卡和batch size。训练完成后用best.pt对单张图片做推理yolo detect predict \ modelruns/detect/demo_parking/weights/best.pt \ source/path/to/test_image.jpg \ conf0.5 \ saveTrue模型检测结果会保存到runs/detect/predict/目录用图像查看器打开看看效果。如果置信度阈值0.5下有大量漏检可以调到0.3再试。反过来如果框特别多且重叠严重就把阈值调高一些。5.2 检测结果如何与停车位占用判断联动真正部署到停车场管理系统里只输出车位框还不够还得判断每个车位的占用状态。这里有两种常见做法一种是在车位框内部再做一次车辆检测两者叠加判断。车位框内检测到车辆且IOU超过一定比例就判为占用否则为空闲。这种方案需要两个模型协同但逻辑清晰可靠性高。另一种是直接用车位框区域的图像特征做二分类比如用颜色、纹理、边缘信息判断有没有车停在里面。实现简单但光线变化、阴影干扰会影响准确率适合对精度要求不高的场景。我实际用的是第一种方案车位检测模型和车辆检测模型都从YOLOv8训练出来然后写个后处理逻辑做匹配。关键参数是判断“占用”的IOU阈值我调下来0.3比较好用——车辆只要压过车位边线就算占用不用整个车身都在框内。5.3 模型导出与部署要点训练完的模型要部署到实际环境需要从PyTorch格式导出。YOLOv8官方支持导出ONNX、TensorRT、OpenVINO等多种格式yolo export \ modelbest.pt \ formatonnx \ opset12 \ dynamicFalse导出的ONNX模型可以用ONNXRuntime做推理也可以进一步转成TensorRT引擎发布到嵌入式设备或服务端。这里有个细节导出时imgsz要和训练时一致如果导出参数里固定了输入尺寸推理时图片会被拉伸到这个尺寸车位框的形状会被扭曲检测精度会掉。尽量保持宽高比不变用letterbox方式做resize。在GTX 1660Ti这类显卡上ONNX模型用ONNXRuntime推理输入640x640单帧耗时大概10到20毫秒完全满足实时视频流处理的需求。如果还要更快用TensorRT那几毫秒的提升在停车场景里感知不明显可以先不折腾。6. 常见问题与排查技巧实录6.1 训练loss正常但检测效果差的排查思路这是最让人头疼的情况loss一路下降mAP曲线也不错但放到新视频里就是一塌糊涂。遇到这种情况我建议按以下顺序排查第一看标注质量。在训练集上做推理如果训练数据本身的检测效果都一般那多半是标注有问题比如框过于随意、类别不一致。第二看验证集和测试集的数据分布。验证集的mAP好不代表泛化好要确认验证集和训练集是否来自同一场景如果同一场景模型很大概率是记住了场景特征而不是车位特征。第三看输入图像的实际分辨率。训练是640但部署时如果直接resize一个1920x1080的图到640车位框的像素信息被压缩得很厉害小目标基本没法检测。建议部署时用更大的推理尺寸或者做tiling切块检测。6.2 误检多尤其是柱子、地面纹理被当成车位这个问题的根源是训练数据里缺少“难负样本”。模型没见过柱子的阴影、地面箭头、减速带自然会把它们当作车位。解决办法有三招一是主动采集包含这些干扰物的图片不加标注放进训练集作为负样本。YOLOv8训练时没有标注的图片会被当作背景参与负样本学习。二是增加数据增强特别是随机裁剪、旋转、色彩抖动。这能让模型更关注车位本身的几何特征而不是地面颜色。三是调整置信度阈值。部署时把conf从0.25提高到0.5甚至0.6很多误检框的置信度其实不高提高阈值能直接滤掉。6.3 小目标车位检测不到怎么办如果你的摄像头视野很大远处的车位框可能只有20x20像素这种小目标检测几乎很难用普通参数做好。从数据角度可以尝试更高分辨率输入768或1024从模型角度P2层的特征融合对小目标检测有帮助YOLOv8的部分改进版本加入了P2检测头但需要改网络结构工程量不小。如果不想动网络结构更实用的思路是切块检测。把大图按区域切成几块每块放大后分别检测再做坐标融合。这样单帧推理会增加但小目标的检测率改善非常明显。我做过一个项目用这个方法远处车位的召回率从0.6提到了0.9左右。6.4 标注数据和训练代码相关的小坑汇总列几个我踩过不止一次的坑标签文件和图片文件前缀不一致导致图片没有对应标注被静默跳过。用脚本统计一下每个目录的文件数量对不上就说明有问题。类别编号从1开始而不是从0开始这是从别的框架迁移过来时常见的错。YOLOv8要求从0开始编号。训练集里混入了PNG带透明通道的图片虽然一般能训练但有些版本会报警告。统一转成RGB三通道的JPG比较省心。混入了标注文件为空的黑图片这些图片只提供负样本信息对单类别任务来说问题不大但如果空标签数量太多模型会偏向输出背景而漏检。7. 项目扩展方向与个人实操体会停车位检测这个项目做完之后可以往几个方向扩展。一个方向是多类别检测把“空车位”和“占用车位”做成一类还是两类可以对比实验。我自己的感受是按状态分两类更适合业务使用因为下游逻辑不用再做二次判断但训练时要保证两类样本均衡否则模型会倾向于预测占比多的那类。另一个方向是结合跟踪算法比如ByteTrack或DeepSORT检测出车位框之后对每个框做稳定跟踪这样即使某一帧漏检了前一帧的跟踪结果也能补上提高了整个系统的鲁棒性。这个在视频流分析的场景下特别实用。最后说一下我在这个项目里感触最深的一个点数据集质量决定了模型效果的上限。模型结构再新、训练技巧再多如果标注乱来、数据分布不当最后都白搭。我前几版模型效果不理想回头排查发现是标注时有一个停车场的车位线被统一标大了一圈导致那个场景下预测框全部偏大。把标注修正重训之后mAP直接涨了三个点。所以如果你也准备做停车位检测我的建议是前期在数据采集和标注上多花一倍时间后期训练和调参能省三倍时间。这套流程不仅适用于停车位检测几乎所有垂直场景的目标检测项目都可以复用这个思路——先定义清楚任务边界再打造贴合场景的数据集最后用YOLOv8这类成熟的工具快速迭代模型。本文还有配套的精品资源点击获取
返回列表