ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于E_bicycle2数据集的YOLOv5电动车检测实战:从VOC标注到边缘部署

基于E_bicycle2数据集的YOLOv5电动车检测实战:从VOC标注到边缘部署 简介本资源面向从事机器视觉与目标检测的开发者及学生聚焦非机动车违规停放场景下的电动车识别任务提供YOLOv5可直接训练使用的已标注数据集。压缩包内共1976个文件包含994张jpg图片与982个xml标注文件整体约160.58MB图片与标注一一对应可直接用于模型训练与验证。该资源属于非机动车数据集中的电动车E_bicycle2分类涵盖共享电动车等多种车型标注格式规范省去自行采集与标注的时间成本。目前已有212人学习下载适合需要快速搭建电动车检测实验、验证YOLOv5训练流程或开展违规停放识别研究的读者可作为课程设计、毕业设计及算法调参的实用数据基础。1. 从 994 张共享电动车标注图说起这套 E_bicycle2 数据集到底能干什么如果你正在做园区、校园或街区的非机动车违规停放识别大概率绕不开一个现实问题公开数据集里电动车样本太少类别又杂直接拿 COCO 或 VOC 通用集训出来的模型遇到共享电动车这种密集、遮挡、姿态随意的场景召回率会掉得很难看。这份 E_bicycle2 资源就是冲着这个痛点来的——它是整个非机动车数据集里电动车分类的第二类包含 994 张共享电动车图片和对应的 XML 标注图片命名形如「共享电动车_777.jpg」标注格式是标准的 Pascal VOC XML可以直接喂给 YOLOv5 做训练。它解决的不是「从零搭一个检测框架」的问题而是「我有了 YOLOv5 代码但缺一批干净、已标注、类别聚焦的电动车数据」的问题。适合谁做机器视觉课程设计的学生、要快速验证违规停放检测思路的算法工程师、以及需要给树莓派或 RK3568 这类边缘设备准备微调数据集的开发者。一句话这是让你跳过最耗时的标注环节、直接进入训练和调参阶段的落地素材。2. 数据集结构与 VOC XML 标注格式拆解先看懂再动手2.1 目录组织与文件命名规律拿到压缩包解压后你看到的不会是 YOLO 那套 images/labels 平行目录而是图片和 XML 混在一起或者分两个文件夹存放。常见做法是 JPEGImages 放图、Annotations 放 XML但这份资源因为是从大分类里切出来的子集命名上更直接图片是「共享电动车_编号.jpg」标注文件同名但后缀是 .xml。编号不连续说明是从更大的池子里抽出来的994 张里极个别重复这个在训练前要自己去重。先做一件事统计图片和 XML 是否一一对应。缺标注的图会让训练直接报错早发现早处理。# 统计图片数量和XML数量确认是否配对 ls *.jpg | wc -l ls *.xml | wc -l # 找出有图无标注的文件假设图在 images/标注在 annotations/ comm -23 (ls images/ | sed s/.jpg// | sort) (ls annotations/ | sed s/.xml// | sort)第一段命令看总数是否都是 994 左右第二段用 comm 做差集输出的是只有图没有标注的编号。如果差集不为空要么补标要么把这几张图移出训练集。参数上没什么可调的关键是养成「先配对再训练」的习惯我见过太多人直接开训跑到一半 loss 变 NaN 才发现有张图没标注。2.2 VOC XML 里到底存了什么打开一个 XML结构是这样的folder、filename、size 记录图片宽高和通道object 节点下 name 是类别名bndbox 是 xmin/ymin/xmax/ymax 四个坐标。这份数据的 name 大概率是 E_bicycle 或者中文类别名具体以你解压后看到的为准。坐标是绝对像素值不是归一化值这点和 YOLO 的 txt 格式有本质区别。annotation folderE_bicycle2/folder filename共享电动车_777.jpg/filename size width640/width height480/height depth3/depth /size object nameE_bicycle/name bndbox xmin112/xmin ymin203/ymin xmax398/xmax ymax441/ymax /bndbox /object /annotation这里要盯三个点一是 size 里的宽高必须和实际图片一致不一致说明标注时用的不是原图训练会错位二是 bndbox 的坐标不能越界xmax 不能大于 width三是 name 字段要统一如果有的写「电动车」有的写「E_bicycle」后面做类别映射时会多出莫名其妙的类。我一般会写个脚本扫一遍所有 XML把异常的先揪出来。import os import xml.etree.ElementTree as ET from PIL import Image def check_xml(xml_dir, img_dir): issues [] for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() filename root.find(filename).text img_path os.path.join(img_dir, filename) if not os.path.exists(img_path): issues.append(f图片缺失: {filename}) continue img Image.open(img_path) w, h img.size for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) if xmax w or ymax h or xmin 0 or ymin 0: issues.append(f坐标越界: {filename} {name} ({xmin},{ymin},{xmax},{ymax}) 图片尺寸 {w}x{h}) return issues if __name__ __main__: problems check_xml(annotations, images) for p in problems: print(p) print(f共发现 {len(problems)} 个问题)这段脚本做三件事解析 XML、核对图片是否存在、检查坐标是否越界。参数上 xml_dir 和 img_dir 按你实际目录改。跑完如果输出为空说明数据干净可以进入格式转换如果有输出按提示修完再往下走。别小看这一步坐标越界在训练时不会立刻报错但会让模型学到错误的框回归目标表现为 mAP 死活上不去。3. 从 VOC 到 YOLO 格式转换脚本、划分策略与 data.yaml 配置3.1 写一个能复用的 voc2yolo 转换脚本YOLOv5 要的是每张图对应一个 txt每行是class_id x_center y_center width height全部归一化到 0~1。转换的核心就是把 VOC 的绝对坐标做归一化和中心点换算。下面这个脚本我用了很多次直接抄改路径就能跑。import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射按你的实际类别名改 CLASS_MAP {E_bicycle: 0} def voc_to_yolo(xml_path, img_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() img Image.open(img_path) img_w, img_h img.size lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue cls_id CLASS_MAP[name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化中心点和宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪到0~1防止浮点误差越界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w min(max(w, 0), 1) h min(max(h, 0), 1) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) def batch_convert(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue base os.path.splitext(xml_file)[0] xml_path os.path.join(xml_dir, xml_file) img_path os.path.join(img_dir, base .jpg) if not os.path.exists(img_path): print(f跳过图片不存在: {base}) continue out_txt os.path.join(out_dir, base .txt) voc_to_yolo(xml_path, img_path, out_txt) print(转换完成) if __name__ __main__: batch_convert(annotations, images, labels)逻辑说明CLASS_MAP 是类别名到 id 的映射这份数据只有电动车一类所以 id 是 0如果你后面把自行车、三轮车也加进来这里要扩展成多类。归一化时用图片实际宽高不是 XML 里写的 size因为 XML 的 size 偶尔会写错以 PIL 读出来的为准。最后裁剪到 0~1 是防止浮点误差导致 YOLO 报「non-normalized coordinates」错误。参数上 out_dir 是输出 txt 的目录YOLOv5 默认要求 labels 和 images 平行所以建议目录结构是datasets/e_bicycle/images/和datasets/e_bicycle/labels/。3.2 训练集验证集划分别用随机划分糊弄994 张不算多划分策略直接影响验证指标的可信度。常见做法是 8:1:1 或者 8:2但随机划分有个坑同一辆共享电动车可能被拍了好几张随机分会导致训练集和验证集出现几乎一样的图验证 mAP 虚高。我一般会按文件名里的编号做间隔划分比如每 10 张抽 1 张进验证集这样同一场景的图不会同时出现在两边。import os import random import shutil def split_dataset(img_dir, label_dir, out_root, val_ratio0.1, seed42): random.seed(seed) imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] imgs.sort() # 按排序后间隔抽样降低同场景泄漏 val_imgs imgs[::int(1/val_ratio)] train_imgs [f for f in imgs if f not in val_imgs] for subset, files in [(train, train_imgs), (val, val_imgs)]: img_out os.path.join(out_root, images, subset) lbl_out os.path.join(out_root, labels, subset) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for f in files: shutil.copy(os.path.join(img_dir, f), os.path.join(img_out, f)) lbl os.path.splitext(f)[0] .txt src_lbl os.path.join(label_dir, lbl) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(lbl_out, lbl)) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)}) if __name__ __main__: split_dataset(images, labels, datasets/e_bicycle, val_ratio0.1)这里用imgs[::int(1/val_ratio)]做等间隔抽样val_ratio0.1 时就是每 10 张取 1 张。seed 固定保证可复现。参数上 val_ratio 别低于 0.05994 张的 5% 才 50 张再少验证指标波动会很大。划分完检查一下 train 和 val 的图片数量加起来是不是等于总数少了说明有文件被漏掉。3.3 data.yaml 与模型配置的关键字段YOLOv5 训练入口认的是 data.yaml里面四个字段必须对path、train、val、nc、names。这份数据 nc1names 写 [E_bicycle]。path: ./datasets/e_bicycle train: images/train val: images/val nc: 1 names: [E_bicycle]path 是数据集根目录train 和 val 是相对 path 的路径。常见翻车点是路径写成绝对路径后换机器就找不到建议用相对路径。nc 必须和 names 长度一致不一致 YOLOv5 会在建模型时直接抛错。names 的顺序要和转换脚本里 CLASS_MAP 的 id 对应id 0 对应 names[0]写反了模型学到的类就是错的。4. YOLOv5 训练这套电动车数据的参数怎么设从超参到显存4.1 选模型规格与输入尺寸YOLOv5 有 n/s/m/l/x 五个规格994 张图属于小数据集直接上 l 或 x 会过拟合我一般从 yolov5s 起步。输入尺寸默认 640共享电动车在图中占比较大640 够用如果图片里车很小、密集可以提到 960但显存和训练时间会涨。命令行这样起python train.py \ --data data/e_bicycle.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/train \ --name e_bicycle_s参数说明--weights 用预训练权重小数据集必须用从头训基本学不出东西--batch 16 在 8G 显存上跑 640 尺寸比较稳显存不够就降到 8--epochs 100 对小数据集够收敛配合早停--device 0 指定第一块 GPUCPU 训练会慢到无法接受。如果报显存不足优先降 batch 而不是降 img因为降 img 会改变特征尺度影响小目标检测。4.2 超参数里最该动的三个YOLOv5 的 hyp.scratch-low.yaml 是给大数据集从头训用的我们这种小数据集微调重点看三个lr0、lrf、mosaic。lr0 初始学习率默认 0.01微调时建议降到 0.001 左右太大容易把预训练权重带偏lrf 是最终学习率比例默认 0.01保持即可mosaic 数据增强默认 1.0小数据集上它能显著提升泛化但如果你的图里电动车都是完整大目标mosaic 拼四张图会让目标变小反而有害可以降到 0.5 试试。# data/hyp.finetune.yaml 关键片段 lr0: 0.001 lrf: 0.01 mosaic: 0.5 mixup: 0.0 copy_paste: 0.0改完在 train.py 里加--hyp data/hyp.finetune.yaml。别一次改太多先只动 lr0跑一轮看 loss 曲线再决定要不要动 mosaic。我见过有人把 mixup 开到 0.5结果 994 张图训出来模型连完整车都框不住血泪经验。4.3 训练过程看什么指标启动后终端会打印每轮的 box_loss、obj_loss、cls_loss 和 mAP0.5。box_loss 应该稳定下降如果震荡剧烈说明 lr 太大obj_loss 反映目标置信度如果一直不降检查标注是不是有大量空 txtmAP0.5 在 50 轮后应该能到 0.8 以上这份数据类别单一、场景聚焦正常能到 0.9 左右。如果 mAP 卡在 0.5 不动八成是标注坐标有问题回到 2.2 的检查脚本重新扫一遍。5. 避坑与排查这份数据集最容易翻车的五个地方5.1 现象训练报「No labels found」原因YOLOv5 找不到 labels 目录或者 txt 文件和图片不在平行目录。它默认在 images 同级找 labels且文件名必须一致。解决确认目录结构是datasets/e_bicycle/images/train/xxx.jpg和datasets/e_bicycle/labels/train/xxx.txt用ls labels/train | head看有没有 txt没有就说明转换脚本输出路径写错了。5.2 现象mAP 一直是 0 或者极低原因类别映射错位。XML 里 name 是「E_bicycle」但 data.yaml 里 names 写成了别的或者转换时 CLASS_MAP 没匹配上导致 txt 里没有有效行。解决随便打开一个 txt看第一列是不是 0如果是空文件说明 name 没匹配上再核对 data.yaml 的 names 和 CLASS_MAP 的键是否完全一致大小写敏感。5.3 现象验证集指标远高于实际效果原因训练集和验证集有重复或高度相似的图。994 张里极个别重复随机划分会把重复图分到两边。解决用 3.2 的等间隔划分或者先做一遍图片去重用感知哈希找出相似图只保留一张。验证时拿几张没参与训练的实拍图跑推理看真实效果。5.4 现象推理时框重叠严重、同一辆车出多个框原因NMS 的 iou 阈值不合适或者训练时 obj_loss 没收敛。解决推理时加--conf-thres 0.4 --iou-thres 0.45conf 太低会保留大量低置信框iou 太高会让重叠框都留下。如果调完还这样回训练日志看 obj_loss 是不是还在高位是的话加 epoch 或检查标注里有没有把一辆车标成多个框。5.5 现象换到树莓派或 RK3568 上推理速度极慢原因直接用了 PyTorch 权重在边缘设备跑没做量化和格式转换。解决先导出 ONNX再用工具链转 RKNN 或 NCNN。导出命令是python export.py --weights runs/train/e_bicycle_s/weights/best.pt --include onnx --img 640量化时注意校准集要从训练集里抽别用验证集否则量化精度评估会偏乐观。6. 把 994 张用到极致增量扩充与边缘部署的验证技巧994 张单类数据训一个能用的检测器够了但想让它扛住真实场景的多样性得学会「以小博大」。我的习惯是先把这份 E_bicycle2 当种子集训出第一版模型然后拿它去跑未标注的园区监控截图把置信度在 0.3~0.6 之间的难例挑出来人工复核补进训练集。这样迭代两三轮数据量能翻倍而且补的都是模型真正困惑的样本比盲目堆图有效得多。具体做法是用detect.py批量推理输出带置信度的 txt再写个脚本按置信度区间筛图。python detect.py \ --weights runs/train/e_bicycle_s/weights/best.pt \ --source /path/to/unlabeled_imgs \ --conf-thres 0.3 \ --save-txt \ --save-conf \ --project runs/detect \ --name hard_examples跑完在 runs/detect/hard_examples/labels 里就是每张图的检测结果每行最后一列是置信度。筛出置信度在 0.3 到 0.6 之间的图这些就是模型「犹豫」的样本优先标注。参数上 --save-conf 必须加不然 txt 里没有置信度列没法筛。另一个验证技巧是交叉验证。994 张做 5 折每折用 800 张训、194 张验跑完看 5 折 mAP 的方差。如果方差超过 0.05说明数据分布不均匀某些场景的图太少得针对性补。这个比单次划分的指标可信得多代价就是训练时间乘 5但小数据集一轮快值得。部署到边缘设备前我强制走一遍「量化前后对比」先在 PC 上用 ONNX 跑一遍验证集记录 mAP转 RKNN 或 NCNN 后再跑同一批图对比 mAP 掉多少。掉超过 3 个点就说明量化校准集没选好得重新抽。这个习惯帮我省过好几次「部署完发现精度崩了再回头查」的后悔药。从那以后我每次导出模型都先把验证集推理结果存下来做基线转完格式再跑一遍对比不通过就不上设备。希望帮到你。本文还有配套的精品资源点击获取
返回列表