尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

VOC转YOLO格式实战:从XML到TXT的坐标转换与数据集分割指南

VOC转YOLO格式实战:从XML到TXT的坐标转换与数据集分割指南 简介面向计算机视觉数据预处理的源代码与配套文档专门解决将VOC格式数据集的XML标注转换为YOLO模型所需的TXT文本并同步完成训练集与测试集的随机划分适用于目标检测方向的大学生课程设计、期末大作业、毕业设计也可作为算法工程师日常实验中的批量提效工具。整个资源压缩包体积仅有2KB非常轻量内部包含两个Python脚本一个脚本负责解析VOC标注文件并生成归一化后的YOLO标签另一个脚本根据预设比例把图像和标签一同分割为训练子集与测试子集两个脚本都不依赖任何第三方库可直接运行。当前已有560人学习或下载从侧面反映出该小工具在数据准备阶段的真实需求。代码采用参数化编程方式路径、划分比例、随机种子等关键参数均可自由调整注释与输出信息非常完善并附带运行结果便于初学者反复对照通过研读代码可以直观理解标注坐标的归一化过程、类别映射表的设计以及数据划分时的文件遍历逻辑从而减少重复造轮子提高项目启动效率。所有代码均经过多次运行测试结果稳定适合直接嵌入实际工作流。1. 为什么每个训练YOLO的人都要先过这一关VOC转YOLO的烦恼在labelimg里吭哧吭哧标了一晚上目标框存出来全是VOC格式的xml结果打开yolov8的官方教程一看人家要的是每个图片配一个txt、每行五列数字的YOLO标注还有一个只有训练集和验证集的文件夹结构。网上代码一搜十个脚本九个跑不通剩下的那个还要手工改一堆绝对路径。这篇就讲清楚VOC格式数据集转换成YOLO格式并且分割训练集与测试集这件事的完整做法两种格式的坐标换算关系、一个改改就能用的转换脚本以及分割数据集时最容易踩的坑。适合手里有VOC标注、准备用yolov8或yolov5训练自己的目标检测模型但不想在数据格式上耗一晚上的开发者。2. 两种格式到底差在哪先看懂再写转换代码很多人把格式转换当成黑匣子脚本能跑就万岁了结果训练出来的模型要么类别全错要么框全飞了。其实VOC和YOLO的数据结构没有半点玄学就是两种不同的存法搞懂换算关系转换脚本就是几十行的事。2.1 VOC的xml和YOLO的txt各存了什么一张图把数据结构对齐VOC格式下每一张图片对应一个同名的xml文件里面用bndbox记录目标的真实像素坐标。核心字段是xmin、ymin、xmax、ymax分别对应目标框左、上、右、下的像素位置外加一个name记录目标类别名比如person、car。YOLO格式下每一张图片对应一个同名的txt文件每一行对应一个目标class_id x_center y_center width height其中class_id是从0开始的整数x_center、y_center是目标框中心点相对于图片宽高的比例width和height是目标框宽高相对于图片宽高的比例。注意这四个数值全部归一化到0到1之间跟图片的绝对像素大小无关。坐标系上两种格式的原点都在图片左上角x轴向右、y轴向下所以不需要翻转坐标轴。转换公式就是简单的算术x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height类别名的处理更关键。VOC里是一个字符串YOLO需要的是整数索引这个索引由你定义的类别映射表决定。比如你想让模型识别人和车那就要明确{person: 0, car: 1}转换时把name查表换成数字。这个映射表对应后面YOLO训练配置里的names列表顺序必须完全一致否则训练出来的模型类别就全错位了。提示写类别映射表时先把项目最终需要检测哪些类定死中途加类会让已经转换好的txt全部作废又得重新来一遍。2.2 用Python把VOC转成YOLO核心转换脚本与参数说明弄清楚关换算关系脚本就是顺理成章的事。下面这份是我常用的转换脚本基于Python标准库加Pillow不需要安装额外依赖Pillow装一下就行直接复制到项目里改三个路径和一个类别表就能跑。import os import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image def voc2yolo(xml_path, img_path, class_map): 把单个VOC xml文件转换为YOLO格式的标注文本行。 参数: xml_path: VOC标注文件路径 img_path: 对应的图片路径用来读取真实宽高 class_map: 类别名到索引的映射字典例如 {person: 0, car: 1} 返回: list[str] 每个元素是YOLO格式的一行标注文本 with open(xml_path, r, encodingutf-8-sig) as f: tree ET.parse(f) root tree.getroot() # 从xml里读取标注框注意用iter而不是findall # 因为有的工具导出xml会套多层object层级 boxes [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: print(f[跳过] {xml_path.stem} 里有不认识的类别: {name}) 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) boxes.append((class_map[name], xmin, ymin, xmax, ymax)) # 读取图片宽高 with Image.open(img_path) as img: img_width, img_height img.size lines [] for class_id, xmin, ymin, xmax, ymax in boxes: # 中心点和宽高的归一化公式 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 6位小数足够训练使用太多位反而让文件体积变大 lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return lines def batch_convert(xml_dir, img_dir, out_dir, class_map): 批量转换文件夹内所有VOC xml为YOLO txt输出到out_dir。 xml_dir Path(xml_dir) img_dir Path(img_dir) out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) xml_files list(xml_dir.glob(*.xml)) if not xml_files: print(xml目录为空请检查路径) return success 0 for xml_file in xml_files: stem xml_file.stem img_path None # 优先尝试常见的图片后缀 for suffix in (.jpg, .jpeg, .png, .bmp): candidate img_dir / (stem suffix) if candidate.exists(): img_path candidate break if img_path is None: print(f[警告] {stem}.xml 找不到对应图片跳过) continue lines voc2yolo(xml_file, img_path, class_map) if not lines: print(f[警告] {stem}.xml 没有可用的标注框跳过) continue # 写入txt文件名与xml保持一致 out_path out_dir / (stem .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) success 1 print(f转换完成成功 {success} 个失败 {len(xml_files) - success} 个) if __name__ __main__: # 把这三行改成你自己的路径即可 XML_DIR VOCdevkit/VOC2007/Annotations IMG_DIR VOCdevkit/VOC2007/JPEGImages OUT_DIR yolo_labels CLASS_MAP {person: 0, car: 1, bicycle: 2} batch_convert(XML_DIR, IMG_DIR, OUT_DIR, CLASS_MAP)逻辑说明脚本先遍历xml目录里的所有xml文件逐个解析后查找对应图片读取真实宽高再套用中心点和宽高的归一化公式生成一行标注文本。顺序上先找图片后换算因为换算依赖图片的真实宽高。最终输出的txt文件名和xml文件名一致这样之后和图片配对不会乱。几个关键参数要解释一下。xml_path传的是单个VOC标注文件img_path是对应的图片路径二者必须能对得上脚本里对缺失图片做了跳过处理。class_map里的值决定YOLO训练时的类别索引这个映射表要和后续的data.yaml里的names顺序保持一致。另外用utf-8-sig读取xml能处理一些标注工具导出的文件带BOM头导致解析报错的情况。跑完这个脚本你会得到一堆txt每个txt里每行五个数字。先不要急着下一步打开几个txt和原始图片对一下确认数字看起来在合理范围内。这一步花不了两分钟但能提前拦住八成错误。3. 分割训练集与测试集随机、干净、可复现转换完格式之后就是数据分割了。别小看这一步很多人直接把这个环节做成shutil.copyfile的搬运工复制完才发现训练集里没有某些小类别的样本、验证集里缺了一个类训练半天指标虚高。设计分割方案时有三个要点值得先想清楚。3.1 分训练集和测试集不只是复制文件三个设计要点第一个要点是随机种子的确定性。日常用random.shuffle加random.seed(42)固定随机序列保证每次跑出来分割结果一致方便复现和排错。第二个要点是分层抽样。YOLO数据集的类别往往是不均衡的某些类别只有几十个样本如果纯随机划分小类别的全部样本有可能都落在训练集里验证集里一个都没有。训练时这个类别在验证集上的指标要么虚高因为验证集里没有真值可算要么直接异常。所以分割之前先统计一下每张图片包含哪些类别再按类别维度做比例控制保证每个类别的样本在训练集和验证集里都有合理占比。第三个要点是文件组织方式。YOLOv5和YOLOv8都支持标准的目录结构即images/train、images/val、labels/train、labels/val四个目录。有些老教程还会让你生成train.txt和val.txt文件列表新版YOLO早就不是必需的了。我用的是目录方式因为更直观yolov8和yolov5都认。如果你有大量数据需要多次读取用硬链接而不是复制能省一半磁盘空间。3.2 分割脚本按比例切分并生成YOLO训练所需的文件清单下面这个脚本读取上一步转换好的images和labels两个目录按比例分割后复制到标准目录结构里。默认85%做训练集、15%做验证集你可以按自己的数据量调整。import random import shutil from pathlib import Path def split_dataset(image_dir, label_dir, output_dir, train_ratio0.85, seed42): 把转换好的YOLO格式数据分割为训练集和验证集目录结构。 参数: image_dir: 存放所有图片的源目录 label_dir: 存放所有txt标注的源目录 output_dir: 输出目录将在里面生成 images/train, images/val, labels/train, labels/val train_ratio: 训练集占比默认0.85 seed: 随机种子固定后保证可复现 random.seed(seed) image_dir Path(image_dir) label_dir Path(label_dir) output_dir Path(output_dir) # 收集所有图片名不含后缀同时允许图片有多种后缀 image_stems {} for img_path in image_dir.iterdir(): if img_path.suffix.lower() in (.jpg, .jpeg, .png, .bmp): image_stems[img_path.stem] img_path print(f共找到 {len(image_stems)} 张图片) # 只保留有txt标注的图片避免把没有标注的文件带进训练集 valid_stems [] for stem in image_stems: label_path label_dir / (stem .txt) if label_path.exists(): valid_stems.append(stem) missing_label len(image_stems) - len(valid_stems) if missing_label 0: print(f警告{missing_label} 张图片缺少txt标注已过滤) # 打乱顺序并按比例切分 random.shuffle(valid_stems) split_idx int(len(valid_stems) * train_ratio) train_stems valid_stems[:split_idx] val_stems valid_stems[split_idx:] print(f训练集 {len(train_stems)} 张验证集 {len(val_stems)} 张) # 构建输出目录结构 for split_name, stems in [(train, train_stems), (val, val_stems)]: img_out output_dir / images / split_name lab_out output_dir / labels / split_name img_out.mkdir(parentsTrue, exist_okTrue) lab_out.mkdir(parentsTrue, exist_okTrue) for stem in stems: shutil.copy(image_stems[stem], img_out / image_stems[stem].name) shutil.copy(label_dir / (stem .txt), lab_out / (stem .txt)) # 额外生成文件列表方便某些版本YOLO使用 for split_name, stems in [(train, train_stems), (val, val_stems)]: list_file output_dir / (split_name .txt) with open(list_file, w, encodingutf-8) as f: for stem in stems: f.write(str(image_stems[stem]) \n) print(分割完成目录结构已生成) if __name__ __main__: split_dataset( image_dirdataset/images, label_dirdataset/labels, output_dirdataset, train_ratio0.85, seed42 )逻辑说明脚本先扫描所有图片再和txt标注做配对只保留“图片和txt都存在”的有效样本从源头避免把缺标注的文件带进训练流程。然后把有效样本打乱顺序按train_ratio比例切分成训练集和验证集最后复制到标准目录结构同时生成一份train.txt和val.txt的绝对路径列表以备老版本yolov5使用。参数上train_ratio0.85是常规值数据量比较小时可以提高到0.9甚至0.95但验证集太小的话训练指标波动会很大。seed42固定了随机顺序下次重跑结果完全一样。如果你有大规模数据集把shutil.copy换成os.link硬链接可以避免复制文件的IO开销前提是源目录和输出目录在同一个磁盘分区。这套分割脚本跑完项目的目录结构会是这样dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── train.txt └── val.txt其中labels和images两棵树完全同步同名文件的txt和图片一一对应。这个结构是yolov8直接认的也是yolov5官方推荐的目录风格。到这一步格式转换和数据分割就都完成了接下来就是把这份目录交给训练框架去跑。4. 从零跑通整条链路VOC目录到YOLOv8开训数据分割完成后整条链路还差最后两步写一个数据集配置文件data.yaml然后启动训练。这一章把从VOC原始目录到yolov8真正跑起来的过程串一遍每一步要做什么、文件放哪里、命令怎么敲都给出可直接复制的版本。4.1 整理VOC原始目录先统一素材再动手很多人的VOC数据集是从网络上东拼西凑的有的目录叫JPEGImages有的是VOCdevkit/VOC2007/JPEGImages还有的是从别人的项目里拷来的散装文件夹。第一步不是急着转换而是把所有图片和xml文件分别汇总到两个干净目录里避免在转换脚本里写一长串难维护的绝对路径。我一般会建一个工作目录叫dataset_raw里面分images和annotations两个子目录。用下面这段命令把散落的文件归拢# 把所有jpg/png图片汇总到 dataset_raw/images mkdir -p dataset_raw/images dataset_raw/annotations find . -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) -exec cp {} dataset_raw/images/ \; # 把所有xml汇总到 dataset_raw/annotations find . -type f -name *.xml -exec cp {} dataset_raw/annotations/ \;逻辑说明find配合-exec cp把分散在子目录里的文件都复制到统一目录里。完成后检查一下两个目录里的文件数量如果数量和预期差很多多半是有重名文件互相覆盖了先处理再继续。这一步不强求用代码脚本手动在文件管理器里整理也行但用命令更省事尤其是文件特别多的时候。整理完的目录里只做一件事检查是否有文件名重复但内容不同的情况比如两个000001.jpg来自不同数据集这会直接导致转换和训练时互相覆盖。4.2 编写data.yamlYOLO训练的数据入口yolov8训练自己的数据集时靠一个yaml文件描述数据和类别信息。这个文件放在哪里都行但通常放在和dataset平级的位置写绝对路径比较容易踩坑我习惯用相对路径让项目整个目录可以整体迁移。在dataset目录下新建data.yaml# 训练集和验证集图片所在目录相对路径基于当前工作目录 train: images/train val: images/val # 类别数量 nc: 3 # 类别名称列表顺序必须和转换脚本里的CLASS_MAP一致 names: 0: person 1: car 2: bicycle逻辑说明train和val指向的是图片目录不是txt目录。YOLO在训练时会在同级的labels目录下自动找对应标注文件。nc是类别总数names是类别名列表索引必须和转换脚本里的CLASS_MAP对齐。这里的顺序一旦不对模型断训饱和后跑出来的结果就是类别语义全错位排查起来很返工。注意有些教程里会把train写成绝对路径比如/home/user/dataset/images/train。换机器之后还得改直接用相对路径更省心前提是你把训练命令的工作目录设成dataset的上一级。4.3 启动训练常用命令与参数调优入口yolov8和yolov5在训练数据导入上逻辑一样只是命令格式不同。下面把两个版本都列出来你在哪个环境就用哪套。yolov8需要先装好ultralytics包和对应的PyTorch环境正式开跑之前至少确认import torch和import ultralytics都不报错。yolov8命令yolo detect train datadata.yaml modelyolov8s.pt epochs100 batch16 imgsz640yolov5命令在yolov5仓库目录下执行python train.py --data ../dataset/data.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch 16两个命令的语义基本一致data指向yaml文件model或weights指定预训练权重epochs控制迭代轮数batch按显存调整imgsz或img是训练分辨率。如果是小尺寸目标建议imgsz640起步想更稳就上1280但显存占用会明显增加。首次训练建议先用最小的yolov8n.pt或yolov5s.pt跑通整个流程确认数据加载、标注读取、loss计算都没问题再换大模型。数据格式上的错误往往在第一个epoch就暴露出来用最小模型能帮你最快发现问题。看到进度条正常前进、每轮loss下降再考虑正式训练。从VOC原始目录到这一步整个过程已经全部跑通了。后面的路就是训练和调参了这篇的重点是格式与分割所以训练细节点到为止。5. 避坑指南数据转换最常见的五个翻车现场格式转换和数据分割涉及大量文件操作和数学计算任何一个环节出错都会让训练结果变得莫名其妙。下面这五个坑都是我实际见过甚至自己踩过的按“现象、原因、解决”的顺序逐一说明希望你能在运行脚本之前就避开。5.1 类别索引从0还是从1开始错位得让人毫无察觉现象转换脚本跑完打开一个txt看到第一列全是1、2、3训练时loss正常下降但最后用模型预测把person识别成了car。原因labelimg里界面显示的类别标签从1开始很多人习惯性地把类别写成{person: 1, car: 2}但YOLO的类别索引必须从0开始。索引错一位所有类别语义整体偏移。解决写转换脚本时在voc2yolo内部加一条断言检查所有class_id是否落在[0, len(class_map))区间内。如果发现从1开始脚本直接报错而不是默默写进txt。一个简单的做法是转换后用下面的脚本快速检查所有txt里出现的类别索引cat yolo_labels/*.txt | awk {print $1} | sort -n | uniq输出结果应该是从0开始的连续整数序列。如果最小的是1说明映射表写错了。5.2 归一化时除错了分母坐标直接飞出画布现象转换后的txt里width或height大于1甚至出现1.3741这种明显越界的数字。训练时模型无法收敛或者画框全画到图片外面。原因公式里x相关的值要用图片宽度做分母y相关的值要用图片高度做分母。有人图省事统一除以图片宽度或者把xmax - xmin除以了图片高度结果框的宽高比例全错。解决在写入txt之前加一个检查所有归一化后的值必须落在[0, 1]区间内出现越界立即报错。下面这行代码可以在写入前过滤掉异常数据if not (0 x_center 1 and 0 y_center 1 and 0 width 1 and 0 height 1): raise ValueError(f归一化结果越界: {x_center}, {y_center}, {width}, {height})这条检查能保住一大堆后续问题别偷懒不加。5.3 图片和xml文件不对齐训练到一半才报错现象转换脚本提示“找不到对应图片”或者在yolov8训练到中途时直接报错“Cant find image: xxx.jpg”。原因数据来源比较复杂有的xml标注了但同名图片被删了有的图片文件名多了空格或后缀大小写不一致。转换脚本按stem去匹配文件名遇到大小写混用或者后缀不匹配就落空。解决在转换脚本的batch_convert里做两件事。第一列出所有xml和所有图片的文件名做交集只转换两边都存在的文件第二使用Path.glob模糊匹配后缀而不是硬编码.jpg。更省事的做法是转换前跑一遍文件对比先把缺图片的xml或缺xml的图片移到一个orphans目录别让它们混在数据集里。5.4 分割时没考虑类别均衡验证集里缺了一个类现象训练结束后某个类别的验证集mAP是0但训练集的loss表现正常检查代码逻辑也没错。原因分割时纯随机打乱小类别样本数量少恰好全部落在了训练集里验证集一个样本都没有。验证集没有真值这个类别在评估时自然得0分。解决写分割脚本时增加分层抽样逻辑先按每张图片包含的类别做统计再在切分时确保每个类别在训练集和验证集都保留一定比例。最简单粗暴的办法是每个类别最少留出10%的样本到验证集剩余再放回随机池子里。用5.1的检查方法在分割完成后按类别统计一下训练集和验证集的样本数分布大致均匀就可以放心用了。from collections import Counter val_counter Counter() for txt_path in Path(dataset/labels/val).glob(*.txt): with open(txt_path) as f: for line in f: if line.strip(): val_counter[int(line.split()[0])] 1 print(验证集类别分布:, val_counter)5.5 xml文件编码或格式异常解析时莫名其妙崩现象转换脚本跑到一半抛ParseError或者某个xml解析出来name是乱码。原因部分打标工具导出的xml文件带了BOM头文件开头多了几个不可见字符或者某个环节用记事本手动编辑过留下了奇怪的编码。ET.parse默认按UTF-8解析遇到BOM头直接报错。解决在读取xml时用encodingutf-8-sig代替默认编码这个编码会自动吃掉开头的BOM标记。脚本里已经用过这个读法了如果你自己写的脚本没有改成下面这样就行with open(xml_path, r, encodingutf-8-sig) as f: tree ET.parse(f)如果还想更稳可以在batch_convert里加上异常捕获单个xml解析失败时打印文件名并跳过而不是让整个转换流程中断。这样一次能跑完整个目录最后看日志统一处理有问题的文件。6. 转换完先别急着训练用可视化验证结果数据转换和分割全部跑完直接丢给yolov8开训当然可以但99%的人都忽略了一步把转换后的txt标注画回图片上肉眼确认一遍。格式转换的错误在数字上很难看出来但画到图片上一眼就能发现框没对齐、类别标反、坐标越界这些问题。6.1 把YOLO格式的标注画回图片上看一眼下面的脚本随机抽取几张验证集的图片把txt里的标注框画上去保存到本地。跑完直接打开看框应该贴合在目标上类别名和实际物体一一对应。import random from pathlib import Path from PIL import Image, ImageDraw def visualize_labels(image_dir, label_dir, class_names, output_dirvis, num_samples10): image_dir Path(image_dir) label_dir Path(label_dir) output_dir Path(output_dir) output_dir.mkdir(exist_okTrue) images list(image_dir.glob(*.jpg)) list(image_dir.glob(*.png)) random.shuffle(images) for img_path in images[:num_samples]: label_path label_dir / (img_path.stem .txt) if not label_path.exists(): continue img Image.open(img_path).convert(RGB) draw ImageDraw.Draw(img) w, h img.size with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) x_center, y_center, box_w, box_h map(float, parts[1:5]) # 把归一化坐标还原成像素绝对坐标 x1 int((x_center - box_w / 2) * w) y1 int((y_center - box_h / 2) * h) x2 int((x_center box_w / 2) * w) y2 int((y_center box_h / 2) * h) draw.rectangle([x1, y1, x2, y2], outlinered, width2) draw.text((x1, max(0, y1 - 10)), class_names[cls_id], fillred) out_path output_dir / img_path.name img.save(out_path) print(f保存可视化结果: {out_path}) if __name__ __main__: visualize_labels( image_dirdataset/images/val, label_dirdataset/labels/val, class_names[person, car, bicycle], num_samples10 )逻辑说明脚本读取一张图片及其同名txt把归一化的框坐标换算回像素坐标用PIL画矩形框和类别名最后保存到vis目录。num_samples10表示随机抽10张图来看够用了。如果你发现框明显偏移或者类别名对不上那一定是前面哪一步出了问题按第5章的内容排查。画完框之后还要确认一件事txt的坐标和图片是否来自同一分辨率。有的图片被resize过但xml里的坐标还是原始尺寸画框时就会看到框整体错位。如果有这种情况需要先统一图片分辨率再转换。6.2 用数据分布统计来提前判断训练效果可视化看的是单张图片的准确性但要评估整个数据集的质量还需要统计一下类别分布和框尺寸分布。一段很短的脚本就能给出全局数据画像import statistics from collections import Counter from pathlib import Path def stat_dataset(label_dir): label_dir Path(label_dir) class_counter Counter() box_widths [] box_heights [] for txt_path in label_dir.glob(*.txt): with open(txt_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) class_counter[cls_id] 1 box_widths.append(w) box_heights.append(h) total sum(class_counter.values()) print(各类别样本数及占比:) for cls_id in sorted(class_counter): print(f 类别 {cls_id}: {class_counter[cls_id]} 个框, f{class_counter[cls_id] / total:.1%}) print(f框平均宽占比: {statistics.mean(box_widths):.3f}) print(f框平均高占比: {statistics.mean(box_heights):.3f}) if __name__ __main__: stat_dataset(dataset/labels/val)逻辑说明这段脚本统计验证集里每个类别的框数量和占比顺带算一下框的平均宽高占比。如果某个类别只占不到1%那这个任务很可能存在严重的类别不均衡训练时要么提升类别的loss权重要么考虑做数据增强补充样本。框尺寸占比偏小说明目标很小后续训练要适当调大imgsz或者关注小目标检测层的输出。我自己的习惯是每换一个数据集转换完第一件事不是训练而是先跑可视化和统计脚本。不眼见为实总觉得数据集里藏着什么“惊喜”。做了这一步训练时的心态会稳很多。数据转换和分割这件事本身不难难的是每个环节都确认到位。希望帮到你少走这些冤枉路。本文还有配套的精品资源点击获取
返回列表