ARTICLE DETAIL

资讯详情

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

多角度猕猴桃数据集实战:VOC转YOLO格式与YOLOv8训练全流程

多角度猕猴桃数据集实战:VOC转YOLO格式与YOLOv8训练全流程 简介这是一套面向目标检测教学与实操的猕猴桃数据集专为YOLO、SSD等单阶段检测模型训练而设计。图片均为猕猴桃放入盘中后的不同角度摆拍场景简洁、目标尺度清晰包含单类别Kiwi矩形框标注5255个不同视角样本有助于提升模型对摆放姿态变化的鲁棒性。压缩包共2000个文件以VOC格式XML标注、TXT标签及说明文件为主整体约74.58MB目录结构清晰可按训练、测试或类别直接检索。对刚接触目标检测的开发者借助XML与TXT双格式对比可深入理解VOC与YOLO两种标签体系的坐标归一化差异对已有经验者可直接替换预设路径接入YOLO训练流程。资源已有106人学习适合作为高校实验课、毕设或业余练手项目的训练数据补充。1. 为什么一个“摆拍”的猕猴桃数据集值得当真很多做目标检测的朋友拿到一个陌生数据集第一反应是扫一眼标签就开训。这个标题里“1700张”“VOCYOLO”都不稀奇真正值钱的是“不同角度摆拍图”这七个字——意味着同一个猕猴桃被拍了很多个视角而不是网上随手爬来的零散照片。目标检测落地到果园分拣、货架盘点这类场景模型必须对同一个果在不同姿态下都稳得住这种多视角样本正好用来验证你的检测器有没有真正“认得”猕猴桃而不是只记住了某一个角度。适合谁想走通“数据集→训练→验证”全流程的工程师以及正在为农业视觉项目做算法选型、需要先有一个干净可控底座数据集的人。2. 拆开看看VOC和YOLO两套标注格式如何共存互补很多下发数据集都喜欢同时给VOC和YOLO两份标注因为标注工具习惯导出VOC训练框架更爱吃YOLO。两份格式本质上描述完全一样的框但组织方式和使用边界差得远先搞清这一点后面转换才不会出幺蛾子。2.1 VOC的xml与YOLO的txt同一批框的两种描述方式先看VOC侧每个图片对应一个同名xml典型内容长这样annotation folderJPEGImages/folder filenamekiwi_015_side_02.jpg/filename size width1920/width height1080/height depth3/depth /size object namekiwi/name difficult0/difficult bndbox xmin622/xmin ymin384/ymin xmax1218/xmax ymax916/ymax /bndbox /object /annotation对应到YOLO侧同样这一张图的txt里只有一行0 0.479166 0.601852 0.310416 0.492592VOC是像素绝对坐标xmin/ymin/xmax/ymax直接落在原图分辨率上人眼直观也方便在LabelImg这类常用标注工具里复核YOLO则是归一化中心坐标前两项是框中心在整图中的比例位置后两项是框的宽高占整图的比例。YOLO系训练框架Ultralytics YOLO、YOLOX、MMYOLO统一吃归一化txt因为阅读、缩放、数据增强时都不需要关心原始分辨率效率高且不容易破框。维度VOC XMLYOLO txt坐标基准像素绝对坐标归一化中心坐标存储方式一个框一组标签节点每行类ID 4个浮点数可读性人可读、工具兼容好机器高效、文件小训练适配需转换为YOLO格式YOLO系框架直接读取常见工具LabelImg导出、LabelStudio导出Ultralytics框架自动读取这里有个细节VOC里的difficult字段经常被忽略。如果这个字段为1表示该目标太小或遮挡严重标注员都不确定。转到YOLO时如果不过滤这类模糊正样本会被当成正常目标去算损失单类数据集里尤其容易把模型带偏。所以转换脚本里留着这个开关有用。2.2 解压后先看目录VOC与YOLO的布局差异常见压缩包解压出来是两棵树一边是VOCFormat一边是YOLOFormatKiwifruit1700/ ├── VOCFormat/ │ ├── JPEGImages/ # 1700张jpg原图 │ ├── Annotations/ # 1700个xml标注 │ └── ImageSets/Main/ # 可选train.txt/val.txt └── YOLOFormat/ ├── images/ │ ├── train/ # 按比例分好的图 │ └── val/ └── labels/ ├── train/ # 与images同名的txt └── val/如果你的压缩包是这种结构YOLOFormat下的images和labels已经按比例切好直接用Ultralytics YOLO训练就行VOCFormat更多是用来复核标注质量、在LabelImg里重新检查。要是解压后只有VOCFormat就得自己划分目录并转格式这个流程第3章会完整走一遍。一个容易忽略的地方YOLO标签文件后缀虽然是.txt但文件名前缀必须和图片一字不差jpg、png无所谓前缀不一致框架就会静默跳过。解压后我习惯先随机抽10对“图txt”对一下ls YOLOFormat/labels/train | head -5 ls YOLOFormat/images/train | head -5如果前几个txt在images里能找到同名图片说明这批标签对齐了如果txt比图多或者图比txt多先排查再训练多半是导出标注时漏了难例或重复导出。2.3 从VOC到YOLO的转换脚本与坐标边界如果拿到的是纯VOC或者你想按自己的规则重新划分转换这一步绕不开。下面这个脚本是常见做法不依赖额外库纯Python标准库就能跑适合丢进任何训练环境import xml.etree.ElementTree as ET from pathlib import Path CLASS_MAP {kiwi: 0} # 类名 - class_id单类猕猴桃 def voc_to_yolo(xml_path: Path, out_txt_path: Path, min_w: float 2.0, min_h: float 2.0): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue difficult int(obj.find(difficult).text) if obj.find(difficult) is not None else 0 if difficult 1: continue # 难例框直接跳过 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) w xmax - xmin h ymax - ymin if w min_w or h min_h: continue # 过滤小于2像素的退化框 # 归一化中心坐标务必用浮点除法 x_center ((xmin w / 2) / img_w) y_center ((ymin h / 2) / img_h) norm_w w / img_w norm_h h / img_h # 越界截断防止出现1.000001这种让加载器报错的值 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) norm_w min(norm_w, 1.0 - x_center) norm_h min(norm_h, 1.0 - y_center) lines.append(f{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}) if lines: out_txt_path.write_text(\n.join(lines) \n) # 调用遍历Annotations目录逐个生成同名txt ann_dir Path(VOCFormat/Annotations) label_dir Path(converted_labels) label_dir.mkdir(exist_okTrue) for xml_file in ann_dir.glob(*.xml): voc_to_yolo(xml_file, label_dir / (xml_file.stem .txt))逻辑说明脚本按XML里的size做归一化逐object输出一行difficult1的框跳过是因为小目标或严重遮挡样本在单类数据里只会制造噪声坐标截断是为了防越界很多框架在读取标签时会把不在0-1范围的坐标当损坏样本丢进黑匣子报错还很不显眼。参数说明min_w和min_h默认2像素过滤掉退化框CLASS_MAP决定输出class_id多类扩展时改成完整字典即可。这个脚本隐含了几个边界坑XML的bndbox可能框出图像边界、整数除法会丢精度、size字段可能缺失、中文和空格文件名可能让框架解析路径失败。每一个都在第4章的排查清单里这里先不展开。3. 用1700张猕猴桃数据训练YOLOv8从数据划分到首次收敛的命令级流程3.1 按“猕猴桃个体”分组划分数据别随机切1700张是个不大的量最怕的就是划分时把同一个猕猴桃的不同角度拆进训练集和验证集。随机划分看起来公平但同一颗果的侧视图在train、俯视图在val等于把“见过这个果”的信息泄漏给了模型验证分数虚高。真正上线时模型面对的是它没见过的其他果表现会明显掉一截。怎么避免常见做法是按“原对象”分组划分。如果文件名是类似kiwi_015_side_01.jpg这种带编号的结构就按“kiwi_015”这个前缀分到同一组如果文件名没规律用感知哈希也不一定灵——不同角度下同一颗果的像素差异太大。我一般先用正则提取编号前缀按前缀分组再对组列表做8:2划分import random, os from pathlib import Path from collections import defaultdict img_dir Path(YOLOFormat/images_all) # 所有原图集中放在这 imgs sorted(os.listdir(img_dir)) groups defaultdict(list) for name in imgs: # 假设文件名形如 kiwi_015_side_01.jpg取“kiwi_015”作为果体编号 prefix name.rsplit(_, 2)[0] # - kiwi_015 groups[prefix].append(name) gids list(groups.keys()) random.seed(42) random.shuffle(gids) split int(len(gids) * 0.8) train_gids set(gids[:split]) val_gids set(gids[split:]) train_imgs [f for g in train_gids for f in groups[g]] val_imgs [f for g in val_gids for f in groups[g]] print(ftrain {len(train_imgs)} imgs / {len(train_gids)} fruits, fval {len(val_imgs)} imgs / {len(val_gids)} fruits)逻辑说明先把图按果体编号收集再在果体级别上打乱和划分保证同一个果的所有角度都落在同一边。假设1700张来自50个果、每个果34张按果划分后train/val在果级别互斥得到的mAP会比随机划分低几个点但那个分数才是模型面对新果的真实能力。参数说明seed42固定复现结果rsplit(_, 2)按“编号_角度_序号”的命名假设提取前缀。如果你的文件名不是这个结构换成其他分离方式。划分完把两个img列表各写一个txt清单供data.yaml引用也可以直接把图片移动或复制进images/train和images/val。3.2 训练命令与四个必调参数有了划分好的目录先写一个极简的data.yamlpath: /data/Kiwifruit1700 train: YOLOFormat/images/train val: YOLOFormat/images/val names: 0: kiwi nc: 1训练命令yolo detect train \ modelyolov8n.pt \ datakiwi.yaml \ epochs200 \ imgsz640 \ batch16 \ patience50 \ cacheTrue \ close_mosaic20 \ projectrun_kiwi逻辑说明modelyolov8n.pt是预训练权重而不是yolov8n.yaml这很重要。1700张小数据集不要从零初始化COCO预训练带来的底层纹理特征能明显加速收敛也让前几十轮的loss不至于在一和二之间反复震荡。参数说明imgsz640是平衡点。摆拍图如果是1920×1080缩到640后猕猴桃主体还剩足够像素如果果实占比很小可以试832但batch要减半。batch16在V100或3080级别上跑yolov8n占用约4-6GB顺手显存小就降到8。batch太大会让单类小数据在几十轮内快速过拟合到背景。patience50早停小数据训练到150轮后val mAP会进平台期早停自动保留最佳权重。cacheTrue把1700张图缓存进显存或内存省去每轮反复读盘。close_mosaic20表示最后20轮关掉mosaic增强用来精修。单类目标检测里mosaic在1700张上容易让一个框里混进多只果的碎片最后阶段关掉能稳一截。参数取值说明modelyolov8n.pt用预训练权重不用yamlimgsz640果实占比小时调832batch16单卡V100/3080级别epochs200 patience50早停兜底cacheTrue1700张适合缓存close_mosaic20最后20轮关mosaic精修3.3 训练结果怎么读先看混淆矩阵和Precision别只盯mAP训练结束后项目目录下的results.csv一行一个epoch。我一般用Python拉最后几行同时看mAP、Precision、Recall和box_lossimport csv with open(run_kiwi/results.csv) as f: rows list(csv.DictReader(f)) for r in rows[-5:]: print(r[epoch], round(float(r[metrics/mAP50(B)]), 4), round(float(r[metrics/precision(B)]), 4), round(float(r[metrics/recall(B)]), 4), round(float(r[val/box_loss]), 4))逻辑说明单类数据集里mAP50如果上不了0.9先别急着调模型回去查标注有没有漏框错框上了0.95以后mAP50-95比mAP50更有区分度。摆拍数据角度跨度大框的IoU波动会直接反映在50-95这个指标上。这里有个常见困惑训练完看confusion_matrix.png明明验证集只有几百个框kiwi那一行加起来却对不上。这不是权重坏了是混淆矩阵带了一个背景类把“该出框但没出框”和“背景被误判成目标”两件事都摊了进来而且显示的是归一化比例。真正要看单类召回直接读metrics/recall(B)更直观。4. 避坑记录摆拍数据、角度标注和格式转换的五个翻车点换过不少数据集猕猴桃这种高纹理球体其实挺典型表面绒毛、角度形态差异大、摆拍光照可控但背景单一。下面这五个坑是这个数据集类型里最常踩的也是我自己反复翻车后才攒下的血泪经验。4.1 混淆矩阵总和为什么对不上样本数现象训练完打开confusion_matrix.png发现kiwi那一行的数值加起来和验证集猕猴桃数量不一致第一反应以为权重坏了或者标签丢了。原因Ultralytics YOLO的混淆矩阵带了一个背景类别。真实框没有被任何预测框匹配时会被记到背景相关的位置预测框落在背景区域也会被计进背景行。矩阵做了归一化显示的是比例直接相加自然对不上。解决看归一化后的值别拿它数样本数。要验证一个epoch到底召回多少框直接用metrics/recall(B)指标或者回看results.csv里的recall曲线这比盯混淆矩阵靠谱。4.2 角度摆拍的“同果不同框”标注标准不统一现象同一个果俯视角度框的宽高比接近1:1侧视角度变成1:1.8训练出来的检测框忽胖忽瘦在连续帧里同一颗果的框视觉上在抖。原因摆拍图来自不同角度果皮绒毛边缘在逆光下看不清标注员有时把绒毛轮廓收进去有时只框果肉主体框的判定标准漂移了。解决如果自己标规范写成“框可见轮廓的最大外接矩形”从图上看得到的果体像素全部框进去阴影不算如果只有成品数据集没法重标训练时把旋转增强关小fliplr可以开但degrees别超过15度不然模型会把角度差异和框宽高比混在一起学框更飘。4.3 数据增强过度把绒毛纹理增强没了现象训练集mAP到了0.97放几张现场实拍图漏检明显增多回去看增强后的图猕猴桃的绒毛纹理几乎被抹平果皮颜色也偏了。原因默认增强里的hsv_h、hsv_s对颜色扰动较猛mosaic又把多张图的碎片拼在一起单类模型退化成靠整体颜色找目标一旦现场光照偏暖就失手。解决训练参数里把hsv_h调到0.01、hsv_s调到0.2mosaic和mixup关掉或只在前半程开。1700张单类数据集不依赖重度增强轻增强反而让模型把注意力放在果体轮廓和纹理上。4.4 转换脚本造成的越界坐标与精度丢失现象训练日志里出现类似“WARNING: 1 labels found in xxx are invalid”的提示对应图片的标签没参与训练框数少了一部分。原因VOC转YOLO时没做浮点归一化和越界截断。比如xmax刚好等于图片width除以width后右边缘就是1.000001会被框架判定为非法坐标另一种常见情况是Python里整数除以整数虽然Py3默认浮点除但如果没有先把w/h显式转float边界计算仍可能丢掉精度。解决转换时统一用float()显式转换最后加一道min(max(...))截断第2.3小节的脚本已经把这层处理写进去了。转完批量检查一遍所有txt的值有没有超过1.0awk {for(i2;i5;i) if($i0 || $i1) print FILENAME, $0} YOLOFormat/labels/train/*.txt这个awk命令会逐行读标签打印出任何越界的文件名和原始内容。如果输出为空说明坐标全部合法。4.5 摆拍背景太干净模型一进果园就胆小现象验证集里全是纯色台面和固定光源换到果园实拍有枝条、杂草、阴影检测率从0.95掉到0.6。原因摆拍数据背景分布极窄模型把“台面纹理”和“猕猴桃”的特征耦合在了一起。这种域差不会因为在线增强消失因为增强是在同一批背景上做的再怎么调色阶也模拟不出真实的枝叶遮挡。解决现阶段把摆拍数据当作基座不做微调就上线的项目多半翻车。正确路径是后续用果园实拍图做第二轮训练或者用第5章的Copy-Paste合成方式补充复杂背景样本让模型有机会把前景和背景解耦。5. 这个数据集适合做什么从摆拍基座到能上线的检测器5.1 用1700张把候选模型过一遍筛子这类规模的数据集最适合做模型选型验证。我通常先定一个及格线mAP50不低于0.92、单张推理在目标设备上不超过30ms然后拿两三个候选模型跑同一套数据。常见做法是YOLOv8n、YOLOv8s加上RT-DETR这类结构做个横向对比。对比项YOLOv8nYOLOv8s说明参数量最小中等边缘设备选n1700张下mAP50基准通常高1-2个点小数据差距缩小推理耗时约5-8ms约10-15ms以实际部署设备为准适用场景Jetson、边缘盒子服务器端推理结合算力定在1700张这种量级上n和s的差距没有想象中大s的mAP50可能高1-2个点但推理时间接近翻倍。如果最终部署在Jetson或树莓派上直接选n别贪那一两个点。5.2 用Copy-Paste和合成背景把1700张扩成实用规模单类小数据最有效的扩增是Copy-Paste把图里的猕猴桃抠出来贴到新背景上。摆拍图背景干净抠图反而容易。常见做法是先按标注框裁剪再把裁剪块做随机缩放、轻微旋转后贴回果园背景图。Albumentations里可以这样组合在线增强import albumentations as A train_transform A.Compose([ A.LongestMaxSize(max_size640), A.PadIfNeeded(min_height640, min_width640), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.1, p0.5), A.HueSaturationValue(hue_shift_limit5, sat_shift_limit15, p0.3), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.1, rotate_limit15, p0.6), ])逻辑说明LongestMaxSize加PadIfNeeded保证送入网络的尺寸统一ShiftScaleRotate的rotate_limit压到15度因为猕猴桃是长条形状旋转太大框里会混入过多背景。这套pipeline做在线增强时标注框要同步变换Albumentations的BboxParams会自动跟着变前提是传入的框是归一化的[x,y,w,h]格式。参数说明brightness_limit0.15模拟果园早晚光照变化hue_shift_limit5克制色偏避免把果皮特征改没。如果额外做Copy-Paste贴图时要避开原图已有框的重叠区域否则两个框叠在一起会让模型对遮挡的响应变得神经质。5.3 三个量化指标验证摆拍数据是否可靠判断这个数据集值不值得作为项目底座我只看三个数不纠结单张loss曲线指标计算方式健康阈值角度AP极差各角度子集mAP的max-min小于5个百分点负样本误检率非目标图片被检出目标的比例不超过2%摆拍vs实拍mAP差两个子集mAP之差小于20个百分点角度AP极差衡量的是模型是否学到了果实本身还是只记住了部分角度的形态负样本误检率衡量模型对“不是猕猴桃”的东西有多大反应摆拍和实拍的mAP差则直接告诉你当前数据离生产环境还有多远。三个数跑一遍比守在tensorboard前看loss更能判断模型离上线还有多少活。6. 最后一个技巧用“角度敏感性测试”判断检测器真的学会了猕猴桃训练完best.pt后把验证集按拍摄角度拆成俯角、平角、仰角三个子集分别跑一次val比较各组的mAP50。如果数据集文件名里没有角度标记肉眼挑也够用每个角度抽50张就好。需要三个小的data yaml内容差异只在val路径path: /data/Kiwifruit1700 val: YOLOFormat/images/val_flat names: 0: kiwi nc: 1然后分别执行yolo detect val modelrun_kiwi/weights/best.pt datakiwi_val_flat.yaml yolo detect val modelrun_kiwi/weights/best.pt datakiwi_val_top.yaml yolo detect val modelrun_kiwi/weights/best.pt datakiwi_val_angle.yaml逻辑说明三个命令共用同一个权重只切换验证集目录。对比输出里的mAP50如果某个角度明显低说明模型在该角度的典型形态上没学够。我自己在这个环节吃过亏整体mAP50已经到0.98仰角切成0.72后来把仰角样本在训练集里补到三成才拉回0.9以上。这个测试还能顺手决定推理时要不要开多尺度。角度差异大的模型推理时把imgsz提到832有时能顶回几个点代价只是多几毫秒耗时。角度敏感性测试不挑数据集对任何多视角摆拍数据集都适用。我自己的习惯是把它写进每次训练后的检查清单跑一遍再决定要不要补数据而不是看整体指标好看就收工——这也是我做过一次之后再也不想省掉的步骤。希望帮到你。本文还有配套的精品资源点击获取
返回列表