简介:本资源为面向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_shoot" |
| x_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(f"tea-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 \ data=tea-shoot-detection/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.01 \ patience=20 \ project=runs/tea \ name=baseline逐个说参数。model=yolov8n.pt用 nano 版,100 张图的数据量上大模型纯属浪费,n 版足够看趋势。epochs=100配合patience=20,意思是 20 轮验证指标不涨就早停,防止过拟合。imgsz=640是 YOLO 默认输入尺寸,茶芽目标偏小,如果发现漏检多,可以提到 960 甚至 1280,但显存和速度要权衡。batch=8是保守值,显存够就往上加,batch 太小 BN 层统计不稳。lr0=0.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 \ model=runs/tea/baseline/weights/best.pt \ data=tea-shoot-detection/data.yaml \ imgsz=640输出会给每类的 P、R、mAP50、mAP50-95。茶芽检测里召回率 R 比精确率 P 更值得盯——漏掉一个芽是少采,误检一个背景是白跑一趟,农业场景通常宁可多检不可漏检。如果 R 明显低于 P,说明模型偏保守,可以调低推理时的conf阈值。
推理单张图看效果:
yolo detect predict \ model=runs/tea/baseline/weights/best.pt \ source=tea-shoot-detection/images/val \ conf=0.25 \ save=Trueconf=0.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 API,v10 和 v11 又引入了新的 head 结构。数据本身不用动,动的是训练入口。
| 版本 | 训练入口 | 配置文件 | 注意点 |
|---|---|---|---|
| v5 | train.py | data/*.yaml | 需 clone 官方 repo |
| v7 | train.py | data/*.yaml | 同上,锚框机制不同 |
| v8 | yolo detect train | 同 yaml | Ultralytics 统一接口 |
| v9 | yolo detect train | 同 yaml | 依赖较新 |
| v10 | yolo detect train | 同 yaml | 无 NMS 变体需注意导出 |
| v11 | yolo 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 一直是 0,loss 却不降。原因:标签坐标没归一化,或者 class 索引写成了字符串。 解决:跑 2.2 里的检查脚本,确认四个坐标都在 0~1,class 是整数。归一化问题最隐蔽,因为文件能读、不报错,只是学不到东西。
现象三:推理时框全挤在图像左上角。原因:训练时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(f"Fold {i}: train={len(train_imgs)}, val={len(val_imgs)}")random.seed(42)保证每次切分一致,方便复现。imgs[i::k]是步长切片,把打乱后的列表按间隔取,天然均分。每折重建目录后各训一次,记录 mAP。五折平均和标准差一起报,标准差大说明数据分布不均,某些折的验证图特别难。
标签复核是另一件值得花时间的事。100 张图,人工过一遍也就一两个小时,但能揪出漏标、错标、框太松的问题。茶芽检测里最常见的标注毛病是框把旁边老叶也圈进去了,模型学到的"芽"就带了背景噪声。复核时重点看三类:芽尖被截断的、密集区域漏标的、框明显偏大的。改完标签重训,mAP 往往能涨几个点,比调参来得实在。
注意:交叉验证会训五次,时间成本是单次的五倍。100 张图单次十几分钟,五折也就一个多小时,可以接受。但如果换成上千张的数据集,就得权衡是否值得。
从那以后我每次拿到小数据集,都强制先跑一遍文件配对检查和标签越界检查,再动手训练。这两个脚本加起来不到五十行,省下的却是几小时对着 loss 曲线发呆的时间。希望帮到你。
本文还有配套的精品资源,点击获取