停车场车位识别这个项目,我在真实场景里前后折腾了快两个月。从最开始用摄像头对着空荡荡的地面拍照,到后来在阴雨天、夜间、被挡了一半车头的复杂工况下还能稳定输出空位状态,中间踩过的坑确实不少。这篇文章就把我这套完整方案从数据、模型、部署到排查全流程捋一遍,给正要入坑或者已经被坑住的朋友一个直接能抄作业的参考。1. 项目整体设计与方案选型1.1 先想清楚这个项目到底要解决什么问题很多人一听到“停车场车位识别”,第一反应就是“用目标检测框出车来”。这个想法不完全错,但如果你真按这个思路去做,后面一定会返工。原因很简单:停车场管理方要的不是“有没有车”,而是“哪个车位被占了、哪个车位空着”。这两个需求看起来差不多,实际在工程实现上是两条完全不同的路。检测车辆只需要输出一张图上所有车的包围框,然后把框和车位区域的坐标做交集判断,就能推断出占用状态。但这里有个很实际的麻烦:摄像头视角通常比较倾斜,车辆会互相遮挡,一个框可能同时压住两个车位;而且车头的形状在不同角度下差异巨大,检测框不稳定,空位判断就会跟着抖。我在项目初期就是吃了这个亏,模型精度做到90%以上,但落到具体车位编号的占用率统计上,准确率只有八成出头,根本没法上线。所以后来我换了个思路:把问题拆成“车位区域定位 + 区域内车辆存在性判断”两个子任务。车位区域用人工标定的固定多边形来定义,车辆存在性用检测模型对每个区域做判定。这样既能保证车位编号和服务端的数据库一一对应,又避免了自由检测框和车位映射的错位问题。搞清楚这一点,后面所有技术选型都顺了。1.2 技术路线对比:传统视觉方案为什么被淘汰我最早试的是纯OpenCV方案,具体做法是车位地面上画框,然后对每个车位区域做背景差分或者边缘检测。背景差分在室内灯光稳定的地下车库效果还行,光线均匀、地面颜色一致,车开走之后背景恢复得很快。但遇到室外停车场就完全崩了——树影晃动、云层遮挡、早晚色温变化,这些都会让背景模型跟着“漂移”,几分钟之后它自己都不知道哪个是背景了。边缘检测加颜色特征的办法也试过,主要思路是统计车位框内白色引导线的完整度。空车位的时候,地面标线是连成一条的;有车停进去之后,标线会被车身打断,同时出现大量不属于标线颜色的像素。这个思路在标准化的室内车位里偶尔能跑通,但遇到车体颜色和地面接近的“白色车停白色线框”场景就失灵了,更别说车身反光、地面污渍这些干扰因素。深度学习方案的优势在于它不需要人肉设计“什么叫有车”的特征,而是直接从大量标注数据里学。尤其是YOLO系列出现之后,模型体积、推理速度和检测精度之间取得了很好的平衡,跑在边缘设备上也能做到实时。选深度学习的另外一个实际原因是可维护性:传统方案的阈值参数散布在各种预处理函数里,换一个停车场的摄像头,光照条件变了,得调半天参数;深度学习模型虽然也需要微调,但泛化能力明显强得多,换场地后重新标注少量数据就能适配。1.3 检测方案的核心决策点:全局检测还是区域分类技术路线定了之后,还有一个必须想清楚的问题是输出粒度。我当时在“整图检测所有车辆再映射车位”和“只对每个固定车位区域做判断”之间反复权衡,最后选了后者。这个决策不是我拍脑袋拍的,三个原因:第一个是稳定性。固定车位的多边形区域是人工标定的,坐标精确且和业务编号绑定,服务端可以直接查到“3号车位空着”这样结构化的结果。如果用车辆检测框去映射车位,摄影师角度稍微一变或者车头朝向不同,IoU计算就飘了。第二个是计算量。假设一个摄像头管20个车位,区域分类方案只需要对20个小区域做推理判断,而全局检测需要处理整张高清图。虽然单张图差异不大,但多个摄像头同时接入的时候,计算量的差距会被放大,边缘设备压力明显不同。第三个是可解释性。车位状态判断错了,区域分类方案可以直接把那个车位的裁剪图调出来看模型到底看到了什么;车辆检测+映射方案出了错,很难定位是检测框的问题还是映射逻辑的问题。后面我还会详细讲这个排障思路,它在真实项目里救了我很多次。2. 数据采集、标注规范与预处理细节2.1 数据集从哪里来,怎么采最有效率训练数据永远是这类项目最头疼的部分。公开数据集我参考过一些通用的车辆检测数据集,但它们大多是街景或行车记录仪视角,和停车场摄像头的俯视斜视角差距很大,直接拿来训练,迁移效果很差。所以我的做法是自己在目标停车场采集原始视频,然后抽帧标注。采集阶段有几个关键细节。第一是覆盖时段,我连续采集了三天,确保包含晴天、阴天、雨天、清晨、正午、傍晚、夜间各时段的画面。夜间的车灯反光、路灯不足造成的暗部盲区,这些如果不进训练集,上线必翻车。第二是覆盖状态,既要拍车辆正常停放,也要拍大量空车位和半遮挡状态——比如车头超出车位线压到旁边车位、行人路过挡住镜头、车辆临时停靠双闪等,这些边缘情况才是模型精度的分水岭。抽帧不是均匀抽就行。监控视频一秒钟25帧,早晚高峰车辆进出频繁,均匀抽帧会得到大量重复的静止画面。我先用OpenCV做了简单的帧间差分析,画面变化超过阈值的帧才保留,然后再从中随机抽帧用于标注。这样同样的标注工作量,样本的信息量能多出不少。另外一个很多人忽略的点是摄像头本身的参数。我用的摄像头鱼眼畸变比较明显,车位在画面边缘会弯得很厉害。这就需要先做畸变矫正再抽帧标注,否则标注框和实际车位多边形对不齐,模型学习的时候特征本身就是歪的,后面怎么调都白搭。2.2 标注规范决定了模型精度的上限标注质量对车位识别项目的影响比想象中大得多。我第一版训练集是自己用脚本半自动生成的,结果模型训练完精度一直卡在85%左右上不去。后来复盘发现,问题出在标注标签上——我只标了“occupied”和“empty”,但实际车位区域里经常同时出现“车头已经进入车位、车身还在通道上”的中间状态,这种状态被强行归到occupied还是empty都不对,模型学到的边界就很模糊。后来我把标签体系改成三分类加一个辅助标签:标签含义处理建议empty车位完全空置正常训练样本occupied车辆完整停在车位内正常训练样本partial车辆部分进入或部分遮挡车位训练时并入occupiedshadow树影/建筑阴影覆盖车位辅助标注,增强阴影鲁棒性partial状态我一开始是单独建模的,但后来发现服务端业务逻辑里,partial大概率是“即将停入”或“正在驶出”,短时间内状态还会变化。强行让模型预测中间态意义不大,反而消耗样本量。所以我把它合并到occupied里,但保留了这个标签的图片样本,让模型见过更多遮挡形态。标注工具我用的是labelme,多边形标注比矩形框更适合车位这种不规则区域。注意标注的是车位区域而不是车体,这一点和常规目标检测不一样。框选车位的时候,我要求标定到车位线的内边缘,不要把线本身框进去,因为线宽会随透视角度变化,导致区域坐标不稳定。还有一个实操经验:一个摄像头对应一套固定的车位多边形坐标。标注完第一次之后,把坐标持久化保存到配置文件里,后续每次推理都复用这套坐标,不需要重新标注。换摄像头角度或者挪动摄像头位置,才需要重新标定一遍。2.3 预处理:透视矫正与ROI裁剪停车场的摄像头通常是立杆斜装,画面里车道和车位都是透视变形的——车位看起来是梯形甚至不规则的四边形,远处车位很小,近处车位很大。直接拿这种图做区域分类,模型需要自己学会尺度不变性,这对小模型来说是很大的负担。我做的预处理分两步。第一步是透视矫正,用车位区域的四个角点做透视变换,把整个停车区投影成俯视平面图。矫正之后,所有车位理论上都是近似相同的矩形,模型只需要关注“这个区域里有没有车”这一个维度,不需要同时应付尺度变化和透视形变,训练难度明显下降。第二步是按标定的车位多边形裁剪ROI。推理的时候,把矫正后的图中每个车位区域裁剪出来,缩放到统一尺寸(我用的是128x128),再送入分类模型。这个尺寸是经过测试的:再小会丢失车灯、后视镜等局部细节,再大推理速度会受影响,边缘设备上容易掉帧。有一点必须提醒:透视矫正会拉伸图像,远处的车位会被放大很多,近处的车位被压缩,插值算法会带来一定程度的模糊。我实测双线性插值比最近邻插值效果好很多,虽然计算量稍微大一点,但模型精度提升了2到3个百分点,这点开销完全值得。3. 模型选型、训练策略与后处理逻辑3.1 模型选型:小模型还是大模型,轻量化的平衡模型选择上我对比过好几条路线。最初用Faster R-CNN做检测,精度确实不错,但在边缘设备上的推理速度只有几帧每秒,完全达不到实时监控的要求。后来转向YOLO系列,速度和精度都满意,但发现自己把问题改成了“区域分类”之后,杀鸡用牛刀了——每个车位只是一张小图,不需要那么大感受野的检测头。最终我采用的是YOLOv8n作为特征提取骨干,网络结构本身是个检测模型,但我只保留它前几层的特征提取能力,后面接了自定义的分类头。这样做的理由有两点:一是YOLOv8n的权重在COCO上预训练过,对车辆这种常规物体的底层特征已经学得很好了,迁移到我的车位图上有现成的特征基础;二是只分类不回归包围框,省掉了anchor匹配和NMS的计算开销,推理更快。如果你不想这么折腾,直接用最朴素的ResNet18或MobileNetV3做分类也能跑通。区别在于:通用分类网络对“车辆”的先验知识不如检测模型丰富,如果训练数据不够多,小模型的精度上限会低一些。从我实测来看,在每类2000张左右的数据规模下,YOLO特征+分类头的方案比直接用MobileNetV3高大约4个点的F1,差距还是比较明显的。3.2 训练策略:数据增强、损失函数与超参数训练数据增强这一块,我刚开始做得很保守,只用了随机翻转和裁剪,因为担心车位是固定视角的,增强过度会让模型学到不存在的视角。后来实证下来,翻转确实要慎用——左右翻转之后,车位里的车头和车尾方向会颠倒,如果训练集里两种方向都有且比例均衡,翻转没问题,但如果你的停车场是单行道,车辆只能一个方向驶入,那翻转就是灾难。我最终确定的增强组合是:亮度扰动:亮度系数随机在0.8到1.2之间变动,模拟早中晚光照差异。对比度扰动:对比度系数0.85到1.15,主要为了让模型对阴影和反光不那么敏感。高斯噪声:轻微噪声,模拟摄像头传感器在高ISO下的颗粒感。随机遮挡:在小图上随机挖掉一块,模拟行人或邻车遮挡。损失函数方面,二分类任务用交叉熵就够了,但我加了标签平滑。标签平滑系数设0.05,目的是防止模型对训练样本过于自信,提升泛化能力。这个细节在最终测试里拉低了过拟合现象,F1稳定了一些。训练超参数我是这么定的:batch size 64,初始学习率0.001,用余弦退火调度,总计训练60个epoch。优化器选AdamW,weight decay设为1e-4。这些参数不是什么金科玉律,但我测试下来在这个数据规模下很稳,你可以作为起点,再根据自己数据量调整。3.3 后端处理:时序平滑才是真正能上线的关键模型输出单个车位的占用概率后,直接拿这个概率当最终结果发布,你会发现线上表现比测试集差很多。原因在于单帧误判是随机出现的,直接展示会导致车位状态在“空”和“占用”之间疯狂跳变,用户看着就是闪烁,体验极差。我的做法是引入一个基于时间窗口的滑动平均。具体逻辑是:维护每个车位最近10帧的预测概率,取平均值作为当前帧的最终占用概率;只有当平均值连续跨过阈值时,才更新车位状态。阈值我设为0.7:连续3帧平均概率大于0.7判定为占用,小于0.3判定为空闲,0.3到0.7之间保持上一状态不变。这个机制引入之后效果立竿见影。原本单帧偶尔出现的误判,因为时序上不连续,基本被滤掉了。代价是状态切换有大约1到2秒的延迟,但对停车引导场景来说完全可接受。如果有车辆快速驶离,状态更新滞后两三秒,屏幕上多显示几秒“占用”,不会造成严重后果。另外还有一个容易被忽略的后处理:遮挡处理。当一辆车的车头伸到邻车位上,或者行人从车位前走过,模型的预测可能短暂波动。时序平滑能挡住大部分瞬时干扰,但如果是持续的半遮挡,比如一辆大SUV的车头一直压着旁边车位,单纯靠时序平滑就没用了。我的补充策略是合并车位区域——如果两个相邻车位共享一条边且其中一个持续处于partial状态,服务端把两个车位合并显示为“一个大会车位”,避免用户看到“空位”开过去却发现停不进去的尴尬。4. 硬件选型、推理优化与部署方案4.1 边缘设备怎么选,算力怎么分配部署方案我考虑过云端纯计算和边缘端本地推理两条路线。云端方案的优点是可以随时扩容算力,缺点是带宽和延迟——停车场摄像头数量多,持续推送高清画面到云端,流量费是个不小的成本,而且网络抖动会导致状态更新延迟。边缘端推理正好相反,模型跑在摄像头旁边的计算盒子上,只上报结构化结果,一个车位占用的数据量不过几十字节,带宽压力几乎为零。我最后用的是边缘端方案,设备选的是带GPU的嵌入式工控机。选型标准很简单:算力够跑我的模型而且推理延迟低于50毫秒,功耗不能太大,散热要能扛得住机房或者弱电箱的闷热环境。实测下来,YOLOv8n骨干加分类头的模型在INT8量化之后,单帧20个车位的推理总耗时大约是35毫秒,稳定跑在25帧以上,完全满足监控场景。多摄像头的算力分配也值得说一说。如果一台边缘设备接多路摄像头,最稳妥的做法是分时复用GPU:每路视频按顺序推理,中间插入队列缓冲,保证不丢帧。实测4路1080P视频接入时,每路分配到大约6帧每秒的推理频率,对车位状态监控这种低频变化的应用足够了。千万不要让各路并行跑满GPU,温度一上来,推理不稳定掉帧不说,设备寿命也会受影响。4.2 模型压缩:量化与剪枝的取舍模型压缩我只做了INT8量化,没有做剪枝。量化的收益非常直接:模型体积缩小将近4倍,推理速度提升2到3倍,而且只要校准集选得合适,精度损失可以控制在1个百分点以内。剪枝的收益也大,但调试成本很高,需要反复验证剪枝后哪些层敏感、哪些层冗余,对项目周期的压力比较大,所以我没采用。量化的关键在校准数据集。TensorRT或ONNX Runtime的量化工具都支持校准集,但我发现很多人随手拿验证集当校准集,这是错误的。校准集应该从训练集里随机抽样,并且要覆盖模型可能遇到的各种光照和遮挡情况。我用的是每类500张、总共1000张的训练子集,量化后精度只掉了0.8个百分点,后来部署到现场表现稳定。4.3 服务端对接与状态同步机制模型推理只是链路里的一环,真正给客户交付的是车位状态数据。我把边缘端的输出定义为一个简单的JSON结构,每个字段对应一个车位编号和状态,通过HTTP或WebSocket推送到服务端。服务端再对接LED引导屏、小程序或APP接口。这里有一个同步机制的坑必须提:边缘端设备断电重启后,内部的时序平滑状态会清零,模型恢复到空状态,但它上报给服务端的还是重启前的数据。如果不做对账,服务端会以为车位上还有车停着。我的解决办法是边缘端启动时先上报一条“全量状态”消息,服务端收到后强制覆盖旧状态。同时每30秒做一次心跳,如果心跳丢失,服务端标记该摄像头离线,引导屏显示该区域状态未知,而不是显示错误的空位信息。5. 上线后的常见问题与排查技巧实录5.1 高频问题速查表运行了几个月之后,我总结出下面这些最常遇到的问题,附上排查思路和解决方案:现象可能原因排查与解决同一个车位白天正常晚上频繁误判夜间光照不足,暗部细节丢失检查是否开启了红外补光;对夜间样本做针对性增强,或单独训练夜间模型空车位显示占用,且持续不恢复车位区域被长时间遮挡(如邻车越线)检查车位多边形标定是否被邻车车体占用;合并相邻车位逻辑是否生效雨天车位状态随机跳变地面反光和水渍造成特征混乱增加雨天样本;在预处理里加入去反光算法,或用偏振镜硬件方案摄像头角度松动后全部误判透视矫正矩阵失效,ROI偏移定期巡检物理摄像头状态;做画面相似度检测,画面突变时自动告警推理延迟越来越高边缘设备老化或系统日志膨胀清理系统日志,重启服务;检查是否有其他进程占用GPU显存5.2 实战调试心得:如何快速定位是模型问题还是逻辑问题部署现场出问题时,第一件事不是调模型,而是先确认问题出在链路哪一段。我的排查顺序是这样:先看边缘端原始画面的模型输出日志,确认模型本身判没判错。如果模型输出是对的,问题在后处理或通信层;如果模型输出就是错的,才需要回到模型和数据上找原因。举个例子,有一次现场的5号车位一直显示占用,但车早就开走了。我先调出5号车位的最近10帧裁剪图和模型原始概率,发现模型概率一直在0.9以上,说明模型确实认为有车。再看原图,才发现是旁边SUV的车头一直压着5号车位。这就不是模型的问题,而是车位空间被邻近车辆物理占用了,合并车位的逻辑又因为概率没有回落到阈值以下没有触发。最后我把partial状态的判定阈值从0.7降到0.5,合并逻辑提前介入,问题就解决了。这套方法帮我避免了很多无效调参。模型出问题通常表现为随机误判、某个时段整体性能下降、特定车位稳定出错;而后处理和通信问题通常表现为状态跳变无规律、重启后状态错乱、延迟攀升。先分类再定位,效率会高很多。5.3 长期运行维护的几个小建议项目上线不代表结束,长期维护才是真正考验工程能力的地方。第一个建议是给边缘设备加上看门狗机制。嵌入式设备常年运行,偶尔会出现进程卡死或者显存泄漏,看门狗能够自动重启服务,避免现场出现长时间无人维护的真空期。第二个建议是保留每帧画面的缩略图,存个几天就够。出问题的时候,直接回看当时画面,比对着日志猜要直观得多。注意控制缩略图的分辨率和压缩比,一张图几十KB,一天下来几百MB,用环形存储滚动覆盖就行。第三个建议是模型要定期做增量微调。停车场环境不是一成不变的——新划线的车位、装了新的照明灯、旁边盖了楼导致阴影变化,这些都可能让模型性能缓慢退化。我给自己定的节奏是每三个月收集一次现场的新数据,人工筛选后加入训练集做一次小规模的增量训练,每六个月做一次全面评估和模型替换。整体下来,项目从上线到现在精度一直没有明显掉过。最后想分享一个我个人的体会:车位识别这类项目,算法模型的难度其实只占四成,剩下六成是数据管理、设备运维和状态同步这些“不起眼”的工程问题。很多朋友一头扎进模型调参里出不来,结果是被现场各种边缘情况反复打脸。如果你正在做类似的项目,希望你从一开始就把视野放宽——把数据规范、设备监控、状态对账这些环节都当成一等公民来对待,线上运行的时候你会感谢提前做的这些铺垫。
停车场车位识别这个项目,我在真实场景里前后折腾了快两个月。从最开始用摄像头对着空荡荡的地面拍照,到后来在阴雨天、夜间、被挡了一半车头的复杂工况下还能稳定输出空位状态,中间踩过的坑确实不少。这篇文章就把我这套完整方案从数据、模型、部署到排查全流程捋一遍,给正要入坑或者已经被坑住的朋友一个直接能抄作业的参考。1. 项目整体设计与方案选型1.1 先想清楚这个项目到底要解决什么问题很多人一听到“停车场车位识别”,第一反应就是“用目标检测框出车来”。这个想法不完全错,但如果你真按这个思路去做,后面一定会返工。原因很简单:停车场管理方要的不是“有没有车”,而是“哪个车位被占了、哪个车位空着”。这两个需求看起来差不多,实际在工程实现上是两条完全不同的路。检测车辆只需要输出一张图上所有车的包围框,然后把框和车位区域的坐标做交集判断,就能推断出占用状态。但这里有个很实际的麻烦:摄像头视角通常比较倾斜,车辆会互相遮挡,一个框可能同时压住两个车位;而且车头的形状在不同角度下差异巨大,检测框不稳定,空位判断就会跟着抖。我在项目初期就是吃了这个亏,模型精度做到90%以上,但落到具体车位编号的占用率统计上,准确率只有八成出头,根本没法上线。所以后来我换了个思路:把问题拆成“车位区域定位 + 区域内车辆存在性判断”两个子任务。车位区域用人工标定的固定多边形来定义,车辆存在性用检测模型对每个区域做判定。这样既能保证车位编号和服务端的数据库一一对应,又避免了自由检测框和车位映射的错位问题。搞清楚这一点,后面所有技术选型都顺了。1.2 技术路线对比:传统视觉方案为什么被淘汰我最早试的是纯OpenCV方案,具体做法是车位地面上画框,然后对每个车位区域做背景差分或者边缘检测。背景差分在室内灯光稳定的地下车库效果还行,光线均匀、地面颜色一致,车开走之后背景恢复得很快。但遇到室外停车场就完全崩了——树影晃动、云层遮挡、早晚色温变化,这些都会让背景模型跟着“漂移”,几分钟之后它自己都不知道哪个是背景了。边缘检测加颜色特征的办法也试过,主要思路是统计车位框内白色引导线的完整度。空车位的时候,地面标线是连成一条的;有车停进去之后,标线会被车身打断,同时出现大量不属于标线颜色的像素。这个思路在标准化的室内车位里偶尔能跑通,但遇到车体颜色和地面接近的“白色车停白色线框”场景就失灵了,更别说车身反光、地面污渍这些干扰因素。深度学习方案的优势在于它不需要人肉设计“什么叫有车”的特征,而是直接从大量标注数据里学。尤其是YOLO系列出现之后,模型体积、推理速度和检测精度之间取得了很好的平衡,跑在边缘设备上也能做到实时。选深度学习的另外一个实际原因是可维护性:传统方案的阈值参数散布在各种预处理函数里,换一个停车场的摄像头,光照条件变了,得调半天参数;深度学习模型虽然也需要微调,但泛化能力明显强得多,换场地后重新标注少量数据就能适配。1.3 检测方案的核心决策点:全局检测还是区域分类技术路线定了之后,还有一个必须想清楚的问题是输出粒度。我当时在“整图检测所有车辆再映射车位”和“只对每个固定车位区域做判断”之间反复权衡,最后选了后者。这个决策不是我拍脑袋拍的,三个原因:第一个是稳定性。固定车位的多边形区域是人工标定的,坐标精确且和业务编号绑定,服务端可以直接查到“3号车位空着”这样结构化的结果。如果用车辆检测框去映射车位,摄影师角度稍微一变或者车头朝向不同,IoU计算就飘了。第二个是计算量。假设一个摄像头管20个车位,区域分类方案只需要对20个小区域做推理判断,而全局检测需要处理整张高清图。虽然单张图差异不大,但多个摄像头同时接入的时候,计算量的差距会被放大,边缘设备压力明显不同。第三个是可解释性。车位状态判断错了,区域分类方案可以直接把那个车位的裁剪图调出来看模型到底看到了什么;车辆检测+映射方案出了错,很难定位是检测框的问题还是映射逻辑的问题。后面我还会详细讲这个排障思路,它在真实项目里救了我很多次。2. 数据采集、标注规范与预处理细节2.1 数据集从哪里来,怎么采最有效率训练数据永远是这类项目最头疼的部分。公开数据集我参考过一些通用的车辆检测数据集,但它们大多是街景或行车记录仪视角,和停车场摄像头的俯视斜视角差距很大,直接拿来训练,迁移效果很差。所以我的做法是自己在目标停车场采集原始视频,然后抽帧标注。采集阶段有几个关键细节。第一是覆盖时段,我连续采集了三天,确保包含晴天、阴天、雨天、清晨、正午、傍晚、夜间各时段的画面。夜间的车灯反光、路灯不足造成的暗部盲区,这些如果不进训练集,上线必翻车。第二是覆盖状态,既要拍车辆正常停放,也要拍大量空车位和半遮挡状态——比如车头超出车位线压到旁边车位、行人路过挡住镜头、车辆临时停靠双闪等,这些边缘情况才是模型精度的分水岭。抽帧不是均匀抽就行。监控视频一秒钟25帧,早晚高峰车辆进出频繁,均匀抽帧会得到大量重复的静止画面。我先用OpenCV做了简单的帧间差分析,画面变化超过阈值的帧才保留,然后再从中随机抽帧用于标注。这样同样的标注工作量,样本的信息量能多出不少。另外一个很多人忽略的点是摄像头本身的参数。我用的摄像头鱼眼畸变比较明显,车位在画面边缘会弯得很厉害。这就需要先做畸变矫正再抽帧标注,否则标注框和实际车位多边形对不齐,模型学习的时候特征本身就是歪的,后面怎么调都白搭。2.2 标注规范决定了模型精度的上限标注质量对车位识别项目的影响比想象中大得多。我第一版训练集是自己用脚本半自动生成的,结果模型训练完精度一直卡在85%左右上不去。后来复盘发现,问题出在标注标签上——我只标了“occupied”和“empty”,但实际车位区域里经常同时出现“车头已经进入车位、车身还在通道上”的中间状态,这种状态被强行归到occupied还是empty都不对,模型学到的边界就很模糊。后来我把标签体系改成三分类加一个辅助标签:标签含义处理建议empty车位完全空置正常训练样本occupied车辆完整停在车位内正常训练样本partial车辆部分进入或部分遮挡车位训练时并入occupiedshadow树影/建筑阴影覆盖车位辅助标注,增强阴影鲁棒性partial状态我一开始是单独建模的,但后来发现服务端业务逻辑里,partial大概率是“即将停入”或“正在驶出”,短时间内状态还会变化。强行让模型预测中间态意义不大,反而消耗样本量。所以我把它合并到occupied里,但保留了这个标签的图片样本,让模型见过更多遮挡形态。标注工具我用的是labelme,多边形标注比矩形框更适合车位这种不规则区域。注意标注的是车位区域而不是车体,这一点和常规目标检测不一样。框选车位的时候,我要求标定到车位线的内边缘,不要把线本身框进去,因为线宽会随透视角度变化,导致区域坐标不稳定。还有一个实操经验:一个摄像头对应一套固定的车位多边形坐标。标注完第一次之后,把坐标持久化保存到配置文件里,后续每次推理都复用这套坐标,不需要重新标注。换摄像头角度或者挪动摄像头位置,才需要重新标定一遍。2.3 预处理:透视矫正与ROI裁剪停车场的摄像头通常是立杆斜装,画面里车道和车位都是透视变形的——车位看起来是梯形甚至不规则的四边形,远处车位很小,近处车位很大。直接拿这种图做区域分类,模型需要自己学会尺度不变性,这对小模型来说是很大的负担。我做的预处理分两步。第一步是透视矫正,用车位区域的四个角点做透视变换,把整个停车区投影成俯视平面图。矫正之后,所有车位理论上都是近似相同的矩形,模型只需要关注“这个区域里有没有车”这一个维度,不需要同时应付尺度变化和透视形变,训练难度明显下降。第二步是按标定的车位多边形裁剪ROI。推理的时候,把矫正后的图中每个车位区域裁剪出来,缩放到统一尺寸(我用的是128x128),再送入分类模型。这个尺寸是经过测试的:再小会丢失车灯、后视镜等局部细节,再大推理速度会受影响,边缘设备上容易掉帧。有一点必须提醒:透视矫正会拉伸图像,远处的车位会被放大很多,近处的车位被压缩,插值算法会带来一定程度的模糊。我实测双线性插值比最近邻插值效果好很多,虽然计算量稍微大一点,但模型精度提升了2到3个百分点,这点开销完全值得。3. 模型选型、训练策略与后处理逻辑3.1 模型选型:小模型还是大模型,轻量化的平衡模型选择上我对比过好几条路线。最初用Faster R-CNN做检测,精度确实不错,但在边缘设备上的推理速度只有几帧每秒,完全达不到实时监控的要求。后来转向YOLO系列,速度和精度都满意,但发现自己把问题改成了“区域分类”之后,杀鸡用牛刀了——每个车位只是一张小图,不需要那么大感受野的检测头。最终我采用的是YOLOv8n作为特征提取骨干,网络结构本身是个检测模型,但我只保留它前几层的特征提取能力,后面接了自定义的分类头。这样做的理由有两点:一是YOLOv8n的权重在COCO上预训练过,对车辆这种常规物体的底层特征已经学得很好了,迁移到我的车位图上有现成的特征基础;二是只分类不回归包围框,省掉了anchor匹配和NMS的计算开销,推理更快。如果你不想这么折腾,直接用最朴素的ResNet18或MobileNetV3做分类也能跑通。区别在于:通用分类网络对“车辆”的先验知识不如检测模型丰富,如果训练数据不够多,小模型的精度上限会低一些。从我实测来看,在每类2000张左右的数据规模下,YOLO特征+分类头的方案比直接用MobileNetV3高大约4个点的F1,差距还是比较明显的。3.2 训练策略:数据增强、损失函数与超参数训练数据增强这一块,我刚开始做得很保守,只用了随机翻转和裁剪,因为担心车位是固定视角的,增强过度会让模型学到不存在的视角。后来实证下来,翻转确实要慎用——左右翻转之后,车位里的车头和车尾方向会颠倒,如果训练集里两种方向都有且比例均衡,翻转没问题,但如果你的停车场是单行道,车辆只能一个方向驶入,那翻转就是灾难。我最终确定的增强组合是:亮度扰动:亮度系数随机在0.8到1.2之间变动,模拟早中晚光照差异。对比度扰动:对比度系数0.85到1.15,主要为了让模型对阴影和反光不那么敏感。高斯噪声:轻微噪声,模拟摄像头传感器在高ISO下的颗粒感。随机遮挡:在小图上随机挖掉一块,模拟行人或邻车遮挡。损失函数方面,二分类任务用交叉熵就够了,但我加了标签平滑。标签平滑系数设0.05,目的是防止模型对训练样本过于自信,提升泛化能力。这个细节在最终测试里拉低了过拟合现象,F1稳定了一些。训练超参数我是这么定的:batch size 64,初始学习率0.001,用余弦退火调度,总计训练60个epoch。优化器选AdamW,weight decay设为1e-4。这些参数不是什么金科玉律,但我测试下来在这个数据规模下很稳,你可以作为起点,再根据自己数据量调整。3.3 后端处理:时序平滑才是真正能上线的关键模型输出单个车位的占用概率后,直接拿这个概率当最终结果发布,你会发现线上表现比测试集差很多。原因在于单帧误判是随机出现的,直接展示会导致车位状态在“空”和“占用”之间疯狂跳变,用户看着就是闪烁,体验极差。我的做法是引入一个基于时间窗口的滑动平均。具体逻辑是:维护每个车位最近10帧的预测概率,取平均值作为当前帧的最终占用概率;只有当平均值连续跨过阈值时,才更新车位状态。阈值我设为0.7:连续3帧平均概率大于0.7判定为占用,小于0.3判定为空闲,0.3到0.7之间保持上一状态不变。这个机制引入之后效果立竿见影。原本单帧偶尔出现的误判,因为时序上不连续,基本被滤掉了。代价是状态切换有大约1到2秒的延迟,但对停车引导场景来说完全可接受。如果有车辆快速驶离,状态更新滞后两三秒,屏幕上多显示几秒“占用”,不会造成严重后果。另外还有一个容易被忽略的后处理:遮挡处理。当一辆车的车头伸到邻车位上,或者行人从车位前走过,模型的预测可能短暂波动。时序平滑能挡住大部分瞬时干扰,但如果是持续的半遮挡,比如一辆大SUV的车头一直压着旁边车位,单纯靠时序平滑就没用了。我的补充策略是合并车位区域——如果两个相邻车位共享一条边且其中一个持续处于partial状态,服务端把两个车位合并显示为“一个大会车位”,避免用户看到“空位”开过去却发现停不进去的尴尬。4. 硬件选型、推理优化与部署方案4.1 边缘设备怎么选,算力怎么分配部署方案我考虑过云端纯计算和边缘端本地推理两条路线。云端方案的优点是可以随时扩容算力,缺点是带宽和延迟——停车场摄像头数量多,持续推送高清画面到云端,流量费是个不小的成本,而且网络抖动会导致状态更新延迟。边缘端推理正好相反,模型跑在摄像头旁边的计算盒子上,只上报结构化结果,一个车位占用的数据量不过几十字节,带宽压力几乎为零。我最后用的是边缘端方案,设备选的是带GPU的嵌入式工控机。选型标准很简单:算力够跑我的模型而且推理延迟低于50毫秒,功耗不能太大,散热要能扛得住机房或者弱电箱的闷热环境。实测下来,YOLOv8n骨干加分类头的模型在INT8量化之后,单帧20个车位的推理总耗时大约是35毫秒,稳定跑在25帧以上,完全满足监控场景。多摄像头的算力分配也值得说一说。如果一台边缘设备接多路摄像头,最稳妥的做法是分时复用GPU:每路视频按顺序推理,中间插入队列缓冲,保证不丢帧。实测4路1080P视频接入时,每路分配到大约6帧每秒的推理频率,对车位状态监控这种低频变化的应用足够了。千万不要让各路并行跑满GPU,温度一上来,推理不稳定掉帧不说,设备寿命也会受影响。4.2 模型压缩:量化与剪枝的取舍模型压缩我只做了INT8量化,没有做剪枝。量化的收益非常直接:模型体积缩小将近4倍,推理速度提升2到3倍,而且只要校准集选得合适,精度损失可以控制在1个百分点以内。剪枝的收益也大,但调试成本很高,需要反复验证剪枝后哪些层敏感、哪些层冗余,对项目周期的压力比较大,所以我没采用。量化的关键在校准数据集。TensorRT或ONNX Runtime的量化工具都支持校准集,但我发现很多人随手拿验证集当校准集,这是错误的。校准集应该从训练集里随机抽样,并且要覆盖模型可能遇到的各种光照和遮挡情况。我用的是每类500张、总共1000张的训练子集,量化后精度只掉了0.8个百分点,后来部署到现场表现稳定。4.3 服务端对接与状态同步机制模型推理只是链路里的一环,真正给客户交付的是车位状态数据。我把边缘端的输出定义为一个简单的JSON结构,每个字段对应一个车位编号和状态,通过HTTP或WebSocket推送到服务端。服务端再对接LED引导屏、小程序或APP接口。这里有一个同步机制的坑必须提:边缘端设备断电重启后,内部的时序平滑状态会清零,模型恢复到空状态,但它上报给服务端的还是重启前的数据。如果不做对账,服务端会以为车位上还有车停着。我的解决办法是边缘端启动时先上报一条“全量状态”消息,服务端收到后强制覆盖旧状态。同时每30秒做一次心跳,如果心跳丢失,服务端标记该摄像头离线,引导屏显示该区域状态未知,而不是显示错误的空位信息。5. 上线后的常见问题与排查技巧实录5.1 高频问题速查表运行了几个月之后,我总结出下面这些最常遇到的问题,附上排查思路和解决方案:现象可能原因排查与解决同一个车位白天正常晚上频繁误判夜间光照不足,暗部细节丢失检查是否开启了红外补光;对夜间样本做针对性增强,或单独训练夜间模型空车位显示占用,且持续不恢复车位区域被长时间遮挡(如邻车越线)检查车位多边形标定是否被邻车车体占用;合并相邻车位逻辑是否生效雨天车位状态随机跳变地面反光和水渍造成特征混乱增加雨天样本;在预处理里加入去反光算法,或用偏振镜硬件方案摄像头角度松动后全部误判透视矫正矩阵失效,ROI偏移定期巡检物理摄像头状态;做画面相似度检测,画面突变时自动告警推理延迟越来越高边缘设备老化或系统日志膨胀清理系统日志,重启服务;检查是否有其他进程占用GPU显存5.2 实战调试心得:如何快速定位是模型问题还是逻辑问题部署现场出问题时,第一件事不是调模型,而是先确认问题出在链路哪一段。我的排查顺序是这样:先看边缘端原始画面的模型输出日志,确认模型本身判没判错。如果模型输出是对的,问题在后处理或通信层;如果模型输出就是错的,才需要回到模型和数据上找原因。举个例子,有一次现场的5号车位一直显示占用,但车早就开走了。我先调出5号车位的最近10帧裁剪图和模型原始概率,发现模型概率一直在0.9以上,说明模型确实认为有车。再看原图,才发现是旁边SUV的车头一直压着5号车位。这就不是模型的问题,而是车位空间被邻近车辆物理占用了,合并车位的逻辑又因为概率没有回落到阈值以下没有触发。最后我把partial状态的判定阈值从0.7降到0.5,合并逻辑提前介入,问题就解决了。这套方法帮我避免了很多无效调参。模型出问题通常表现为随机误判、某个时段整体性能下降、特定车位稳定出错;而后处理和通信问题通常表现为状态跳变无规律、重启后状态错乱、延迟攀升。先分类再定位,效率会高很多。5.3 长期运行维护的几个小建议项目上线不代表结束,长期维护才是真正考验工程能力的地方。第一个建议是给边缘设备加上看门狗机制。嵌入式设备常年运行,偶尔会出现进程卡死或者显存泄漏,看门狗能够自动重启服务,避免现场出现长时间无人维护的真空期。第二个建议是保留每帧画面的缩略图,存个几天就够。出问题的时候,直接回看当时画面,比对着日志猜要直观得多。注意控制缩略图的分辨率和压缩比,一张图几十KB,一天下来几百MB,用环形存储滚动覆盖就行。第三个建议是模型要定期做增量微调。停车场环境不是一成不变的——新划线的车位、装了新的照明灯、旁边盖了楼导致阴影变化,这些都可能让模型性能缓慢退化。我给自己定的节奏是每三个月收集一次现场的新数据,人工筛选后加入训练集做一次小规模的增量训练,每六个月做一次全面评估和模型替换。整体下来,项目从上线到现在精度一直没有明显掉过。最后想分享一个我个人的体会:车位识别这类项目,算法模型的难度其实只占四成,剩下六成是数据管理、设备运维和状态同步这些“不起眼”的工程问题。很多朋友一头扎进模型调参里出不来,结果是被现场各种边缘情况反复打脸。如果你正在做类似的项目,希望你从一开始就把视野放宽——把数据规范、设备监控、状态对账这些环节都当成一等公民来对待,线上运行的时候你会感谢提前做的这些铺垫。