
简介这份资源面向从事机器视觉与目标检测的开发者、学生及竞赛选手聚焦非机动车违规停放场景下的电动车识别需求提供YOLOv5可直接训练使用的已标注数据。压缩包内共1722个文件以864张jpg图片和858个xml标注文件为主图片与标注一一对应整体约100MB解压后即可投入模型训练与验证。该批数据为雅迪电动车图像属于非机动车数据集十个分类中的第七类样本覆盖多角度、多场景实拍标注格式规范便于直接接入YOLOv5训练流程。目前已有2332人学习下载适合用于违规停放检测、电动车目标识别等课题的模型训练、算法对比与课程实践。借助这批已标注样本读者可省去繁琐的人工标注环节快速搭建训练集与验证集验证检测精度并迭代模型也能作为扩充自有数据集的补充来源。1. 从一堆电动车乱停的图片说起这套方案到底能干什么小区消防通道被电动车堵死、商场门口非机动车横七竖八、校园里共享单车叠成小山——这些场景的治理难点不在发现而在持续发现。人工巡查一天跑三趟剩下二十一个小时照样有人停。我最早接触这类需求是在一个园区项目里物业经理的原话是我不要什么高大上的系统我就要摄像头能自己看出来哪辆车停得不是地方然后给我发个消息。这就是yolov5非机动车违规停放已标注数据集机器视觉识别电动车E_bicycle7_images_xmls这个组合要解决的问题。拆开看YOLOv5 负责在画面里框出电动车、自行车、摩托车这些非机动车目标E_bicycle7_images_xmls是一份已经标注好的数据集图片配 XML 标注文件省掉了从零标数据的苦力活违规停放则是业务逻辑层的事——框出车之后判断它是否落在禁停区域内、是否压线、是否占用通道。适合谁用做智慧社区、园区安防、城市管理类项目的算法工程师和集成商手里有摄像头、有边缘盒子或服务器缺的是一套能跑通的检测加判定流程。提示这份数据集的名字里带E_bicycle7说明它聚焦的是电动车/自行车类别不是机动车。如果你的场景里汽车和电动车混停需要额外补充机动车类别的标注数据否则模型会把汽车漏掉。2. 数据集到手先别急着训练E_bicycle7_images_xmls 的清洗与格式转换2.1 先搞清楚 XML 标注里到底有什么E_bicycle7_images_xmls这个命名方式很直白images 目录放图片xmls 目录放同名的 XML 标注文件。XML 格式大概率是 Pascal VOC 风格每个文件里包含图片尺寸、目标类别名和边界框坐标。拿到手第一件事不是写训练脚本而是统计类别分布和框的尺寸分布。import os import xml.etree.ElementTree as ET from collections import Counter xml_dir E_bicycle7_images_xmls/xmls cls_counter Counter() w_list, h_list [], [] for fname in os.listdir(xml_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, fname)) root tree.getroot() for obj in root.findall(object): cls_name obj.find(name).text.strip() cls_counter[cls_name] 1 bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) w_list.append(w) h_list.append(h) print(类别分布:, cls_counter) print(框宽 min/median/max:, min(w_list), sorted(w_list)[len(w_list)//2], max(w_list)) print(框高 min/median/max:, min(h_list), sorted(h_list)[len(h_list)//2], max(h_list))这段代码做三件事遍历所有 XML、统计每个类别的实例数、收集所有边界框的宽高。逻辑说明——类别分布告诉你有没有长尾类别需要过采样框的尺寸分布告诉你 anchor 需不需要重新聚类。参数说明——xml_dir指向你的标注目录如果 XML 里坐标是浮点数float()转换不会丢精度如果遇到坐标越界xmax 大于图片宽度说明标注质量有问题需要单独挑出来修正。2.2 VOC 转 YOLO 格式转换脚本与四个边界坑YOLOv5 训练需要的是每张图对应一个.txt文件每行格式为类别索引 中心x 中心y 宽 高且坐标要归一化到 0~1。转换本身不复杂但边界情况容易翻车。import os import xml.etree.ElementTree as ET # 类别映射根据上一步统计结果手动指定顺序一旦确定不要改 CLASS_MAP {ebicycle: 0, bicycle: 1, motorcycle: 2} xml_dir E_bicycle7_images_xmls/xmls out_dir labels os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(xml_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, fname)) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: continue # 坑1遇到未映射类别直接跳过不要硬塞 bbox obj.find(bndbox) xmin max(0, float(bbox.find(xmin).text)) # 坑2坐标截断到图片范围内 ymin max(0, float(bbox.find(ymin).text)) xmax min(img_w, float(bbox.find(xmax).text)) ymax min(img_h, float(bbox.find(ymax).text)) if xmax xmin or ymax ymin: continue # 坑3宽高为0的无效框丢弃 cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{CLASS_MAP[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if not lines: continue # 坑4整张图没有有效目标就不生成空txt避免训练时读入空标签 with open(os.path.join(out_dir, fname.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明——逐文件解析 XML提取图片尺寸做归一化按 YOLO 格式写入 txt。参数说明——CLASS_MAP的索引从 0 开始必须和后续data.yaml里的names列表顺序完全一致否则模型学出来的类别是错位的。四个坑分别是未映射类别跳过、坐标截断、无效框丢弃、空标签不生成。我见过有人把xmax大于图片宽度的框直接保留训练时 loss 直接飙到 nan排查了半天才发现是标注越界。2.3 划分训练集验证集别用随机划分常见做法是 8:2 或 9:1 划分但随机划分有个隐患——如果数据集里同一辆车的多角度图片被分到训练和验证两边验证指标会虚高。我一般会先按场景或拍摄批次分组再在组内划分。如果数据集没有场景标签至少保证验证集里包含各种光照和遮挡情况。# 假设图片在 images 目录标签在 labels 目录 mkdir -p datasets/ebicycle/images/train datasets/ebicycle/images/val mkdir -p datasets/ebicycle/labels/train datasets/ebicycle/labels/val # 用 python 做分组划分这里示意按文件名前缀分组 python -c import os, shutil, random random.seed(42) imgs sorted(os.listdir(E_bicycle7_images_xmls/images)) groups {} for img in imgs: prefix img.split(_)[0] # 按文件名前缀分组根据实际命名调整 groups.setdefault(prefix, []).append(img) keys list(groups.keys()) random.shuffle(keys) val_keys set(keys[:max(1, len(keys)//5)]) for k, files in groups.items(): split val if k in val_keys else train for f in files: shutil.copy(fE_bicycle7_images_xmls/images/{f}, fdatasets/ebicycle/images/{split}/{f}) txt f.rsplit(.,1)[0] .txt shutil.copy(flabels/{txt}, fdatasets/ebicycle/labels/{split}/{txt}) print(划分完成) 逻辑说明——按文件名前缀分组后随机选 20% 的组作为验证集避免同组图片泄漏。参数说明——random.seed(42)保证可复现len(keys)//5是 20% 的组数如果组数太少可以改成按图片数量比例划分。这一步做完数据集目录结构就符合 YOLOv5 的要求了。3. YOLOv5 训练自己的数据集从 data.yaml 到超参数调优3.1 写对 data.yaml 是训练成功的一半YOLOv5 的数据配置文件看着简单但路径写错、类别数对不上、names 顺序错位是新手翻车最多的三个点。# datasets/ebicycle/data.yaml path: ./datasets/ebicycle train: images/train val: images/val nc: 3 names: [ebicycle, bicycle, motorcycle]逻辑说明——path是数据集根目录train和val是相对路径。参数说明——nc必须等于names列表长度且索引顺序和转换脚本里的CLASS_MAP完全一致。我一般会在训练前跑一行代码验证assert len(names) nc。另外如果图片和标签不在同级目录YOLOv5 默认会把images替换成labels去找标签所以目录结构要对称。3.2 选模型规格与训练命令yolov5s 还是 yolov5mYOLOv5 有 n/s/m/l/x 五个规格参数量和精度递增。非机动车检测这个任务目标尺寸中等、场景相对固定yolov5s通常是性价比最高的起点。如果漏检严重再升到yolov5m。训练命令如下python train.py \ --data datasets/ebicycle/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train \ --name ebicycle_v1 \ --cache逻辑说明——--weights yolov5s.pt表示从 COCO 预训练权重开始微调比从头训练收敛快得多。参数说明——--img 640是输入分辨率如果图片里电动车很小可以升到 1280但显存占用会翻倍--batch 16根据显存调整8G 显存跑 640 分辨率大概能到 16--epochs 100对几千张图的数据集通常够用看验证集 mAP 不再上升就可以停--cache把图片缓存到内存加快训练速度但数据集大时注意内存占用。--hyp选hyp.scratch-low.yaml是因为自定义数据集通常不需要 COCO 那套强增强。3.3 三个必调超参数学习率、锚框、增强策略YOLOv5 的超参数在hyp.yaml里大部分保持默认就行但有三个值得根据你的数据集调整。第一个是初始学习率lr0。默认 0.01如果数据集很小几百张调到 0.001 更稳否则容易震荡。第二个是锚框anchors。YOLOv5 自带的是 COCO 锚框如果你的电动车目标尺寸和 COCO 里的差异大可以用 k-means 重新聚类import numpy as np # 假设前面已经收集了所有框的宽高归一化后 # wh 是 Nx2 数组每行是 (w, h) from scipy.cluster.vq import kmeans k 9 wh np.array(list(zip(w_list, h_list))) / 640 # 归一化到 0~1 centroids, _ kmeans(wh, k) centroids centroids[np.argsort(centroids[:, 0] * centroids[:, 1])] print(anchors:) for c in centroids: print(f - [{c[0]:.4f},{c[1]:.4f}])逻辑说明——用 k-means 把训练集里所有框的宽高聚成 9 类按面积排序后填入hyp.yaml的anchors字段。参数说明——k9对应 YOLOv5 的三个检测头各三个锚框归一化除以 640 是因为锚框在配置文件里也是相对值。第三个是增强策略mosaic和mixup。mosaic默认开启对小数据集提升明显mixup默认关闭如果过拟合严重可以开到 0.1~0.2但太高会导致欠拟合。3.4 训练过程看什么loss 曲线与 mAP 的解读训练启动后runs/train/ebicycle_v1目录下会生成results.csv和 TensorBoard 日志。重点看三个指标train/box_loss、val/box_loss、metrics/mAP_0.5。正常情况是训练 loss 持续下降验证 loss 先降后升——如果验证 loss 很早就开始升说明过拟合需要加数据或加增强。mAP_0.5在非机动车检测任务上几千张标注图训练 100 epoch通常能到 0.85 以上。如果低于 0.6优先检查标注质量和类别映射而不是调模型。注意YOLOv5 训练日志里的P和R是精确率和召回率。违规停放场景更看重召回率——宁可误报也不能漏报因为漏报意味着通道被堵了系统却没发现。可以在推理时把置信度阈值调低到 0.25 左右来提召回。4. 违规停放判定逻辑检测框出来之后怎么判断违停4.1 用多边形区域定义禁停区检测模型只告诉你哪里有车是否违停需要业务逻辑。最常见也最可靠的做法是在摄像头画面里画禁停多边形然后判断检测框的中心点或底部中点是否落在多边形内。import cv2 import numpy as np # 禁停区域多边形顶点按实际画面标定 FORBIDDEN_ZONE np.array([[100, 400], [500, 400], [500, 700], [100, 700]], dtypenp.int32) def is_violation(bbox, zoneFORBIDDEN_ZONE): x1, y1, x2, y2 bbox # 用底部中点判断更符合车停在地上的直觉 foot_point (int((x1 x2) / 2), int(y2)) result cv2.pointPolygonTest(zone, foot_point, False) return result 0 # 0表示在边界上正数表示在内部逻辑说明——cv2.pointPolygonTest返回正值表示点在多边形内0 表示在边界负值表示在外。参数说明——FORBIDDEN_ZONE的顶点坐标需要根据你的摄像头画面手动标定可以用cv2.selectROI或写个简单的鼠标点击脚本获取。用底部中点而不是框中心是因为电动车倒地或倾斜时框中心可能偏移底部中点更接近轮胎接地点。4.2 加入时间维度停留超过 N 秒才算违停单帧检测到车在禁停区就报警会有一堆误报——比如有人骑车路过禁停区边缘。实际落地时一定要加时间过滤同一位置连续 N 秒检测到目标才触发告警。import time from collections import defaultdict # 记录每个跟踪ID在禁停区的起始时间 zone_start {} ALERT_SECONDS 10 def check_alert(track_id, bbox, current_time): if is_violation(bbox): if track_id not in zone_start: zone_start[track_id] current_time elif current_time - zone_start[track_id] ALERT_SECONDS: return True else: zone_start.pop(track_id, None) # 离开禁停区就重置 return False逻辑说明——配合目标跟踪如 ByteTrack 或 DeepSORT拿到稳定的track_id记录每个 ID 进入禁停区的时间戳超过阈值才告警。参数说明——ALERT_SECONDS根据场景调整消防通道可以设 5 秒普通禁停区设 15~30 秒。没有跟踪器时可以用简单的 IOU 匹配在帧间关联检测框但稳定性差一些。4.3 误报过滤把人推着车和车停着分开非机动车违停检测有个特殊难点人推着电动车经过禁停区和车停在那里不动在单帧画面上几乎一样。除了时间过滤还可以加两个辅助判断一是检测框的宽高比停放的电动车通常侧面或正面朝向摄像头宽高比相对稳定二是结合人体检测如果框内或框附近有人且框在移动大概率是推行而非停放。def filter_by_aspect(bbox, min_ratio0.3, max_ratio3.0): x1, y1, x2, y2 bbox w, h x2 - x1, y2 - y1 ratio w / h if h 0 else 0 return min_ratio ratio max_ratio逻辑说明——过滤掉极端宽高比的检测框这些通常是误检。参数说明——min_ratio和max_ratio根据你的数据集统计结果设定前面第 2.1 节统计的框宽高分布在这里派上用场。如果数据集中电动车的宽高比集中在 0.5~2.0就把范围收窄一些。5. 避坑与排查训练和部署中最容易翻车的五个点5.1 现象训练 loss 正常下降但 mAP 始终为 0原因类别映射错位。data.yaml里的names顺序和转换脚本里的CLASS_MAP不一致模型学到的类别和验证时计算的类别对不上。解决打印data.yaml的names和转换脚本的CLASS_MAP逐项核对索引。我习惯在转换脚本末尾加一行print(CLASS_MAP)训练前截图保存。5.2 现象验证集 mAP 很高但实际部署漏检严重原因训练集和实际场景分布不一致。E_bicycle7_images_xmls的图片可能来自特定角度或光照而你的摄像头是另一个角度。解决从实际摄像头截取 200~500 张图标注后加入训练集做微调。另外检查推理时的预处理是否和训练一致——YOLOv5 默认 letterbox 填充如果你自己写推理脚本用了直接 resize精度会掉。5.3 现象模型把自行车和电动车混为一类原因标注时类别定义模糊或者E_bicycle7数据集本身就把两者标成了同一类。解决先确认数据集的类别定义。如果确实需要区分要么重新标注要么在业务层接受非机动车这个粗类别——很多违停场景其实不需要区分自行车和电动车。5.4 现象边缘设备上推理速度只有几帧原因模型规格太大或输入分辨率太高。解决优先换yolov5n或yolov5s输入从 640 降到 416 或 320。如果还不行考虑量化——YOLOv5 支持导出 ONNX 后用 TensorRT 或 RKNN 量化INT8 量化通常能提速 2~3 倍精度掉 1~2 个点。树莓派 4B 上跑yolov5n加 320 输入大概能到 5~8 FPS够用但不算流畅。5.5 现象禁停区判定在画面边缘误报多原因透视畸变导致画面边缘的底部中点位置不准。解决禁停区多边形不要画到画面最边缘留出 10% 的边距或者用透视变换把画面转成俯视图再做判定但标定成本更高。我一般建议先用简单多边形跑起来误报实在多再上透视变换。6. 进阶技巧用验证集反推标注质量与模型边界训练完模型不是终点验证集的表现能反过来告诉你标注哪里有问题。具体做法跑val.py生成每张图的预测结果然后按mAP从低到高排序挑出最差的 20 张图人工看。python val.py \ --data datasets/ebicycle/data.yaml \ --weights runs/train/ebicycle_v1/weights/best.pt \ --img 640 \ --task val \ --save-json \ --project runs/val \ --name ebicycle_eval跑完后在runs/val/ebicycle_eval下会有预测的可视化图片。重点看三类问题漏检的目标是不是特别小或被遮挡、误检的框是不是标错了类别、定位不准的框是不是标注边界本身就模糊。我自己的习惯是每轮训练后抽 30 分钟看这些图比盯着 loss 曲线有用得多。另一个技巧是用验证集确定置信度阈值。YOLOv5 默认推理阈值是 0.25但最优值因数据集而异。可以写个脚本遍历 0.1 到 0.5 的阈值画 P-R 曲线选 F1 最高的点。import numpy as np # 假设已经收集了所有预测的置信度和是否正确 # confs: 预测置信度列表, correct: 是否正确的布尔列表 thresholds np.arange(0.1, 0.55, 0.05) best_f1, best_thr 0, 0.25 for thr in thresholds: pred_pos np.array(confs) thr tp np.sum(pred_pos np.array(correct)) fp np.sum(pred_pos ~np.array(correct)) fn np.sum(~pred_pos np.array(correct)) precision tp / (tp fp 1e-6) recall tp / (tp fn 1e-6) f1 2 * precision * recall / (precision recall 1e-6) if f1 best_f1: best_f1, best_thr f1, thr print(f最佳阈值: {best_thr:.2f}, F1: {best_f1:.3f})逻辑说明——遍历阈值计算每个阈值下的精确率、召回率和 F1选 F1 最高的。参数说明——np.arange(0.1, 0.55, 0.05)是阈值搜索范围根据你的模型表现调整。违规停放场景如果更看重召回可以把阈值再调低 0.05。最后说个血泪经验别在训练完第一版模型后就急着部署。先用验证集把标注问题修一轮再重新训练通常第二版的 mAP 能涨 5~10 个点。标注质量决定模型上限调参只能逼近这个上限。希望帮到你。本文还有配套的精品资源点击获取