停车场车位识别,听起来好像是个挺成熟的活儿,市面上方案一抓一大把,但真正自己从头到尾做一遍,你会发现坑多到怀疑人生。我最近刚好把一个基于视觉的停车场车位识别系统从零搭到了能稳定跑起来,中间踩了不少雷,也总结了一些在文档里翻不到的经验。这篇文章不整虚的,就把我实际用到的技术选型、代码思路、训练细节和部署时那些血泪教训全部分享出来,给正准备做类似项目或者想入坑计算机视觉落地场景的朋友一个参考。
先交代一下背景。我做的这个项目不是那种只用一张俯视全景图做演示的Demo,而是要对接一个真实的露天停车场,摄像头装在立柱上,视角斜着往下看,一个机位大概覆盖八个到十二个车位。识别任务分两块:第一,车位上有没有车;第二,如果有车,这车占的是哪个车位。听起来简单,但实际做起来,光线变化、车辆遮挡、视角畸变、地面标识不清,任何一个因素都能让你的模型在测试集上表现优秀、在真实场景里原形毕露。
1. 整体思路与方案选型
1.1 先想清楚要解决什么问题
很多新手一上来就想着用目标检测模型直接框出“空闲车位”和“占用车位”,这其实是个误区。车位不是一个像人、车那样有明显边界的目标,画框的话框多大、框在哪都很难定,而且不同摄像头的视角差很多,换个场景就要重新标数据、重新训练,成本极高。
我最终采用的是“两步走”方案:先做车位区域的固定坐标映射,再做每个车位上的占用状态分类。这个思路的核心在于,停车场的物理车位位置是固定的,摄像头装好之后不会动,所以车位的坐标只需要标一次。之后真正需要模型处理的,只是独立判断每个固定区域内是否停着车。
这样做有几个好处:
- 模型任务从“检测任意位置的车位状态”简化成“分类固定位置的状态”,难度直接降了一个量级
- 换摄像头或者换车位布局时,只需要重新标定区域,不需要重新训练模型
- 可以很方便地在不同车位上使用不同的判断规则,比如有些车位被柱子遮挡了一部分,单独处理即可
1.2 主方案的选型对比
在动手写代码之前,我把市面上几种常见的做法都梳理了一遍,也做了个小实验验证,下面是我的评估结果。
| 方案 | 优点 | 缺点 | 我的结论 |
|---|---|---|---|
| 纯目标检测(YOLO系列直接框车位) | 端到端、看起来直观 | 车位边界定义模糊、标注难度大、跨场景泛化差 | 不推荐 |
| 传统图像处理(边缘检测+形态学) | 速度快、无需训练 | 对光照和阴影极其敏感,稍微变天就失效 | 可作辅助,不建议做主识别 |
| 深度学习的车位状态分类(VGG/ResNet轻量化变体) | 准确率高、泛化能力强 | 需要自己标数据、需要训练环境 | 推荐 |
| 语义分割(PSPNet/U-Net等) | 能精细区分地面和车辆 | 标注成本高、推理速度慢、有点大材小用 | 不推荐,除非有特殊需求 |
我的最终选择是:车位区域用多边形坐标预先标定,状态识别用轻量级卷积神经网络分类器。每个车位区域,从摄像头画面中按坐标抠出来,经过透视矫正和缩放后,送入一个二分类网络(有车/无车)。
这套方案本质上把一个大问题拆成了两个小问题,每个小问题都可以用最成熟的技术解决。坐标映射部分是纯几何操作,稳定可靠;状态识别部分是标准的图像分类任务,数据好标,模型也容易训练。
2. 数据集的准备与标注细节
2.1 数据采集的难点与应对
数据是这套方案里最关键的环节,模型能不能扛住真实场景,完全取决于训练数据的质量。我一开始图省事,只从下午两三点光线最好的时候拍了大概两千张图,结果第一次现场测试时,早上和傍晚的准确率惨不忍睹。后来我总结出几个必须要覆盖的变化维度:
- 光照变化:晴天、阴天、雨天、清晨、正午、傍晚、夜间(有路灯和无路灯)
- 车辆类型:轿车、SUV、面包车、卡车,不同颜色的车(深色车和浅色车在视觉特征上差异很大)
- 遮挡情况:相邻车位大型车遮挡、树木遮挡、行人经过
- 停放姿态:斜停、压线停、车头朝里和朝外
采集方式我用了两种结合。第一种是每隔五分钟从摄像头自动抓帧,跑上整整一周,能覆盖大部分光照和天气情况。第二种是刻意挑选一些特殊场景手动拍摄,比如雨天后地面反光、黄昏时分的强逆光等。
2.2 标注策略:坐标和状态分开做
标注分成两步。首先在某一帧画面上,把每个车位的边界用多边形标出来,存成一份配置文件。这个动作只需要做一次,但要注意几个容易犯的错:
- 多边形尽量贴合车位线的内沿,但不要精确到线上,因为车停进去之后轮胎会压住边线,圈小了容易把车的一部分切掉
- 相邻车位之间不要留空隙,稍微重叠一点都没关系,避免缝隙处的小块区域被漏掉
- 如果车位有一部分长期被柱子或树遮挡,就把遮挡部分在坐标里排除掉,宁可少判,不要误判
状态标注用半自动方式。我先写了个脚本,把每个车位区域扣出来,拼成一张大图,然后用一个简单的颜色阈值初筛(比如地面和车辆的颜色差异),筛完后再人工逐张确认。一千多张图画下来,大概花了两个半天,比纯手工从零标快很多。
另外建议给每个车位单独建一个子目录,按车位ID分好,方便后面做数据均衡和问题追溯。我一开始把所有数据混在一起,结果发现模型在个别车位上准确率特别低,回头一查才发现那个车位的数据量偏少,而且大部分是同一个方向的车头朝向。
2.3 数据增强的必要参数
数据增强在车位识别这个项目里不像在通用分类任务里那么随意,有些操作是绝对不能用的,比如随机裁剪。车位区域一旦裁掉了一部分,车的特征就不完整了,模型会学到错误的信息。我实际用的是这些:
- 水平翻转:停车方向左右对称,翻转后语义不变,非常安全
- 亮度/对比度扰动:模拟不同时段的光照
- HSV颜色空间轻微扰动:应对白平衡漂移
- 高斯噪声:增强鲁棒性,但强度要小
- 随机旋转:控制在正负三度以内,大了会改变车位语义
随机擦除对我来说效果一般,可能因为车辆外形比较整体,擦掉一块反而让模型去依赖某些不该依赖的局部特征。
3. 核心算法实现与训练调优
3.1 车位坐标映射与透视矫正
摄像头是斜着往下拍的,车位区域是一个不规则的四边形,直接送到网络里,模型很难学到统一的空间特征,而且相邻车位之间的透视变形差异也会干扰判断。我的做法是先用原图上的多边形坐标把区域抠出来,再做透视矫正,变成一个规整的矩形,再缩放成分类网络的输入尺寸。
透视矫正的关键是找到四边形的四个顶点。这里有一个细节,多边形可能不止四个点,因为车位线的边界可能被画成带圆弧的或带斜角的形状。我的处理方式是取多边形的最小外接矩形,然后手工微调四个角点,保证车辆在矫正后的画面里是正向的、完整的。
矫正之后的分辨率我定的是 96x96。这个尺寸看着小,但足够分辨一辆车是否占用了车位,而且推理速度快。如果你用的摄像头分辨率是200万像素,一个车位在原图里大约占200x300像素,缩到96x96之后,车牌之类的细节消失,但车辆的轮廓、车顶、车窗这些特征足够保留了。
透视矫正可以用OpenCV的 getPerspectiveTransform 和 warpPerspective 来做,整个过程大概几十行代码就能跑通。
3.2 分类网络的结构选择
在模型结构上,我试了三个方案:ResNet-18、MobileNetV2和一个自己搭的四层卷积小网络。结果比较有意思:
- ResNet-18准确率最高,在小规模数据集上很稳,但推理速度一般,在CPU上单帧要30毫秒左右
- MobileNetV2准确率略低,但速度快很多,CPU上十毫秒以内
- 自搭小网络在数据量不够大时表现不稳定,容易过拟合
我最终选了MobileNetV2,并把最后的全连接层改成两个输出节点,加softmax。实际测试下来,准确率在白天场景能到99%以上,夜间在路灯照明下也能维持到96%左右。如果你对速度没有那么敏感,又希望模型更稳,可以考虑ResNet-18,泛化能力确实更好。
训练过程中的几个关键参数:
- 优化器:Adam,初始学习率0.001
- 损失函数:交叉熵
- Batch Size:32
- 训练轮数:60轮
- 学习率衰减:在40轮和50轮时降为原来的0.1倍
还有一个很重要的点:类别不均衡。停车位有时候全天都是满的,或者全天都是空的,导致两个类别的样本数差距悬殊。我的处理方式是给少数类加权重,计算损失时乘以一个系数,系数等于多数类样本数除以少数类样本数。如果某个车位长期全满或全空,就需要多补充一些反例数据。
3.3 后处理策略:状态平滑与置信度过滤
模型输出的是一堆每帧的0/1判断,但直接用于系统层面会产生很多抖动。比如一辆车正在倒车入库,这个过程中车位区域会连续切换几次有车/无车状态,如果直接上报给收费系统,就会产生无效的进出记录。
我的解决办法是加一个时间维度的平滑逻辑。具体做法是维护每个车位的最近十帧判断结果,只有连续五帧以上判定为同一状态,才认为状态发生了切换。这个“五帧”的阈值需要根据摄像头的帧率来调整,如果是10帧每秒,五帧就是0.5秒,足够过滤掉绝大多数的瞬时误判。
另外,分类器会输出一个置信度分数。当置信度介于0.4到0.6之间时,我会标记为“不确定”,不参与状态切换,等待后续帧确认。这个操作在光线剧烈变化时(比如云层突然遮住太阳)特别有用,能大幅减少误报。
置信度的阈值不要设得太高,否则状态切换会有明显延迟。我试过0.8的高阈值,结果是车开走后大约十秒才更新空位状态,系统体验很僵硬。最后调到0.6,延迟和准确率达到了一个可接受的平衡。
4. 一体化部署与性能优化
4.1 从单张图片到实时视频流的架构
最开始我写的是一个最简单的脚本:传入一张图片,输出车位的状态列表。但这个脚本没法直接用于现场环境,因为现场摄像头出的是RTSP视频流,还要考虑多路并发、状态缓存、异常恢复等问题。
我把整体架构设计成了三个模块:
- 采集模块:负责连接摄像头,拉取RTSP流,解码成帧
- 识别模块:对每一帧做车位抠图、矫正、分类推理
- 业务模块:维护每个车位的实时状态、处理状态切换事件、和上层收费系统对接
采集模块和识别模块之间用队列解耦。采集线程按固定帧率抓图,识别线程从队列里取图处理,这样即使识别速度暂时变慢,也不会丢帧或者阻塞采集。
帧率不需要太高,两到三帧每秒就足够了。车位状态本身是一个变化很慢的事件,过高的帧率只会增加无谓的计算消耗。我用的是2帧每秒,CPU占用率不到百分之十五。
4.2 多线程与推理加速的细节
OpenCV解码RTSP流本身是阻塞操作,网络抖动时可能卡住整个程序。我的做法是把摄像头读取放到独立线程里,并且给读取操作设置超时。如果连续三秒没有读到新帧,就主动断开重连。这个重连逻辑是实测中必须有的,因为现场的网络环境不可能一直稳定,摄像头偶尔也会自动重启。
推理部分我用了OpenVINO来加速MobileNetV2。原来的PyTorch模型转成OpenVINO的IR格式之后,在CPU上的推理时间从十毫秒左右降到了五毫秒以内。这里有一个需要留意的地方:OpenVINO对模型算子有限制,转换前最好先确认一下模型里的算子都在支持列表里。我的MobileNetV2转换过程完全没有遇到问题,ResNet-18也顺利转过去了。
如果你的部署环境是NVIDIA显卡,可以考虑用TensorRT或者直接使用PyTorch的CUDA推理。如果只有CPU,OpenVINO基本是首选。
4.3 性能基准:到底能跑多快
我在一台普通的工控机上做了完整测试,配置是六核CPU、16GB内存、无独立显卡。单路摄像头、2帧每秒的情况下,CPU占用约12%,内存占用约800MB(主要是OpenCV解码缓冲和队列堆积)。换算一下,这个配置撑四到六路摄像头问题不大。
如果想要更高的帧率或者更低的延迟,可以考虑把输入尺寸再缩小一点,或者把抠图后的尺寸从96x96降到64x64。但64x64是个底线,低于这个尺寸之后,深色车和地面阴影的区别就变得非常模糊了。
5. 常见问题与排查技巧实录
5.1 夜间识别准确率骤降
一开始我的夜间准确率只有70%,仔细排查后发现主要问题在于训练数据里夜间样本太少了。后来我专门挑了几个夜晚,每小时存一批图,连续存了三天,把夜间样本补到总数的百分之三十左右,准确率才提上去。
第二个影响因素是红外补光灯。有些摄像头的红外光在很近的距离会过曝,车头部分的细节完全变成白花花的一团。这种情况下,仅仅靠补数据是不够的,更有效的办法是调整摄像头的红外亮度参数,让车头的轮廓能够看清。
5.2 停车位上长期停着一辆“假车”
有没有遇到过,车位明明是空的,模型却一直报警说车还在。我把这个问题排查了很久,最后发现是地面上的一摊油渍导致的。那摊油渍的形状和颜色,恰好与俯视视角下的车顶非常相似,模型被欺骗了。
这种问题靠调阈值解决不了,因为油渍的外观确实和车非常接近。我用了一个很笨但有效的方法:在判断逻辑里加入一个时间维度。如果某个车位被判定为有车超过七天从未变化而且系统没有记录到对应的入场事件,就自动标记为“疑点状态”,由人工二次确认。这个规则虽然简单,但在实际运营中解决了很多类似的问题。
5.3 大雨天后摄像头镜头模糊
露天摄像头的镜头遇到大雨加风沙,很容易变得模糊。模糊状态下的画面,分类器基本等于瞎猜,准确率会直线下降。这是一个很随便的运维问题,但也需要有一套自动检测机制。我用的是拉普拉斯方差来做清晰度检测,当画面整体方差低于某个阈值时,触发“雾天/脏镜头”告警,通知运维人员清洁镜头。
另外,如果摄像头是自动对焦的,注意不要把对焦模式设成连续自动对焦,否则夜间会频繁拉风箱,导致画面一直处于模糊状态。固定焦距是更适合停车场场景的选择。
5.4 状态切换事件丢失
业务侧偶尔会报:车明明出场了,系统还显示占用。问题不在于识别模型,而在于状态平滑逻辑太保守了。前面提到了连续五帧才确认状态切换,但如果期间有十帧的连续断帧(比如网络闪断),五帧的门槛就很难达到,状态就一直不更新。
我的修复办法是加一个超时兜底:如果某个车位连续一段时间内,画面内容发生了明显变化(用帧间差异衡量),就暂停平滑逻辑,直接采用当前分类结果。同时把断帧情况和状态变化都写入运行日志,方便事后回溯。
6. 场景扩展与方案复盘
6.1 这个方案还能用在哪些地方
车位识别这个方案本质上是“固定区域+状态分类”的通用架构,换个场景也可以复用。比如:
- 室内停车场的车位指示灯引导系统,逻辑差不多,只是把识别的结果接到LED指示牌上
- 高速服务区或者商场停车楼的多层车位统计,每层装一个摄像头,做汇总
- 临时停车场、活动场地的车辆计数,虽然有变化,但坐标标定一次也能用
如果想在现有基础上升级,可以考虑加入车辆类型识别(区分轿车/SUV/货车)、车牌号关联(配合车牌识别API),甚至用目标跟踪来辅助判断长期占位和违停。
6.2 整个项目的时间线复盘
从零开始做的话,大概的时间分配是这样的:
- 数据采集(一周):摄像头安装、位置调整、连续自动抓帧
- 数据标注(两天到三天):车位坐标标定、状态数据半自动标注
- 模型训练(一天到两天):搭建训练脚本、调参、验证
- 系统部署(三天到四天):流媒体接入、多线程服务、平滑逻辑、对外接口
- 现场调优(至少一周):观察真实运行状态、修正数据偏差、处理边缘情况
这个时间线里面最容易被低估的是现场调优。实验室里跑得好好的,装到现场就会冒出一堆意料之外的情况,没有一个真实场景是完全干净的。要给现场调优预留足够的时间。
6.3 关于这套方案的几个心得体会
我自己做完这个项目,最大的感受是:算法模型的准确率只是项目成功的一部分,甚至不是最重要的部分。真正决定系统能不能稳定运行的,是数据质量、异常处理、运维便利性这些“看不见”的环节。
另外一个很有价值的反思是:不要一上来就追求高大上的模型,也不要盲目相信端到端的解决方案。在固定场景下,充分利用先验信息(车位位置固定、相机位置固定)做任务拆分,往往比堆模型参数更有效也更可靠。
如果你正在计划做类似的识别项目,我建议你从最小的闭环开始。先标一两个车位,训练一个简单模型,跑通一版完整的识别流程,然后再慢慢扩展到全部车位。这样每个环节的风险都会在早期暴露出来,不会到最后一次性爆发。
这个项目做完之后,我最大的体会是:方案设计阶段的取舍,远比写代码和调模型对最终结果影响更大。希望这篇拆解能让你少走一些弯路。