☰
从YOLOv5到YOLOv12:停车位检测系统实战升级全复盘
2026/10/19 2:42:28 网站建设 项目流程

停车位检测这个题材,在目标检测圈里几乎是被问得最多的落地型项目之一。从最早用YOLOv5跑通一个简单的车位占用检测demo,到后来我实际参与过厂区停车管理、商场车位引导这类真实需求,再到最近把整套系统从YOLOv5一路升级到YOLOv11、YOLOv12重新对比精度和速度,这个过程中踩过的坑、总结出的可复用套路,足以写一篇完整的实战记录:数据集怎么标、训练怎么调参、检测界面怎么串联、模型最终怎么部署,每一步都有讲究。

这篇文章就是我的停车位检测系统从YOLOv5升级到YOLOv12的完整复盘。整体内容包括完整可跑的代码、基于PyQt5的检测界面,以及一套可复用的车位标注数据集方案。适合正在做课设、毕设的在校开发者,也适合刚入手YOLO想找个真实场景练手的新人,准备做智慧停车相关方案的朋友同样可以从中提取不少经验。

1. 停车位检测系统要解决什么问题:三条技术路线怎么选

很多人一听“停车位检测”就觉得是“用YOLO框出空车位”,这个认知不算错,但太单一了。真实场景里,停车位检测面对的问题比想象中复杂:车位线的色差、傍晚的长阴影、地面积水反光、车头遮挡车位线、摄像头俯仰角不一致、同一个车位被多辆车抢框……如果只用一个检测模型硬扛所有场景,效果大概率不理想。

常见的实现路线主要有三种,我从实际项目经验出发逐一分析。

1.1 路线一:纯目标检测(直接回归车位框)

这是最简单、最直观的做法:把“空车位”和“已占用车位”分别作为两个类别,直接训练YOLO检测框。优点是流程短、代码少、容易跑通;缺点是车位本身是矩形框,YOLO输出的是水平边界框,如果摄像头角度比较斜,车位在画面里是平行四边形或梯形,水平框会框到旁边的车,导致定位不准。

这种路线适合用在俯视视角、角度相对正、车位线清晰的场景,比如无人机俯拍、高位枪机正对车位区。我做第一个版本的时候就是用YOLOv5走这条路,二十多天就能出一个能看的效果,但一旦换到低位摄像头,mAP往下掉得厉害。

1.2 路线二:车位角点检测 + 分组匹配

这是很多论文和工业系统的做法:不直接检测整个车位,而是检测车位线的四个角点,再通过几何关系把四个角点拼接成一个四边形车位。优点是能精确还原车位的形状,视角适应性很强;缺点是管线变复杂,角点漏检会导致大量车位无法配对,需要额外写分组逻辑和后处理,调试成本翻倍。

这种路线在真实项目中经常要配合一个固定车位坐标映射表来用——摄像头固定不动,车位在画面里的位置基本固定,可以先手工标一次车位多边形,后续只用YOLO判断“这个多边形区域里有没有车”。这也是很多商用智慧停车系统实际在跑的方案。

1.3 路线三:两阶段融合(车辆检测 + 车位占用判定)

我最后选的是这条融合路线:先用YOLO做通用目标检测,识别画面里的车辆;同时维护一份固定车位区域的定义(多边形或矩形坐标集合);然后通过“车位区域与车辆框的交叠比例”来判断该车位是否被占用。车位本身的框不用YOLO去回归,只负责判断状态。

这条路线的好处很明显:车位位置不依赖模型输出,只要摄像头不移动,位置永远是准的;车辆检测的样本比车位占用检测的样本更容易获取,训练难度小;换摄像头角度时只需要重新标一次区域,不需要重新训练模型。坏处也明确:摄像头一旦被移动或画面裁切,所有区域坐标全部失效,必须重新标定。

我的最终系统就是在这条路线基础上,叠加了一个“轻量车位分类”分支:对每个区域裁切出图像块,用一个小分类网络判断空/占,这样在车辆被遮挡、检测框不完全时也能给出较稳的结果。整体架构上,这算是把规则、检测、分类三者结合了,实际跑下来比单纯YOLO检测车位框稳定很多。

2. YOLOv5到YOLOv12到底改了什么:升级决策背后的权衡

做升级之前,先得弄明白各版本之间到底改了什么、提升了什么、代价是什么。网上铺天盖地的“v5到v12精度暴涨”的说法,很多都是拿不同参数量、不同硬件、不同数据集的结果混在一起比,参考价值有限。我从实际工程角度把版本差异捋一遍。

2.1 版本演进主线:从工程化拼图到注意力机制

YOLOv5是最多人入门的一代,它的核心价值是工程化做得极好:训练、验证、导出、部署一条龙,文档全,生态大,随便搜都能找到答案。模型侧采用CSPDarknet骨干和anchor-based检测头,速度和精度平衡不错。但它的代码是社区维护的,迭代比较自由,版本间差异也大,换版本可能换一套行为。

YOLOv8是官方框架化的一代,彻底转向anchor-free,网络模块更规范,还集成了实例分割、姿态估计、分类等任务。最关键是它把整个训练流程统一成了Ultralytics风格,YOLOv5用户迁移成本极低。我升级到v8时几乎没改数据格式,只改了几行调用,就顺利跑通了。

YOLOv11在v8基础上进一步优化了结构,提出了C3k2等模块,降低了计算量,推理速度更快,模型文件更小。它是目前部署性价比最高的版本之一,我后来做边缘端设备的时候,主力模型就是yolov11s。

YOLOv12则是把研究重心转向注意力机制的一代,引入了更高效的注意力模块,在同样FLOPs下精度有提升,尤其对输入分辨率较高的场景收益更明显。但它的部署生态还没有完全跟上,TensorRT导出需要处理一些新算子,依赖库版本要求也更高。这类更新快的版本,建议先在测试环境验证,不要直接上生产。

2.2 实际对比:升级到v12真的值得吗

我在同一套停车位数据集上做过控制变量对比:YOLOv8s、YOLOv11s、YOLOv12n(为了公平尽量选了相近量级)。结论挺有意思:v8到v11的提升主要体现在推理速度和大目标召回上,v11s在难样本上的mAP高1到2个点;v12n在车位场景下,对昏暗环境、遮挡区域的漏检率确实比v11s低一些,但模型推理耗时更高,在同样设备上大概慢了15%到20%。

所以我的建议是:不要盲目追求最新版本,先看你的部署约束。如果算力紧张、摄像头数量多,用v11s甚至v8s是性价比最高的;如果场景光照复杂、遮挡严重、对精度要求优先,再考虑v12。而且需要注意,v12一些官方权重对自定义数据集的支持还没那么成熟,训练前务必检查配置文件格式是否匹配。

3. 数据集构建是决定上限的关键:采集、标注与增强方案

目标检测圈有句老话:模型决定下限,数据决定上限。停车位检测尤其如此,因为车位是一个高度场景化的目标,摄像头角度、光照、车型、车位颜色都会影响检测效果。我在做这套系统时,数据集总共花了大约三周时间,比训练和调参加起来都长。这部分投入绝对不能省。

3.1 数据从哪里来:公开数据集打底 + 自建数据补齐

公开数据集方面,学术界有PKLot、CNRPark这类常见的停车位数据集,适合做算法验证和预热模型。但它们的缺陷也很明显:拍摄场景固定、视角类型有限、天气环境不够丰富,直接用于实际项目会出现明显的域差异,也就是训练时效果不错,一到自己的停车场就掉点。

我的做法是以公开数据集做预训练基础,再从真实场景采集大量数据。自建数据采集有几个关键点:一是覆盖一天中不同时段,清晨、正午、傍晚、夜间的光照差异非常大;二是覆盖不同天气,晴天阴影、阴天散射光、雨后积水反光都要有;三是覆盖不同车型,轿车、SUV、面包车甚至电动车都要尽量纳入,因为大车会遮挡相邻车位线,小车在远处很容易漏检。

3.2 标注规范与工具实操:少走弯路的几个细节

标注工具我推荐先用LabelImg做单机标注,简单直接;如果团队协作或标注量大,可以用Roboflow做在线标注和自动增强,导出YOLO格式非常方便。

标注时我用两个类别:empty(空车位)和 occupied(已占用车位)。这里有一个重要细节:车位框不要标得太紧,四边留出一些余量,尤其是车位线的边缘,因为模型学到的是“整块区域的状态”,而不是精确贴合车位线。框太紧会导致训练样本中正负例边界过于敏感,推理时轻微抖动就判错。

坐标系方面,YOLO格式的标签是归一化的中心点坐标加宽高。我最开始踩过一个坑:用LabelImg默认保存的XML(Pascal VOC格式),忘了转换成YOLO的txt格式,结果训练时读不到任何标签。用Roboflow导出时记得检查,它一般会自动转换。

3.3 数据增强与样本平衡:我的配置表和心得

停车位场景最典型的难点是类别不平衡——大部分时间车位都是满的,空车位样本天然少。我见过不少人直接拿监控视频抽帧,结果一个epoch里空车位的样本不到总数的5%,模型学到最后干脆把所有车位都预测成占用。

我的对策是双管齐下:采集阶段刻意挑选稀疏时段补拍空车位;训练阶段对空车位做过采样复制,再叠加增强。增强操作我用的是马赛克、随机透视、亮度对比度扰动、随机遮挡和轻微模糊,这些在Ultralytics里都有内置参数,但需注意增强强度不要过大,否则车位线会被扭曲成不真实的样子,影响泛化。

最终数据集规模大约在7000张图片,训练集占八成,验证集和测试集各占一成。每个类别的样本数量差距控制在1.5倍以内。事实证明这个平衡策略非常有效,训练出来的模型在验证集上的漏检率直接降了一半以上。

4. 模型训练与调优:核心参数、训练流程与踩坑记录

数据集准备好之后,训练本身其实并不复杂,关键是参数怎么设置、训练过程怎么看、出了问题怎么判断。我把完整流程拆解成下面几个步骤,每个环节都附上实测数值。

4.1 环境配置与目录结构

我用的环境是Python 3.10、PyTorch 2.1、CUDA 11.8,显卡是单张消费级卡(8GB显存)。YOLO系列官方仓库对训练脚本的封装已经比较完善,所以环境配置基本就是装依赖、验版本。

数据集目录结构按官方约定组织:

parking_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── parking.yaml

parking.yaml的内容很关键,路径必须写对,类别数和类别名必须和标注一致:

train: parking_dataset/images/train val: parking_dataset/images/val nc: 2 names: ['empty', 'occupied']

这里最容易出问题的是路径写法。我试过用相对路径在部分系统上可以,但换到不同工作目录运行训练脚本就会报找不到图片,后来统一改成绝对路径,再没出过这个问题。

4.2 训练参数配置与我的推荐值

训练命令本身很简洁,核心参数就那几个:

yolo train model=yolov11s.pt data=parking.yaml epochs=150 imgsz=640 batch=16 project=parking_yolov11

我的推荐参数表如下:

参数推荐值说明
modelyolov11s.pt或yolov8s.pt预训练权重,收敛快得多
epochs150停车位场景不算复杂,100到200足够,多了容易过拟合
imgsz640训练分辨率,显存小可降到480
batch168GB显存跑yolov11s没问题
optimizerSGD在数据量中等时,SGD比AdamW更稳定
lr00.01默认值,跑出来的loss曲线平滑
lrf0.01余弦退火末段学习率
patience30验证集指标连续30轮不涨就早停

训练时我用patience做早停,这次大概跑到第90轮就自动停掉了,因为验证集mAP已经稳定在96%左右。如果一轮跑完看到mAP飙到98%以上,先别高兴,要检查是不是验证集和训练集之间数据泄露了——比如同一批监控视频抽出的相邻帧被同时分进了两个集合,这种情况非常常见,务必按视频或时间段切分数据,而不是按单帧随机切分。

4.3 训练过程怎么看:loss曲线和mAP对比

训练过程中重点看两部分:box_loss和cls_loss是否同步下降且最终平稳;验证集Precision、Recall、mAP@0.5、mAP@0.5:0.95四个指标是否协调。

停车位检测属于中大目标,mAP@0.5一般90%以上算合格。我这次训练出的模型最终结果是:

  • Precision: 0.97
  • Recall: 0.95
  • mAP@0.5: 0.96
  • mAP@0.5:0.95: 0.73

这里有一个值得注意的现象:mAP@0.5:0.95只有0.73,远低于mAP@0.5。原因是车位框边界本身存在模糊性,不同人标注空车位时框的位置有差异,IoU阈值提高后这些误差被放大。这也是为什么停车位系统在实际部署中更适合配合固定区域判定逻辑,而不是只依赖检测框精确度。

4.4 从v5升级到v12的训练差异

我在v5版本里跑过一次完整训练,同样数据、同样epochs,mAP@0.5大约在91%。升级到v8后提升到94%,v11达到96%,v12在小目标召回上有更好表现。v5时代需要手动调anchor,v8之后anchor-free省了不少事。

但升级过程中也遇到过新框架带来的水土不服,最典型的是v11、v12对NumPy版本有要求,装了最新NumPy后某些旧脚本会报错。我在切换时习惯用虚拟环境隔离一套新依赖,老项目继续留在旧环境跑,避免升级一个库导致另一个项目瘫痪。

5. 界面的设计与实现:PyQt5多线程架构与交互逻辑

一个检测系统如果只有命令行脚本,自己调试还行,交付给别人或用做课设演示就缺乏说服力。我给这套系统包了一个PyQt5桌面界面,功能包括实时画面显示、检测结果叠加、车位占用统计、视频/摄像头切换。界面部分看起来工作量不大,但其中最容易翻车的是线程问题。

5.1 为什么必须用多线程:不然卡到你怀疑人生

YOLO模型推理是一个阻塞操作,单张640x640的图片在消费级显卡上大概要20到40毫秒,但在CPU上可能要几百毫秒。如果把推理直接放在PyQt的UI线程里,画面会一卡一顿,按钮点了半天没反应。

我的做法是把视频读取和模型推理放进独立的QThread,UI主线程只负责接收结果并绘制。线程间通过Qt的signal/slot机制通信,推理线程发出“当前帧的检测结果”,主线程用QLabel刷新画面。这样即便推理速度慢,界面依然流畅,拖动窗口不会卡死。

5.2 核心界面逻辑与关键代码

界面大致分为三个区域:左侧是实时视频显示区,右侧是车位占用统计面板,顶部是摄像头源选择和启停控制。

推理线程的伪代码如下:

class DetectionThread(QThread): frame_ready = pyqtSignal(QImage, list) def __init__(self): super().__init__() self.running = True self.model = YOLO("parking_yolov11s.pt") self.cap = cv2.VideoCapture(self.source) def run(self): while self.running: ret, frame = self.cap.read() if not ret: continue results = self.model.predict(frame, conf=0.35, iou=0.45, verbose=False) boxes = results[0].boxes.data.tolist() self.frame_ready.emit(self.cv2qimage(frame), boxes) def stop(self): self.running = False self.wait()

cv2的BGR格式要先转成RGB,再转成QImage才能在界面中正常显示:

def cv2qimage(self, frame): rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape return QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)

这个QImage转换步骤看着不起眼,但我见过不少人卡在这里:图片显示成红蓝色块互换的效果,全是BGR和RGB顺序没调对导致的。

5.3 车位统计逻辑:不是简单数框

界面上需要显示“剩余车位 / 总车位”这类统计信息。在纯YOLO模式下,数一下empty类别框的数量就行;在我最终采用的两阶段融合模式下,统计逻辑是遍历所有固定车位区域,对每个区域裁切图像并判断占用状态,再汇总结果。

这里提示一个细节:如果检测结果中出现同一个车位被输出两个框的情况,不要只依赖NMS参数,NMS阈值设得太高会把相邻两个真实车位合并成一个,设得太低又会出现重复框。我最终把IoU阈值定在0.45,同时在后处理里加了一条规则:两个检测框中心点距离小于30像素且类别相同,则只保留置信度更高的那个。这个规则在垂直视角下特别有用。

6. 部署优化与真实场景适配:从ONNX到边缘端

界面跑通只是第一步,真实项目里还要考虑部署设备。我把系统先后迁移过两种场景:一种是普通PC实时播放监控画面,另一种是边缘小主机并行处理多路视频流。两种场景的优化侧重点完全不同。

6.1 模型导出与量化:一步步做减法

在PC场景下,GPU显存足够,直接跑PyTorch模型即可。边缘端则必须做模型导出和优化。Ultralytics框架提供了一行导出命令:

yolo export model=parking_yolov11s.pt format=onnx dynamic=True

导出ONNX后,如果目标设备是TensorRT,还可以进一步转换engine格式。v11的算子支持比较成熟,v12则相对新一些,转换时如果遇到算子不支持,需要先bin到onnxruntime,再逐层排查。这个排查过程比较繁琐,我的经验是优先看官方文档里标注的支持矩阵,不要等报错了再去查。

为了压缩推理耗时,我还试过FP16半精度推理,检测精度几乎不掉,速度提升明显,在边缘设备上把单帧推理时间从约90毫秒降到了约25毫秒,这个提升非常可观。

6.2 摄像头流的几个坑:地址、缓冲与丢帧

用OpenCV读取IP摄像头时,最常见的问题是画面延迟越来越大、掉帧严重。这通常不是解码算力不足,而是OpenCV内部缓冲积压了太多旧帧。我解决的方案是设置摄像头缓冲为1:

cap = cv2.VideoCapture("rtsp://user:pass@ip:port/stream") cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

另外,RTSP流拉起有时很慢,可以设置超时或重连机制。我的做法是封装一个视频源管理类,连接失败时自动等待3秒重试,连续重试5次后抛出异常。这个机制在真实监控系统中非常关键,摄像头重启或网络抖动时,系统不会直接卡死。

6.3 夜间场景的适配经验

停车位检测最严峻的考验是夜间。车灯直射、地面反光、阴影缺失,都会让模型误判。我在训练集中单独加入了夜间图像,并把亮度对比度增强的幅度加大,但这些还不够。

最终帮我解决问题的是两招:一是对夜间帧先做直方图均衡化预处理,再喂给模型,这个前处理能在不改变模型的前提下显著提升检测效果;二是回归“区域占用判定”逻辑,夜间模型对车辆检测置信度较低时,不是直接判定为空,而是结合区域历史状态做一次平滑决策——连续3帧都判空才最终更新为空。这个小改动让夜间静置场景下误报率降低了非常多。

7. 常见问题排查实录:这些问题我基本都遇到过

最后把我在这个项目里遇到过的典型问题和解决思路整理成一个速查表,按频率从高到低排列,还没做这个项目的朋友可以直接对照排查。

现象可能原因解决方案
训练时loss为nan学习率过大,或数据集有损坏图片降低lr0至0.001,检查图片完整性
验证集mAP很高,测试集掉点数据切分不随机,训练集与验证集存在相关性按视频时间段重新切分数据
空车位频繁误判为占用空车位样本不足过采样空车位样本,调节loss类别权重
车辆框与车位框重叠导致重复识别单个车辆被两条车道都检测到增加车位区域交叠过滤规则
夜间漏检严重增强策略未覆盖夜间光线加入夜间图像、直方图均衡化前处理、时序平滑
RTSP画面延迟越来越大OpenCV缓冲积压设置CAP_PROP_BUFFERSIZE=1
导出ONNX后结果完全错乱BGR/RGB通道顺序不一致检查预处理通道顺序,统一按模型训练时的预处理方式
界面无响应或黑屏推理放在UI线程里阻塞改用QThread单独跑推理,信号槽传结果

这里面我想单独强调一下调参心态:不要一上来就追求完美的mAP,先把数据质量做扎实,把漏洞一个个补上。我见过太多人花一周调参,结果换一个摄像头视角模型就废了,最后回头发现是数据集太单一。

最后分享一个我在实际项目中收益极大的习惯:每次训练前,把数据随机拆分的种子记录下来,保证实验可复现;每次版本升级,保留上一版本的模型和验证集预测结果,做回归对比,而不是只看新版本的指标。这样每换一个模型版本,都能很清楚地量化“这次升级到底带来了多少提升”。做车位检测这类长尾场景项目,这种抠细节的做法比追求算法新鲜度更值得投入。

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

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

立即咨询