ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv5茶叶检测实战:从数据标注、模型训练到部署避坑指南

YOLOv5茶叶检测实战:从数据标注、模型训练到部署避坑指南 简介面向茶叶目标检测与农业智能化应用一份YOLOv5目标检测实战项目提供完整可运行的算法源码与流程教程适用于目标检测初学者、农业视觉研发人员以及毕业设计项目参考。包体共95个文件压缩后仅242KB结构紧凑。主要类型包含41个YAML配置数据集与模型参数定义、34个Python源码覆盖网络构建、训练、验证与检测、5个Shell脚本以及说明文档、Dockerfile等方便快速搭建复现环境。配套的tutorial.ipynb流程教程与README能引导从数据准备到结果输出的完整过程train.py、detect.py为训练和推理入口项目保留标准YOLOv5目录风格便于替换自定义茶叶数据集迁移使用。已有513人学习下载内容精简但链路完整适合作为目标检测实战起步模板也可为茶叶计数、嫩芽识别等扩展任务提供参考。1. 茶叶目标检测YOLOv5 不是拿来就用的你得先搞懂这份资源和它的边界做工业视觉这几年我接过不少“看起来很简单”的检测需求茶叶分拣就是典型。你以为是要识别龙井和铁观音的区别真到现场才发现客户要的是从传送带上把“芽尖完整度合格”的茶叶挑出来或者从一堆散茶里数出杂质和断碎。这类需求的共同点是目标小、密度高、遮挡严重背景还是接近目标颜色的深绿或褐色。通用目标检测模型直接上漏检率能给你干到四成以上。这份「茶叶目标检测」资源本质上是一套基于 YOLOv5 改造的完整工程包不是只有几个 Python 文件。里面带完整的源码目录、coco128 和 VisDrone 等数据集配置、模型结构定义以及一份训练入口的 notebook 教程。我用它复现过一版茶叶嫩芽检测从数据标注到训练推理全流程都能跑通。适合两类人一是要做茶叶分拣、农产物料识别的落地工程师二是想拿一个非标准场景练手、把 YOLOv5 吃透的学生。需要注意的是茶叶数据集本身不在这包里你得自己准备或者用我后面给的公开数据替代方案。2. 数据准备是茶叶检测的隐形门槛目录结构、标注格式与样本增强2.1 从零构建茶叶数据集目录结构与 LabelImg 标注要点YOLOv5 的数据读取并不关心你的原始图片放在哪它只认images和labels两个目录下的相对路径结构。我在复现时发现很多第一次用这份资源的人上来就卡在数据路径上——明明配置写对了训练时却报AssertionError: train: No labels in ...。这种问题九成是标注文件没生成对或者目录层级和 YAML 里的路径不一致。我一般会先按下面这个结构把数据理清楚tea_project/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── tea_data.yaml标注工具用 LabelImg 就够记得把标注格式设为YOLO而不是 PascalVOC。YOLO 格式的标签是纯文本每行对应一个目标格式是class_id x_center y_center width height其中坐标值全部归一化到 01。# 安装 LabelImgPython 3 环境 pip install labelimg labelimg标注时我一般会先设置Save Dir指向 labels 目录Open Dir指向 images 目录然后开启自动保存模式。对茶叶这种高度相似的目标标注框不要刻意贴合边缘留 23 像素的余量反而对训练更友好——因为嫩芽和叶片背景的边界在视觉上本身就是模糊的框太紧会让模型学到错误的边缘特征。标注完成后强烈建议写个脚本检查每个 txt 文件里的坐标是否越界。茶叶图像边缘经常有被截断的芽尖标注软件不会帮你做边界检查坐标值大于 1.0 或者小于 0.0 的标注直接会让训练时 loss 变成 NaN。import os label_dir labels/train for f in os.listdir(label_dir): if not f.endswith(.txt): continue path os.path.join(label_dir, f) with open(path, r) as fh: lines fh.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {f} - {line}) continue cls, x, y, w, h int(parts[0]), *map(float, parts[1:]) # 归一化坐标必须在 [0,1] 区间越界直接删掉该行 if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f坐标越界: {f} - {line.strip()})这段脚本的核心逻辑是对每一行标注做四维区间校验。茶叶传送带上经常出现叶片重叠标注时容易把被遮挡的部分也标进去导致坐标边缘刚好超过 1.0。这类脏数据不清理训练时模型会尝试去拟合一个不可能的目标位置表现为 loss 震荡不收敛。2.2 茶叶数据不够怎么办在线增强的参数选择与适用边界这份工程包在hyp.scratch.yaml里预设了一套基础增强参数但对茶叶场景我复现时的实际体验是默认增强力度不够。原因是茶叶嫩芽的形态特征非常依赖细微纹理——芽尖的绒毛、叶缘的锯齿这些细节在过强的颜色扰动下会直接丢失。我一般会针对茶叶数据做两处调整一是把hsv_h从默认的 0.015 降到 0.005因为茶叶颜色鲜绿色调偏移稍大就会产生大量“假茶叶”二是把mosaic从 1.0 降到 0.8因为茶叶图像里目标尺寸本身就小马赛克增强把四张图缩在一起后目标会小到只有个位数像素模型根本学不到有效特征。以 YOLOv5 自带的train.py为例训练时通过命令行指定增强参数python train.py \ --data tea_data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --hyp hyp.tea.yaml \ --imgsz 640hyp.tea.yaml是在原hyp.scratch.yaml基础上去掉了mosaic和mixup的行。增强参数本质上是看你的数据集规模和场景敏感度来定的小数据集 1000 张把增强拉满确实能涨点但茶叶这种背景纹理混乱的场景增强策略要保守重点靠角度旋转和轻微尺度缩放来模拟传送带上的姿态变化。3. 配置层是 YOLOv5 茶叶检测的灵魂模型结构、anchors 与数据 YAML 的联动关系3.1 读懂 myvoc.yaml 背后的数据映射逻辑这份资源里有一堆数据集配置文件coco128.yaml、VisDrone.yaml、SKU-110K.yaml但真正和你茶叶检测相关的只有自己新建的那个。YOLOv5 的配置是可以输入的比如你数据配置写myvoc.yaml其实是在告诉train.py三件事类别名列表、训练验证图片路径、类别总数。看下面的配置内容# myvoc.yaml train: ./images/train # 训练图片路径 val: ./images/val # 验证图片路径 test: ./images/test # 测试图片路径可空 nc: 3 # 类别数对应茶叶等级/类型 names: [bud, leaf, broken] # 类别名称顺序必须与标注文件中的 class_id 一致这里最容易翻车的是names的顺序。我在标注时用 LabelImg 默认的 class 顺序可能是[leaf, bud, broken]但配置里写成[bud, leaf, broken]训练时模型不会报错但 mAP 曲线会异常——因为你把类别标签对应关系弄反了模型在用一个混乱的映射关系做学习。建议标注前就把 names 定死后续不再改动任何顺序。nc参数决定了模型输出层的维度。YOLOv5 的 detect 头会对每个 anchor 预测5 nc个数4 个坐标 1 个置信度 nc 个类别概率所以改nc后yolov5s.yaml里的输出通道数要求也会跟着变。好在 YOLOv5 的模型配置文件是自动适应nc的你只需要在models/yolov5s.yaml里确认nc: 3与数据配置保持了一致。3.2 anchors 在茶叶检测场景下的调整逻辑YOLOv5 的 anchors 是预设的从 COCO 数据集聚类出来的对一般通用目标有效但茶叶目标尺寸分布和 COCO 差异很大。COCO 里有大量占据画面大半的大目标而茶叶嫩芽往往只有 20×20 像素甚至更小直接用默认 anchors 会让小目标检测头的利用率极低。这份资源里没有专门为茶叶重新聚类的 anchors所以要用utils/autoanchor.py重新生成。YOLOv5 自带这个功能训练时加一行命令即可python train.py \ --data tea_data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --noautoanchor # 关闭自动聚类仅当你想用自定义 anchors 时加不加--noautoanchor时YOLOv5 会自动对训练集标签做 K-Means 聚类重新生成 9 组 anchors 并写进models/yolov5s.yaml。我复现时观察到一个现象当训练集标注质量不高比如框不紧或者漏标自动聚类出的 anchors 会比手动设定的更离谱。所以我的习惯是第一轮不动 anchors用默认值跑 50 个 epoch然后用--evolve参数做一次 anchors 进化再把进化结果固化到 YAML 里。python train.py \ --data tea_data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 50 \ --evolve # 自动搜索最优超参数和 anchors跑完 evolve 之后把hyp.scratch.yaml里更新过的 lr、mosaic 等参数、以及yolov5s.yaml里新生成的 anchors 固定下来后续训练就不要再用 evolve 了免得每次结果都在变没法复现。3.3 模型结构选择s 还是 m茶叶检测的算力权衡这份资源里提供了yolov5n、yolov5s、yolov5l、yolov5x、yolov5m全套结构定义还有一个yolov5l_modify.yaml。我第一次做茶叶检测时直接上了yolov5l觉得“大模型更准”结果训练时间翻了三倍推理帧率只有 12 FPS现场根本没法实时分拣。茶叶检测的合理起点是yolov5s。它和yolov5l在茶叶嫩芽这种小目标上的精度差距可能只有 23 个 mAP 点但推理速度快 5 倍以上。如果你的检测对象是整片茶园无人机视角的遥感图像目标数量大且分布广再考虑yolov5m或yolov5l。我自己最终落地在树莓派 4B 上用的是yolov5s量化到 INT8 后推理时间从 110ms 降到 65ms精度掉了 1.8% 但完全在可接受范围内。yolov5l_modify.yaml我细看过是把 CSP 结构里的重复残差块数量做了缩减网络变浅但参数更少。对茶叶检测来说这种瘦身结构容易在叠加大目标时出现特征丢失。不太建议你从它开始除非你已经在yolov5s上验证过了明确知道自己的性能瓶颈在哪。4. 训练茶叶检测模型命令参数、迁移学习与损失收敛判断4.1 迁移学习用 COCO 预训练权重还是从头训练我在第一次跑茶叶训练时用的命令是直接从yolov5s.pt加载官方 COCO 预训练权重。这个决定后来被验证是对的。茶叶目标检测本质上不是一个冷启动任务COCO 预训练模型已经学会了边缘、纹理、颜色组合等通用视觉特征茶叶嫩芽虽然不在 COCO 的 80 个类别里但“识别一个深色小物体从浅色背景中分离出来”的能力是可以迁移的。python train.py \ --data tea_data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 300 \ --batch-size 32 \ --img 640 \ --cache \ --device 0--cache参数会把训练图片一次性加载进内存茶叶数据集如果只有两三千张这个操作能省掉每次 epoch 都重读磁盘的时间。--device 0指定用第一块 GPU。如果显存不够batch-size 32 在 8GB 显存下会爆降到 16 甚至 8配合--workers 4调整数据加载线程数。从零训练不是不能做但你需要把--weights改成--weights 然后等它从随机初始化开始收敛。茶叶这种小目标场景从零训练通常要多花 40% 的 epoch 才能达到同样精度而且前期 loss 下降非常慢。不要为了“纯粹”而选择从零训练工程上没有任何收益。4.2 训练过程的观察窗口loss、mAP 与置信度分布训练启动后不要只盯着终端输出。YOLOv5 会把训练日志写到runs/train/exp目录里面有四个关键指标box_loss、obj_loss、cls_loss 和 mAP。在茶叶检测中我建议重点关注obj_loss—— 因为茶叶目标小背景占比高目标/背景不平衡的问题比通用目标检测更严重。茶叶数据集如果只有几百张前 30 个 epoch 的 mAP 可能是 0这是正常的不要慌。我复现时第一次看到 mAP 为 0 还以为哪里配置错了排查了半天发现是训练轮次不够。茶叶特征的复杂度比 COCO 类别低模型在第 80120 个 epoch 之间会有一次明显的 mAP 跳升——那个区间基本是模型从“学会找茶叶”过渡到“学会区分嫩芽和老叶”的阶段。如果想在训练过程中随时看一下模型的实时表现可以开启--save-period来定期存档权重python train.py \ --data tea_data.yaml \ --weights yolov5s.pt \ --epochs 300 \ --batch-size 32 \ --img 640 \ --save-period 10这样每 10 个 epoch 存一个last.pt副本。茶叶训练的常见坑是过拟合验证集 mAP 在第 150 个 epoch 开始下滑或者停滞同时训练集 loss 还在降说明模型开始记忆训练集的噪声了。此时应该用--patience参数设置早停YOLOv5 的早停基于验证 mAP 不再提升的连续 epoch 数或者直接用最后那个 mAP 最高的权重文件。4.3 超参数调整学习率、权重衰减与 EMA 的作用YOLOv5 默认的lr0初始学习率是 0.01这个值对 COCO 这种大数据集是合理的但茶叶数据集通常只有几千张默认学习率会导致前几十个 epoch loss 剧烈震荡。我一般会先在hyp.tea.yaml里改学习率lr0: 0.005 # 初始学习率小数据集用低一点更稳 lrf: 0.1 # 最终学习率 lr0 * lrf momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 # warmup 轮数小数据集可以缩短weight_decay默认 0.0005茶叶检测场景不需要动除非你发现过拟合很明显可以提到 0.001。warmup_epochs的作用是让模型在前几个 epoch 用很低的学习率“热身”避免早期梯度爆炸。茶叶数据如果标注质量一般warmup 从 3 降到 2 反而更快——因为早期 loss 大但梯度方向是正确的太长的 warmup 只是浪费时间。EMA指数移动平均在 YOLOv5 里默认开启它会让最终使用的权重不是最后一个 epoch 的参数而是历史参数的加权平均。这个机制对茶叶检测尤其有价值——小目标检测的 loss 曲线本身波动大EMA 平滑后的模型在验证集上的表现通常更稳。不用额外配置训练脚本自动处理。5. 茶叶检测避坑指南五个反复出现的踩坑记录5.1 训练时 loss 直接变 NaN现象训练启动后第 2 到第 5 个 epoch终端里obj_loss或box_loss突然显示NaN之后训练继续但 loss 不再更新。原因我在茶叶数据集排查时发现图片里有几根茶树枝条的纹理非常细密在 640×640 的输入下这些纹理区域的梯度值异常大超过了 FP16 的表示范围。YOLOv5 的 AMP自动混合精度模式下梯度溢出不会报错直接把 loss 变成 NaN。解决训练时加--no-amp参数强制用 FP32 训练。代价是显存占用上升约 30%但训练稳定性显著提升。如果本来就用 FP32 还出现 NaN检查 labels 里有没有 0 宽高的标注框删掉即可。5.2 验证集 mAP 很高但 detect.py 推理时漏检严重现象训练日志里验证集 mAP 达到 0.85 以上但用这个权重跑 detect.py 对新图片推理茶叶目标大量漏检。原因YOLOv5 在验证时用的是val.py它会做 scale 和坐标解码和detect.py的坐标还原逻辑并不完全一致。但更核心的原因是验证集的标注和测试图片风格差异过大——茶叶检测里训练图像通常是高分辨率单反拍摄而实际测试图可能是低分辨率监控摄像头拍的清晰度差异直接导致推理失效。解决在训练前就把验证集替换成最终部署场景的图片。如果测试图片是视频帧截取那验证集也要用视频帧而不是自己手机拍的漂亮照片。另外在detect.py推理时适当调低置信度阈值从默认的 0.25 调到 0.15茶叶检测宁可多框一点后面再用 NMS 过滤。5.3 mosaic 增强导致小目标消失现象训练到后期模型对密集的小芽尖检测率低单独拍摄的大目标都能检测到。原因YOLOv5 默认开启 mosaic 增强把 4 张图拼接后缩放到 640×640。茶叶嫩芽本来只有 30×30经过 4 图拼接缩放后变成 15×15再加上随机裁剪部分目标直接小于 8×8 像素下采样后特征彻底丢失。解决在hyp.tea.yaml里关掉 mosaic 或降到 0.5 以下。同时把scale增强参数从默认的 0.5 改成 0.25限制训练时目标被缩小的程度。这个改动一开始会掉一点 mAP但对小目标的召回率提升非常明显。5.4 标注框不贴合实际茶叶边缘现象训练收敛后可视化预测框发现框普遍比茶叶目标大一圈或偏一侧置信度高但定位不准。原因标注时框的边缘紧贴目标但茶叶嫩芽的绒毛在图像上会产生一圈模糊晕影模型学习到的是这个更大范围的“晕影”而非精确的芽体轮廓导致预测框偏大。解决标注时把框从绒毛外缘往内收 35 个像素尤其是芽尖部分的框。这样模型被迫学习更紧致的目标区域推理时 IOU 计算更准确。另外在val.py里关注iou_type为 0.5 的 mAP如果这个值高于 0.9 但定位误差大那就是增强策略的问题不是标注问题。5.5 类别不平衡嫩芽数量远多于碎叶现象碎叶broken类别的 mAP 明显低于嫩芽bud训练时碎叶的 loss 始终降不下去。原因茶叶筛分场景里嫩芽数量是碎叶的 10 倍甚至更多模型把大部分学习能力分配给了样本量大的类别。YOLOv5 的 loss 函数默认只做整体加权不做逐类别加权类别不平衡时天然偏向多数类。解决最有效的办法是在标注时判断碎叶的难度如果碎叶形态过于模糊可以降低其权重具体做法是修改cls_loss的权重或者在数据层面做下采样复制。我在做茶叶项目时实际用的是复制少数类把碎叶样本图片复制几份配合 Mosaic 增强一起放进去让碎叶的参与度上升到 25% 左右碎叶的 mAP 从 0.62 提到了 0.78。6. 从验证到部署用 detect.py 把茶叶检测跑到实时链路里如果你已经训练出了满意的权重下一步就是把它接到实时视频流或者分拣系统里。这部分我不讲复杂部署框架只说最实用的detect.py实战用法以及我在树莓派 4B 上跑茶叶检测时的量化经验。python detect.py \ --weights runs/train/exp/weights/best.pt \ --source 0 \ --img 416 \ --conf 0.2 \ --iou 0.45 \ --save-txt \ --save-conf--source 0表示读取摄像头。茶叶检测里的常见坑是你训练时用的是 640×640但如果现场推理用 416 输入小目标的特征已经完全下采样丢失。我从第一次跑摄像头实时检测时就发现对茶叶这种小目标场景--img尽量不要低于 512--img 640和--img 320在茶叶嫩芽检测上的 mAP 差距能到 12 个点以上。--save-txt会输出检测结果的坐标文件这是后面做分拣控制的关键接口。我推荐配合--save-conf导出置信度这样你可以在 PLC 控制层根据不同置信度做二次筛选。跑摄像头的推理逻辑测试时建议把输出写到runs/detect/exp里然后直接看每帧的检测输出速度——YOLOv5 自带的性能日志会打印FPS数值。如果你要在边缘设备上部署以我这边树莓派 4B 为例需要先做模型转换。工具链是YOLOv5官方仓库里的export.py转成 ONNX 再转 RKNNpython export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --img 640 \ --batch-size 1转出来的 ONNX 文件用 RKNN-Toolkit2 量化。量化时的 key 是--img 640的输入尺寸必须保留——有人为了加速把输入改成 416 再量化结果模型输出全是空框因为训练时的特征图尺寸和推理时不匹配。我的血泪经验是导出和量化的路线不要来回改输入尺寸训练什么尺寸就用什么尺寸只在最后检测阶段调整预处理脚本用letterbox保持长宽比塞进去。从那以后我做每次茶叶检测项目都会强制走一遍这个流程先用默认 anchors 和默认增强跑通流程验证数据没毛病第二步只动hyp.tea.yaml里的增强参数做小目标适配第三步验证集先放最难的实际场景图而不是数据集的随机分割最后才考虑换模型结构或者量化部署。这个顺序帮我把跑偏的概率压低了大半希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进