ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLOv5的非机动车违规停放识别:从数据集到边缘部署

基于YOLOv5的非机动车违规停放识别:从数据集到边缘部署 简介这份资源面向从事机器视觉与目标检测的开发者、学生及竞赛选手聚焦非机动车违规停放场景下的电动车识别需求提供YOLOv5可直接训练使用的已标注数据。压缩包内共1722个文件以864张jpg图片和858个xml标注文件为主图片用于模型训练与验证xml则记录对应目标框与类别信息整体约100MB。该批数据为雅迪电动车E_bicycle7类别属于非机动车数据集中的第七类图片数量约864张标注完整可直接接入YOLOv5训练流程。目前已有2332人学习下载说明其在同类数据中具备一定参考价值。对于需要快速搭建电动车检测基线、验证标注格式或扩充非机动车违规停放数据集的读者可借助该资源省去采集与标注成本直接投入模型训练与调优也可作为数据清洗、格式转换与类别平衡实验的起点。1. 从一堆电动车乱停的图片说起yolov5非机动车违规停放识别到底在做什么小区门口、地铁站出口、写字楼消防通道电动车横七竖八停一片物业和城管靠人巡一圈下来半小时回头一看又停满了。这个场景催生了一个很具体的需求用机器视觉识别非机动车违规停放。标题里的E_bicycle7_images_xmls就是为这件事准备的一份已标注数据集格式是图片配 XML 标注文件标注对象是电动车E_bicycle。配合 yolov5 这套检测框架整条链路的目标很明确——让模型在一张监控画面里框出电动车再结合停车区域规则判断它是否停在违规位置。这件事适合谁做做智慧社区、园区安防、城市治理类项目的工程师手里有摄像头但缺一套能跑起来的检测方案也适合正在学 yolov5 训练自己数据集的学生和转行者因为非机动车目标单一、场景重复度高是练手目标检测的合适切入点。它不解决自动贴罚单这种闭环执法问题解决的是从海量画面里快速筛出违规停放嫌疑的初筛问题。想清楚这个边界后面选型、调参、部署才不会跑偏。2. 数据集与标注格式E_bicycle7_images_xmls 怎么读、怎么转、怎么分2.1 先搞清楚 XML 标注里到底存了什么E_bicycle7_images_xmls这个命名透露了三个信息图片images、XML 标注xmls、编号 7 大概率是批次或类别编号。XML 标注通常是 Pascal VOC 风格一张图对应一个同名 XML里面记录图片尺寸、目标类别名和每个目标的边界框坐标xmin、ymin、xmax、ymax。动手前先抽几个 XML 看一眼确认三件事类别名是否统一是E_bicycle还是ebike还是中文、坐标是不是左上右下、有没有宽高为 0 的脏框。这三件事任何一件出问题后面训练都会以玄学掉点的形式报复你。import xml.etree.ElementTree as ET import os from collections import Counter xml_dir E_bicycle7_images_xmls/annotations cls_counter Counter() bad_boxes 0 for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() cls_counter[name] 1 bbox obj.find(bndbox) xmin int(float(bbox.find(xmin).text)) ymin int(float(bbox.find(ymin).text)) xmax int(float(bbox.find(xmax).text)) ymax int(float(bbox.find(ymax).text)) if xmax xmin or ymax ymin: bad_boxes 1 print(类别分布:, cls_counter) print(异常框数量:, bad_boxes)这段脚本做的是体检cls_counter统计每个类别出现次数如果出现多个类别名说明标注不统一需要先归一化bad_boxes统计坐标非法右下小于左上的框这类框在训练时会直接报错或产生 NaN loss。跑完先别急着转格式把类别名统一成ebike这种英文短名再进入下一步。2.2 VOC 转 YOLO坐标归一化和目录结构yolov5 不吃 XML它要的是每张图一个同名.txt每行类别索引 cx cy w h且坐标是相对图片宽高归一化到 0~1 的。转换时最容易翻车的是归一化用错宽高——必须用该图自己的尺寸不能用固定值。import xml.etree.ElementTree as ET import os classes [ebike] # 与 data.yaml 的 names 顺序严格一致 xml_dir E_bicycle7_images_xmls/annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if not f.endswith(.xml): continue root ET.parse(os.path.join(xml_dir, f)).getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in classes: continue cls_id classes.index(name) b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) # 归一化中心点与宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, f.replace(.xml, .txt)), w) as fp: fp.write(\n.join(lines))classes列表的顺序就是模型输出的类别索引一旦定了就不能改改了要重新转。cx cy w h保留 6 位小数足够精度再高对训练没帮助。转换完做一次抽查随便挑一张图用脚本把归一化坐标还原画框肉眼看框是否贴合电动车这一步能拦住 90% 的坐标错误。2.3 划分训练集与验证集别让同一辆车跨集数据集划分不是随机切就完事。如果同一辆电动车、同一个角度在训练集和验证集都出现验证指标会虚高上线就露馅。常见做法是按拍摄时段或摄像头点位划分比如 7 个点位5 个进训练、2 个进验证。目录结构按 yolov5 约定来dataset/ images/ train/ val/ labels/ train/ val/# 按 8:2 划分图片和标签必须同名同步移动 python - PY import os, shutil, random random.seed(42) img_dir, lbl_dir images_all, labels for sub in [images/train,images/val,labels/train,labels/val]: os.makedirs(sub, exist_okTrue) files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg,.png))] random.shuffle(files) cut int(len(files)*0.8) for i, f in enumerate(files): split train if i cut else val shutil.copy(os.path.join(img_dir, f), fimages/{split}/{f}) shutil.copy(os.path.join(lbl_dir, f.rsplit(.,1)[0].txt), flabels/{split}/{f.rsplit(.,1)[0]}.txt) print(train:, cut, val:, len(files)-cut) PYrandom.seed(42)固定随机种子保证每次划分一致方便复现。图片和标签必须同名同批移动漏一个标签 yolov5 会把它当负样本等于教模型这里有车但没框直接污染训练。3. yolov5 训练自己的数据集从 data.yaml 到第一次收敛3.1 data.yaml 与模型选型s/m/l 怎么挑yolov5 的配置文件data.yaml是训练入口写错路径是最常见的启动失败原因。path: /home/user/dataset train: images/train val: images/val nc: 1 names: [ebike]nc是类别数非机动车违规停放场景通常只检测电动车所以是 1如果还要区分自行车、三轮车就按实际改names顺序必须和转换脚本里的classes完全一致。模型选型上yolov5s 参数量小、推理快适合边缘设备yolov5m 精度略高但显存吃紧yolov5l 在数据量不足时反而容易过拟合。数据集只有几千张时我一般从 yolov5s 起步先跑通再谈精度。# 克隆与安装用官方仓库版本以你拉到的为准 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # 开始训练 python train.py \ --data data/ebike.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0--img 640是输入分辨率监控画面里电动车往往只占画面一小块分辨率太低小目标直接丢可以试 960 但显存翻倍。--batch 16按显存调爆显存就减半。--weights yolov5s.pt用预训练权重做迁移学习比从零训练收敛快得多这是小数据集能出效果的关键。3.2 超参数怎么调学习率、anchor 与数据增强yolov5 的超参数集中在data/hyp.scratch.yaml新手别一上来全改先动三个lr0初始学习率默认 0.01小数据集可降到 0.001 更稳、mosaic马赛克增强默认 1.0能提升小目标但会让早期 loss 抖动数据少时保留、scale缩放增强0.5 左右。anchor 方面yolov5 自带自适应 anchor 计算训练前会自动聚类你的标注框一般不用手动改但如果你的电动车框普遍偏小可以跑一次python utils/autoanchor.py看聚类结果是否合理。训练过程中盯三个指标box_loss应持续下降mAP0.5应上升val/box_loss若在训练后期反弹就是过拟合信号此时该早停或加数据。第一次训练别追求 100 epoch 跑满先跑 30 epoch 看曲线趋势趋势对了再加。3.3 训练完怎么验证混淆矩阵与误检分析训练结束在runs/train/exp/下会生成confusion_matrix.png和results.png。混淆矩阵能看出模型把什么误判成电动车——常见的是把摩托车、共享单车甚至行人误检。误检集中在某一类说明训练集里这类负样本太少需要补背景图不含目标的纯背景图进训练集这是提升精度的低成本手段。# 用验证集跑一次看每类 AP python val.py --data data/ebike.yaml --weights runs/train/exp/weights/best.pt --img 640best.pt是验证集上表现最好的权重last.pt是最后一轮部署一般用best.pt。如果mAP0.5低于 0.6先别怀疑模型回头查标注质量和划分方式八成问题在数据。4. 违规停放判定检测框出来之后规则怎么加4.1 用多边形区域圈定禁停区检测模型只告诉你哪里有电动车是否违规要靠业务规则。最直接的做法是在画面里画禁停多边形判断检测框中心点是否落在多边形内。用 OpenCV 的pointPolygonTest就能做。import cv2 import numpy as np # 禁停区多边形顶点按实际画面标定 forbidden_zone np.array([[100, 300], [500, 300], [500, 600], [100, 600]], np.int32) def is_violation(box_center, zone): # 返回 1 在内 / 0 在边界 / -1 在外 return cv2.pointPolygonTest(zone, box_center, False) 0 # 假设检测结果 center(320, 450) print(is_violation((320, 450), forbidden_zone)) # True 即违规forbidden_zone的顶点坐标要在实际摄像头画面分辨率下标定换摄像头就得重标这是这套方案最重的维护成本。判断用框中心点还是框底边中点取决于视角俯拍用中心点斜拍用底边中点更贴近车辆实际落地点。4.2 加时间维度过滤瞬时误检单帧判定容易误报——一辆车刚好路过禁停区边缘就被判违规。加一个时间窗口连续 N 帧比如 15 帧约 0.5 秒都判定为违规才触发告警。用一个简单的计数器或队列实现。from collections import deque history deque(maxlen15) def confirm_violation(is_violate_now): history.append(1 if is_violate_now else 0) # 窗口内违规帧占比超过 80% 才确认 return sum(history) / len(history) 0.8maxlen15是时间窗口长度按帧率换算成秒阈值 0.8 是经验值调高更保守、误报少但漏报多调低反之。这个逻辑对车辆短暂经过和真正长时间停放的区分很有效是减少无效告警的关键一步。5. 避坑与排查训练和部署里最容易翻车的 5 个点5.1 现象训练 loss 一直是 nan 或直接报错退出原因标注框坐标非法xmax 小于 xmin或归一化后出现 0 宽高。解决回到 2.1 的体检脚本把所有非法框筛出来要么修正要么删除对应标注重新转换。5.2 现象mAP 很高但上线全是误检原因训练集和验证集划分时同一场景泄漏验证指标虚高或训练集缺少负样本纯背景、其他车型。解决按摄像头点位或时段重新划分往训练集补 10%~20% 的纯背景图和其他车型图。5.3 现象小目标电动车检测不到原因输入分辨率太低或 anchor 尺寸与目标不匹配。解决把--img提到 960 或 1280 试同时确认标注框本身没漏标小目标必要时重跑 autoanchor。5.4 现象换一台摄像头后精度骤降原因新摄像头视角、光照、分辨率与训练数据分布不一致。解决采集新摄像头下的少量图片几百张做微调冻结主干只训检测头比从头训练省时。5.5 现象部署到边缘设备后帧率极低原因模型未量化或输入分辨率过高。解决用 yolov5 自带的 export 导出 ONNX 或量化模型边缘设备上优先考虑 INT8 量化分辨率按实际检测距离权衡别盲目上 1280。6. 从训练到边缘部署量化与一次真实提速模型训好只是开始真正落地要过部署这一关。以常见的边缘推理场景为例yolov5 训练出的best.pt先导出 ONNX再针对目标硬件做量化。导出命令python export.py --weights runs/train/exp/weights/best.pt --include onnx --img 640 --dynamic--dynamic让导出的 ONNX 支持动态 batch 和尺寸方便后续适配。导出后务必用onnxruntime跑一遍和 PyTorch 输出对比确认精度没掉——这一步是后悔药等部署到设备上再发现数值对不上排查成本翻几倍。量化到 INT8 时校准集要选有代表性的真实画面别随便拿几张图凑数校准集分布不对量化后小目标精度会明显下降。我一般从训练集里抽 200~500 张覆盖不同光照和角度的图做校准。验证提速效果别只看单帧耗时要看端到端解码 推理 后处理 违规判定。用一段真实视频跑统计平均帧率和 P99 延迟P99 才是卡顿的元凶。下面这个表格是我常用的对比维度维度FP32 ONNXINT8 量化说明单帧推理基准约降 40%~60%与硬件强相关模型体积基准约降 75%便于边缘存储mAP0.5基准掉 1~3 个点校准集质量决定内存占用基准明显下降边缘设备关键最后说个习惯每次改完数据或参数先跑 5 个 epoch 的小实验看 loss 趋势趋势不对立刻停别等 100 epoch 跑完才发现白跑。数据集版本、超参数、导出模型我都用日期加备注命名隔两周回头看还能对上号。这套 yolov5 加非机动车违规停放的方案难点从来不在模型本身而在数据质量和业务规则的贴合度。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进