基于YOLO26的城市违停检测系统:从目标跟踪到边缘部署的完整实践
城市违停治理是个典型的“看着简单、落地要命”的场景。很多项目一上来就拉一个目标检测模型把车辆框出来然后对着屏幕画几个禁停区域就算完事。结果真正部署到路口、公交站、消防通道边上才发现误报和漏报同时爆炸。基于 YOLO26 做城市违停车辆检测难点从来不在“YOLO26 怎么训练”而在于如何把检测结果、坐标投影、目标跟踪、时长统计和业务规则串成一条可靠链路。这篇文章把我做过的一个模拟项目X的完整落地方法拆开讲从数据采集、标注、训练调优到违停判定逻辑和边缘端部署属于可以直接抄作业的工程实现路线。适合做智慧城市算法、算法岗实习、相关毕设课题的读者参考。你会看到模型只占整个系统大约三分之一的工作量剩下的坑都在工程细节里。1. 需求拆解与整体方案设计1.1 违停检测的本质先定规则再谈模型先说一个很容易被忽视的事实车辆检测只是违停识别的第一步甚至不是最关键的一步。违停本质上是一种行为判定它要同时满足三个条件车辆落在禁止停车的区域内、车辆在这里持续停留超过规定时间、且当前状态不属于法规豁免场景比如正在执行任务的特种车辆、紧急避险等。所以做这个项目时第一步不是在服务器上装深度学习框架而是把所有“禁停场景”梳理清楚。是禁止停在黄色网格线区域还是公交站前后30米还是消防通道、人行横道边缘不同场景的判定逻辑差别很大。黄色网格线要求的是“任何情况下不得停留”而普通道路侧的禁停标线往往允许短暂上下客只要驾驶员不离车、限时内驶离即可。如果这些规则没定清楚模型再准告警出来的结果也很难被业务方接受。这个认知直接影响后续技术选型模型负责稳定、准确地输出车辆位置和类别规则系统负责判断“违停是否成立”。不要期待用一个模型同时完成“检测车”和“判断违停”两件事后者交给逻辑层去做既灵活又便于后续调整规则还不用因为规则修改而重新训练模型。1.2 技术选型为什么是 YOLO26违停检测系统对检测器的要求很直接实时性要好因为要处理多路视频流小目标能力要强因为远端路口的车辆可能只有几十个像素部署要方便因为最终往往要跑到边缘计算盒子上。YOLO26 在这个场景里是比较顺手的选择。作为 YOLO 系列较新的一次大版本演进它在主干网络里加强了对小尺寸目标的特征提取检测头使用动态标签分配策略能更稳定地处理拥挤场景里的相互遮挡。加上训练策略里已经内置了多尺度、马赛克增强这类技巧不需要自己拼一大堆增强流程。对我来说更重要的是它的导出生态很成熟训练好的权重可以方便地转成开放神经网络交换格式再转成 GPU 推理加速引擎做边缘部署精度和速度都能保住。在具体变体上我倾向于选带 s 的轻量版本也就是 YOLO26s 这种规模。城市摄像头场景下检测目标主要是车辆类别少大模型带来的精度提升有限但推理耗时和显存占用会明显上升。轻量模型配合 TensorRT 半精度推理在一台普通的嵌入式 GPU 设备上能做到多路实时处理这是业务方真正需要的。1.3 系统整体流程从视频流到告警的完整链路整个系统可以拆成下面这条流水线视频接入通过 RTSP 或其他流媒体协议获取摄像头画面按需解码抽帧。车辆检测用 YOLO26 对每一帧执行目标检测输出车辆边界框、置信度。目标跟踪为每个车辆分配稳定的 ID形成运动轨迹。坐标投影把检测框底边中点从图像像素坐标映射到路面平面坐标。区域判定判断车辆位置是否落在禁停多边形内。停留计时对进入禁停区的车辆以跟踪 ID 为维度累计停留时间。告警输出超过时间阈值且满足业务条件时触发告警并附带抓拍信息。这条链路的每一环都有各自的问题。检测漏了后面全白搭跟踪 ID 频繁跳变停留时间统计就会出错投影矩阵标定不准明明是路边的车坐标可能偏移到车道中间去了。所以我在后面几节里会把每个环节的实操方法、参数怎么定、坑在哪里一条条展开讲。2. 数据准备与标注实战2.1 数据采集一个视角的差异就足以让模型崩溃目标检测模型对“视角”非常敏感这在城市摄像头场景里尤其明显。枪机安装在立杆上往下俯拍车辆在画面里是接近俯视的球机拉近变倍后车辆在画面里接近侧视低处摄像头拍到的车可能只有车头或车尾的一部分。如果训练数据全部来自一个点位模型换到另一个点位之后精度暴跌是非常正常的。我在模拟项目X里做数据采集时按“点位、时段、天气”三个维度做了覆盖矩阵。每个点位至少采集早高峰、午间、晚高峰、夜间四个时段的数据天气状况能覆盖晴天、阴天、雨天最好实在没有雨天样本就用亮度扰动和噪声模拟。数据量不需要一味追求大但样本多样性必须够。一个由6万帧图像组成的数据集如果全部来自同一个点位、同一个时间段效果可能还不如从三个点位采集的2万帧。另外要特别留一部分“负面样本”也就是没有违停车辆的普通通行画面。很多检测模型在训练时把所有样本都当成有目标的正样本会导致模型在实际场景里把路牌、树影、路面水渍误检成车辆。负样本能显著降低误检率但标注时不需要标任何框只需在训练配置里作为背景图像引入即可。2.2 标注规范与工具链配置车辆检测的类别定义我的建议是只标一个类别vehicle。不要按轿车、SUV、卡车去细分。原因有两层一是违停判定完全依赖位置和停留时间跟车型关系不大二是细分车型会稀释每个类别的样本量尤其是卡车、大巴这类样本本来就少强行分类反而会增加漏检风险。如果业务确实需要识别“货车违停”那应该单独再训练一个车型分类头而不是让检测模型一次做完。标注规范要提前写清楚否则几个人一起标注框的风格五花八门模型学出来的特征会很混乱。我常用的标注规则如下只有可见区域超过边界框面积20%的目标才需要标注。车辆相互遮挡时按照可见部分画框不要把被遮挡的完整车身脑补出来。标注框紧贴车辆外轮廓不要刻意留边距。太小的目标比如长边小于8像素一般不标因为标注噪声对训练弊大于利。截断目标按可见区域标注但截断超过80%时跳过。标注工具方面用开源社区常见的标注工具即可导出成 YOLO 格式的 txt 标签文件每行内容为类别序号、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。目录结构保持下面这种经典布局后面训练时可以直接复用datasets/ vehicle/ images/ train/ val/ test/ labels/ train/ val/ vehicle.yamlvehicle.yaml里只需要定义路径、类别数和类别名示意内容如下path: datasets/vehicle train: images/train val: images/val nc: 1 names: 0: vehicle2.3 数据增强不止是为了防过拟合数据增强对 YOLO26 这类目标检测模型来说作用有两面一是防止过拟合二是模拟真实场景中的各种变化。城市摄像头场景中最影响模型表现的是光照变化和视角变化所以增强策略要围绕这两点展开。比较有效的一组增强组合是这样的马赛克增强、复制粘贴增强、随机平移缩放、水平翻转、随机亮度对比度、随机高斯噪声、随机运动模糊。马赛克增强把四张图拼接成一张训练图可以让模型在小尺寸目标上看到更多上下文复制粘贴则把一张图中的车辆目标粘贴到另一张图的背景中有效扩充低密度场景。但是有两个坑必须避开。第一不要在验证集和测试集上使用强增强否则评估结果完全失真。第二增强幅度要克制尤其是几何变换不要用力过猛。随机旋转角度如果超过30度车辆外形会严重失真模型学到的是奇怪的形状而不是车辆本身的特征。亮度扰动也要在合理范围内过度的变暗会让模型在白天正常画面上也表现不稳定。2.4 数据划分按片段切分别按帧打乱很多初学者在用目标检测训练集时喜欢把图片全部丢进一个文件夹然后随机划分这是很危险的。城市摄像头视频流相邻帧高度相似如果同一段视频的帧一部分在训练集、一部分在验证集验证分数会虚高但模型一上场就露馅。更合理的做法是按时间段或视频片段来划分数据。我习惯的做法是把每个点位一小时内的所有帧视为一个片段划分时以“片段”为单位。验证集至少覆盖两个完全没有出现在训练集里的点位或时间段这样才能真实反映模型跨场景泛化的能力。数据量比例上训练、验证、测试按 8:1:1 左右即可测试集只在最后做整体评估时使用训练过程中不要碰。还有一个经验如果正样本违停画面占比过低可以先筛选出违停事件片段在这些片段里多抽一些帧进入训练集把正负样本比例压到 1:3 到 1:5 之间。完全均衡反而没必要因为实际场景中绝大多数时间里都没有违停模型必须见过足够多的“没有违停”的画面。3. YOLO26 模型训练与调优3.1 环境搭建与数据目录组织训练 YOLO26 并不需要特别复杂的工程环境核心就是深度学习框架、GPU 计算库、以及对应的检测代码库。建议使用容器化环境把 Python 版本、CUDA 版本和依赖一次性锁住避免后面部署阶段因为环境不一致而反复返工。假设你已经准备好了上一节的数据目录在训练脚本里最重要的就是确保数据配置中的路径可以直接被训练入口读取。数据目录建议放在独立数据盘上不要和代码混在一起因为后续会反复做模型版本迭代数据路径固定下来会省很多事。训练入口可以是一个命令行类似下面这种示意yolo train \ modelyolo26s.pt \ datadatasets/vehicle/vehicle.yaml \ epochs150 \ imgsz1280 \ batch16 \ lr00.01 \ valTrue注意以上是示意命令具体实现取决于你使用的 YOLO26 代码仓库。重点在于把输入尺寸传到 1280这对后面要讲的小目标问题非常关键。3.2 超参选择与训练策略超参里面第一个要死磕的是输入分辨率。城市摄像头拍出来的画面分辨率往往很高但远端的目标很小。如果直接缩放到 640 分辨率训练一辆在画面远端只有 40 像素宽的轿车缩放后可能只剩 15 像素模型很难学到有效的特征。把输入分辨率设成 1280远端的车辆在特征图上能占据更多网格漏检率会明显下降。负面影响是训练变慢、显存占用变大但这是值得的。第二个要关注的是训练轮数。对于这种单类别检测任务使用预训练权重的情况下120到150轮基本足够。如果从头训练建议把轮数拉到300轮以上并且配合学习率预热和余弦退火。我实际用的配置是前5轮学习率从 0.0001 线性升到 0.01之后按余弦曲线下降到 0.0001。优化器选择上SGD 在目标检测里仍然很稳如果追求更快收敛也可以用 AdamW但要适当调低学习率比如从 0.001 开始。还有一个容易忽略的参数是 batch size。显存足够时尽量用大 batch比如 16 或 32。batch 太小会导致 BatchNorm 统计不稳定尤其是用 1280 分辨率训练时单张图占显存很大很多人会把 batch 压到 4结果训练过程断断续续精度也不理想。我的做法是先用 640 分辨率做一轮快速预训练让模型先适应数据集风格再用 1280 分辨率继续微调这样既省时间又能保住大分辨率带来的小目标收益。3.3 针对小目标和夜间场景的专项调优如果测试后发现远端小目标漏检仍然明显优先考虑“切片推理”这个手段。它的思路很简单把大图切成若干有重叠的小图块分别送进模型检测最后再把检测框坐标拼回原图。由于每个小图块里的目标相对尺寸变大小目标检测难度会显著下降。代价是整体推理耗时上升所以部署阶段通常只对远端区域做切片近端区域走全图检测实测下来漏检率可以下降几十个百分点。夜间场景是另一个拦路虎。最好的办法是在数据层面解决采集真实的夜间视频或者在训练时应用更强的亮度降低、高斯噪声和对比度扰动。如果现场夜间画面确实很差可以在检测之前加一个图像预处理步骤比如自适应直方图均衡化把暗部细节提亮。但这个预处理在训练阶段也要同步使用否则训练和推理的输入分布不一致效果会打折扣。如果做双路模型也就是白天一个权重、夜间一个权重效果一般会比单路模型好但工程复杂度直接翻倍。城市摄像头从下午到傍晚是渐变过程分两路会带来频繁切换的问题。我更推荐单模型覆盖全天场景把亮度增强范围调大让模型自己适应这一连续变化。3.4 测评估量与模型导出常规指标可以看 mAP0.5 和 mAP0.5:0.95但对违停场景来说这两项只能作为参考。真正要关心的是“禁停区域召回率”也就是只要车辆停在禁停区模型能否稳定检出。这个指标要结合后面的业务判定逻辑一起算。我通常的做法是先跑一段标注好的真实点位视频统计模型输出的每辆车轨迹看哪些目标在禁停区内停留超过阈值却没有被连续检出逐条分析是检测漏了还是跟踪丢了。模型训练完成后导出部署格式是必经一步。通用做法是先导出成开放神经网络交换格式再转换成 GPU 推理加速引擎精度用半精度保存如果设备性能还吃紧再考虑整型量化。整型量化需要准备一小批校准图像校准图像的分布最好和实际场景一致否则量化后精度下降会很严重。每次导出后都要在真实视频上重新跑一遍评估不要相信训练阶段指标。4. 违停行为判定与业务逻辑实现4.1 坐标投影把检测框从像素空间放回路面直接用图像上的像素坐标判断车辆是否越线、是否进入禁停区是不靠谱的。摄像头斜着看路面透视效应让距离出现非线性畸变近处道路上一米可能占100个像素远处同一米只有十几个像素。所以必须做透视投影把检测框位置还原到路面坐标系。我们用的是单应性变换。具体方法是在每个摄像头画面里选取一块已知尺寸的矩形路面区域标出四个角点在图像中的像素坐标同时记录这四个角点对应的真实路面坐标可以简单地设路面左下角为原点单位是米。四个对应点足够求解出一个 3×3 的单应性矩阵代码示意如下import cv2 import numpy as np # 图像上的四个点顺序不能乱 src_points np.array([ [x1, y1], # 矩形左上角 [x2, y2], # 矩形右上角 [x3, y3], # 矩形右下角 [x4, y4], # 矩形左下角 ], dtypenp.float32) # 对应的真实路面坐标单位米 dst_points np.array([ [0.0, 0.0], [12.0, 0.0], [12.0, 8.0], [0.0, 8.0], ], dtypenp.float32) H, _ cv2.findHomography(src_points, dst_points, cv2.RANSAC) def image_to_road(bbox_center_bottom): point np.array([bbox_center_bottom[0], bbox_center_bottom[1], 1.0]) road H.dot(point) return road[0] / road[2], road[1] / road[2]车辆接地位置取检测框底边中心点比取框中心更符合路面投影逻辑。因为检测框中心可能对应车辆挡风玻璃的位置投影到地面时偏移很大。底边中心点接近轮胎接触地面的位置作为“车辆坐标”更准确。4.2 禁停区域判断多边形包含算法禁停区域在路面上画出来通常是任意多边形比如消防通道、公交站区域、黄色网格线范围。可以提前把这些多边形配置成 JSON 文件每个点位一份不用为了调整区域重新训练模型。判断一个车辆坐标是否落在禁停区域内使用经典的射线法ray casting即可从目标点向右发一条水平射线统计它与多边形边界的交点个数如果是奇数就在内部偶数就在外部。代码实现很简洁def point_in_polygon(x, y, polygon): inside False j len(polygon) - 1 for i in range(len(polygon)): xi, yi polygon[i] xj, yj polygon[j] if (yi y) ! (yj y) and (x (xj - xi) * (y - yi) / (yj - yi) xi): inside not inside j i return inside配置禁停区域时建议把多边形边界往外做 0.2 到 0.5 米的缓冲区。因为车辆检测框本身有误差投影后也会有少量抖动直接卡在边界上会导致车辆刚压线就频繁触发边界内外的状态不断跳变。缓冲区的存在可以过滤掉大部分抖动让后续计时逻辑更稳定。4.3 停留计时目标跟踪与状态机违停判定的核心在于“停留时间”这必须依赖多目标跟踪。每辆车在画面中出现时跟踪器会给它分配一个唯一 ID之后每一帧的检测框都关联到这个 ID 上。这样就能统计该车从进入禁停区到离开禁停区到底过了多少帧。跟踪方案不需要上重型的重新识别模型。城市场景的摄像头位置固定车辆不会长时间完全离开画面帧间匹配用运动特征加外观特征就够用。我使用的是偏轻量的跟踪方法维护每个轨迹的检测框位置预测和特征向量每一帧做级联匹配。关键参数有三个最大丢失帧数、匹配的余弦距离阈值、以及最小轨迹长度。实测下来最大丢失帧数设置在 30 到 50 帧比较合适也就是说车辆短暂被遮挡半秒左右还能保住 ID超过这个时间就重新分配新 ID避免把两辆车混淆成一辆。计时逻辑我用了一个简单状态机车辆坐标在禁停区外状态为“正常通行”。车辆坐标进入禁停区记下首次进入时间状态变为“疑似违停”。若车辆一直保持在禁停区内且坐标连续有效累计时间超过业务阈值比如 90 秒状态变为“违停确认”。车辆离开禁停区或出现了重新分配的新 ID计时清零。这里还要考虑一种情况车辆在禁停区边缘反复横跳也就是压线又退出、再压线。处理办法是允许短暂的“容忍时间”比如 5 秒内连续进出都按停留在处理否则会切得太碎。4.4 告警输出与误报抑制告警是整个系统的出口不能想到什么报什么否则业务方很快会关闭你的告警源。我在这个环节做了三个层面的抑制。第一层是置信度抑制。检测置信度低于 0.4 的框不参与跟踪更不参与告警。这个值不能设太高城市下着小雨、离摄像头远时模型置信度普遍不高设太高会漏报。第二层是连续帧确认。同一个跟踪 ID 只有在连续多帧都被判定为“在禁停区内”时才进入计时状态防止单帧误检造成假告警。第三层是告警去重。同一个跟踪 ID 一旦触发违停告警在 5 分钟内不再重复告警。如果车辆一直停着不走业务方只需要收到第一次告警后续状态由人工复核或定时巡更处理。告警信息通过消息中间件推给后端平台同时携带一张静帧截图和一小段视频片段。截图应该在检测框置信度最高、目标最完整的某一帧保存而不是告警触发的那一瞬间。这个细节经常被忽略但人工复核时一张清晰的抓拍图可以节省大量时间。5. 常见问题与排查技巧实录5.1 车辆太小老是漏检怎么办这是城市摄像头场景里最普遍的问题尤其当画面里同时出现近处大卡车和远处小轿车时算法往往只看见离得近的大车。排查步骤有一个优先级顺序。先确认训练分辨率如果训练输入只有 640换成 1280 重新微调通常就能解决一部分。再检查标注数据里小目标的占比如果训练集中长边小于 32 像素的目标数量很少模型自然学不好小目标。可以在小目标出现的片段里多抽帧并适当复制增强。最后再上切片推理将画面中的远端区域切块放大分别检测后合并结果。推理耗时允许的情况下这是效果最直接的手段。不要在第一步就盲目换更大的模型YOLO26x 虽然精度更高但对小目标的本质提升有限反而让边缘设备性能吃紧。5.2 夜间和雨天误报频发夜间误报往往不是模型变差了而是输入分布和训练分布不一致。摄像头夜间画面普遍偏暗、对比度低、有坏点噪点模型会把一些亮斑、车灯残影当成车辆。解决办法不是单纯加高检测阈值因为那会同时送走真实目标。我常用的手段是在训练集里加入大量“夜间负样本”也就是没有车辆但车灯倒影、路面积水反光很明显的夜间图片。让模型见过这些干扰项后误检会下来很多。如果线上画面实在弱光可以在采集端开红外补光同时把检测前图像做一次自适应直方图均衡化让暗部细节更接近训练样本的分布。注意这个预处理在训练样本上也应同步进行或者作为数据增强的固定分支写进预处理管线。5.3 压线、等红灯、临时停靠如何过滤违停系统上线后最常见的三类投诉性误报车辆压着黄色网格线等红绿灯、出租车在路边临时停靠上下客、私家车在禁停区边缘短暂压线。等红灯的问题要从区域设定上解决。黄色网格线区域往往和路口重叠车道内等红灯的车辆可能会停在网格线内。一个实用的业务规则是在网格线多边形基础上挖掉“车道等待区”或者直接在告警逻辑里对“路口方向”做豁免。如果摄像头能看到停止线和车牌朝向可以判断车辆是否处于排队状态。最笨也有效的办法是提高停留阈值对上游路口区域单独配置更长的容忍时间。临时停靠上下客最难处理因为它确实在禁停区域里且停留时间可能不短。我建议接入一个车内状态分类器用第二个轻量分类模型判断车辆前挡风玻璃区域是否有人脸或者车辆尾灯和车门是否有明显变化。如果判断驾驶员在车内且车辆处于怠速状态可以视为临时停靠不触发告警。这个分类器需要单独采集一些车内视角的样本对摄像头安装角度有要求不是所有点位都能做。5.4 边缘设备卡顿与掉帧城市项目会同时接入几十路摄像头边缘设备性能再强也顶不住全程全分辨率推理。我的优化思路按成本从低到高排序。用 GPU 推理加速引擎跑半精度模型这一步通常能让推理速度提升一到两倍。对没有车辆运动的画面做跳帧先用轻量背景差分或帧差判断画面是否变化没有变化就每隔 5 帧检测一次有变化则恢复到每帧检测。对多路摄像头做分时调度错开每路的推理时间而不是所有视频流同时打满 GPU。最后才是模型剪枝或整型量化。整型量化会影响到小目标精度尤其是在暗光场景里必须用真实点位视频重新评估后再上线。我遇到过最让人头疼的问题不是算力不足而是跳帧参数设置太激进导致一辆车进入禁停区后跳帧时段内它的跟踪断掉了停留时间统计直接被清零。所以跳帧逻辑必须和跟踪模块联动跳帧期间要依据上一帧的车辆位置做匀速外推预测避免 ID 丢失。5.5 踩坑心得汇总最后整理几条用真实代价换来的经验禁停区域标定坐标时一定要选在路面上做不要直接在地图截图里画多边形。地图的投影方式和摄像头单应性矩阵对不上偏一点就是一两米告警位置会完全不一样。跟踪 ID 一旦切换所有已有的停留时间都会归零。排查漏报时不要只盯着检测模型先看跟踪是否频繁断链。告警截图必须在触发确认帧里保存而不要在车辆进入禁停区的第一帧保存那时候目标可能被遮挡图片没法用。每次调整规则或模型后都保留一份当时的完整视频片段用来回放和复盘。很多误报问题在静态图上说不清楚一放视频立刻明白。项目里最花时间的不是模型调参而是反复和业务方确认“什么样的违停需要告警”。规则文档建议从一开始就写清楚每改一版都让业务方签字确认。模型做的是识别车规则系统做的是识别行为这两件事的耦合程度决定了这个项目能不能真正跑起来。如果让我重新做一次这个项目我会在动手标注之前先把所有摄像头点位走一遍看清楚每一个位置能看到什么、看不到什么再把禁停规则按点位列成表。YOLO26 不会替你判断什么是违停它只是帮你稳定地找到每一辆车的位置。真正让系统变得可用的是后面那一大段看起来不性感的坐标投影、跟踪、计时和告警去重工程。这些细节不处理好算法再先进也只是一堆准确率很高的误报。