尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

YOLOV5实战:20分类VOC数据集训练与部署全流程

发布时间:2026/9/23 18:50:04

资讯中心
01
ARTICLE

YOLOV5实战:20分类VOC数据集训练与部署全流程

YOLOV5实战:20分类VOC数据集训练与部署全流程
简介这份资源是面向目标检测初学者与进阶开发者的YOLOv5实战项目包围绕VOC格式的20分类数据集展开涵盖火车、船、人、电视、飞机等常见类别适合用来练习数据标注解析、模型训练与推理部署的完整流程。压缩包共约2000个文件以1921个txt标签与说明、40个Python脚本、23个yaml配置、9个sh脚本及若干md文档为主整体约357.51MB目录结构清晰便于按模块查阅。项目已迭代100个epochruns目录下保存了训练结果最佳精度达到map0.50.62、map0.5:0.950.42并在runs/detect中保留了网络推理训练集的全部结果可直接观察实际检测效果。训练集含13700张图片及对应标签测试集含3425张图片及对应标签代码经测试可直接运行。目前已有159人学习适合希望快速复现YOLOv5训练与推理、理解参数配置与结果分析的读者参考。1. 从一份 20 分类 VOC 数据集说起YOLOV5 实战到底在练什么手里拿到一份 20 分类的 VOC 格式目标检测数据集很多人第一反应是直接train.py一把梭结果跑完发现 mAP 卡在 0.3 上不去回头查才发现标注里有一堆宽高为 0 的脏框类别名和data.yaml对不上训练集里还有几张图压根没标注文件。YOLOV5 实战项目配 VOC 数据集这件事难点从来不在模型本身而在数据从 VOC 的 XML 结构搬到 YOLO 的 txt 结构这一段的工程细节。这篇笔记就围绕「20 分类 VOC 数据集怎么喂给 YOLOV5」这条主线把格式转换、类别映射、超参数设置、训练排错、部署验证整条链路拆开讲。适合已经跑通过官方 COCO 示例、想换成自己业务数据集的人也适合被 VOC 转 YOLO 格式坑过一次、想搞清楚边界条件的熟手。读完你应该能独立把一份 20 类的 VOC 数据集跑出可用的检测模型并且知道每一步出错时该看哪个文件。2. VOC 的 XML 和 YOLO 的 txt 差在哪先把格式对齐再谈训练2.1 两种标注格式的结构差异VOC 格式的核心是每张图对应一个同名 XML 文件里面用object节点描述每个目标包含name类别名和bndbox下的xmin/ymin/xmax/ymax四个绝对像素坐标。YOLO 格式则是每张图对应一个同名 txt 文件每行一个目标格式是class_id x_center y_center width height后四个值全部是相对图像宽高的归一化值范围 0 到 1。这个差异决定了转换脚本必须做两件事把类别名映射成从 0 开始的整数索引把绝对坐标除以图像宽高做归一化。20 分类意味着类别索引是 0 到 19映射关系必须固定且和data.yaml里的names列表顺序严格一致。我见过最常见的翻车就是转换时按set()去重类别名结果每次运行顺序不一样训练时标签全乱loss 看着在降但 mAP 死活上不去。正确做法是先人工确定一个类别顺序写死在脚本里。2.2 转换脚本从 XML 批量生成 YOLO txt下面这个脚本是我一般会用的版本处理 20 分类 VOC 数据集同时做脏框过滤和图像尺寸校验。import os import xml.etree.ElementTree as ET from PIL import Image # 类别顺序必须和 data.yaml 的 names 完全一致写死不要用 set 动态生成 CLASSES [ aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor ] CLASS_TO_ID {name: i for i, name in enumerate(CLASSES)} def convert_annotation(xml_path, img_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() # 用真实图像尺寸做归一化不要信 XML 里的 size 节点经常和实际图不符 with Image.open(img_path) as im: w, h im.size lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASS_TO_ID: continue # 类别不在 20 类里直接跳过避免索引越界 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) # 裁剪到图像边界内VOC 里常有超出边界的框 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) bw, bh xmax - xmin, ymax - ymin if bw 1 or bh 1: continue # 宽高过小的脏框直接丢训练时是纯噪声 xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h lines.append(f{CLASS_TO_ID[name]} {xc:.6f} {yc:.6f} {bw/w:.6f} {bh/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 stem os.path.splitext(xml_file)[0] img_path os.path.join(img_dir, stem .jpg) if not os.path.exists(img_path): print(f缺图跳过: {stem}) continue convert_annotation( os.path.join(xml_dir, xml_file), img_path, os.path.join(out_dir, stem .txt) ) if __name__ __main__: batch_convert(./VOCdevkit/VOC2007/Annotations, ./VOCdevkit/VOC2007/JPEGImages, ./labels/train)逻辑上分三步先读 XML 拿到所有 object再用 PIL 打开原图拿真实宽高最后逐框做边界裁剪、脏框过滤、归一化写出。参数上CLASSES列表是唯一真相来源data.yaml必须和它逐字对应bw 1 or bh 1这个阈值可以根据数据集调整小目标多的场景可以放宽到 0.5 像素但不要设成 0否则会引入大量无效框。xc/yc/bw/bh保留 6 位小数是 YOLOV5 官方脚本的习惯精度足够且文件不会太大。2.3 划分训练集验证集并生成 data.yaml转换完 txt 之后按 8:2 或 9:1 划分 train 和 val注意划分的是图像和标签成对移动不要只移一边。data.yaml是 YOLOV5 读取数据的入口20 分类的写法如下path: /home/user/voc20 train: images/train val: images/val nc: 20 names: 0: aeroplane 1: bicycle 2: bird 3: boat 4: bottle 5: bus 6: car 7: cat 8: chair 9: cow 10: diningtable 11: dog 12: horse 13: motorbike 14: person 15: pottedplant 16: sheep 17: sofa 18: train 19: tvmonitornc必须等于 20names的键值对顺序必须和转换脚本里的CLASSES一致。YOLOV5 在加载时会校验标签里的 class_id 是否小于nc如果转换时用了别的顺序这里不会报错但训练结果会完全错乱这是最隐蔽的坑之一。3. 用 conda 把 YOLOV5 环境配起来版本对齐比装得快更重要3.1 环境配置的版本选择YOLOV5 对 PyTorch 和 CUDA 的版本比较敏感尤其是涉及torchvision的 NMS 算子。我一般用 conda 建独立环境Python 选 3.8 或 3.9PyTorch 选 1.12 到 2.0 之间的稳定版CUDA 11.6 或 11.8。不要盲目追最新版YOLOV5 仓库的requirements.txt里对某些包有上限约束装太新反而会触发兼容问题。conda create -n yolov5_voc python3.9 -y conda activate yolov5_voc # 按本机 CUDA 版本选对应 wheel这里以 cu118 为例 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt装完后跑一句python -c import torch; print(torch.cuda.is_available())确认 GPU 可用。如果返回 False先查驱动版本和 CUDA 运行时是否匹配不要急着改代码。requirements.txt里会装opencv-python、matplotlib、pyyaml这些如果服务器没有图形界面opencv-python可能报 libGL 缺失换成opencv-python-headless即可。3.2 用预训练权重做一次冒烟测试在正式训练自己的 20 分类数据集之前先用官方 COCO 预训练权重跑一次推理确认环境和模型加载链路没问题。python detect.py --weights yolov5s.pt --source data/images/bus.jpg --device 0这条命令会用yolov5s.pt对示例图做推理输出到runs/detect/exp/。如果能看到带框的结果图说明环境、权重加载、推理后处理都通了。这一步的意义在于把环境问题和数据问题隔离开后面训练出问题就不用怀疑环境。--device 0指定第一块 GPUCPU 推理去掉这个参数即可但速度会慢很多20 分类数据集不建议用 CPU 训练。3.3 训练命令与关键超参数正式训练 20 分类 VOC 数据集我一般从yolov5s起步显存够的话上yolov5m。命令如下python train.py \ --data data/voc20.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --device 0 \ --workers 8 \ --project runs/train \ --name voc20_exp1--img 640是输入分辨率小目标多的数据集可以提到 1280但显存占用会翻倍。--batch 16要根据显存调8G 显存跑 640 分辨率大概能到 1612G 能到 32。--hyp选hyp.scratch-low.yaml是因为从预训练权重微调时低学习率增强策略更稳如果数据集很小每类不到 500 张可以换成hyp.finetune.yaml。--workers是数据加载线程数设成 CPU 核数的 1 到 2 倍设太高反而会因为 IO 争抢变慢。训练过程中重点看三个指标box_loss、obj_loss、mAP0.5。正常情况下 box_loss 在前 10 个 epoch 快速下降obj_loss 缓慢下降mAP 在 20 epoch 后开始爬升。如果 box_loss 一直不降大概率是标签格式有问题如果 mAP 卡在 0.1 以下检查data.yaml的路径和类别数。4. 20 分类 VOC 训练避坑那些让 mAP 原地踏步的细节4.1 类别不平衡导致小类几乎不召回现象训练完看混淆矩阵person、car这类大类 mAP 能到 0.7但pottedplant、diningtable这些小类 mAP 接近 0。原因VOC 数据集本身类别分布极不均匀20 类里前几类占了 70% 以上的框模型倾向于预测高频类。解决在data.yaml同级目录建一个train_balanced.txt对小类图像做重复采样或者用--hyp里的cls参数调大分类损失权重从默认 0.5 提到 0.8 到 1.0。更彻底的做法是用WeightedRandomSampler改 dataloader但改动量大一般先调cls权重试试。4.2 图像和标签文件名不匹配现象训练启动时报No labels found或者assertion error但明明 txt 文件都在。原因YOLOV5 默认按图像路径把扩展名替换成.txt去找标签如果图像是.jpeg而标签是.txt或者图像在images/train而标签在labels/train但目录结构不对就会找不到。解决确认目录结构是images/train/xxx.jpg对应labels/train/xxx.txt文件名主干完全一致。YOLOV5 支持通过--img-path和标签路径的约定来定位不要手动改代码里的路径拼接逻辑按官方约定放就行。4.3 缓存文件导致改了标签不生效现象改了某个 txt 里的类别重新训练发现结果没变化。原因YOLOV5 第一次训练会在同目录生成.cache缓存文件记录图像尺寸和标签索引后续训练直接读缓存。解决删掉labels/train.cache和labels/val.cache或者加--noval先跑一个 epoch 重建缓存。这个坑很隐蔽因为缓存文件是隐藏的ls不加-a看不到。4.4 显存溢出和 batch 设置现象训练到第几个 batch 突然CUDA out of memory。原因--batch设太大或者--img分辨率太高或者--workers太多导致数据加载占用显存。解决先把--batch减半如果还溢出就降--img到 512 或 416。另外 YOLOV5 支持--batch-size -1自动批大小会根据显存自动调整不确定时用这个。注意自动批大小在训练中途不会动态调整只是启动时探测一次。4.5 验证集 mAP 波动大现象每个 epoch 的 mAP 上下跳动超过 0.1。原因验证集太小20 分类如果验证集只有几百张统计噪声很大。解决把验证集比例提到 20% 以上或者用--val时增加--conf-thres和--iou-thres的稳定性。另外检查验证集里是否有类别缺失如果某个类在验证集里一张都没有那一类的 mAP 会是 0 并拉低整体。用python -c from collections import Counter; ...统计一下验证集类别分布确保 20 类都有样本。5. 训练完怎么验证和部署从 mAP 到实际推理5.1 用 val.py 做完整评估训练结束后不要只看训练日志里的 mAP用val.py单独跑一次完整评估输出每类的 P、R、mAP0.5、mAP0.5:0.95。python val.py \ --data data/voc20.yaml \ --weights runs/train/voc20_exp1/weights/best.pt \ --img 640 \ --batch 16 \ --task val \ --device 0输出会按类别列出指标重点看小类的召回率。如果某个类 mAP 低于 0.2回到第 4 章查类别不平衡和标注质量。--task val是标准验证模式--task test会用 test 集--task speed只测速度不测精度。best.pt是验证集 mAP 最高的权重last.pt是最后一个 epoch 的一般用best.pt部署。5.2 导出 ONNX 并在 Python 里推理YOLOV5 部署最常见的是导出 ONNX然后用onnxruntime推理跨平台且不依赖 PyTorch。python export.py --weights runs/train/voc20_exp1/weights/best.pt --include onnx --img 640 --batch 1导出后在 Python 里加载import onnxruntime as ort import numpy as np import cv2 sess ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR 转 RGB 再转 CHW img np.ascontiguousarray(img, dtypenp.float32) / 255.0 img img[None, ...] # 加 batch 维度 outputs sess.run(None, {sess.get_inputs()[0].name: img}) # outputs[0] 形状是 [1, 25200, 25]25 4 框坐标 1 obj 20 类 pred outputs[0][0] scores pred[:, 4:5] * pred[:, 5:] # obj 置信度乘类别概率参数上providers选CUDAExecutionProvider走 GPU没有 GPU 就换CPUExecutionProvider。25200是 640 分辨率下三个检测头的锚框总数25是 4 个框参数加 1 个 objectness 加 20 个类别概率。后处理需要自己做 NMSonnxruntime不包含 NMS 算子可以用cv2.dnn.NMSBoxes或者torchvision.ops.nms。如果部署到树莓派这类边缘设备建议导出时加--include onnx后用onnxsim简化再转成 NCNN 或 TFLite速度会好很多。5.3 一个容易被忽略的验证技巧训练完先别急着部署拿几张训练集里没见过的图用detect.py跑一遍把结果图和原图并排看。重点看两类错误一类是漏检尤其是小目标和遮挡目标另一类是误检把背景纹理当成目标。如果误检集中在某个类别比如把sofa误判成chair说明这两个类的特征在 20 分类里区分度不够可以考虑合并类别或者增加这两类的难例样本。这个肉眼验证步骤比看 mAP 数字更直接我一般会花半小时做这件事能发现很多指标反映不出来的问题。6. 把 20 分类 VOC 数据集跑稳的几个进阶习惯训练 20 分类 VOC 数据集这件事跑通一次不难难的是每次换数据集都能稳定复现。我自己的习惯是先把转换脚本和data.yaml放进版本控制每次改类别顺序或增删类别都留 commit这样出问题能回滚。另外训练前一定跑一遍数据校验脚本统计每类框数、图像数、宽高分布把异常值打印出来。下面这个校验片段我基本每次都会用import os from collections import Counter def check_labels(label_dir, nc20): cls_counter Counter() empty_files 0 for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname)) as f: lines [l for l in f.read().strip().split(\n) if l] if not lines: empty_files 1 continue for line in lines: cid int(line.split()[0]) assert 0 cid nc, f{fname} 类别越界: {cid} cls_counter[cid] 1 print(每类框数:, dict(sorted(cls_counter.items()))) print(空标签文件数:, empty_files) check_labels(./labels/train)这个脚本能一次性暴露类别越界、空标签、类别分布三个问题。空标签文件在 YOLOV5 里是合法的表示这张图是纯背景但如果空标签占比超过 10%说明数据集里负样本太多需要检查是不是漏标了。类别分布打印出来后如果最大类和最小类框数差 100 倍以上就要考虑第 4 章说的重采样或调cls权重。还有一个习惯是固定随机种子。YOLOV5 默认不固定种子每次训练结果会有小幅波动做对比实验时容易误判。在train.py里加--seed 42配合--deterministic能让结果可复现。注意--deterministic会稍微降低训练速度但换来的是实验可比性调参阶段值得开。最后说一个我踩过的坑有次换了一份 20 分类数据集data.yaml里names写的是中文类别名训练能跑但导出 ONNX 后推理结果全乱。原因是 ONNX 导出时类别名编码不一致后处理按索引取名字时对不上。后来统一改成英文类别名中文只在可视化时映射问题就没了。所以类别名尽量用英文或拼音别在data.yaml里直接写中文这个习惯能省掉后面部署环节的很多麻烦。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。