
简介面向目标检测与农业智能化场景的草莓成熟度检测数据集包含412张真实拍摄的草莓植株图像采用labelImg工具完成精细标注涵盖开花、生长、成熟3个关键阶段共1932个目标框类别分布均衡且便于分类辨识。数据集同时提供Pascal VOC与YOLO两种主流标注格式jpg图片、xml标注文件和txt标注文件一一对应可直接衔接YOLO系列、Faster R-CNN等常见检测框架的训练管线既适合计算机视觉初学者上手目标检测任务也可用于草莓采摘机器人、产量预估等农业项目的模型验证。压缩包为7z格式共1238个文件以jpg图像、xml与txt标注文件为主整体体积仅23.43MB轻量便携、下载效率高。目前已有508人学习下载图片与标注配对存放的目录结构可省去繁琐的数据整理环节让使用者集中精力进行数据划分、超参数调节与模型效果优化。1. 草莓成熟度检测数据集VOCYOLO 412张3类别先搞清这三件事再动手训练很多拿到训练数据集的人第一反应是直接开训结果在解压、格式、类别映射上白耗两三天。这个“草莓成熟度检测数据集VOCYOLO格式412张3类别.7z”是一个标准的双格式目标检测数据集412张真实拍摄的草莓图片未熟、半熟、成熟三个类别同时提供VOC XML和YOLO txt两套标注适合用来训练YOLOv8等检测模型做成熟度分级。要把这412张用好有三件事比模型调参更靠前一是在Windows/Linux下正确解压7z并按目录结构核对标注文件二是搞清VOC与YOLO坐标的换算规则、类别ID从0还是从1开始三是先做合理的数据划分并统计类别分布再动训练命令。这篇按这个路径讲每一步都给出可复现的命令和参数。2. 解压7z包与核对目录别让压缩包在第一步卡住2.1 三种系统下解压7z的命令与图形工具日常使用中Windows双击.7z文件默认没有关联程序系统会提示“Windows无法打开此文件”这让第一次接触7z数据集的人误以为压缩包损坏。其实7z只是高压缩率的存档格式绝大多数数据集用它是为了减小传输体积解压工具用对了就能顺利解开。Linux下最省事的方式是安装p7zip-full包sudo apt update sudo apt install p7zip-full # Debian/Ubuntu sudo yum install p7zip p7zip-plugins # CentOS/RHEL/Fedora 7z x strawberry_ripe.7z -o./strawberry_data7z x会保留压缩包里的目录层级-o指定解压输出目录。注意两点-o与输出路径之间不要加空格否则路径会被截断解压结束后马上用7z t strawberry_ripe.7z做一次完整性测试退出码为0才说明文件没有损坏。Windows下官方7-Zip是常见做法安装后右键选择“解压到当前文件夹”即可。命令行场景尤其后续要在脚本里批量解压时可以这样写C:\Program Files\7-Zip\7z.exe x C:\data\strawberry_ripe.7z -oC:\data\strawberrymacOS不自带7z支持先装p7zipbrew install p7zip 7z x strawberry_ripe.7z -o./strawberry_data图形界面也可以用Keka或The Unarchiver但遇到加密压缩包时它们对7z加密算法的支持不如命令行p7zip稳定。所以我的习惯是不管在哪个平台第一选择都是命令行方便复现也方便排查问题。解压完成后先用du -sh strawberry_data和find . -name *.7z确认文件是否齐全。如果里面还有嵌套的压缩包继续用同样的命令解到同名目录即可。这一步做完就进入结构核对阶段。2.2 数据集目录结构与VOC/YOLO双格式对照解压后按照常见做法目录会是这样实际略有差异时以根目录里的README或文件夹名称为准strawberry_ripe/ ├── VOC/ │ ├── Annotations/ # 412个XML标注 │ ├── JPEGImages/ # 412张JPG图片 │ └── ImageSets/Main/ # 训练集与验证集划分文件 └── YOLO/ ├── images/ # 全部图片或按train/val分子目录 └── labels/ # 全部YOLO txt标注拿到结构后第一件事不是训练是对数量echo XML: $(ls VOC/Annotations | wc -l) echo JPG: $(ls VOC/JPEGImages | wc -l) echo TXT: $(ls YOLO/labels | wc -l)412张图对应412个XML和412个TXT是数据集完整性的前提。如果txt比图片少了说明YOLO标注缺失反过来多了可能残留重复文件。这两种情况都要先处理否则训练时YOLO会报警告并跳过无标注图片导致你的“412张”实际只用了一部分。VOC与YOLO是两种完全不同的标注组织方式。VOC的XML里每个目标的类别名和坐标以XML节点形式存放annotation filenameIMG_001.jpg/filename sizewidth640/widthheight480/height/size object nameripe/name bndbox xmin120/xmin ymin80/ymin xmax210/xmax ymax195/ymax /bndbox /object /annotation而YOLO的txt每一行对应一个目标格式是2 0.257812 0.286458 0.140625 0.239583第1列是类别ID第2到第5列是归一化后的中心点x、中心点y、宽、高。注意XML里的width/height不一定是真实图片尺寸我遇到过不少数据集XML里写的尺寸和实际JPG不一致所以转换时不建议直接读XML的size节点而是用PIL或cv2读取真实图片宽高这个点第3章会再强调。维度VOC格式YOLO格式标注文件后缀.xml.txt图片路径VOC/JPEGImagesYOLO/images坐标单位绝对像素归一化0~1类别标识字符串如ripe整数ID如22.3 类别分布统计训练前先看这几行数字不少人拿到数据集直接写data.yaml开始训练等训练完看mAP才意识到某一类学不动。其实一个简单统计就能提前看到这个问题。用Python把所有labels读一遍统计每个类别出现的目标数量import os from collections import Counter label_dir YOLO/labels counter Counter() for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name)) as f: for line in f: cls_id line.split()[0] counter[cls_id] 1 print(counter)输出类似{2: 812, 1: 486, 0: 214}。如果某一类目标数量只有其他类的四分之一后面训练时就要考虑类别权重或数据增强策略。这一步跑完你对这个数据集的认知就从“3类412张”具体到“每一类到底有多少个框”后面调参时心里更有底。3. 把VOC标注转成YOLO txt转换脚本与坐标系边界3.1 为什么拿到了双格式还要自己动手转一遍虽然这个数据集同时给了VOC和YOLO格式看起来可以直接用YOLO目录开训但实际工程里我建议还是自己过一遍VOC转YOLO的流程。第一你没办法确定别人给的YOLO txt是不是从VOC XML直接生成的一旦转换时类别映射顺序出错YOLO目录里的标签就是错的而使用时根本看不出来。自己转一遍相当于用VOC XML作为标准答案做一次校验。第二农业视觉项目通常不会只用完一个静态数据集就结束。后续你很可能自己补充拍摄图片用LabelImg或X-AnyLabeling标注时默认导出的是VOC XML。如果不会转换新数据就接不进YOLOv8训练流程。第三这个数据集要同时跑通两种工具链的情况很常见YOLOv8吃txtMMDetection、Detectron2这些框架则从VOC XML读取数据。懂转换的人换框架时不会被格式卡住。回到数据本身VOC的边界框用的是绝对像素坐标xmin, ymin, xmax, ymax而YOLO需要的是归一化后的中心点坐标和宽高比例。换算公式很简单x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height看起来就是把像素坐标变相对值但真正容易翻车的不是公式而是三个边界问题一是图片真实尺寸和XML里记录的size节点可能不一致二是目标框越界xmax超过图片宽度时的裁剪三是类别ID映射的稳定性。下面这个脚本把这些问题一起处理了。3.2 一个可直接运行的转换脚本坐标、尺寸与类别映射import os import cv2 import xml.etree.ElementTree as ET class_map {unripe: 0, half: 1, ripe: 2} xml_dir VOC/Annotations img_dir VOC/JPEGImages out_dir YOLO/labels_rebuilt os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text img_path os.path.join(img_dir, filename) if not os.path.exists(img_path): base os.path.splitext(xml_name)[0] img_path os.path.join(img_dir, base .jpg) img cv2.imread(img_path) h_img, w_img img.shape[:2] # 从图片实际尺寸取宽高 lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: print(f跳过未知类别: {xml_name} - {name}) continue cls_id class_map[name] 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) # 越界裁剪防止坐标落在[0,1]之外 xmin max(0, min(xmin, w_img - 1)) xmax max(0, min(xmax, w_img - 1)) ymin max(0, min(ymin, h_img - 1)) ymax max(0, min(ymax, h_img - 1)) if xmax xmin or ymax ymin: print(f空框跳过: {xml_name} - {name}) continue x_center (xmin xmax) / 2 / w_img y_center (ymin ymax) / 2 / h_img box_w (xmax - xmin) / w_img box_h (ymax - ymin) / h_img lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_name os.path.splitext(xml_name)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines))这个脚本的关键点我再展开说图片宽高用cv2.imread读真实shape而不是用XML的size节点。实践中经常出现XML里记录的尺寸和实际图片不一致一旦不一致所有坐标整体偏移或缩放。越界裁剪的边界值用w_img - 1而不是w_img目的是防止除以宽度后坐标等于1.0。YOLO在训练时要求归一化坐标严格小于1等于1的框会被当成无效标签。类别映射class_map必须显式写死不要用动态字典。一旦XML里类别拼写或前后顺序有变化动态映射会让同一个类在不同批次转换时使用不同ID。3.3 数据划分与路径一致性校验小数据集只有412张一般按8:1:1划分训练、验证、测试或至少留80张做验证。划分脚本要同时处理图片和标签保证两边文件名一一对应import os import random img_dir YOLO/images label_dir YOLO/labels names [f[:-4] for f in os.listdir(img_dir) if f.endswith(.jpg)] random.seed(42) random.shuffle(names) val_names names[:80] train_names names[80:] def copy_split(names, split_name): os.makedirs(fYOLO/{split_name}/images, exist_okTrue) os.makedirs(fYOLO/{split_name}/labels, exist_okTrue) for n in names: for ext in [.jpg, .txt]: src os.path.join(img_dir if ext .jpg else label_dir, n ext) dst os.path.join(fYOLO/{split_name}/images if ext .jpg else fYOLO/{split_name}/labels, n ext) os.rename(src, dst) copy_split(val_names, val) copy_split(train_names, train)这里用os.rename直接把文件从根目录移进train/val子目录比复制更省空间。移动完要做一次双向检查每个train图片都有对应的txt每个txt都有对应的图片一个简洁的脚本cd YOLO/train ls images | sed s/.jpg// /tmp/img.txt ls labels | sed s/.txt// /tmp/lbl.txt diff (sort /tmp/img.txt) (sort /tmp/lbl.txt) echo OKdiff不输出内容就说明两边文件名完全一致。这一步是训练前最后一道闸门如果漏做等训练时YOLO打印“WARNING: 图片XXX无标签”再回头查就晚了。4. 用YOLOv8训练草莓成熟度检测从data.yaml到一条训练命令4.1 data.yaml的正确写法与完整训练命令处理完数据集用于yolov8训练后下一步就是写data.yamltrain: /absolute/path/to/YOLO/train/images val: /absolute/path/to/YOLO/val/images nc: 3 names: [unripe, half, ripe]三个容易出错的地方train和val路径指向图片目录而不是标签目录YOLO会自动在同级目录下找labels。建议写绝对路径尤其Windows下C:\盘符在YAML里要加引号或改成正斜杠否则解析会出问题。names的顺序必须和labels里的class_id含义一致。上面转换脚本里用了{unripe:0, half:1, ripe:2}这里就要按相同顺序写。训练命令yolo detect train datastrawberry.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20 device0modelyolov8n.pt指定预训练权重。如果本地没有YOLO会自动下载预训练模型网络稳定时几分钟就完成。对比yolov8s/m/l/x这个数据集只有412张用n或s就足够了大模型在小数据集上过拟合速度很快而且不会带来mAP的稳定提升。device0指第一块GPU没有GPU时改成devicecpu训练会很慢但小数据集也能跑完。如果你第一次用YOLO还应该知道当前版本的Ultralytics包在命令行里统一用yolo detect train。部分旧教程里的yolov8 detect train写法在v8之后仍然兼容但新项目建议直接yolo。这个差别虽小却能让你少走弯路。4.2 必调参数与训练日志八个参数按这张表设下表是这套数据集上比较稳的参数起点参数推荐值说明imgsz640草莓果实算中等目标640性价比最高batch8~168G显存用816G显存用16epochs100~150配合patience早停patience20连续20轮验证集不涨就停optimizerauto让Ultralytics自动选择lr00.01小数据集不要调太高mixup0.2少量mixup可缓解类别不平衡close_mosaic10最后10轮关掉mosaic让模型适应真实分布训练过程中控制台每轮会打印box_loss、cls_loss、mAP50等指标但更完整的曲线要看runs/detect/train/results.png。我判断训练是否正常的顺序是先看val/cls_loss是否持续下降再看mAP50有没有爬升。如果val/cls_loss在前10轮不降反升多半是标签类别ID写错了模型在拿错误监督信号硬学。显存不够是常见的翻车点。8G显存的卡跑imgsz640, batch16很容易OOM表现是训练刚开始就报CUDA out of memory。解决顺序是先降batch到4再不行降imgsz到512。不要一开始就用--amp去赌显存它会引入额外的不确定性等batch尺寸确定后再开。一个常见误区是看到loss还在降就一路加epochs。对小数据集loss降不代表泛化好验证集mAP才是最终判断依据。patience20就是让它别在一个局部震荡里浪费算力。4.3 混淆矩阵和per-class指标怎么反推标注质量训练结束后runs/detect/train/下会生成confusion_matrix.png和results.csv。小数据集上我最先看的是results.csv里metrics/mAP50(B)最终停在多少再看每个类别的平均精度。这里有一个经常被忽略的点当“未熟”和“半熟”这两类互相误检的比例明显偏高时不要再折腾模型结构或损失函数了问题极大概率出在标注标准上。因为草莓成熟度本身就是连续变化过程不同批次拍摄的图片标注员心里的“未熟”与“半熟”边界可能并不一致。这种情况下回到数据集做一致性检查比换网络涨点快得多。可以写个小脚本从results.csv里读取最后一轮的per-class AP辅助判断哪一类是短板import csv with open(runs/detect/train/results.csv) as f: reader csv.DictReader(f) rows list(reader) last rows[-1] for k in last: if mAP in k or precision in k: print(k, last[k])这个脚本只是辅助真正有价值的判断还是看混淆矩阵非对角线上的数字。如果背景误检高说明模型输出阈值偏低如果某两类互相混淆高优先怀疑标注标准再回第5章排查。5. 常见问题避坑7z、标签与训练里的5个典型翻车点5.1 7z密码正确却一直报错先测完整再换工具现象解压时输入正确密码工具却反复提示“密码错误”或“数据错误”文件解到一半就中断。原因多数情况下不是密码记错而是解压软件对7z加密头的兼容问题。Windows自带的资源管理器不支持7z格式部分集成解压工具只支持旧版压缩参数碰到AES-256加密的包就会误报。另一个高发原因是文件下载不完整断点续传导致文件尾部缺失7z解到后面才发现数据校验失败。解决用官方7-Zip命令行直接指定密码解压7z x strawberry_ripe.7z -p你的密码 -o./out如果还报错先跑7z t strawberry_ripe.7z -p你的密码做完整性测试。若t也报数据错误就是压缩包本身不完整重新下载即可。这条流程处理过很多次“密码正确但一直报错”的情况九成是工具版本或文件完整性问题不是密码问题。5.2 YOLO标签坐标越界或类别ID错位扫描脚本找回现象训练能跑但val的mAP低得离谱画出来的预测框贴着图片边缘或者“未熟”类别完全学不出来loss曲线异常陡。原因VOC转YOLO时没有对坐标做越界裁剪归一化后出现大于1.0的值或者类别映射顺序不一致例如labels里第一列出现了3而不是0/1/2说明类别ID从1开始编号了。解决先用脚本扫描所有txt找出不合法坐标import os for name in os.listdir(YOLO/labels): with open(os.path.join(YOLO/labels, name)) as f: for line in f: parts line.split() cls_id int(parts[0]) coords list(map(float, parts[1:])) if cls_id not in [0, 1, 2]: print(name, ID错误:, cls_id) if any(v 0 or v 1 for v in coords): print(name, 坐标越界:, coords)找到问题文件后按第3章脚本重新转换并修正class_map。这里给一条血泪经验不要试图用数据增强去“弥补”越界坐标标签错误只会被增强放大不会消失。5.3 类别不平衡导致某一类mAP偏低先统计后增强现象整体mAP50还可以但看per-class指标“未熟”明显低于“成熟”训练日志里这一类的前背景损失都没怎么降。原因数据集只有412张如果“未熟”样本只有几十张且拍摄场景单一模型很难把“未熟”和“半熟”的边界学出来。解决先精确统计每类目标数量用2.3节那段脚本。然后再决定手段给“未熟”类做亮度扰动、轻微旋转等增强或者用YOLOv8的class_weights参数。注意不要在验证集上做任何过采样否则验证指标失真。对小数据集来说把少数类样本复制到训练集也是常见做法但复制后要在训练集里增加一些背景图帮助模型区分误检。5.4 混淆矩阵总和与验证集样本数对不上先看背景误检现象验证集80张图、共120个目标但训练出来的confusion_matrix.png里各类数字加起来不到120。原因混淆矩阵默认按IOU阈值判定匹配没有匹配到GT的预测框会被分到background这一类所以矩阵对角线之和小于GT总数是正常现象。另一个可能原因是验证集划分后有些图片的txt丢失导致这些图片不参与评估。解决先检查val/labels数量和val/images数量是否一致再用yolo val输出的明细对照。如果确实有几张图没有参与回到第3章的diff校验脚本排查。不要一看到对不上就怀疑数据集有问题先把背景误检那一格看清楚。5.5 BN崩溃或loss突然飙升降batch再加warmup现象训练到第10轮左右train/box_loss突然冲高loss曲线出现尖峰之后再也降不回正常水平。原因小数据集常用大batch较大的batch会让BN统计量不稳定另一个常见原因是某张图里出现了极端目标尺寸或标签越界梯度瞬间被拉爆。解决先降batch到4~8并加warmup_epochs3让BN统计量平稳。如果复现检查训练集里是否存在异常标签和坏图。还有一个反直觉的坑imgsz从640改成1024后目标相对尺寸变化不大但显存压力变大batch被迫减小反而更容易BN抖动。所以调大imgsz的时候要同步观察训练曲线不要只看mAP。6. 进阶用法用训练结果反向检查标注把412张物尽其用6.1 先跑小模型当标注质检工具拿到这个数据集的第一个动作我建议先不调任何参数用yolov8n训练20个epoch。如果val mAP低但训练loss能下降说明模型容量基本够问题多半在标注质量如果loss就不下降先怀疑标签ID或路径配错。小模型对噪声标签更敏感反而是有效的标注质检工具。这一招在数据不足的农业项目里非常管用。6.2 按置信度排序抽样复检训练完成后把验证集图片的预测框画出来按置信度排序逐张过。重点看两类置信度很高但框明显偏的和置信度低但GT真实存在的。如果问题文件集中在某些文件名前缀多半是某个拍摄批次或标注批次的系统性问题。修正标注后重训常常比换网络涨点更明显。我一般用Python脚本输出每张图的最高置信度排行按Top10逐张过一遍半小时能查完80张验证图。6.3 导出onnx做部署前验证如果最终目的是把成熟度检测落到简易设备训练结束后导出yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出后用onnxruntime跑一遍对比YOLO原生推理和onnx推理的输出。差异过大时检查输入归一化和坐标解码方式。一个小数据集上把全流程跑通后面换更大数据集只是时间问题。数据质量永远比模型架构值钱这是我在农业视觉项目里最大的体会希望帮到你。本文还有配套的精品资源点击获取