简介:本资源为面向YOLO系列目标检测算法的茶树芽检测数据集,适用于农业视觉识别、茶芽生长状态监测等场景,可帮助初学者与算法工程师快速搭建训练与验证流程。压缩包共201个文件,包含100张jpg图像、100个对应txt标注文件及1个yaml配置文件,整体约6.59MB,标注采用YOLO标准格式,记录类别索引与归一化后的中心点坐标、宽高比例,可直接用于yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等模型训练,也可按需转换为VOC格式。数据集已完成划分,省去自行整理与标注的环节,便于直接投入实验。目前已有234人学习下载,适合作为茶芽检测任务的入门数据或算法对比基准,帮助读者快速验证模型效果并理解目标检测数据组织方式。
1. 一百张茶芽图,够不够训一个能用的 YOLO 检测器
先说结论:够,但前提是你把它当成"验证链路"而不是"生产模型"。茶树芽检测这个任务,本质是在嫩梢里区分"一芽一叶""一芽二叶""单芽"和"对夹叶"这几类目标,目标小、密集、遮挡重、颜色和背景高度接近,是典型的细粒度小目标检测。一百张带标签图像,按 8:1:1 切分只有 80 张进训练集,指望它直接下地干活不现实;但用它跑通"标注格式转换 → 训练 → 验证 → 推理可视化"这条完整链路,判断这套方案在你的场景里值不值得扩到几千张,完全够用。
我见过太多人卡在第一步:拿到一个tea-shoot-detection.zip,解压一看是 YOLO 格式的 txt 标签,却不知道类别顺序、不知道坐标是归一化还是像素、不知道要不要重新标。这篇就按我实际做这类小数据集的顺序讲:先确认数据长什么样,再决定用哪个 YOLO 版本、怎么切分、怎么调参,最后把踩过的坑摊开。适合手里已经有一份小样本数据集、想快速验证可行性的算法工程师和做农业视觉的开发者。
2. 先摸清数据底细:一百张图里到底藏了什么
拿到压缩包别急着写训练脚本,先花二十分钟把数据"体检"一遍。这一步做扎实,后面能省掉至少半天排查时间。茶芽检测数据集常见的坑是:类别不均衡(单芽样本远多于对夹叶)、标注框过小(很多框边长不到 20 像素)、图像分辨率不统一。这三件事直接决定你后面 anchor 怎么设、输入尺寸怎么定。
2.1 目录结构与标签格式的确认
YOLO 格式的数据集通常长这样:images/放图,labels/放同名 txt,外加一个classes.txt或data.yaml。先跑一段脚本统计,别靠肉眼翻。
import os from pathlib import Path from collections import Counter root = Path("tea-shoot-detection") img_dir = root / "images" lbl_dir = root / "labels" # 1. 图片与标签是否一一对应 imgs = {p.stem for p in img_dir.glob("*.jpg")} lbls = {p.stem for p in lbl_dir.glob("*.txt")} print("图片数:", len(imgs), "标签数:", len(lbls)) print("有图无标签:", imgs - lbls) print("有标签无图:", lbls - imgs) # 2. 统计每个类别的框数量与框尺寸分布 cls_counter = Counter() box_sizes = [] for txt in lbl_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: print("异常行:", txt.name, line) # 格式错误直接暴露 continue c, x, y, w, h = int(parts[0]), *map(float, parts[1:]) cls_counter[c] += 1 box_sizes.append((w, h)) # 归一化宽高 print("类别分布:", dict(cls_counter)) if box_sizes: ws = sorted(s[0] for s in box_sizes) hs = sorted(s[1] for s in box_sizes) print("宽中位数:", ws[len(ws)//2], "高中位数:", hs[len(hs)//2]) print("最小框宽:", ws[0], "最小框高:", hs[0])这段脚本干三件事:核对图文配对、统计类别分布、看框的归一化尺寸。逻辑上,parts长度不等于 5 说明标签行有脏数据,必须当场揪出来,否则训练时 dataloader 会直接抛异常或者静默跳过。参数上,w、h是归一化到 0~1 的值,如果打印出来中位数在 0.05 以下,说明目标极小,后面输入尺寸就得往 640 甚至 960 提。
提示:如果
classes.txt里类别顺序和标签里的数字对不上,训练出来的模型会把"单芽"认成"对夹叶",而且 loss 曲线看起来还挺正常,这是最阴的坑。
2.2 类别定义与标注质量抽查
茶芽检测的类别定义在不同数据集里差别很大。有的只分"可采摘/不可采摘"两类,有的细分到"单芽、一芽一叶、一芽二叶、一芽三叶、对夹叶"五类。类别数直接决定检测头输出维度,也决定你的业务能不能落地——如果下游只关心"能不能采",那五类纯属给自己找麻烦。
抽查标注质量我一般随机抽 10 张,用脚本把框画回图上存出来看。重点看三样:框有没有框到叶柄、密集区域有没有漏标、边界目标有没有被截断还硬标。茶芽密集时,标注员很容易把相邻两个芽标成一个大框,这种错误在 loss 上体现不出来,但推理时就是漏检。
import cv2 import random from pathlib import Path img_dir = Path("tea-shoot-detection/images") lbl_dir = Path("tea-shoot-detection/labels") out_dir = Path("check_vis"); out_dir.mkdir(exist_ok=True) for txt in random.sample(list(lbl_dir.glob("*.txt")), 10): img = cv2.imread(str(img_dir / f"{txt.stem}.jpg")) H, W = img.shape[:2] for line in txt.read_text().strip().splitlines(): c, x, y, w, h = int(line.split()[0]), *map(float, line.split()[1:]) x1, y1 = int((x - w/2) * W), int((y - h/2) * H) x2, y2 = int((x + w/2) * W), int((y + h/2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(c), (x1, y1-4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1) cv2.imwrite(str(out_dir / f"{txt.stem}_vis.jpg"), img)坐标反算公式(x - w/2) * W是 YOLO 归一化中心点格式的标准还原方式,别写成x * W - w,那是像素格式的算法,混用会导致框整体偏移。抽检这一步看着笨,但它是我判断"这份数据能不能直接用"的唯一可靠手段。
3. 选 YOLOv8 还是 v5:小数据集上的真实差别
一百张图这个量级,模型选型的原则是"别用大模型、别用花哨结构"。参数量越大,小数据集上过拟合越快,验证集 mAP 可能第三轮就冲到 0.9 然后一路抖。我在这类农业小目标任务上一般从 YOLOv8n 或 YOLOv5s 起步,两者都能跑,差别在工程体验。
3.1 版本选择与预训练权重
YOLOv8 的 API 更干净,ultralytics一个包搞定训练验证推理,配置文件用 yaml,对新手友好;YOLOv5 生态老、教程多、改网络结构方便,但仓库结构相对啰嗦。如果你只是要快速验证数据可行性,选 v8n;如果后面要魔改 backbone 加注意力,v5 的代码可读性反而更好。
预训练权重必须用。一百张图从零训,收敛慢且极易崩。用 COCO 预训练权重做迁移,前几轮就能看到 loss 明显下降。注意类别数要改:COCO 是 80 类,你的茶芽可能是 2 到 5 类,改nc之后检测头会重新初始化,这部分参数学习率可以适当调高。
# tea_shoot.yaml path: ./tea-shoot-detection train: images/train val: images/val test: images/test nc: 4 # 按你实际类别数改 names: 0: single_bud # 单芽 1: one_bud_one_leaf # 一芽一叶 2: one_bud_two_leaf # 一芽二叶 3: banjhi # 对夹叶这个 yaml 是 v8 的标准数据配置。path是根目录,train/val/test是相对路径。类别名用英文避免编码问题,中文名在部分版本的可视化里会乱码。nc一定要和 names 的条目数一致,不一致训练直接报错,这个错还算友好。
3.2 数据切分:80 张训练到底怎么分才不亏
一百张图,我建议 70/15/15 而不是 80/10/10。原因很简单:验证集太小,mAP 波动会大到没法判断模型好坏。15 张验证集虽然也少,但至少能看出趋势。切分要分层抽样,保证每个类别在三个子集里都有代表,尤其是样本少的"对夹叶"类。
import random, shutil from pathlib import Path from collections import defaultdict root = Path("tea-shoot-detection") lbl_dir = root / "labels" img_dir = root / "images" # 按主类别分层 cls2stems = defaultdict(list) for txt in lbl_dir.glob("*.txt"): classes = {int(l.split()[0]) for l in txt.read_text().strip().splitlines()} for c in classes: cls2stems[c].append(txt.stem) random.seed(42) splits = {"train": [], "val": [], "test": []} for c, stems in cls2stems.items(): stems = list(set(stems)); random.shuffle(stems) n = len(stems) n_val, n_test = max(1, int(n*0.15)), max(1, int(n*0.15)) splits["val"] += stems[:n_val] splits["test"] += stems[n_val:n_val+n_test] splits["train"] += stems[n_val+n_test:] for split, stems in splits.items(): (root / "images" / split).mkdir(parents=True, exist_ok=True) (root / "labels" / split).mkdir(parents=True, exist_ok=True) for s in set(stems): shutil.copy(img_dir / f"{s}.jpg", root / "images" / split / f"{s}.jpg") shutil.copy(lbl_dir / f"{s}.txt", root / "labels" / split / f"{s}.txt") print(split, len(set(stems)))分层逻辑是:先按每张图包含的类别归类,再在每类内部随机切。random.seed(42)固定随机种子,保证你和我切出来一样,方便复现。max(1, ...)是防止某个类别样本太少时切出 0 张验证图。注意这里用set(stems)去重,因为一张图可能同时属于多个类别,会被重复加入。
4. 训练参数怎么设:小数据集的关键几个旋钮
小数据集训练,参数比模型结构重要得多。一百张图,你改 backbone 带来的收益远不如把学习率、增强、早停调对。这一章讲我实际会动的几个参数,以及为什么。
4.1 输入尺寸、batch 与学习率
输入尺寸直接决定小目标能不能被看见。茶芽框归一化宽高如果中位数在 0.05 左右,640 输入下框只有 32 像素,勉强够;如果更小,建议上 960。但尺寸越大显存吃得越狠,batch 就得降。一百张图用 batch=8 或 16 都行,梯度累积可以模拟更大 batch。
学习率用预训练权重的默认值附近,v8 默认lr0=0.01,小数据集我一般降到0.001到0.005,避免前期把预训练特征冲垮。配合余弦退火和 warmup,前 3 轮 warmup 让模型慢慢适应新数据分布。
yolo detect train \ data=tea_shoot.yaml \ model=yolov8n.pt \ imgsz=640 \ epochs=200 \ batch=8 \ lr0=0.003 \ lrf=0.01 \ warmup_epochs=3 \ cos_lr=True \ patience=30 \ cache=True \ device=0逐项说:imgsz=640是起点,小目标多就改 960;epochs=200配patience=30,30 轮验证集没提升就早停,小数据集很容易过拟合,早停是后悔药;cos_lr=True余弦退火让后期学习率平滑下降;cache=True把图缓存进内存,一百张图完全放得下,能明显加速;lrf=0.01是最终学习率相对初始值的比例。
4.2 数据增强:哪些开、哪些关
小数据集靠增强撑多样性,但茶芽检测有个特殊性:颜色和形态是判别关键,过度增强会破坏语义。我一般开 Mosaic、随机翻转、小幅 HSV 抖动,关掉大角度旋转和剪切。Mosaic 在训练后期建议关掉,v8 有close_mosaic参数,默认最后 10 轮关闭,让模型在真实分布上收尾。
# 在训练命令里追加增强参数 hsv_h: 0.015 # 色调抖动,茶芽颜色敏感,别调大 hsv_s: 0.5 # 饱和度 hsv_v: 0.3 # 明度 degrees: 10.0 # 旋转角度,茶芽有方向性,别超过 15 translate: 0.1 scale: 0.3 # 缩放,模拟不同拍摄距离 flipud: 0.0 # 上下翻转关掉,茶芽不会倒着长 fliplr: 0.5 # 左右翻转保留 mosaic: 1.0 close_mosaic: 10flipud=0.0是我踩过坑之后的固定设置:茶树芽朝上生长,上下翻转会造出自然界不存在的样本,模型学到的是噪声。degrees控制在 10 度以内,同理。hsv_h给 0.015 是保守值,茶芽的嫩绿和背景老叶的深绿靠色调区分,抖动太大会让这个边界模糊。
5. 避坑与排查:一百张图训练最容易翻车的五件事
这一章是我做小数据集检测时反复遇到的真实问题,按"现象 → 原因 → 解决"写,你对照自己的训练日志看。
现象一:loss 一直不降,mAP 卡在 0.01 附近。原因通常是标签路径配错或类别数不匹配。YOLO 找不到标签文件时不会报错,而是把背景当负样本训,loss 看起来在动但学不到东西。解决:训练前用第 2 章的脚本核对图文配对,确认data.yaml里nc和实际类别数一致,跑一轮后看labels统计输出里每类框数是否正常。
现象二:训练集 mAP 0.95,验证集 0.3,差距巨大。典型过拟合,一百张图太容易发生。原因可能是增强太弱、模型太大、训练太久。解决:换更小的模型(v8n 而不是 v8m),加强增强,开早停,把patience降到 20,必要时加 dropout 或 weight decay。
现象三:密集区域漏检严重,单个芽检测正常。原因是 NMS 阈值或 anchor 匹配问题。茶芽密集时框重叠度高,NMS 阈值太低会把相邻的真框当重复框删掉。解决:把推理时的iou阈值从默认 0.7 提到 0.8,训练时检查 anchor 是否匹配小目标,v8 是 anchor-free 的,重点看输入尺寸是否够大。
现象四:模型把"一芽一叶"和"一芽二叶"混为一谈。这两类形态差异小,一百张图里如果每类只有二三十个样本,模型分不开很正常。原因不是模型不行,是数据不够。解决:要么合并这两类降低难度,要么针对性补充这两类的难例样本,别指望调参能解决。
现象五:推理时框位置整体偏移。八成是坐标格式搞混了。YOLO 用归一化中心点格式(x_center, y_center, w, h),如果你按(x1, y1, x2, y2)像素格式理解,画出来的框就会偏。解决:回到第 2 章的可视化脚本,把框画回原图确认,偏移就是格式问题,不用怀疑模型。
注意:小数据集训练,验证集指标只能看趋势,不能当绝对性能。15 张验证图,mAP 差 0.1 可能只是某一张图检没检出来,别为这个数字反复调参。
6. 从一百张到能用:验证方法与扩样策略
一百张图跑通之后,真正的问题是"这套方案值不值得投入扩样"。我的判断方法是:先看模型在验证集上的错误类型分布,再决定扩样的方向。如果错误集中在漏检小目标,扩样就补密集场景;如果集中在类别混淆,扩样就补难例;如果错误随机分布,说明数据量本身不够,得整体扩。
验证不能只看 mAP 一个数。我一般导出混淆矩阵和 PR 曲线,看每一类的召回和精确率。茶芽检测里,"对夹叶"这类样本少的类别召回往往很低,但它在业务上可能恰恰是要剔除的对象,漏检代价高。这时候就要单独看这一类的指标,而不是被整体 mAP 掩盖。
# 训练完成后在验证集上评估,导出混淆矩阵和 PR 曲线 yolo detect val \ model=runs/detect/train/weights/best.pt \ data=tea_shoot.yaml \ imgsz=640 \ conf=0.25 \ iou=0.8 \ plots=True \ save_json=Trueconf=0.25是评估时的置信度阈值,iou=0.8配合密集目标。plots=True会生成混淆矩阵、PR 曲线、F1 曲线到runs/detect/val/下。重点看混淆矩阵的非对角线:如果"一芽一叶"大量被预测成"一芽二叶",说明这两类特征重叠,扩样时要专门收这两类的边界样本。
扩样策略上,我的经验是"宁要多样场景,不要重复相似图"。一百张如果都是同一片茶园、同一个光照、同一个角度拍的,扩到一千张同分布也没用。真正提升泛化的是不同品种、不同季节、不同拍摄高度、不同光照的样本。每类至少补到 300 到 500 个实例,模型才谈得上稳定。
最后一个具体技巧:用训练好的模型去跑未标注的新图,把高置信度的预测当伪标签,人工修正后加入训练集,这是小数据集扩样性价比最高的路子。但伪标签的置信度阈值要设高,我一般用 0.7 以上,低于这个的宁可手标,否则错误会累积。我自己做这类项目,习惯是每轮扩样后重新切分验证集,保证验证集始终是"没参与过训练和伪标注"的干净数据,不然指标会虚高,最后上线翻车。这个习惯帮我省过好几次返工。希望帮到你。
本文还有配套的精品资源,点击获取