ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

bicycle_VOCtrainval2012数据集YOLOv5训练自行车检测模型全流程

bicycle_VOCtrainval2012数据集YOLOv5训练自行车检测模型全流程 简介这份资源是面向计算机视觉初学者与目标检测实践者的YOLOv5自行车检测数据集源自PASCAL VOC 2012训练验证集专门抽取自行车类别可用于智能交通、安全监控等场景下的模型训练与算法验证。压缩包共1810个文件包含603张jpg原始图像、603个xml详细标注文件与604个txt简洁标注文件xml记录边界框、类别及目标尺寸等信息txt则便于直接转换为YOLOv5训练格式整体约77.49MB目录结构清晰。目前已有846人学习下载适合需要快速上手目标检测项目、验证YOLOv5训练流程的读者。借助该数据集可完成从标签解析、格式转换到模型训练与评估的完整实践理解SPP-Block、PANet等结构在自行车检测中的表现并积累数据增强与迁移学习的调参经验。1. 拿到 bicycle_VOCtrainval2012 这个压缩包先别急着解压你从某个数据集聚合站下载了一个叫bicycle_VOCtrainval2012-自行车数据集.rar的包双击之后发现要密码或者解压出来一堆 XML 和 JPEG 混在一起完全不知道从哪下手。这不是你一个人的问题——我见过太多人卡在「rar 解压」这一步或者解压完直接把整个文件夹丢给 YOLOv5结果训练脚本报了一屏的路径错误。这个数据集的核心价值在于它把 PASCAL VOC 格式的自行车标注单独抽了出来让你不用从 VOC2012 全量数据里自己筛。但它的目录结构、标注质量、以及和 YOLOv5 默认数据格式之间的差距才是真正决定你能不能跑通的关键。这篇文章面向的是手里已经拿到这个包、想用它训练一个能用的自行车检测模型的工程师不管你是第一次碰 YOLOv5还是已经跑过 COCO 预训练权重想换自己的数据。2. 拆开 rar 之后bicycle_VOCtrainval2012 的目录结构与标注格式2.1 解压后你实际会看到什么假设你已经用常见的 rar 解压工具把包展开了大概率会得到一个类似bicycle_VOCtrainval2012/的根目录。里面通常包含三个子文件夹Annotations/、JPEGImages/、ImageSets/。Annotations/里是每张图对应的 XML 文件文件名和图片名一一对应JPEGImages/里是原始 jpg 图片ImageSets/Main/下一般会有train.txt、val.txt、trainval.txt这几个文本文件每行是一个不含扩展名的图片 ID。这里有一个很容易翻车的点有些二次打包的 rar 会把 VOC2012 全量数据里的自行车类单独抽出来但 XML 里可能还保留着其他类别的标注框。你打开一个 XML 看一眼object节点下的name如果里面除了bicycle还有person、car之类的标签那这个数据集就不是纯自行车数据集而是「包含自行车的 VOC 子集」。这两种情况对训练策略的影响完全不同——前者可以直接做单类检测后者要么忽略其他类要么改成多类训练。2.2 VOC XML 里哪些字段真正影响 YOLOv5 训练一个典型的自行车标注 XML 长这样annotation folderbicycle_VOCtrainval2012/folder filename2008_000123.jpg/filename size width500/width height375/height depth3/depth /size object namebicycle/name poseLeft/pose truncated1/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax340/xmax ymax300/ymax /bndbox /object /annotation对 YOLOv5 来说size里的宽高用来做归一化bndbox的四个坐标是绝对像素值需要转成 YOLO 格式的class x_center y_center width height全部归一化到 0~1。truncated和difficult这两个字段经常被忽略但在自行车检测里它们很要命truncated1表示目标被截断difficult1表示难以识别。VOC 官方评测时会跳过 difficult 样本但 YOLOv5 默认不区分如果你直接把 difficult 样本当正样本训模型在遮挡场景下的误检率会明显上升。我的做法是在转换脚本里把difficult1的框直接丢弃truncated1的框保留但心里有数——后面调 NMS 阈值时要考虑这部分带来的漏检。2.3 图片尺寸分布与 YOLOv5 输入尺寸的匹配自行车数据集里的图片尺寸非常杂VOC2012 原图长边从 300 多到 1000 多像素都有。YOLOv5 默认img-size是 640训练时会把短边缩放到 640、长边按比例填充。如果你直接用默认值小目标自行车远处、密集停放在缩放后会变得非常小特征图上的响应很弱。我一般会先统计一下 XML 里size的分布import os import xml.etree.ElementTree as ET from collections import Counter ann_dir bicycle_VOCtrainval2012/Annotations sizes [] for f in os.listdir(ann_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, f)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) sizes.append((w, h)) # 按长边分桶 long_edges [max(w, h) for w, h in sizes] buckets Counter([(le // 200) * 200 for le in long_edges]) print(sorted(buckets.items()))如果长边集中在 500 以下说明图片本身分辨率不高强行上 640 意义不大反而增加计算量如果长边普遍在 800 以上可以考虑img-size 768或896但要注意显存。这个统计结果直接决定你后面data.yaml里要不要改img-size以及训练时 batch size 怎么设。3. 把 VOC 标注转成 YOLOv5 能吃的格式脚本、参数与验证3.1 转换脚本的核心逻辑与完整代码YOLOv5 官方仓库里其实带了一个voc2yolo.py之类的脚本但版本迭代快很多老教程里的路径已经对不上了。我一般自己写一个逻辑清晰、可控性强。下面这个脚本处理的就是从bicycle_VOCtrainval2012到 YOLO 格式的完整转换import os import xml.etree.ElementTree as ET import shutil # 输入路径 voc_root bicycle_VOCtrainval2012 ann_dir os.path.join(voc_root, Annotations) img_dir os.path.join(voc_root, JPEGImages) split_dir os.path.join(voc_root, ImageSets, Main) # 输出路径 out_root bicycle_yolo for sub in [images/train, images/val, labels/train, labels/val]: os.makedirs(os.path.join(out_root, sub), exist_okTrue) # 类别映射只保留 bicycle classes [bicycle] class_to_id {c: i for i, c in enumerate(classes)} def convert_bbox(size, box): VOC 绝对坐标 - YOLO 归一化中心点宽高 dw 1.0 / size[0] dh 1.0 / size[1] x (box[0] box[1]) / 2.0 y (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return x * dw, y * dh, w * dw, h * dh def process_split(split_name): 处理 train 或 val 划分 split_file os.path.join(split_dir, f{split_name}.txt) if not os.path.exists(split_file): print(f[WARN] {split_file} 不存在跳过) return with open(split_file) as f: ids [line.strip() for line in f if line.strip()] for img_id in ids: xml_path os.path.join(ann_dir, f{img_id}.xml) img_path os.path.join(img_dir, f{img_id}.jpg) if not os.path.exists(xml_path) or not os.path.exists(img_path): continue tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_to_id: continue difficult obj.find(difficult) # 丢弃 difficult 样本 if difficult is not None and int(difficult.text) 1: continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界裁剪防止越界 xmin max(0, min(xmin, w - 1)) ymin max(0, min(ymin, h - 1)) xmax max(0, min(xmax, w - 1)) ymax max(0, min(ymax, h - 1)) if xmax xmin or ymax ymin: continue bb convert_bbox((w, h), (xmin, xmax, ymin, ymax)) lines.append(f{class_to_id[name]} .join([f{v:.6f} for v in bb])) if not lines: continue # 复制图片 dst_img os.path.join(out_root, images, split_name, f{img_id}.jpg) shutil.copy(img_path, dst_img) # 写 label dst_lbl os.path.join(out_root, labels, split_name, f{img_id}.txt) with open(dst_lbl, w) as f: f.write(\n.join(lines)) process_split(train) process_split(val) print(转换完成)这段代码有几个关键处理第一difficult1的框直接跳过这是自行车检测里减少误检的有效手段第二坐标做了边界裁剪VOC 里有些框会超出图片边界不裁剪的话归一化后会出现负值或大于 1 的值YOLOv5 训练时虽然不会报错但会引入噪声第三if not lines: continue保证没有有效标注的图片不会被复制过去避免空标签文件。3.2 data.yaml 的写法与路径陷阱转换完之后你需要一个data.yaml告诉 YOLOv5 去哪里找数据path: ./bicycle_yolo train: images/train val: images/val nc: 1 names: [bicycle]这里最常见的翻车点是path的写法。YOLOv5 不同版本对相对路径的解析基准不一样有的相对于data.yaml所在目录有的相对于你执行训练命令的当前目录。我一般直接用绝对路径或者把data.yaml放在和bicycle_yolo同级的位置然后用path: ./bicycle_yolo。如果你在train.py里看到No labels found或者Dataset not found九成是这里路径没对上。另一个坑是names的顺序必须和转换脚本里的classes列表一致否则类别 ID 会错位——虽然单类检测里这个问题不明显但如果你后面想加类别顺序错了就是灾难。3.3 转换后的完整性校验转换完不要直接开训先跑一个校验脚本确认图片和标签一一对应、标签格式合法import os img_dir bicycle_yolo/images/train lbl_dir bicycle_yolo/labels/train imgs set(os.path.splitext(f)[0] for f in os.listdir(img_dir)) lbls set(os.path.splitext(f)[0] for f in os.listdir(lbl_dir)) print(f图片数: {len(imgs)}, 标签数: {len(lbls)}) print(f有图无标签: {imgs - lbls}) print(f有标签无图: {lbls - imgs}) # 检查标签内容 bad [] for f in os.listdir(lbl_dir): with open(os.path.join(lbl_dir, f)) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: bad.append((f, 字段数不对)) break vals [float(v) for v in parts[1:]] if any(v 0 or v 1 for v in vals): bad.append((f, 归一化值越界)) break print(f异常标签: {bad[:10]})这个校验能帮你提前发现大部分数据问题。我遇到过最隐蔽的一个坑是某些 XML 里size的宽高和实际图片尺寸不一致导致归一化后框的位置整体偏移。校验脚本里加一步用 PIL 读实际图片尺寸和 XML 对比能抓出这类问题。4. 用 YOLOv5 训练自行车检测模型参数、显存与收敛判断4.1 从预训练权重起步还是从头训自行车检测的数据量通常不大——VOC2012 里自行车类大概几千张图trainval 划分后训练集可能就两三千张。这个量级从头训基本没戏必须用预训练权重。YOLOv5 官方提供了yolov5s.pt、yolov5m.pt等我一般从yolov5s.pt开始因为自行车属于中等尺度目标s 版本的容量够用训练快部署也轻。如果你后面要上 RK3568 或者树莓派s 版本量化后的精度损失也更可控。训练命令的核心参数python train.py \ --data bicycle_yolo/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train \ --name bicycle_s--hyp这个参数很多人直接忽略用默认的hyp.scratch-low.yaml。但对于小数据集我建议把lr0从 0.01 降到 0.001 左右warmup_epochs设 3~5让模型在预训练权重的基础上慢慢适应新数据。自行车检测里还有一个特殊点自行车经常成排出现密集场景下 NMS 的iou_thres默认 0.45 可能偏低导致相邻自行车被误抑制。可以在验证时把--conf-thres 0.25 --iou-thres 0.5作为起点根据 PR 曲线再调。4.2 显存不够时的降级策略如果你只有一张 8G 显存的卡--batch 16 --img 640大概率会 OOM。降级顺序应该是先降 batch 到 8再考虑--img 512最后才动模型尺寸。不要一上来就换yolov5nn 版本在自行车这种有遮挡、有截断的场景下召回率掉得厉害。另一个省显存的技巧是开--cache ram或--cache disk把解码后的图片缓存起来减少 dataloader 的重复 IO。但注意--cache ram在图片多的时候会吃很多内存两三千张 640 的图大概占 2~3G 内存自己权衡。4.3 看什么指标判断该停还是该调YOLOv5 训练日志里重点看三个box_loss、obj_loss、mAP0.5。自行车检测里obj_loss的下降趋势比box_loss更能反映模型有没有学到东西。如果obj_loss在 20 个 epoch 后还在震荡不降大概率是学习率太高或者数据标注有问题。mAP0.5到 0.6 以上基本可用到 0.75 以上算不错。但要注意如果验证集里有很多截断或小目标自行车mAP0.5:0.95会明显低于mAP0.5这是正常的不要为了刷高mAP0.5:0.95去删验证集里的难样本。5. 自行车检测的避坑与排查从标注到部署的 5 个血泪教训5.1 现象训练 loss 正常下降但推理时满屏都是自行车框原因标注里存在大量「背景误标」——把路边的广告牌、停着的摩托车甚至行人误标成了 bicycle。VOC 数据集本身就有一定噪声二次打包的自行车子集更严重。解决训练前用--save-json导出验证集预测人工抽查 top-50 高置信度误检把对应图片的标签修正或直接剔除。我一般会花半天做这件事比后面调参管用得多。5.2 现象模型对密集停放的自行车漏检严重原因NMS 的iou_thres默认 0.45两辆自行车并排时框的 IoU 很容易超过这个值导致其中一辆被抑制。解决推理时把--iou-thres提到 0.55~0.6同时把--conf-thres从 0.25 降到 0.2先保召回。如果还不行考虑换--agnostic-nms或者用 soft-NMS但 YOLOv5 原生不支持 soft-NMS需要自己改后处理。5.3 现象转换后的标签文件里出现nan或空文件原因XML 里bndbox的坐标是浮点数且可能带科学计数法或者xmax小于xmin。解决在转换脚本里加try/except和数值合法性检查遇到异常坐标直接跳过该框并记录日志。不要试图「修复」这些框VOC 里这类标注本身就是错的修不如扔。5.4 现象在树莓派或 RK3568 上部署后推理速度远低于预期原因训练时用了--img 640但部署时没有做对应的预处理对齐或者模型导出 ONNX 时没有做简化。解决导出时用python export.py --weights best.pt --include onnx --img 640 --simplify然后在目标板上用 ONNXRuntime 或 TensorRT 跑。RK3568 上建议走 RKNN 工具链量化时用训练集里的 200~500 张图做校准不要用随机图。树莓派 4B 上跑 YOLOv5s ONNX单帧大概 200~400ms如果要求实时得降到--img 416或者换 n 版本。5.5 现象验证集 mAP 很高但实际场景里误检漏检一堆原因验证集和训练集同分布但实际场景的光照、角度、背景差异大。解决从实际场景里采 100~200 张图人工标一批作为「测试集」单独评估。如果测试集 mAP 比验证集低 20 个点以上说明模型过拟合了训练集的分布需要加数据增强--augment或者补充实际场景的训练数据。自行车检测里最有效的增强是mosaic和mixup但mixup对小数据集容易过拟合慎用。6. 进阶用 TTA 和切片推理把自行车召回率再提一截训练完模型、调完 NMS 之后如果召回率还是卡在 0.7 左右上不去可以试试测试时增强TTA和切片推理SAHI。这两个技巧在自行车检测里特别管用因为自行车目标尺度变化大且密集场景多。TTA 的做法是在推理时对同一张图做水平翻转、多尺度缩放然后把所有预测框合并再做 NMS。YOLOv5 的detect.py里加--augment就能开 TTA但默认只做了翻转和缩放。我一般会手动跑三个尺度--img 512、--img 640、--img 768把结果合并。合并时用--iou-thres 0.6因为多尺度预测的框位置会有偏移IoU 阈值太低会误合并。切片推理更适合高分辨率图片。比如你有一张 2000x1500 的街景图直接缩到 640 会丢失大量小目标细节。SAHI 的思路是把大图切成有重叠的小块每块单独推理再把结果映射回原图坐标合并。YOLOv5 配合 SAHI 的代码大概长这样from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov5, model_pathruns/train/bicycle_s/weights/best.pt, confidence_threshold0.25, devicecuda:0 ) result get_sliced_prediction( test_street.jpg, detection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2 ) result.export_visuals(export_dirdemo/)slice_height和slice_width一般设成和训练时--img一致overlap比例 0.2 左右能覆盖大部分跨块目标。这个方案在密集自行车场景下召回率通常能比直接推理高 10~15 个点代价是推理时间线性增加。如果部署在边缘设备上建议只在云端或服务器端用切片推理边缘端还是走单次推理加 TTA。最后说一个我自己的习惯每次训完一个自行车检测模型我都会拿同一段 30 秒的街景视频跑一遍肉眼数漏检和误检。指标再好看不如自己盯着看一遍来得实在。这个数据集不大但把标注清洗、格式转换、参数调优、部署验证这条链路走通一遍比刷十个公开榜单都有用。希望帮到你。本文还有配套的精品资源点击获取
返回列表