基于YOLO系列的智能停车位检测系统落地实践:从模型选型到状态判断
智能停车位检测系统这两年是真的火从商场停车场到路边泊位到处都在谈用YOLOv5、YOLOv8、YOLOv10做检测。但说句实话网上能跑通的Demo很多真拿到现场去用的却很少。我做过几套完整的落地系统从算法选型、数据准备、模型训练到后端状态判断都趟过一遍这篇就把基于YOLO系列做智能停车位检测的完整思路和实现细节梳理出来给打算自己动手做一套的朋友当个参考。1. 停车位检测多数人一开始就搞错了目标先纠正一个最常见的误区停车位检测的核心不是“检测车位”而是“检测车 区分车位区域 判断车位是否被占用”。很多新手拿到项目就直接去找“空车位检测数据集”想训练一个模型输出哪些车位是空的这种做法在固定场景下还能凑合一换停车场就彻底崩盘。原因很简单——每个停车场的泊位线颜色、角度、磨损程度、光照都不一样直接检测“空车位”这个抽象概念等于让模型记住“某一种特定画风的空车位”而不是学会理解“这个区域里有车和没车的差别”。而反过来把任务拆成“检测车辆”和“划分车位编号区域”模型学的就是更通用的语义信息车长什么样车位在什么物理位置。车辆是刚性目标跨场景的泛化能力远强于车位状态。真正落到算法链路上整个系统应该长这样用固定的相机或一组相机覆盖一片停车场每个相机画面里预先把车位坐标标好车位可以是旋转变换后的矩形框。用YOLO系列模型实时检测画面里的所有车辆。把检测到的车辆框和标定好的车位框做匹配判断车位框与车辆框的交叠面积超过一定比例就认为该车位被占用。对每个车位做状态投票或时序平滑输出最终的空闲/占用状态。这也是我建议的架构模型只负责检测车车位状态交给后处理逻辑去推。这样替换停车场的时候只需要重新标一次车位坐标模型完全不用重训维护成本低很多。那YOLOv5、YOLOv8、YOLOv10在这个链路里各自扮演什么角色它们其实都是车辆检测器选型决定了你的精度上限、推理速度、训练成本以及后续部署的顺滑程度。下一节细说。1.1 相机视角决定了任务难度停车场检测和普通的行人检测、路面车辆检测最大的区别在于拍摄视角。俯视的枪机探头还好如果是水平视角的球机车辆彼此遮挡会很严重车位靠里的车只能看到车顶或半个车尾。我遇到的真实场景里一个停车位在不同视角下可能呈现三四种形态正后方视角看是一大块平面侧面视角看是一整个车身侧面斜45度视角看是车尾加一点侧身。模型的鲁棒性必须靠数据多样性扛住所以我会在数据集里刻意混合多种视角而不是只用自己的相机截图。1.2 车位框不是矩形那么简单另外一个隐蔽的坑是车位框的形状。正常的地面车位用旋转矩形标定还好如果是斜列式车位视觉上是个平行四边形直接用一个水平矩形框会包含大量无关路面导致状态判断时车辆和车位的IoU计算失真。我个人的做法是车位用四点多边形保存匹配时转成掩码算重叠面积而不是简单用矩形。这一步能在后处理阶段减少至少10%的误判后面我会专门讲状态判断逻辑。2. YOLOv5、YOLOv8、YOLOv10选型不是越新越好先把三者的关键差异摆出来再讲我的选型逻辑。毕竟网上到处是“新版吊打旧版”的说法但落地项目里要考虑的东西远比精度指标多。对比维度YOLOv5YOLOv8YOLOv10模型结构C3 anchor-basedC2f anchor-free可切anchorC3k2 anchor-free NMS-free检测头耦合头带obj分支解耦头两分支clsreg解耦头两分支 one-to-many/one-to-one双头训练成本低显存友好中稍高中高双头计算略重推理速度较快快最快去掉了NMS生态成熟度极高部署资料最多很高周边工具全较新坑需要自己趟典型场景边缘设备上追求极致稳定大部分服务端/边缘通用任务高吞吐实时系统、低延迟需求从原理上讲YOLOv5还在用anchor机制模型要先在预设的锚框基础上做回归对目标尺寸变化需要谨慎设置anchorsYOLOv8把检测头改成anchor-free直接预测中心点和宽高训练时就少了很多anchor超参YOLOv10在v8的基础上把NMS都省了用one-to-one匹配让每个目标只对应一个预测框推理链路更短。OpenCV和ONNX Runtime部署时少一个NMS后处理模块能省不少事。但省了NMS不代表没有后处理YOLOv10输出的置信度分布和v5/v8不太一样你需要重新校准判断阈值这是很多人没有准备好的点。2.1 我的结论选YOLOv8起步边缘设备选v5高并发实时场景考虑v10给一个明确的建议别犹豫如果你是第一版系统或者团队里有人对YOLOv5已经非常熟那就继续用v5。它最大的优势是社区资料多任何部署问题都能搜到答案而且轻量化版本跑在Jetson这类板子上非常稳。如果你要从零开始搭一套新系统直接上YOLOv8。v8的anchor-free机制让训练更省心模型精度和速度均衡官方对导出ONNX和TensorRT的支持也最完善后处理写起来是最顺手的一版。如果你做的是几十路视频流同时分析的高并发系统且单路延迟要求低于30msYOLOv10值得认真考虑。去掉NMS后一张图从推理到出结果省掉的那几毫秒在高并发下会放大成很可观的吞吐提升。停车位检测这个任务本身不是极难目标车辆目标大、特征清晰真正决定系统好坏的反而是数据覆盖和状态判断逻辑。所以我的建议是选你最熟悉、部署链路最顺的那一版不要在换模型上纠结太久。2.2 车辆检测在停车场景的特殊性车辆检测模型本身不算难点停车场的环境比开放道路简单很多——没有红绿灯、没有行人横穿、车速极低。但有两个特殊情况需要专门处理第一是重叠遮挡。车挨着车停侧面视角里前车的车尾可能和后车的车头叠在一起YOLO的NMS很容易把两个目标合并成一个框。我训练时会特别注意阶段性地降低NMS的IoU阈值比如从默认的0.45降到0.35给遮挡检测留余地。当然YOLOv10没有传统NMS它的one-to-one机制天然对重叠压制更友好一些。第二是车辆子类差异大。小轿车、SUV、货车、甚至电动车在停车场里都是常客。数据集中如果不刻意平衡这些子类的数量小车会主导训练导致SUV在后面的时候检测框偏短、偏窄影响后续车位占用判断的准确性。3. 数据准备停车场数据比你想的更难搞停车位检测系统的成败数据占七分。且难点不在标注而在如何让模型适应你自己的场景。3.1 数据来源的三种路线我实际用过的路线有三种成本和效果从低到高排列纯公共数据集。像某个开源停车场数据集、通用车辆检测数据集如UA-DETRAC、BDD100K都可以用来预训练。优点是省事缺点是视角和你的现场大概率不一样直接用在现场会漏检。现场采集 人工标注。自己架相机采集不同时间段、不同天气、不同车位占用率下的监控画面然后用LabelImg或X-AnyLabeling标注车辆和车位区域。这是最稳的路线成本是时间。仿真合成数据。用游戏引擎或3D场景搭建停车场自动生成各种光照、视角、车型的样本给模型打底。适合做预训练和补充极端样本但纯合成数据直接上线还是有点虚建议和真实数据混合使用。我推荐的组合是先用公共数据预训练一轮再混入30%到50%的现场数据做微调。这样既保证了模型见过各种车辆形态又能在自己的机位下学习到特有的视角和背景。3.2 标注策略把“车位状态”踢出标注任务关键的标注决策不要标注“空车位”或“占用车位”这类类别只标注“car”这一类目标。把车位状态交给后处理判断好处有三模型的类别空间小训练更快准确率更高车位被车辆部分遮挡、或者画线模糊时模型不需要对“空/占用”做模糊推理换了停车场只需要重新标车位坐标不需要重新训练模型。需要额外标注的是车位的物理区域。这个不一定要做成模型训练数据也可以直接用标注工具把每个车位的多边形坐标导出来存成一个JSON文件给后处理逻辑用。标注框的相对大小也要注意。有些目标检测教程要求框紧贴目标但停车位场景我建议稍微外扩1%到2%尤其是车尾视角因为车辆边缘被车位线干扰时紧贴框容易把后保险杠切掉一截影响后续和车位区域的交叠计算。3.3 数据增强配置不只是加噪YOLO训练默认自带马赛克Mosaic、随机仿射变换等增强。停车场视频流的场景里我额外推荐打开两样HSV色域扰动停车场的光线变化极大同一车位白天和晚上色温完全不同。把hue、saturation、value的扰动范围适度调大比如value±30模型对光线的鲁棒性会明显提升。随机透视变换模拟相机安装角度偏差稍微旋转、缩放、错切一个小角度让模型不至于过拟合某个固定视角。停车场的枪机基本都是固定的但不同相机之间的位姿差异靠这个增强来吸收。4. 训练全流程配置、命令、监控一个都不能少无论用YOLOv5、v8还是v10训练流程的骨架是一样的准备数据、写配置文件、跑训练、看指标、挑模型、导出部署。这里以YOLOv8为例因为它最通用。4.1 环境安装与数据组织虚拟环境装ultralytics包一条命令的事。数据集目录按YOLO格式组织dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里需要指定train和val的图片路径、类别数量nc1以及类别名称。图片路径建议写绝对路径不然有时候相对路径解析会出问题。4.2 核心训练命令与参数选择训练命令如下yolo train modelyolov8s.pt datadata.yaml imgsz640 epochs100 batch16 device0几个关键参数我逐个说因为这一块很多人都是默认跑完看天意modelyolov8s.pt是small版本也是我在停车场景用得最多的。停车场的车是大目标不需要追求非常高的特征分辨率small版本在精度和速度上最均衡。追求极致精度就换yolov8m.pt追求边缘部署就换yolov8n.pt。imgsz默认是640。如果你的相机分辨率是1080P以上可以把imgsz开到960甚至1280。停车位检测里远处的车位车辆在640分辨率下可能只有几十个像素太小了不好判断。但要记住imgsz是影响推理延迟的最大变量之一训练用大的部署时如果为了速度可以调低一些。我这里实测800x800左右的输入尺寸能兼顾精度和帧率。epochs100是起步值我的经验是150到200更稳因为停车场数据通常不多容易欠拟合多跑一些让模型充分收敛。batch取决于显存。如果显存够大12G以上batch直接给16或32训练会稳定很多。4.3 数据不平衡问题大部分时间模型只见过SUV这是我最想强调的一个坑停车场里SUV和轿车比例可能接近1:1但标注时如果没意识去平衡框的大小分布会被某一类占据。更关键的是不同位置的车辆像素大小差异巨大——靠近镜头的车占半个画面远处的车只有几个像素宽。很多人只关注类别平衡忽略了尺度平衡。YOLO里有mosaic增强它会把四张图拼在一起训练一定程度上缓解尺度问题但我还是建议在准备数据时按目标尺寸分层抽样确保小目标样本远处车辆占比不低于20%否则模型在远处车位的表现会非常拉胯。4.4 看训练监控别只盯着mAP训练过程中要看的指标主要有三个训练损失box loss、cls loss、dfl loss、验证集mAP50和mAP50-95、以及Precision/Recall曲线。正常情况下训练损失会持续下降然后趋于平稳验证集mAP会跟着上升。但停车场场景我留意最多的是Recall。因为漏检车位被车占了但模型没检测到车比误检把背景当车更致命——漏检会导致车位状态判断成“空位”用户开车进去才发现占了位置体验非常糟糕。如果发现Recall偏低比如在0.9以下优先看训练集样本里是不是大量存在遮挡车尾的场景。如果验证集是现场视频抽帧还可以跑一次批量推理把预测结果画出来人工扫一遍比看指标更直观。4.5 导出部署模型训练完成后把torch的.pt权重导出成ONNX或TensorRT格式yolo export modelruns/train/exp/weights/best.pt formatonnx imgsz640 yolo export modelruns/train/exp/weights/best.pt formatengine device0 imgsz640TensorRT格式在Jetson系列板卡上能带来成倍的加速但不能跨GPU架构使用。如果只是快速验证ONNX配合ONNX Runtime就足够了。5. 状态判断逻辑真正决定系统Smart与否的部分模型把车辆框检测出来之后如果直接把“框里有没有车”当作车位状态那是远远不够的。这一步是整个智能停车位系统里最容易被低估、也最能让系统变聪明的部分。5.1 车位与车辆匹配的核心公式用集合计算的方式描述假设有N个车位区域每个车位区域由一组多边形顶点定义。模型检测出M个车辆框每个框是旋转矩形的外接水平矩形。状态判断就三步对每个车位多边形P遍历所有车辆框计算车辆框与车位多边形的重叠面积。重叠面积除以车位多边形面积得到占用比例。如果比例超过阈值我一般用0.15到0.25说明该车位里确实有车停着。做时间维度上的稳定性判断连续两帧都判定为占用才真正输出“占用”避免单帧误检造成闪烁。这个15%-25%的阈值不是拍脑袋定的。车位绘制时车辆不一定停得规规矩矩压线、斜停都很常见重叠比例用太高会把压线车漏掉太低会把车位线周围的过路车误算进来。我建议在部署后用现场视频跑一批离线数据画出占用比例的直方图找到分布最分裂的那个点作为阈值。5.2 为什么不能直接把车辆框中心点落在车位里当作判断依据有人喜欢用车辆框中心点是否落在车位多边形内来判断“有车”。这种方法在车正正好好停在车位中央时没问题但只要车斜着停、压线停中心点很可能落在车位外系统就会误报空位。所以我的建议是始终用面积交叠比例而不是点定位。这是从实际停车场数据里得出的教训一开始我用中心点判断斜停率一高准确率立刻掉到90%以下。5.3 多相机覆盖车位区域重叠的处理更大的停车场一个相机覆盖不全需要多个相机各管一片车位可能在两个相机的视野边缘都有出现。处理办法是每个车位只分配给它能看到大部分区域的那一路视频流不要在多个流里重复判断否则可能出现A相机说占用、B相机说空位的矛盾结果。汇总层要做一层“车位到相机”的映射表保证一个车位只有一个判决者。5.4 推理部署代码结构参考部署端如果只是输出车位状态给业务系统可以用Python或者C写一个循环处理线程。核心伪代码如下for frame in camera_stream: detections model.predict(frame, conf0.4, iou0.4)[0].boxes for spot in parking_spots: overlap_ratio compute_overlap(spot.polygon, detections) state OCCUPIED if overlap_ratio SPOT_THRESHOLD else EMPTY spot.append_state(state) # 时序平滑 if spot.recent_state_count(OCCUPIED) 2: spot.final_state OCCUPIED一套纯Python实现的系统在普通桌面CPU上跑720P视频大概在10到20帧之间如果瓶颈明显建议换成ONNX Runtime并开启CPU线程优化或者直接上Jetson设备用TensorRT跑。6. 实测中踩过的坑漏检、误检与结果不可复现任何落地系统都会遇到几个只有现场能暴露的问题这里把我在停车场实拍实测中的“坑”列出来这些比任何代码和模型都值钱。6.1 最经典的漏检车尾行李箱盖打开状态很多两厢车和SUV在停车后会把后备箱打开车尾的形状会瞬间改变和训练数据里“完整车辆”的形态差异很大。YOLO模型的特征是学到了整体轮廓的一旦轮廓被破坏漏检概率陡增。有一次我在现场跑测试一个车位被一辆开着后备箱取东西的车占着系统硬是报了“空位”把测试的同事坑得不轻。规避思路在标注数据时特意收集一些后备箱开启状态的车辆图片单独补一小批数据训练同时在后处理里做时间平滑——如果前一秒还是占用状态后一秒立即跳成空位需要连续两三帧空位才允许翻转能挡住大部分此类误报。6.2 影子也能触发误检傍晚时分车身长影子投射在地面如果影子恰好在车位区域边缘车辆框可能会往外扩导致阴影区域被算进重叠面积最终误判“占用”。这个问题在朝西的停车位尤其严重。规避思路训练数据中大量加入低角度光照样本或者把判断阈值的计算从“车辆框与车位重叠面积”改为“车辆框内像素与车位多边形像素的有效像素”也就是用分割掩码做更精细的计算。实在不想做分割可以在阈值上略微上调代价是压线车漏判率增加需要权衡。6.3 结果不可复现问题数据流顺序导致的随机性这是最容易被忽略的工程坑。当你同时处理多个视频流时如果每个流都开独立线程跑推理而同一时间片段内线程调度不稳定结果就可能出现“跑两次结果不一致”。特别是涉及到帧丢弃策略时不同的丢弃策略下模型看到的关键帧不同状态判断结果自然不同。规避思路为每一路相机设计确定的抽帧策略不按系统负载动态丢帧而是固定每N毫秒取一帧线程调度改为单线程轮询多路相机的模式。虽然牺牲了一点吞吐但换来的是结果可复现这对调试和上线后的回溯非常关键。6.4 模型在训练集指标很好一把现场验证就崩这往往是因为训练数据集里色彩分布太单一。比如很多停车场监控视频都带强烈的暖黄色调直接把模型在纯RGB监控图上训练现场如果碰到阴天或者蓝冷色调照明模型泛化能力就露馅了。规避思路前面提到的HSV色域增强要打开并且在做数据筛选的时候刻意保留不同白平衡的样本不要因为觉得某些画面“不好看”就删掉。模型需要见到的恰恰就是那些“不好看”的画面。最后说点实在的。我做了这么多套检测系统后最大的体会是YOLO系列模型本身已经非常成熟选哪个版本都不会是决定成败的事真正拉开系统差距的是你对业务场景的理解——车位怎么标、状态怎么判、数据怎么补、现场怎么排障。你花在数据上的时间永远比花在调参上的时间回报更高。如果你也打算做一套停车位检测系统先把车位坐标、判断流程、相机映射表画清楚再跑模型你会发现后续的路顺很多。