茶芽检测数据集实战:YOLO全系训练与避坑指南

发布时间:2026/10/13 9:15:57
茶芽检测数据集实战:YOLO全系训练与避坑指南
简介本资源为面向YOLO系列目标检测算法的茶树芽检测数据集适用于yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流框架可直接用于模型训练与验证测试。数据集已按标准划分标签采用归一化坐标格式包含类别索引、目标框中心点及宽高比例值也可转换为VOC格式方便不同训练流程调用。压缩包共201个文件其中100张jpg图像、100个txt标注文件及1个yaml配置文件整体约6.59MB图像与标签一一对应yaml文件用于定义数据集路径与类别信息结构清晰便于快速接入训练脚本。目前已有234人学习下载适合从事农业视觉检测、茶芽识别或目标检测入门实践的研究者与开发者可借助该数据集完成模型训练、验证与测试快速验证算法在茶芽检测任务上的效果。1. 茶芽检测数据集上手100 张图带标签YOLO 全系能直接开训茶芽检测这个方向说大不大说小也不小。做农业视觉的团队常年在田间地头架相机采茶季就那么几十天标注窗口极短真正能拿来开训的公开数据少得可怜。这份tea-shoot-detection数据集就是冲着这个痛点来的100 张带标签图像YOLO 标准 txt 格式已经划分好训练/验证集yolov5、yolov7、yolov8、yolov9、yolov10、yolo11 都能直接吃。文件名像img_0790_0.jpg、img_0441_2.jpg这种是典型的采集批次加序号命名说明来源是连续拍摄后人工筛过的。它解决的不是我要发论文的需求而是我今天就想把茶芽检测的 baseline 跑起来的需求。适合谁做农业 AI 落地的工程师、要快速验证 YOLO 新版本效果的算法同学、以及需要一个小规模干净数据集做教学演示的人。100 张不算多但胜在标签规范、类别单一、开箱即用省掉的是最耗时的数据清洗环节。2. 数据集结构与标签格式先看懂再动手2.1 目录长什么样划分逻辑是什么拿到压缩包解压后常见做法是得到images/和labels/两个平行目录下面再分train/和val/。这份数据集既然标注了已经划分好那大概率是images/train、images/val、labels/train、labels/val四件套。YOLO 系列从 v5 开始就认这个结构Ultralytics 的data.yaml里只要写对train:和val:两个路径剩下的框架自己会去配对。先别急着训练第一步永远是数文件、对文件名。图像和标签必须一一对应差一个都会在训练时报No labels found或者静默跳过。我一般会先跑一段脚本核对import os from pathlib import Path # 按实际解压路径改 root Path(tea-shoot-detection) img_dir root / images / train lbl_dir root / labels / train imgs {p.stem for p in img_dir.glob(*.jpg)} lbls {p.stem for p in lbl_dir.glob(*.txt)} print(图像数:, len(imgs)) print(标签数:, len(lbls)) print(有图无标签:, imgs - lbls) print(有标签无图:, lbls - imgs)这段逻辑很直白用stem去掉扩展名后做集合运算。imgs - lbls就是那些拍了但没标的图lbls - imgs是标签在但图丢了的。两个集合都为空才说明配对干净。参数上唯一要注意的是扩展名有的采集流程会存成.JPG大写glob(*.jpg)就漏了稳妥点写成glob(*.[jJ][pP][gG])。2.2 YOLO txt 标签每一列到底代表什么摘要里给得很清楚class x_center y_center width height。这是 YOLO 从 v5 到 v11 一直没变的归一化格式。四个坐标值都是相对图像宽高的比例范围 0 到 1不是像素。这一点新手最容易翻车——直接把标注工具导出的像素坐标塞进去训练时 loss 会大得离谱框全飘到画面外。字段含义取值范围常见错误class类别索引从 0 开始整数写成类别名 tea_shootx_center框中心 x / 图像宽0~1直接写像素值y_center框中心 y / 图像高0~1同上width框宽 / 图像宽0~1写成绝对宽height框高 / 图像高0~1写成绝对高茶芽检测通常是单类别所以 class 全是 0。如果后面要加一芽一叶一芽二叶这种分级class 就变成 0、1、2但这份数据集是单类别自己脑补。想快速验证标签有没有越界跑这个import numpy as np def check_label(path): bad [] with open(path) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: bad.append((i, 字段数不对)) continue cls, x, y, w, h parts vals list(map(float, [x, y, w, h])) if any(v 0 or v 1 for v in vals): bad.append((i, f越界: {vals})) if float(w) 0 or float(h) 0: bad.append((i, 宽高非正)) return bad # 抽查几个 for name in [img_0790_0, img_0441_2]: print(name, check_label(ftea-shoot-detection/labels/train/{name}.txt))逻辑是逐行拆五个字段先查数量再查归一化范围最后查宽高是否为正。宽高为 0 的框在计算 IoU 时会出 NaN属于必须清掉的脏数据。参数上0~1是硬边界但实际允许极小误差有的标注工具会写出1.0002这种可以容忍超过1.05就得回查。2.3 转 VOC 格式什么时候需要摘要提到也可转成 voc 格式。什么时候要转一是你要用 MMDetection、Detectron2 这类框架它们吃 COCO 或 VOC二是你要用某些可视化工具核对标注质量VOC 的 XML 更直观。转换的核心就是把归一化坐标乘回图像宽高再算左上角和右下角from PIL import Image import xml.etree.ElementTree as ET def yolo_to_voc(img_path, lbl_path, out_xml, class_names): img Image.open(img_path) W, H img.size root ET.Element(annotation) ET.SubElement(root, filename).text img_path.split(/)[-1] size ET.SubElement(root, size) ET.SubElement(size, width).text str(W) ET.SubElement(size, height).text str(H) with open(lbl_path) as f: for line in f: cls, x, y, w, h line.strip().split() x, y, w, h map(float, (x, y, w, h)) xmin int((x - w / 2) * W) ymin int((y - h / 2) * H) xmax int((x w / 2) * W) ymax int((y h / 2) * H) obj ET.SubElement(root, object) ET.SubElement(obj, name).text class_names[int(cls)] bnd ET.SubElement(obj, bndbox) for tag, val in zip((xmin,ymin,xmax,ymax), (xmin,ymin,xmax,ymax)): ET.SubElement(bnd, tag).text str(val) ET.ElementTree(root).write(out_xml)关键在x - w/2和x w/2这两步中心点减半宽得左上加半宽得右下。int()截断会丢一点精度要求高的话用round()。class_names是类别名列表单类就传[tea_shoot]。转完记得抽查一张用标注可视化工具打开框和芽的位置对得上才算过。3. 用 YOLOv8 跑通第一个 baseline配置到训练3.1 data.yaml 怎么写才不出错Ultralytics 系的框架全靠一个data.yaml串起来。这份数据集路径固定yaml 就三块路径、类别数、类别名。path: ./tea-shoot-detection train: images/train val: images/val nc: 1 names: 0: tea_shootpath是根目录train和val是相对根目录的子路径。很多人栽在路径上写成绝对路径换台机器就废写成./images/train又和path重复拼接。稳妥做法就是上面这样path指根下面写相对。nc是类别数单类写 1别写成 0。names用字典或列表都行字典更清晰。提示如果训练时报Dataset xxx images not found九成是path拼接后目录不存在。先cd到 yaml 同级目录手动ls一下拼出来的路径。3.2 训练命令与关键参数环境装好ultralytics后一条命令就能起训yolo detect train \ datatea-shoot-detection/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ lr00.01 \ patience20 \ projectruns/tea \ namebaseline逐个说参数。modelyolov8n.pt用 nano 版100 张图的数据量上大模型纯属浪费n 版足够看趋势。epochs100配合patience20意思是 20 轮验证指标不涨就早停防止过拟合。imgsz640是 YOLO 默认输入尺寸茶芽目标偏小如果发现漏检多可以提到 960 甚至 1280但显存和速度要权衡。batch8是保守值显存够就往上加batch 太小 BN 层统计不稳。lr00.01是初始学习率小数据集上可以降到 0.005 更稳。100 张图训 100 轮在单张消费级卡上大概十几分钟。跑完看runs/tea/baseline/下的results.csv和weights/best.pt。重点看metrics/mAP50这一列单类茶芽如果标注干净mAP50 上 0.8 不难如果一直在 0.3 徘徊先别调参回去查标签。3.3 验证与推理确认模型真的学到了训完必须验证不能只看 loss。用验证集跑yolo detect val \ modelruns/tea/baseline/weights/best.pt \ datatea-shoot-detection/data.yaml \ imgsz640输出会给每类的 P、R、mAP50、mAP50-95。茶芽检测里召回率 R 比精确率 P 更值得盯——漏掉一个芽是少采误检一个背景是白跑一趟农业场景通常宁可多检不可漏检。如果 R 明显低于 P说明模型偏保守可以调低推理时的conf阈值。推理单张图看效果yolo detect predict \ modelruns/tea/baseline/weights/best.pt \ sourcetea-shoot-detection/images/val \ conf0.25 \ saveTrueconf0.25是置信度阈值低于它的框不输出。默认 0.25 偏保守茶芽密集时可以降到 0.15 看召回但误检会增多。结果图存在runs/detect/predict/肉眼过一遍重点看嫩芽和背景老叶有没有混。4. 换 YOLOv5/v10/v11 的差异与避坑4.1 各版本吃同一份数据的兼容性这份数据集标称兼容 v5 到 v11靠的就是 YOLO txt 格式的稳定性。但兼容不等于零改动。v5 用data.yaml加--img命令行v8 之后统一成yoloCLI 或 Python APIv10 和 v11 又引入了新的 head 结构。数据本身不用动动的是训练入口。版本训练入口配置文件注意点v5train.pydata/*.yaml需 clone 官方 repov7train.pydata/*.yaml同上锚框机制不同v8yolo detect train同 yamlUltralytics 统一接口v9yolo detect train同 yaml依赖较新v10yolo detect train同 yaml无 NMS 变体需注意导出v11yolo detect train同 yaml接口与 v8 一致v5 和 v7 要单独拉仓库data.yaml里路径写法略有差别v5 认train:和val:直接指到 txt 列表文件而不是目录。这是最容易踩的兼容坑把 v8 的 yaml 直接丢给 v5会报找不到文件。稳妥做法是每个版本各写一份 yaml别指望一份通吃。4.2 小数据集的过拟合信号怎么读100 张图过拟合几乎是必然要面对的。典型信号是训练 loss 持续降、验证 loss 先降后升mAP50 在某个 epoch 后掉头向下。这时候别急着加数据增强先看results.csv里train/box_loss和val/box_loss的剪刀差。差得越大过拟合越重。应对手段按性价比排第一开mosaic和mixup增强YOLO 默认就开确认没被关掉第二加weight_decay默认 0.0005可以提到 0.001第三早停patience调小到 10第四实在不行就减模型容量从 s 版退回 n 版。加数据永远是最优解但采茶季不等人增强和正则才是现实选择。4.3 常见问题排查现象一训练启动就报No labels found in ...。原因data.yaml里train路径指到了images/train但框架按约定去同级labels/train找结果目录名或层级对不上。 解决确认images和labels是同级平行目录且train/val子目录名完全一致。用ls逐层核对别靠记忆。现象二mAP 一直是 0loss 却不降。原因标签坐标没归一化或者 class 索引写成了字符串。 解决跑 2.2 里的检查脚本确认四个坐标都在 0~1class 是整数。归一化问题最隐蔽因为文件能读、不报错只是学不到东西。现象三推理时框全挤在图像左上角。原因训练时imgsz和推理时不一致或者标签是像素坐标被当成了归一化。 解决训练和推理的imgsz保持一致回查标签像素坐标的典型特征是数值远大于 1。现象四验证集指标远高于训练集。原因验证集太小100 张里可能只划了 10 张做 val指标波动大不具备统计意义。 解决这是小数据集的固有局限别被高 mAP 迷惑。要么交叉验证要么补数据。报告结果时注明验证集规模。现象五换 v5 训练报 yaml 解析错误。原因v5 的data.yaml格式和 v8 不同train字段要指向包含图像路径的 txt 文件不是目录。 解决为 v5 单独生成train.txt和val.txt每行一个图像绝对路径yaml 里指过去。5. 把 100 张用到极致交叉验证与标签复核技巧100 张图的天花板摆在那想榨出更多价值得在用法上做文章。我一般会做两件事五折交叉验证和标签二次复核。五折交叉验证的思路是把 100 张均分五份轮流拿四份训练、一份验证最后把五次的 mAP 平均。这样得到的指标比单次划分可信得多也能看出模型对数据划分的敏感度。Ultralytics 不直接支持 K 折得自己写循环切分import random, shutil from pathlib import Path root Path(tea-shoot-detection) imgs sorted((root / images / train).glob(*.jpg)) random.seed(42) random.shuffle(imgs) k 5 folds [imgs[i::k] for i in range(k)] for i in range(k): val_imgs folds[i] train_imgs [p for j, f in enumerate(folds) if j ! i for p in f] # 按 train_imgs / val_imgs 重建目录再生成对应 data.yaml print(fFold {i}: train{len(train_imgs)}, val{len(val_imgs)})random.seed(42)保证每次切分一致方便复现。imgs[i::k]是步长切片把打乱后的列表按间隔取天然均分。每折重建目录后各训一次记录 mAP。五折平均和标准差一起报标准差大说明数据分布不均某些折的验证图特别难。标签复核是另一件值得花时间的事。100 张图人工过一遍也就一两个小时但能揪出漏标、错标、框太松的问题。茶芽检测里最常见的标注毛病是框把旁边老叶也圈进去了模型学到的芽就带了背景噪声。复核时重点看三类芽尖被截断的、密集区域漏标的、框明显偏大的。改完标签重训mAP 往往能涨几个点比调参来得实在。注意交叉验证会训五次时间成本是单次的五倍。100 张图单次十几分钟五折也就一个多小时可以接受。但如果换成上千张的数据集就得权衡是否值得。从那以后我每次拿到小数据集都强制先跑一遍文件配对检查和标签越界检查再动手训练。这两个脚本加起来不到五十行省下的却是几小时对着 loss 曲线发呆的时间。希望帮到你。本文还有配套的精品资源点击获取