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

资讯详情

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

VOC转YOLO格式实战:YOLOv8小样本目标检测训练指南

VOC转YOLO格式实战:YOLOv8小样本目标检测训练指南 简介这是一份面向目标检测初学者与算法工程师的蜈蚣图像数据集包含237张真实场景图片统一采用Pascal VOC与YOLO两种常见格式标注可直接用于Centipede单类别检测模型的训练、验证与格式转换测试。压缩包共713个文件其中jpg图片237张、xml标注237份、txt标注239份含YOLO格式标注与类别清单整体大小68.14MB目录结构简单明确便于快速加载。所有标注由labelImg工具完成画框规则一致共255个目标框类别统一为Centipede满足YOLO系列、SSD等主流框架的输入要求。数据集不附带模型权重但每张图片的xml与txt标注一一对应且不含分割路径文件适合用于数据预处理、标注格式转换、数据增强实验及模型评估等环节。目前已有96人学习使用可作为课程设计、算法练手或迁移学习的前期数据帮助用户省去自行采集和标注的时间专注验证模型效果。1. 237张蜈蚣图片的数据集VOC与YOLO格式对齐后再谈训练解压zip后看到的是一组 firc_Centipede 前缀的jpg图片每个文件名对应一个VOC格式xml和一个YOLO格式txt。先不急着看图片内容把三套文件按文件名做一次对齐firc_Centipede_44.jpg 是否有同名的 firc_Centipede_44.xml 和 firc_Centipede_44.txtxml里object数量和txt行数是否相等。这套数据集237张图、255个框平均每张图只有1.07个目标绝大多数图片是单目标样本。但蜈蚣躯体细长且姿态弯曲标注框在不同图片中的朝向和长宽比差异很大直接套用YOLOv8默认配置训练容易在细长目标和小目标端漏检。它适合两类场景一是验证VOC与YOLO双格式转换脚本的数据链路是否可靠二是作为小样本目标检测的基准数据配合迁移学习或半监督方案快速跑通完整流程。2. 双格式标注的构成与转换陷阱从XML到TXT的坐标换算2.1 VOC XML 保留了什么又省略了什么典型Pascal VOC数据集通常有Annotations、JPEGImages和ImageSets/Main三层结构其中ImageSets/Main提供train.txt、val.txt等划分索引。这份蜈蚣数据集没有包含分割路径的txt文件解压后根目录直接是jpg图片和对应的xml、txt也就是说它只保留标注本身。好处是使用时不受预划分限制按8:1:1重新随机划分时不用先去解析索引坏处是如果不做一次独立划分训练脚本很可能把同一组图片既当训练又当验证指标虚高。XML节点很精简。object节点里出现的是name和bndboxtruncated、occluded这类细分属性不会写入difficult虽然在labelImg默认模板中存在但未勾选时固定为0相当于不参与难例抑制。difficult字段缺失意味着训练时无法通过该标记跳过困难样本换来的好处是标注规则统一只要框能包住蜈蚣主体就算有效框。处理xml时要按这个前提设计过滤逻辑不要把缺少属性的框当作脏数据删掉。2.2 从XML字段到YOLO归一化坐标的公式YOLO格式txt文件中每行五个数值class_id、x_center、y_center、width、height。class_id是整数从0开始计数这份数据只有Centipede一个类别所以正常txt里每行第一个值固定是0如果发现1或者其他数字说明文件混入了别的来源的标注要以xml为准重新生成。后四个值全部是相对图片宽高的归一化浮点数取值范围在0到1之间即使物体只占几十个像素归一化后也是零点零几的小数。字段计算公式说明x_center(xminxmax)/2 / image_width中心点x坐标除以图片宽y_center(yminymax)/2 / image_height中心点y坐标除以图片高width(xmax-xmin) / image_width框宽归一化后远小于1height(ymax-ymin) / image_height框高蜈蚣框通常长宽比明显下面用一份XML结构示意说明字段含义annotation filenamefirc_Centipede_44.jpg/filename size width640/width height480/height depth3/depth /size object nameCentipede/name bndbox xmin220/xmin ymin100/ymin xmax430/xmax ymax380/ymax /bndbox /object /annotation上面xml表示一张640x480 RGB图片里有一个矩形框框的像素坐标为(220, 100)到(430, 380)。按公式换算x_center (220430)/2/640 0.5078y_center (100380)/2/480 0.5width (430-220)/640 0.3281height (380-100)/480 0.5833对应YOLO txt行0 0.5078 0.5 0.3281 0.5833。这套换算本身不难实际项目里最常出错的不是除法而是宽度高度没有按像素坐标区间计算。像素坐标的连续差值应加1例如宽度为xmax-xmin1当目标只有几个像素大时这个误差会被放大精细评估时建议统一按XML原始整数计算避免不同脚本之间标准不一致。提示yolo txt要求类别索引从0开始。如果解压后txt第一列是1而不是0多数情况是标注时类别表里多了一个背景类必须把全部txt第一列减1再训练否则背景类会占掉一个输出通道。2.3 用Python批量转换并核对标注一致性import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(annotations) txt_dir Path(labels) txt_dir.mkdir(exist_okTrue) for xml_path in sorted(xml_dir.glob(*.xml)): tree ET.parse(xml_path) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) lines [] for obj in root.findall(object): name obj.findtext(name) if name ! Centipede: continue # 只保留指定类别 bnd obj.find(bndbox) xmin float(bnd.findtext(xmin)) ymin float(bnd.findtext(ymin)) xmax float(bnd.findtext(xmax)) ymax float(bnd.findtext(ymax)) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path txt_dir / (xml_path.stem .txt) txt_path.write_text(\n.join(lines), encodingutf-8)这段代码把xml目录里所有Centipede框转换成YOLO格式txt。使用findtext(size/width)直接取值准确性取决于labelImg写入的size节点与真实图片分辨率是否一致所以转换前用PIL读取图片做一次比对更稳妥。类别名过滤写死为Centipede这份数据只有这一个类但通用脚本里应维护类别名到id的映射否则类别顺序一调整所有txt的class_id都会错位。转换后还有三个隐藏陷阱。第一bndbox里可能出现小数坐标float直接使用没问题写回XML时不要round成整数有些标注工具会把框微调出亚像素精度。第二当物体部分超出图片边界时xmin可能小于0或xmax大于img_w直接归一化会得到负数或大于1的值YOLO训练时会警告并裁剪该框这类样本应在划分前单独统计处理而不是让训练阶段静默吃掉。第三检查txt里是否出现空文件空文件表示当前图片没有Centipede目标数量应和xml里object为零的图片数量一致任何不一致都说明文件配对出了问题。3. 训练前的数据体检逐文件解析XML并可视化抽样检查3.1 一致性校验优先于模型训练数据处理链路里最容易丢信息的就是文件配对阶段。237张jpg、237个xml、237个txt三份文件数量完全相等不意味着每个stem都对应。常见错误是某些图片被重复格式化后多出副本或者xml和txt命名规则不同比如jpg是下划线命名xml里filename字段却写成了带路径的完整文件名。先用stem匹配做一次全量检查能避免YOLO训练epoch跑了一轮才发现val集为空。from pathlib import Path img_dir Path(images) xml_dir Path(annotations) txt_dir Path(labels) imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} txts {p.stem for p in txt_dir.glob(*.txt)} print(only images:, imgs - xmls - txts) # 输出只出现在图片集合里的文件 print(only xml:, xmls - imgs - txts) print(only txt:, txts - imgs - xmls)运行结果应为三行空集合。若有交集缺失优先修复xml和txt命名。图片文件名里下划线和数字后缀混排比如firc_Centipede_193和firc_Centipede_20按字符串排序时20会排在193之前导致划分训练集时顺序不稳定所以后续划分要显式设置random.seed再做shuffle。接下来读取所有xml统计每个框宽高比和面积占比。蜈蚣身体的细长形态决定了它的框往往呈长条形但不同图片里的蜈蚣可能卷曲、伸展、或只露出部分躯体造成宽高比分布跨度很大。如果统计结果显示宽高比集中在0.1到0.3或3.0以上说明标注倾向于框出整体延伸方向如果集中在1附近说明为了包住触角把框画大了。这种分布直接影响输入缩放策略和NMS阈值调整方向。3.2 宽高比分布与目标尺寸检查import xml.etree.ElementTree as ET from pathlib import Path from collections import defaultdict stats defaultdict(int) for xml_path in sorted(Path(annotations).glob(*.xml)): root ET.parse(xml_path).getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) for obj in root.findall(object): bnd obj.find(bndbox) w float(bnd.findtext(xmax)) - float(bnd.findtext(xmin)) h float(bnd.findtext(ymax)) - float(bnd.findtext(ymin)) ratio w / h if h 0 else 0 if ratio 0.5: stats[0.5 高瘦] 1 elif ratio 1.0: stats[0.5-1.0 接近正方形] 1 elif ratio 2.0: stats[1.0-2.0 略宽] 1 else: stats[2.0 宽扁] 1 for k, v in sorted(stats.items()): print(f{k}: {v})stats字典按阈值累计宽高比区间。这里按h0过滤是为了防止畸形标注框正常情况下h不会为0。以255个总框数为例实测常见分布可能接近宽高比区间框数判断0.5 高瘦约60细长目标占比较高0.5-1.0 接近正方形约95卷曲姿态比较常见1.0-2.0 略宽约70横向伸展2.0 宽扁约30少数大幅伸展高瘦框占比高时不必像YOLOv5那样聚类anchorYOLOv8是anchor-free结构检测头直接回归中心点和宽高。但输入缩放策略对细长目标影响很大640x640的矩形resize会把窄域目标压得更细建议训练时开启rectTrue让同一batch内按原始长宽比对齐减少resize拉伸对躯体形态的破坏。代价是每个batch的图片尺寸不同训练耗时略长在237张图规模下可以接受。3.3 可视化抽样比看数字更能发现问题脚本统计能得到分布但框是否贴合物体边界、是否漏掉触角、是否把背景一并框进去必须用画框图片抽查。把255个框全部画到图上保存为预览集按文件名排序后每20张抽1张看一遍重点观察三类异常框明显小于物体、框明显不贴合弯曲身体、不同图片之间框坐标标准不一致。from PIL import Image, ImageDraw import xml.etree.ElementTree as ET xml_path annotations/firc_Centipede_44.xml img_path images/firc_Centipede_44.jpg tree ET.parse(xml_path) root tree.getroot() boxes [] for obj in root.findall(object): bnd obj.find(bndbox) boxes.append(( obj.findtext(name), float(bnd.findtext(xmin)), float(bnd.findtext(ymin)), float(bnd.findtext(xmax)), float(bnd.findtext(ymax)), )) with Image.open(img_path) as im: draw ImageDraw.Draw(im) for name, xmin, ymin, xmax, ymax in boxes: draw.rectangle([xmin, ymin, xmax, ymax], outline(255, 0, 0), width3) draw.text((xmin, max(ymin - 14, 0)), name, fill(255, 0, 0)) im.save(preview_44.jpg)PIL绘图时坐标最好先转成int部分Pillow版本对浮点参数会做截断而非四舍五入1像素之差在可视化阶段看不出影响在评估阶段却会让mAP在0.5和0.75阈值之间波动。draw.text里坐标写成(xmin, ymin-14)时如果ymin小于14就会超出画布PIL不报错但文字画到图片外面输出图看起来像丢标签排查时容易误判。上面用max(ymin-14, 0)做了钳制。4. YOLOv8快速训练与Loss观察点4.1 数据集划分与data.yaml配置这份数据没有现成的train.txt和val.txtyolo detect train只读取data.yaml指向的目录因此要先把图片按8:1:1划分到训练、验证、测试三个目录。推荐目录划分而不是生成带绝对路径的txt索引后续做离线增强时只需按目录复制文件不会出现索引路径失效。import random import shutil from pathlib import Path random.seed(42) images sorted(Path(images).glob(*.jpg)) shuffle_images images[:] random.shuffle(shuffle_images) train_dir Path(split/train/images) val_dir Path(split/val/images) test_dir Path(split/test/images) for d in (train_dir, val_dir, test_dir): d.mkdir(parentsTrue, exist_okTrue) n len(shuffle_images) for i, img in enumerate(shuffle_images): if i int(n * 0.8): shutil.copy(img, train_dir / img.name) elif i int(n * 0.9): shutil.copy(img, val_dir / img.name) else: shutil.copy(img, test_dir / img.name)分割后把labels目录按同样规则复制。这里有个数据泄露盲区如果同一张蜈蚣在不同图片中背景相似随机划分可能把高度相似的样本同时放进train和val导致验证指标虚高。firc_Centipede系列文件名带连续编号有很大概率来自同一轮拍摄背景连续性明显更稳妥的做法是以拍摄批次为粒度分组划分但文件名没有提供足够的分组信息退而求其次只能固定随机种子后多次划分取均值。random.seed(42)就是保证复现避免每次重新划分得到不同分布。path: /absolute/path/to/centipede train: split/train/images val: split/val/images test: split/test/images names: 0: Centipede单个类别时names字典只保留0这一项。val图片如果不到30张mAP曲线波动会比较大建议同时盯住P和R曲线的变化趋势而不是单看mAP50峰值波动在5个点以内都正常。4.2 训练命令与关键超参数yolo detect train \ datacentipede.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ device0 \ lr00.005 \ close_mosaic10 \ rectTrue \ seed42 \ cacheTrue \ plotsTruemodel指定yolov8n.pt表示从预训练权重开始迁移。237张图样本量不大选用yolov8n而不是yolov8s可以明显减少过拟合参数量从11M降到3.2M训练时长取决于显卡单张RTX 3060上经验值在20分钟左右。imgsz640会把图像拉伸到640x640如果原始图片尺寸小于640建议先统计所有图片宽高分布选一个接近的32倍数作为训练尺寸避免把小物体进一步缩小。rectTrue在这里比imgsz更值得注意它能保住蜈蚣长条外观不被resize破坏。超参数里最需要手工调整的是lr0和close_mosaic。lr0默认0.01在COCO这种大规模数据上效果不错但这里只有255个框步子太大会让预训练权重在前几十个epoch被破坏性更新使用0.005并配合默认的cos_lr让学习率平滑衰减更合理。close_mosaic10表示最后10个epoch关闭mosaic增强mosaic会把四张图拼在一起蜈蚣细长躯体容易被拼接缝切断最后阶段切回纯样本精修细节。参数推荐值区间作用本数据集建议epochs100-300总训练轮数200batch8-16单次迭代样本量16imgsz416-640输入分辨率640lr00.003-0.01初始学习率0.005patience30-80早停轮数50close_mosaic5-15最后关闭mosaic的轮数10rectTrue/False按长宽比打包batchTrueseed固定整数复现随机流程424.3 单类别训练怎么看Loss训练过程的loss曲线有box_loss、cls_loss、dfl_loss三条主线。box_loss负责预测框与真实框的误差cls_loss负责分类置信度dfl_loss是distribution focal loss用于框回归的细粒度优化。因为只有1个类别cls_loss应当快速下降并保持在很低水平如果cls_loss一百个epoch后仍在波动优先查data.yaml中names是否写错以及标注类别名大小写是否和xml里一致。Box_loss和dfl_loss才是单类别场景需要盯住的两条曲线。正常训练里前几十个epoch下降明显大约在epoch 60到120之间进入平台期后期靠小学习率继续微调。如果box_loss在第1个epoch就异常低或者训练结束后仍在剧烈震荡检查三件事标注坐标是否越界、划分目录是否混入不在labels里的jpg、rectTrue是否导致某几张极小分辨率图片被放大到640后出现严重伪影。这几个问题都排在调模型结构之前因为它们产生的坏样本会持续污染整个训练过程。5. 用离线增强盘活255个框让细长蜈蚣保留完整形态5.1 哪些增强可以保留哪些必须谨慎在线增强已经在训练期间自动应用但想验证增强策略是否合理离线生成一份增强副本更直观。蜈蚣目标细长水平翻转和旋转是小样本下最安全的两个操作不会破坏目标主体。随机裁剪会切断身体除非裁剪范围严格控制否则不建议在小样本场景使用。亮度、对比度和HSV变化可以放心用这类光度失真不影响矩形框位置。5.2 用albumentations生成增强训练集import albumentations as A import cv2 from pathlib import Path transform A.Compose([ A.HorizontalFlip(p0.5), A.Rotate(limit30, border_modecv2.BORDER_CONSTANT, value0, p0.6), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.8), ], bbox_paramsA.BboxParams( formatpascal_voc, label_fields[labels], min_visibility0.8, ))pascal_voc格式对应(xmin, ymin, xmax, ymax)min_visibility0.8表示增强后框与目标的重叠比例低于0.8时该框会被丢弃避免旋转后空框残留在标注里。读取XML框架和写入新XML的代码可复用第2章内容组合起来对每张原图生成2到3份副本使训练集从237张扩充到600张左右。变换顺序要注意先做几何变换再做光度变换旋转后立刻记录新坐标亮度调整不影响框坐标。增强副本导出后把增强图和原图合并重新跑一遍第4章划分脚本训练效果会比直接跑原始数据更容易收敛。判断增强是否有效看两个地方val召回率在低置信度段是否提升漏检是否集中在某一特定朝向。如果召回率提升但精度下降说明旋转幅度过大导致框膨胀超出蜈蚣实际轮廓把Rotate的limit从30降到20并重新调整min_visibility。导出的txt文件若沿用原文件名增强图又与源图放在同一目录YOLO训练会同时读两个同名文件导致标签覆盖所以离线增强输出目录必须独立且不要在增强目录上直接执行第4章的复制脚本要先合并文件名再复制。建议在导出增强副本时把文件名写成firc_Centipede_44_aug1.jpg这种带后缀的格式同时在csv里记录source、aug_params、out_file三个字段后续badcase反查时直接按csv过滤不需要打开图片逐个对比。本文还有配套的精品资源点击获取
返回列表