这个系列写到第四篇今天聊一个大家问得最多的实操话题YOLOV5怎么训练自己的VOC数据集。很多人手里已经有一批VOC格式的标注数据JPEGImages图片加上对应的XML标注文件想用YOLOV5跑目标检测又不太清楚怎么把数据和框架对接起来。这篇文章就把数据准备、格式转换、配置修改、训练命令和常见坑全部梳理一遍。我会尽量按我实际操作的流程来讲不只是贴命令还会说清楚每个步骤为什么这样做方便你跑通之后自己能改、能扩展。先说结论YOLOV5本身并不直接吃XML标签它内部用的是YOLO格式的txt标签文件每一行是“类别id 中心点x 中心点y 宽度 高度”坐标全部归一化到0~1之间。所以用VOC数据集训练YOLOV5核心工作只有三件事把XML解析成txt、把图片和标签按目录规则放好、改配置文件。整个过程不涉及复杂的模型代码只要把数据链路理顺训练就是一个train.py命令的事。1. 在动手之前先搞清楚数据格式之间的差异1.1 VOC格式和YOLO格式到底差在哪VOC格式我以VOC2007/2012那套结构为例的标注信息存放在XML文件里一个XML对应一张图片。XML里面记录了很多信息比如图片文件名、图片宽高、通道数以及每个目标的类别名称和bounding box的绝对像素坐标xmin、ymin、xmax、ymax。YOLO格式则完全不同。不存文件名不存图片尺寸也不存类别名称只存数字。每行5个数依次是类别index、归一化后的中心点x坐标、归一化后的中心点y坐标、归一化后的宽度、归一化后的高度。归一化指的是除以图片的宽度和高度所以这些值全部在0到1之间。这里有个新手常犯的误解归一化的“中心点x”和“中心点y”是以图片左上角为原点的相对坐标不是以框的中心为原点。宽度和高度也都是除以整张图尺寸后的比例。写转换代码的时候如果把这个关系搞反训练出来的模型会非常怪异——比如检测框全跑到图片外面或者在错误的位置出现一团乱框。1.2 训练路径选哪种先转换还是框架自身转换YOLOV5仓库里确实内置了VOC数据集的处理逻辑你在data.yaml里指向VOC的train.txt和val.txt它也能跑起来。但我个人的建议是自己动手写一个脚本把VOC转成YOLO格式的txt标签理由有三个。第一转换逻辑越明确越可控。你跟着仓库的内部逻辑走一旦报错排查路径会绕很远自己写转换脚本出问题一眼就能定位是数据问题还是框架问题。第二转好的txt标签是纯文本任何工具都能查看方便你直接打开检查每张图的标注是否合理。第三实际业务里很少有人直接拿到现成的标准VOC目录往往是混合格式的数据一部分XML一部分JSON甚至一部分纯手工整理的表格最后都会落到一套统一的标签格式上。所以这篇文章会采用“自己写转换脚本”的方案数据链路完全透明训练阶段纯粹用YOLOV5的train.py不用它内置的VOC自动转换逻辑。2. 数据准备从零搭建VOC结构到YOLO标签2.1 先把目录结构调整成标准姿态不管你是用LabelImg标注的还是从网上找的现成VOC数据集第一步都是把目录组织成下面这种结构VOCdevkit/ └── VOC2007/ ├── Annotations/ # 存放所有XML文件 ├── JPEGImages/ # 存放所有图片文件 └── ImageSets/ └── Main/ # train.txt/val.txt/test.txt这个结构非常经典很多开源数据集都沿用这套布局。其中ImageSets/Main里的txt文件记录的是图片文件名不带扩展名每一行一个比如000001、000002。训练集、验证集、测试集的划分就在这里指定。如果是从零开始用LabelImg标注建议直接标注成VOC格式因为它默认输出的就是XML目录也顺手按这个结构建好。如果是自己整理的数据务必备份原始文件不要在源数据上做修改转换脚本单独跑这样出问题还能从头再来。2.2 XML解析核心字段逐个拆解打开一个VOC的XML文件结构大概是这样的annotation folderVOC2007/folder filename000001.jpg/filename source databaseUnknown/database /source size width1280/width height720/height depth3/depth /size object nameperson/name bndbox xmin100/xmin ymin120/ymin xmax300/xmax ymax400/ymax /bndbox /object /annotation我们要提取的就是filename、size宽高、object/name和object/bndbox。其中bndbox的四组坐标决定了检测框位置name决定了类别归属。有一点要提醒有些数据集里面还会出现difficult、occluded、truncated这些标签。difficult1表示这个目标非常模糊正常训练时建议直接跳过不然会引入大量难例噪声模型很难收敛。truncated和occluded可以保留实际业务里目标被遮挡是很常见的情况正好用来训练模型的鲁棒性。2.3 XML转YOLO txt的实用脚本下面这个脚本是我常用的转换逻辑不依赖任何框架只用Python标准库的xml.etree.ElementTree配合os和glob就能跑。注意这里我用到了cv2只是为了读图片尺寸如果XML里面size字段齐全也可以不用OpenCV。import os import glob import xml.etree.ElementTree as ET VOC_ROOT VOCdevkit/VOC2007 OUTPUT_LABEL_ROOT VOCdevkit/VOC2007/labels ALL_CLASSES [person, car, dog, cat] # 按你的类别填 def convert_xml_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) filename root.find(filename).text stem os.path.splitext(filename)[0] lines [] for obj in root.iter(object): difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue name obj.find(name).text if name not in ALL_CLASSES: continue class_id ALL_CLASSES.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 坐标裁剪防止越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 如果裁剪后框已经没有面积直接跳过 if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) os.makedirs(output_dir, exist_okTrue) with open(os.path.join(output_dir, f{stem}.txt), w) as f: f.write(\n.join(lines)) xml_files glob.glob(os.path.join(VOC_ROOT, Annotations, *.xml)) for xml_file in xml_files: convert_xml_to_yolo(xml_file, OUTPUT_LABEL_ROOT) print(f转换完成共处理 {len(xml_files)} 个XML文件)这里有几个细节值得展开说。坐标裁剪很重要因为有些标注工具生成的坐标偶尔会超过图片边界比如xmax比图片宽度还大几像素。如果不做裁剪归一化后会出现大于1的坐标YOLOV5在训练时虽然不报错但会导致loss计算异常最典型的表现是训练过程乱跳、检测结果全是歪框。分类别列表ALL_CLASSES必须和后面data.yaml里面的names顺序完全一致。很多人在这一步踩坑转换脚本里写一套类别data.yaml里写另一套顺序结果模型训练倒是正常但推理时类别名称对不上检测出来的东西张冠李戴。2.4 划分训练集与验证集VOC结构里ImageSets/Main下的train.txt和val.txt只存放文件名不带扩展名。如果原来是老式VOC数据集里面可能已经有现成的文件可以直接用。如果是从零标注的数据你需要自己划分。一个简单稳妥的划分方式是随机抽样把所有图片文件名打乱比如按8:2划分训练集和验证集。下面是按这个思路写的脚本片段import random import os xml_paths os.listdir(VOCdevkit/VOC2007/Annotations) xml_stems [p[:-4] for p in xml_paths if p.endswith(.xml)] random.seed(2024) random.shuffle(xml_stems) split_idx int(len(xml_stems) * 0.8) train_stems xml_stems[:split_idx] val_stems xml_stems[split_idx:] with open(VOCdevkit/VOC2007/ImageSets/Main/train.txt, w) as f: f.write(\n.join(train_stems)) with open(VOCdevkit/VOC2007/ImageSets/Main/val.txt, w) as f: f.write(\n.join(val_stems))这里建议固定随机种子确保每次划分结果一致。别小看这一步如果你跑了好几轮训练中间又调整过数据结果每次划分都不一样就很难对比实验效果是来自模型改动还是来自数据变化。如果你准备把转换后的yolo格式标签放到统一的labels目录下那目录结构就变成下面这样YOLOV5官方推荐的形式VOCdevkit/ └── VOC2007/ ├── images/ │ ├── train/ # 所有训练图片 │ └── val/ # 所有验证图片 ├── labels/ │ ├── train/ # 对应训练图片的YOLO格式txt │ └── val/ # 对应验证图片的txt └── ...图片和标签靠文件名字对应起来比如images/train/000001.jpg对应labels/train/000001.txt。这个对应关系非常重要YOLOV5在训练时不会检查图片里有没有内容它只按名字去读标签如果图片存在但标签不存在就默认当作没有目标的背景图参与训练。3. 工程配置data.yaml与模型配置文件3.1 data.yaml该怎么写在YOLOV5里面数据集的配置是一个yaml文件一般放在yolov5/data/目录下或者你自己新建一个目录。内容大致是这样的train: VOCdevkit/VOC2007/images/train val: VOCdevkit/VOC2007/images/val nc: 4 names: [person, car, dog, cat]train和val指向图片所在目录即可YOLOV5会自动到相邻的labels目录去找同名txt文件labels目录的位置不需要在yaml里配置它是根据图片路径推出来的自动把路径里的images换成labels。这个机制确实非常方便但也容易埋坑——如果你把图片和标签分别放在完全无关的目录里YOLOV5是找不到标签的训练时所有图片都会被当成背景图。如果你的数据是保留VOC原始目录结构直接在ImageSets/Main/train.txt里写文件名YOLOV5也有对应的数据处理逻辑。但我个人建议还是转成images和labels分开的布局因为后续做推理、做数据增广、写自定义数据集加载器都更通用。3.2 模型配置文件与nc修改YOLOV5的模型结构配置在models/目录下最常用的是yolov5s.yaml、yolov5m.yaml、yolov5l.yaml和yolov5x.yaml。打开它们文件里有一个nc字段表示类别数。这个值必须改成你自己的类别数量。例如你只检测人和车两个类别就把nc: 80改成nc: 2。实际上train.py在启动时会检查模型配置文件的nc和data.yaml里的nc是否一致。如果不同有的版本会报错有的版本会直接覆盖。还是建议显式地同时改掉保持一致避免后患。模型配置除了nc其他部分一般不用动。很多新手看到anchors、depth_multiple、width_multiple这些字段会困惑其实它们决定了模型的深度和宽度比例是YOLOV5的“s/m/l/x”系列的核心差异。如果你没有足够的实验依据不要轻易修改这两个multiplier参数。默认yolov5s的配置对大多数中小数据集已经足够训练速度快显存占用小效果也不差。3.3 预训练权重的选择逻辑训练YOLOV5一般都会加预训练权重常见的就是--weights yolov5s.pt。这个权重是模型在大型检测数据集上预训练出来的网络的前面几层已经学会了通用特征边缘、纹理、颜色块等迁移到自己的数据集上能明显加快收敛而且在小数据集上尤其有效。很多人会担心“我的数据集类别不是COCO那80类能用COCO预训练权重吗”答案是能。YOLOV5的加载逻辑会自动根据类别数调整最后检测头那一层的维度前面主干网络的参数原样初始化。第一次加载时你会看到一条提示大意是检测头层因通道数不匹配被跳过这是正常现象不需要担心。如果你的数据集非常特殊比如医学图像、卫星图像和自然图像差异很大那么预训练权重的帮助会小一些但依然可以用。我的习惯是只要数据量少于几万张就先试试预训练权重数据很少时冻结主干前几层训练能有效避免过拟合。4. 训练全流程命令、参数与监控4.1 train.py命令逐项拆解数据准备和配置完成后训练命令其实不长下面是我常用的一个模板python train.py \ --data mydata.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --workers 4 \ --project runs/train \ --name my_voc_experiment逐项解释一下。--data指向数据集配置yaml--weights是预训练权重文件也可以填官方的预训练权重文件名首次会自动下载--epochs是训练轮数--batch-size是每个GPU的批大小这个参数受显存约束--imgsz是输入图片的边长YOLOV5会统一把图片缩放或填充到这个尺寸一般用640如果目标比较小可以适当调大到960或1280但显存占用和训练时间都会上涨--device指定GPU编号多GPU可以写0 1 2 3--workers是数据加载线程数Windows下建议设为0或2Linux下可以高一些线程太高会导致CPU成为瓶颈。关于--epochs很多新手只跑几十轮就收工结果模型还没完全收敛这点我会在后面详细讲。4.2 训练过程中的结果文件怎么读训练启动后屏幕上会打印进度条、loss、精度等指标同时会在runs/train/目录下生成一系列文件。训练结束后最重要的一张图是results.png它把所有关键指标曲线都画在一张图上从上到下依次是box_loss、obj_loss、cls_loss、precision、recall、mAP0.5、mAP0.5:0.95。我判断训练状态有一个固定的习惯先看loss有没有持续下降然后看precision和recall有没有在后期趋于稳定最后看mAP0.5有没有达到业务预期。这张results.png上如果mAP0.5的曲线在训练后期还在缓慢上升说明模型还没完全收敛可以考虑加大epochs继续训练或者用最后一次权重复训。训练过程中我还会定期打开runs/train/下的权重文件目录可以看到last.pt和best.pt两个文件。last.pt是最后一轮结束时的权重best.pt是验证集mAP最高时的权重。实际推理和部署时一般用best.pt。偶尔也会遇到best和last差距很大的情况基本原因都是验证集划分不合理或者最后一轮学习率还在下降导致last过拟合了。4.3 训练完怎么验证效果训练结束后先跑一下验证集用val.py生成客观的精度指标python val.py \ --data mydata.yaml \ --weights runs/train/my_voc_experiment/weights/best.pt \ --imgsz 640 \ --device 0输出会显示每一类的precision、recall和mAP。这时候我习惯做两件事一是看每个类别的AP值是否均衡如果某一类特别低通常说明该类样本太少、标注歧义大或者目标太小难检测二是打开runs/val/exp*/下面的混淆矩阵图看哪个类别之间互相误检严重。然后跑几个真实场景的推理测试python detect.py \ --weights runs/train/my_voc_experiment/weights/best.pt \ --source test_images/ \ --imgsz 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \--source可以是单张图片、图片目录、视频文件甚至摄像头流。--conf-thres是置信度阈值低于这个阈值的框会被过滤掉--iou-thres是NMS的IOU阈值控制两个重叠框是否合并。这两个值对结果影响非常大推理现场如果觉得误检多就调高conf-thres觉得漏检多就调低conf-thres这是个经验值没有绝对正确的设置。5. 常见问题速查表与避坑技巧5.1 典型报错与解决方案下面整理了一份我在训练YOLOV5时见过很多次的报错速查表挑出来最常见的几种现象主要原因解决办法FileNotFoundError: image not found图片路径与yaml中不一致检查train/val目录路径是否正确检查图片格式是jpg还是pngDataLoader workers重复--workers设置过高Windows下改成--workers 2或直接设为0CUDA out of memorybatch-size或imgsz太大减小batch-size或调小imgsz优先减batch-size标签文件全为空白XML解析失败或difficult被过滤单独打印转换结果检查txt文件是否为空用标签可视化脚本检查mAP始终为0标签类别序号与names顺序不一致检查data.yaml里的names顺序与转换脚本里的ALL_CLASSES是否一致loss为nan学习率过高或数据异常如全是黑图降低学习率检查图片是否全为纯色查看标签中是否出现inf坐标验证集全部无检测框模型权重没收敛或conf-thres调太高检查results.png的mAP曲线把conf-thres降到0.1看看检测结果5.2 数据标签层面的几类隐蔽问题训练跑挂的情况并不多更多时候是模型效果差这种“软问题”最难排查。我建议你训练前就把标签可视化一遍直接画出原始标签框覆盖在图片上的效果。这个步骤非常快但能发现很多低级错误框明显偏移、框超出整图边界、某类目标完全没有被框住、类别名拼写错误导致类别数不符合预期等。有一个很经典的坑XML里写的是car转换脚本里类别列表是[person, bike, truck]car不在列表里就会被静默跳过。你看到的结果是很多图片的txt标签是空的模型相当于只拿少部分图片训练检测效果自然差。所以转换脚本里最好打印一下“被跳过的类别和数量”及时发现异常。另一个典型坑是图片格式不统一。有的图片是BMP有的是JPEG还有的是PNG带透明通道。YOLOV5在读取时会统一处理但如果你用OpenCV写转换脚本读图片尺寸可能需要额外处理路径中的中文和空格。建议所有图片文件名都用纯数字或英文字母路径里不要出现中文或特殊符号否则跨平台之后很容易出问题。5.3 模型长时间不收敛怎么办如果训练了很多轮loss始终在一个水平线上不动或者mAP一直很低优先检查这几件事。第一确认标签数据真的是有目标框的而不是全是空文件。打开labels/train目录随便抽查几个txt文件看看行数和数值是否合理。第二确认图片数据本身是清晰可辨的没有严重压缩、加水印或者模糊到人眼都看不清的程度。第三检查类别分布某个类别只有三五十张图模型基本学不会这个类。第四看看学习率设置train.py默认会用内置的学习率调度策略但如果你自行修改过hyp.yaml要小心初始学习率过高导致loss震荡。还有一个容易被忽视的点图片尺寸差距过大。如果你的训练集里同时有1920x1080的大图和240x240的小图都被缩放到640x640的话小图里的物体可能被放大失真大图里的目标被缩小到难以识别。这种数据分布会让模型收敛困难检测效果也飘忽不定。最好按目标实际尺寸对图片做分组训练或者把imgsz调大并配合rect模式矩形训练不强制缩放成正方形。6. 实操中最实用的几个小技巧训练流程跑通了效果也达标了接着还有几个日常使用频率很高的小技巧值得分享。第一个技巧是冻结主干训练。如果数据集很小几万张以内就算小可以先冻结主干层训练几十轮然后再解冻全部层微调几十轮。做法是在train.py前面加一个--freeze 10参数YOLOV5支持这个参数。冻结的好处是防止小数据集上过拟合收敛也更稳定。我的经验是先训练50轮得到初步结果再解冻训练到收敛。第二个技巧是用标签分析脚本快速体检数据。YOLOV5仓库里有一个visdrone相关脚本或者其他自制脚本可以统计每个类别的标签数量、bbox大小分布、图片尺寸分布等。如果没有现成工具可以自己写个简单的统计脚本统计结果的几个关键数字看一眼就知道数据有没有明显问题。尤其是bbox太小比如小于32x32像素的目标占了一半以上时检测难度会非常大模型再怎么调也很难全检出来。第三个技巧是数据扩增别拉满。YOLOV5默认的hyp.yaml里其实带了不少数据增强策略比如马赛克增强mosaic、翻转、颜色扰动、随机缩放等。对小目标增多的业务场景马赛克增强效果很好但如果你发现训练到后面验证集的mAP反而不稳定可以把马赛克概率适当降一些。这个参数在hyp.scratch-low.yaml或hyp.scratch-high.yaml里对应的键是mosaic。实际情况中增强太猛会让模型在训练后期难以收敛到更细的精度。第四个技巧是善用断点续训。train.py支持--resume参数格式是--resume runs/train/exp/。如果训练过程中断电或显存崩了不用从头再来。我个人习惯在每个项目里固定用--name参数区分实验这样每次实验的日志、权重、训练曲线全部分开存放不会互相覆盖对比结果时一目了然。第五个技巧是批量推理时加--save-crop可以自动裁剪所有检测到的目标保存成图片。这个功能在整理业务样例、检查误报漏报、做模型效果汇报时非常好用。我经常用这个功能快速生成一批检测结果样本给团队看不用自己写裁剪代码。做YOLOV5训练自己的VOC数据集说白了就是把数据结构、路径、类别、尺寸这几件事理清楚训练命令本身很简单。这篇文章里的转换脚本、划分脚本和配置模板我基本在每次接新数据时都会复用换了数据集只需要改类别名称和路径。如果你按照上面的流程操作先跑通一个小的验证demo再扩充全量数据训练整个流程会很顺。希望这期内容能帮你少走一些弯路。
阅读完成 · 觉得有帮助?