先把话放在前面:你大概率翻遍公开论文库也搜不到“YOLO26”这个编号,因为严格来说它不是官方发布过的正式版本。在我做的这个项目里,YOLO26是项目组对YOLO系列当前最新一代检测方案的一个内部代号——它继承了端到端预测、解耦检测头、动态标签分配这一整套设计思想,同时也经过了我们针对监控场景的大量改造。这套东西能不能用在“城市违停车辆检测”这种实际业务里?我踩了几个月的坑,把从数据集搭建、模型结构改动、训练调参到边缘设备部署的全过程捋了一遍,这篇就当作一份可以复现的工程笔记。
城市违停检测这个事,难点从来不在“能不能检测出一辆车”,而在“在监控画面里稳定检测出那辆停错位置的车,并且不产生一堆让后台审核人员崩溃的误报”。这篇文章会从业务逻辑、数据、模型、训练、后处理、部署六个层面拆开讲,适合做智慧城市视觉项目的算法工程师,也适合刚入门目标检测、想了解真实落地场景的学生。全文涉及的经验不一定都写在官方文档里,但都是实测后留下来的。
1. 为什么“违停检测”和普通车辆检测不是一回事
1.1 监控视角下的车,和自动驾驶视角下的车完全是两个物种
做检测的人最容易犯的第一个错误,就是把自动驾驶数据集里的车辆检测思路直接搬到违停场景里。自动驾驶的摄像头装在车身上,视角是平视的,车离得近、目标大、遮挡少;城市违停的监控摄像头装在路侧立杆或者高楼上,视角是俯视的,画面里可能同时出现一整条街,一辆违停的轿车在1080P画面里往往只有40到60个像素的宽高。
也就是说,这不是“换个数据集重新训练”就能解决的问题,而是目标尺度分布完全变了。普通车辆检测模型在6米以内近距离检测效果很好,到了10米、20米的距离,框就开始飘,极端情况下连续几帧检测不到同一辆车。我们前期拿一个现成的预训练模型直接跑监控画面,mAP看起来还有0.7左右,但看具体召回情况会发现,30米以外的车辆基本是瞎的,夜间更是几乎全废。
第二个差异是时间维度。自动驾驶检测不需要知道这辆车是停着的还是开着的,但违停检测必须判断“它停了多久”。单帧检测只能告诉你“这里有一辆车”,不能告诉你“这辆车违停”,因为停在禁停区的车可能只是在等红灯,也可能是在合法泊位里。这就决定了方案不能是一个纯粹的静态检测器,必须有跟踪和计时逻辑兜底。
1.2 违停不是“有车就罚”:业务规则比模型复杂
我刚开始做的时候以为只要检测到车、判断它在禁停区域,就能上报。真正接触业务需求后发现,规则要比这复杂得多。
城市道路里的禁停场景大致分几类:一是画了黄色路沿线的路段,全线禁停;二是设有“禁止停车”标志牌的区域,通常有范围限制;三是学校、医院门口的网状线区域,允许临时上下客但禁止长时间停留;四是公交站台附近,社会车辆限时停靠。同样一辆车停在同一个位置,停5分钟和停15分钟,定性可能完全不同。有些路段白天禁停晚上可以停,有些区域只有高峰时段禁停。
这意味着算法输出的“位置+类别+置信度”远远不够,还需要知道车辆是否持续停留、是否越过禁停边界、当前时间段是否适用禁停规则。模型负责把“物理世界里的车”变成“画面里的检测框”,业务层负责把“检测框序列”变成“一条可以立案的证据”。这两层必须分开设计,不能指望模型一个人把活干完。
也正因为如此,实际的系统架构大致是这样的:YOLO26检测模型输出车辆框,一个轻量多目标跟踪器维护每辆车的轨迹,然后轨迹坐标和禁停区域做空间关系判断,最后按时长阈值和时段规则决定是否触发抓拍。模型只是整条链路里的一环,但它决定了整条链路的上限——如果检测器漏检,后面任何逻辑都无从谈起。
2. 数据先行:一套靠谱的违停训练集是怎么建起来的
2.1 数据采集的视角、时段和天气必须覆盖哪些情况
模型效果的上限是数据决定的,这句话在违停检测这个场景里体现得格外明显。监控画面的数据分布和公开数据集差距太大,我们项目组很早就决定以自采数据为主、公开数据为辅来做训练集。
采集阶段需要刻意覆盖几个维度:视角高度、朝向、时段、天气、场景类型。视角高度要覆盖6米到15米的立杆高度,朝向要涵盖顺光、逆光和侧光,时段要覆盖白天、黄昏、夜间、凌晨,天气要覆盖晴天、阴天、雨天、雾天。凌晨和夜间的样本尤其重要,因为很多违停集中发生在晚间,而夜间数据恰恰是公开数据集里最缺的。
这里有个容易忽略的细节:监控摄像头的安装角度会对检测效果产生巨大影响。同一辆车在俯仰角30度和60度的画面里,外形特征差异很大。如果训练数据全是30度左右的画面,部署时遇到60度高点位摄像头,检测效果会明显下降。所以采集时要有意识地找不同安装角度的摄像头,或者在做数据增强时加入随机的透视变换模拟视角偏移。
场景多样性也不能只看“马路”这一个类别。学校门口、医院门口、商业综合体周边、老旧小区内部道路、物流园区,这些地方的违停形态完全不同——乱停的电动车、卸货的货车、临时停靠的私家车,在画面里的呈现差异很大。我们的经验是初期尽可能多接几个场景的摄像头,哪怕每个场景只拿少量数据,也好过在一个单一场景里堆大量相似帧。
2.2 标注规范:边界框、属性标签、复杂场景规则
标注是整套流程里最枯燥但最影响结果的一环。我们团队前后迭代了三版标注规范,踩了不少坑,这里直接把沉淀下来的关键规则列出来。
边界框的标注原则是“所见即所得”:车辆被遮挡时,只标可见部分,不按完整车身轮廓外推。如果一个框里同时出现两辆车且相互重叠严重,宁可少标一辆,也不要标一个把两辆车都包进去的大框。这样做的原因是检测模型学到的是“可见特征到位置”的映射,强行标注完整轮廓会让模型学到错误的位置估计。
属性标签至少要包含:车辆类型(轿车、SUV、货车、客车)、车身颜色(用于后续车牌联动核验)、是否处于运动状态(静止/运动,这个标签用于辅助训练跟踪模块的判别)。车位边界和禁停区域的标注则用多边形标注工具单独分层保存,不参与车辆检测的训练,而是作为后处理阶段的先验知识。
最有争议的是“违停”标签本身要不要在训练阶段就标注。我们试过给车辆框直接标“违停/非违停”,发现效果很差——因为“违停”本质上是空间+时间概念,单帧图像无法表达。后来我们统一规定:训练阶段的标签只有“车辆”和“车辆类别”,是否违停完全交给后端逻辑判断。这样一来,标注工作更简单,模型泛化能力也更好。
2.3 用半自动预标注提升效率的流程
一张1080P监控画面里可能同时出现几十辆车,纯人工框选效率太低。我们的做法是先用一个初版检测模型(从公开车辆数据集上微调)对全部采集视频做批量推理,生成初步检测框,然后人工在标注工具里“修正”而不是“从头画”。这一步能节省约60%的标注时间。
但要注意,预标注引入的错误必须控制,否则模型会在错误标注上重复学习,形成自举偏差。我们的处理方式是:预标注结果必须经过两轮人工审核,第一轮筛掉明显错误的帧,第二轮修正框的边界精度。对于模型完全没检测出来的车辆,人工需要补充标注,这部分是纯增量工作,没有捷径可走。
还有一个提升效率的技巧:视频数据不要逐帧标注,而是每隔5到10帧抽一帧标注。相邻帧之间的车辆位置变化很小,逐帧标注大量重复劳动。抽帧标注配合跟踪算法生成中间的轨迹数据,既保证多样性,又减少工作量。
2.4 类别平衡和数据清洗
违停数据天然存在类别不平衡问题:私家车占比可能超过70%,货车不到5%,客车更少。直接用原始分布训练,模型对货车、客车的召回率会非常低。而实际业务里,货车违停往往占道面积最大、危害最大,恰恰是最需要检测出来的目标。
我们的处理办法是在采样阶段对少类别做重复采样,同时用在线数据增强(比如对货车样本做随机旋转、缩放、色彩扰动)来增加样本多样性。但要注意,重复采样过多会导致模型对少数类过拟合,在我们的实验里,把货车样本权重提升到原始分布的2.5倍左右是一个平衡点,超过3倍后货车检测精度不再提升,而私家车的检测精度开始下降。
数据清洗的重点是去掉三类坏样本:一是画面模糊到人眼都无法判断的帧,二是目标被严重遮挡导致标签位置不可信的帧,三是标注与画面内容明显不符的错误帧。我们的流程是训练前对每个类别计算样本的平均IoU置信度,把异常样本挑出来人工复核。这批坏样本数量虽然不大(大概占总量的3%左右),但对模型指标的拖累非常明显。
3. YOLO26 结构层面的调整:不是拿个预训练权重就完事
3.1 基础结构回顾:Backbone、Neck、Head 我分别动了哪里
YOLO系列发展到最新一代,整体框架依然是Backbone加Neck加Head三段式。Backbone负责从输入图像里提取多尺度特征,Neck负责把不同层次的特征融合起来,Head负责在融合后的特征图上输出检测结果。我们基于这个框架做了四处明显改动。
第一处是Backbone末端增加了一个轻量注意力模块。监控画面里车辆目标小、背景杂,普通卷积容易把注意力分散到路面纹理、树叶晃动这些无关区域。注意力模块的作用是让网络更关注“有车”的区域。具体做法是在Backbone最后两个Stage的输出上分别挂一个通道注意力,计算量增加不到1%,但对小目标的召回提升比较明显。
第二处是Neck部分改成双向特征融合结构。原始YOLO的Neck主要是自顶向下的特征金字塔路径,高层语义信息逐层往下传,但底层的位置信息往上传递的路径很弱。对于小目标检测,底层高分辨率特征里的细节信息太重要了,所以我们增加了一条自底向上的增强路径,让浅层特征能直接参与大特征图上的预测。
第三处是Head部分把分类和回归分支做了更彻底的解耦。车辆检测里类别少(无非轿车、SUV、货车几类),但位置回归要求很高——一个框偏了10个像素,后面的禁停区域判断就可能从“违停”变“合法”。解耦后的回归分支可以专门优化回归损失,不受分类任务干扰。
第四处是小目标检测层的分辨率调整。原始设计里最小的检测头在输入图像的1/32尺寸特征图上做预测,对监控场景来说这个感受野太大了。我们把检测层扩展到输入图像的1/64尺寸,虽然增加了少量计算量,但10米外的小车终于能稳定被召回。
3.2 针对远端小目标的多尺度融合改进
监控画面对小目标检测的挑战,怎么强调都不为过。一幅覆盖50米路段的画面,远端车辆的像素高度可能只有20到30像素,这在COCO数据集的定义里属于“极小目标”范畴。
我们在多尺度融合上做了个比较关键的操作:把Neck里大特征图通道数适当减少,小特征图通道数适当增加,让信息在不同尺度间流动时更均衡。原因是大特征图本身分辨率高,不需要太多通道来编码细节;小特征图分辨率低,需要更多通道来保留语义信息。这个调整让中远距离车辆的召回率提升了4个百分点左右。
另外,我们用了基于特征对齐的上采样方式来替代简单的最近邻插值。特征金字塔融合时,上采样后的高层特征和底层特征在空间位置上往往有偏移,直接逐元素相加会引入位置误差。特征对齐模块学习一个可变形的偏移场,把高层特征“弯”到和底层特征对齐的位置再加起来。这一步对停车线附近车辆和两车紧挨着的场景,效果尤其明显。
3.3 检测头的解耦与动态标签分配
现代检测头一般都把“这个位置有没有目标”和“目标是什么类别”分开预测。我们在这个基础上又加了一层:把“目标中心点的位置”和“目标框的宽高”这两个回归任务也拆开。原因在于监控场景里车辆宽高变化范围极大——一辆大巴和一辆轿车在远端可能都是二三十像素,但宽高比差了好几倍,放在同一个回归头里容易互相干扰。
动态标签分配这块,我们参考了近年来的做法:不再根据固定的IoU阈值来分配正负样本,而是让网络自己学习哪些特征位置应该负责预测哪个目标。具体表现是,对于一辆出现在画面角落的车辆,系统会自动让距离目标中心最近的几个特征点同时参与预测,而不是只靠单个格子。这个机制对小目标检测的提升非常直接,代价是训练时间稍微变长,因为每轮迭代都要重新计算匹配关系。
3.4 轻量化结构选择的取舍
城市监控设备大多是边缘盒子,算力有限。我们在模型设计上做了几次加减法。
Backbone的通道数缩窄了约四分之一,换来的是计算量下降一半,代价是高密度小目标的召回率略有下降。Neck部分的重复堆叠模块从三层减到两层,推理延迟降低了约30%,精度损失在可控范围内。到部署阶段,我们又尝试把注意力模块替换成结构重参数化的方式——训练时用复杂结构,推理时压缩成单路卷积,这个操作基本不损失精度,但推理速度又提了一截。
这里要提醒一句:轻量化不要一刀切。我们试过直接把整个Backbone换成某个极轻量的骨干网络,速度快是快,但小目标检测能力直接崩了,因为骨干网络的降采样太激进,细节信息丢失严重。在违停检测这个场景里,宁可多花一些算力,也要保住远端召回率。毕竟漏掉一次违停,意味着这条证据链就是缺失的,技术指标再好看也没用。
4. 训练阶段:实验记录与调参实战
4.1 数据增强策略:Mosaic、MixUp 在监控画面上的表现
数据增强策略我们前后对比了好几组,结论可能和很多教程里写的不太一样。Mosaic增强(把四张图拼成一张训练)在通用检测任务里很有效,但在违停场景里需要谨慎用。原因是监控画面的空间关系本来就重要——车辆在道路上的位置、和停车线的关系,这些语义信息经过Mosaic拼接后会被打乱。我们最终只在训练的前半段使用Mosaic,后300轮关掉,让模型在最后阶段“回归”到真实的单图分布上。
MixUp(两张图按比例混合)在我们的实验里几乎没有正面作用。监控画面背景复杂、目标小,MixUp会把两个场景的纹理混在一起,给本来就难学的小目标特征增加更多歧义。相比之下,随机亮度扰动、饱和度扰动、HSV扰动这些基础增强反而最有效,因为监控摄像头在不同时间段的白平衡差异很大,色彩扰动可以帮助模型适应这种变化。
这里可以给一个具体的增强配置参考:亮度扰动范围±0.3,饱和度扰动范围±0.3,对比度扰动范围±0.2,随机裁剪比例0.5到1.0。另外要关闭随机翻转——车牌字符和道路方向经过镜像翻转后会造成语义错误,模型学到“车头方向永远朝左”这种错误先验。
4.2 超参数设置:输入分辨率、batch、学习率、EMA
输入分辨率是影响检测效果最直接的因素之一。我们实际对比了640、960、1280三档分辨率,结论是:960是一个性价比很高的点。640下小目标召回率明显不足,1280下精度提升不到1%,但推理耗时增加了将近一倍,边缘设备根本扛不住。最终部署时为了兼顾实时性,推理端用的是960分辨率,但如果后台是批量分析的历史录像,我们会用1280重新跑一遍做二次确认。
batch size取决于GPU显存,我们用的是单卡24GB,batch设成16。学习率采用warmup加余弦退火策略:前10轮线性从1e-5升到2e-4,然后余弦退火降到1e-6。EMA(指数移动平均)权重衰减系数设0.995,实测能让最终模型的mAP稳定提升0.3到0.5个百分点,而且几乎不增加训练时间,强烈建议开。
训练总轮数我们控制在300轮左右。监控数据量级在数万张,300轮足够收敛。如果发现mAP还有上升趋势,加一个“最后50轮关闭Mosaic”的脚本,让模型适应真实分布再结束训练,比简单增加轮数更有效。
4.3 损失函数调整:从CIoU到带有惩罚项的回归损失
回归损失这块我们做了替换。YOLO系列默认使用CIoU损失,它考虑了框的重叠面积、中心点距离和宽高比,在通用场景里表现很好。但监控小目标场景里,小框的像素偏差对CIoU的梯度贡献和对大框完全不同,几个像素的误差在数十像素的小目标上可能就是20%到30%的偏差。
我们改成在CIoU基础上增加了一个针对小目标的惩罚项:当预测框的尺寸小于某个阈值时,额外提高中心点偏移的惩罚权重。调参的时候用过一组对比:这个惩罚系数在0.5时效果最好,小目标AP从0.51提升到了0.57;设到1.5时,模型开始过度激进地贴近中心点,导致框的整体尺寸偏差变大,反而是负面效果。
分类损失用的还是Focal Loss,gamma设为2。和二分类场景不同,违停检测目标类别多(轿车、SUV、货车、客车),类别不均衡问题没那么极端,Focal Loss主要用来压制大量易分类的负样本带来的梯度淹没。
4.4 训练中常见的异常:loss炸了、mAP上不去、过拟合
Loss爆炸最常见的诱因是学习率过大和标签噪声。我们遇到过训练到第80轮时loss突然从几个变成几十,查下来是数据里有几张极其暗的夜间图片,模型在这些样本上产生了极端大的梯度。解决办法是在数据加载管道里加入亮度标准差统计,只要某个batch的亮度方差超过阈值就把该batch跳过。
mAP死活上不去,优先检查三件事:数据分布是否和验证集一致、标签是否有系统性错误、模型容量是否足够。我们有一次mAP停在0.68上不去,排查了很久才发现是标注规范变更后没有重新清洗验证集,验证集里存在几百个“旧规范”的框,导致指标被拉低。重新清洗后直接跳到了0.74。
过拟合在监控场景里不太常见,因为我们有足够多的不同场景视频。但如果你的项目只拿到了单一场景的数据,就需要格外注意:模型的泛化能力会非常差,换个摄像头就废。唯一的弥补方案是加入大量随机透视变换和亮度扰动,强行模拟不同安装视角和时间段,治标不治本,但至少能撑过一个demo演示。
5. 误报歼灭战:后处理与违停判定逻辑
5.1 为什么行人、非机动车和“禁止停车标牌”会成为干扰
模型训完之后,第一个版本在测试视频上跑,肉眼看起来不错,但后台审核反馈说误报太多,一天几百条压根看不过来。我们把误报样本拉出来逐帧分析,发现类型非常集中。
第一类是行人和非机动车产生的框。监控画面里车流密集时,骑车的人紧贴车流边缘,检测器容易把人或者电动车框成“车辆”。这主要是因为训练数据里行人和车辆共存的场景太多,模型学到了一些混淆特征。解决方式不是简单地降低目标置信度阈值——那样会把真正的车也丢掉——而是在检测头里增加一个“物体的车辆置信度”分支,专门预估“这个框里的目标是车辆的概率”,供后端过滤使用。
第二类是建筑物阴影、路面反光、树木倒影造成的“假车”。这种误报在阳光强烈的午后和雨后特别多,特征上和真实车辆确实有相似性,单靠检测模型很难彻底消除。我们的对策是加一个基于车辆边缘结构的二次校验:真实车辆具有明确的矩形轮廓和车轮特征,阴影和反光往往缺乏这些结构信息。在检测框内提取边缘特征做一次线性分类,能过滤掉约70%的阴影误报。
第三类最反直觉:“禁止停车”标牌本身。标牌是蓝底白字,特征鲜明,检测器总能给一个很高的置信度,有时候这个框和牌附近的真实车辆的框发生重叠,导致后台以为这是个超长车辆。这种误报的处理方法很直接:在类别列表里加入一个“标牌”类,训练数据里把画面中出现的一切禁停标牌标出来,让模型学会“这些东西是标牌,不是车”。
5.2 连续帧时序过滤:Tracker模块的引入
单帧检测结果直接上报,会产生大量“闪烁式误报”——某一帧有个影子像车,下一帧就没了。我们把检测结果接入了一个轻量多目标跟踪器,对每辆车分配唯一的ID并持续跟踪。只有同一ID连续出现在N帧以上,且位置基本稳定(中心点位移小于阈值),才判定为“疑似静止车辆”。
这里有一个非常重要的工程细节:跟踪器的置信度传播。当某一帧检测不到某辆车时,不能立刻删掉它的轨迹,而要给它一个“衰减期”。衰减期内如果重新检测到同一ID,就继续跟踪;如果衰减期结束还没出现,才判定轨迹结束。这能把单帧漏检对计时逻辑的干扰降到最低。
我们用的跟踪策略是IOU匹配加卡尔曼滤波预测的简单组合。对违停检测来说,车辆移动缓慢甚至静止,不需要复杂的Re-ID特征匹配,简单策略反而鲁棒。复杂跟踪器在车辆密集时会产生大量ID切换,一旦ID切换,停留计时要重新开始,真实违停就会漏报。
5.3 基于停车位/禁停区域的先验约束
业务上有些区域是允许停车的(比如画了白色泊位线的正规停车位),有些是禁停的(黄色路沿、网状线、禁停标志覆盖范围)。这些信息在画面里是固定的,我们把它们抽象成多边形坐标,存成配置文件,在推理阶段实时加载。
判定逻辑用射线法判断车辆检测框底部中心点是否落在禁停多边形内。这里必须用“框底部中心点”而不是“框中心点”——因为车辆检测框包含车身和车顶,如果车头朝向摄像头,框中心点可能在车身中间位置,和地面接触点偏差很大。框底部中心点更接近车辆的接地位置,判定才准确。
这个先验约束还有一个辅助价值:它可以显著降低误报。一辆停在高架桥下阴影里的车,如果阴影被识别成车,它的框底部中心点大概率落在“桥下非道路区域”——只要该区域不在任何禁停多边形内,就可以直接过滤掉。我们部署后的误报率下降了一半以上,靠的就是这一层规则过滤。
5.4 针对具体误报类型的专项规则
除了通用过滤,我们还针对一些高频误报类型写了专项规则。
公交站台场景:公交车进站会长时间停在站台区域,如果站台区域恰好被划定为禁停区,模型会疯狂上报公交车违停。处理方式是维护一个“豁免车辆类型”名单,公交车类型且处于站台多边形内时,不触发违停逻辑;但如果公交车停在站台以外的禁停区域,依然正常上报。
装卸货场景:货车在物流园区门口停十几分钟是常态,但园区门口往往画了禁停线。我们的规则是增加一个“装卸货时段”参数,对该区域在指定时间段内降低报警优先级,只记录不实时上报,由后台人工复核。这属于向业务规则妥协,但总比产生大量无效工单要强。
车身颜色判断:违停证据链里往往需要车牌号,检测框本身不提供车牌。我们在业务层接了一个车牌识别模块,只对“已判定为违停”的车辆做车牌抠图识别。这里要注意,如果车身颜色和车牌识别结果明显不符(比如视频里是白色车,车牌却识别成黑色),大概率是目标匹配错误,这张证据会被自动标记为待复核。
5.5 阈值怎么选:NMS、conf、类间抑制
关于阈值,我的建议是按业务成本来定,而不是按mAP来定。mAP是全局指标,但实际业务里“漏报一次违停”和“误报一次合法停车”的成本完全不对等。我们最终把置信度阈值定在0.35,比默认的0.25高一些,NMS的IoU阈值定在0.45,比默认的0.5更严格一些。
阈值高一点,漏检会上升,但误报下降幅度更大。因为后台审核人力有限,一天上千条误报会让整个系统失去可用性。如果说错了至少还有人工复核兜底,漏了就是彻底丢了证据。这个逻辑要在项目初期就和业务方达成一致,否则算法团队会被“召回率指标”和“工单数量指标”两头夹击。
类间抑制我们也做了定制。车辆类别之间容易混淆的典型是“SUV”和“轿车”——在俯视角度下,两者的轮廓差异很小。我们允许这两个类别之间共享NMS,即两个类别的框IoU超过阈值时相互抑制。但货车和客车是绝对不能和轿车互相抑制的,因为一辆大货车后面紧贴着一辆轿车,是常见的真实场景,强行抑制会把轿车漏掉。
6. 落地部署:从实验环境到路口设备的迁移记录
6.1 模型导出:从浮点权重到 INT8 TensorRT
训练完成的PyTorch权重不能直接部署到边缘设备。我们导出的标准流程是:先将模型转成ONNX,再通过TensorRT生成FP16和INT8两个版本。FP16版本作为默认推理引擎,INT8版本用于算力特别吃紧的低端设备。
INT8量化需要校准数据集,校准时最容易犯的错误是用训练集做校准。我们的做法是单独留出一段部署场景的实拍视频,抽500帧做校准集。因为校准数据要代表“部署时真正会看到的画面”,训练集和验证集中的画面分布和真实部署场景还是有差距的。
量化之后的效果对比:INT8精度比FP16掉了大约1.8%,但推理显存占用降低了60%。对违停检测来说,精度掉的这1.8%基本都集中在远端小目标上。所以最终我们做了一个混合策略:默认用INT8模型做全画幅初筛,对于检测到疑似车辆的局部区域,再用FP16模型对该区域裁剪图像做二次确认。这个“初筛加精判”的两级结构,兼顾了速度和精度,实测下来误报率比直接用INT8低不少。
6.2 边缘设备上的性能指标(分辨率/批大小/延迟)
部署环境选择的是嵌入式边缘计算盒子,配置大致是8核CPU、16GB内存、一块算力约15TOPS的GPU模块。我们在这套设备上实测的性能数据如下表。
| 模型版本 | 输入分辨率 | 推理延迟(毫秒) | 显存占用(GB) | 备注 |
|---|---|---|---|---|
| FP16 | 960x960 | 28 | 1.8 | 默认部署配置 |
| FP16 | 1280x1280 | 46 | 3.2 | 后台二次确认用 |
| INT8 | 960x960 | 18 | 0.8 | 低端设备配置 |
单路视频25帧每秒的实时处理,用FP16@960完全够用。一台设备最多能同时处理4路1080P视频。如果超过4路,就需要把部分视频源的帧率降到15帧每秒。15帧每秒对违停检测来说仍然够用,因为车辆违停是一个慢变化事件,根本不需要25帧每秒的采样率。
这里有一个很多项目都会踩的坑:边缘设备的推理延迟往往不是瓶颈,数据预处理才是。视频流解码、缩放、归一化的耗时可能比模型推理还高。我们最终把解码和预处理放到CPU线程池里并行执行,把GPU推理单独一个线程,整体吞吐量提高了将近一倍。
6.3 与电子警察系统的对接:车牌识别联动和证据链
算法检测到违停之后,要生成一条完整的“证据链”,一般包含:违停发生的起始时间、中间过程截图、车辆在禁停区域持续停留的证据、车牌号、现场全景图。检测模型提供的是“车辆的位置和轨迹”,车牌号和全景图由其他模块生成。
我们的对接流程是:当某辆车触发违停判定后,后端服务立即对该车辆的检测框区域做一次高分辨率截图,并发送给车牌识别模块。车牌识别模块返回车牌号、车身颜色和车标品牌。如果车牌识别置信度低于阈值,系统会保留车辆图片,由人工在复核平台上补录车牌。
这里要特别注意隐私合规问题。监控画面涉及车牌和个人信息,系统的数据链路要走内网,不允许经过公网传输。所有视频数据在服务端加密存储,截图和检测记录保留时长严格按业务要求执行。这是项目落地的硬性前提,不能有任何侥幸心理。
6.4 断网和弱网条件下的降级策略(队列、本地缓存、补传)
城市监控点位分布广,网络状况参差不齐。我们需要确保的是:即使网络断断续续,违停证据也不丢。设计思路是“边缘计算盒子本地打点,后台服务器统一收敛”。
边缘盒子里维护一个本地结果队列,检测结果先写入本地SQLite数据库,同时按时间戳和摄像头编号组织成记录。网络正常时,数据实时上传到后台;网络断开时,数据暂存在本地,恢复后按时间顺序补传。每一条记录都有唯一的消息ID,后台通过消息ID去重,防止重传导致重复记录。
这个设计里最需要打磨的是补传顺序。如果断网时间较长,本地积累了数千条记录,全部补传可能导致后台服务瞬时压力过大。我们加了一个限流策略:补传时按每秒50条的速度匀速上传,确保后台不会被打爆。经过真实断网测试,断网两小时后再恢复,补传丢包率控制在0.1%以内。
7. 复盘:踩过最深的坑,以及还可以往哪走
7.1 整个项目里,最深的一个坑是什么
如果只总结一个教训,那就是不要低估夜间场景的难度,更不要以为靠数据增强就能解决夜间识别问题。
我们第一版模型在白天测试集上mAP做到0.76,看起来很不错。部署到一个以夜间违停为主的点位后,真实业务效果惨不忍睹:夜间召回率不到白天的一半,误报率却是白天的三倍。原因不光是光线暗,还包括夜间摄像头的自动增益、红外补光导致画面明显发绿、车灯造成局部过曝、路面灯光倒影被识别成车辆目标。
我们尝试过在训练数据里多加入夜间样本、对模型做更激进的数据增强,效果都不理想。最终真正解决问题的是一条“物理链路”上的调整:先对夜间视频流做一次图像增强预处理,使用局部直方图均衡和自适应去噪,把画面整体提亮并降低噪声,然后再送入检测模型。这一步预处理让夜间召回率提升了约15个百分点。
这个坑给我最大的启示是:监控场景的很多问题不在模型侧,而在成像链路侧。摄像头本身的参数设置——曝光时间、增益上限、红外补光强度——对检测效果的影响,可能比换一个更大的模型更明显。做这类项目时,算法工程师一定要和摄像头厂商建立沟通渠道,甚至直接拿到摄像头的参数调整权限。
7.2 后续还可以做的几个方向
第一个方向是多视角接力。目前系统是单摄像头独立分析,但一辆车从A点位驶入禁停区,再绕到B点位停车,单摄像头很难把这两个事件关联起来。如果接入多个摄像头的检测结果,用车辆外观特征和时空轨迹做跨镜头匹配,就能实现连续跟踪和更准确的违停起止时间判定。
第二个方向是检测和语义分割的融合。目前用的是检测框,但禁停区域的边界判定其实更适合用像素级分割来做——比如黄色路沿线的位置、网状线的覆盖范围。检测框只能提供粗略范围,分割可以提供精确的边界信息。将分割结果和检测框叠加,可以在后端规则层做更精细的判定。
第三个方向是大模型辅助数据标注。我们后期在做增量场景时,标注成本仍然很高。尝试了用视觉语言模型先对图片做描述和排序,再人工确认,能减少约30%的无效标注工作。未来如果数据量继续扩大,这个方向还能进一步推开。
这些方向我们有些做了初步实验,有些还在调研。整体的判断是,违停检测的技术挑战已经从“能不能识别一辆车”转移到了“如何在复杂场景里稳定识别一辆停错位置的车”。模型侧的改进空间在缩小,系统和数据侧的工程优化空间还很大。这篇笔记写到这,如果回头再看一遍,最想强调的还是那句话:检测模型是地基,但不是全部;做这类项目,务必从第一天起就把业务规则、数据分布和部署链路一起想清楚。