基于YOLOv5的冬虫夏草小目标检测实战:从数据标注到部署 简介本资源是面向计算机视觉初学者与农业AI应用开发者的YOLOv5目标检测实战项目聚焦冬虫夏草幼苗期生长状态的自动化识别任务解决野外或温室环境下微小目标、低对比度场景下的精准定位难题。压缩包共1552个文件189.89MB涵盖748张高质量RGB训练/验证图像640×482分辨率、615个标准YOLO格式标签txt、52个配置yaml、51个核心Python脚本含训练/推理/可视化模块以及预训练权重.pt、Docker部署文件、评估结果CSV和TensorBoard日志等完整工程资产。已有273人学习下载项目已实测可直接运行训练20 epoch即达mAP0.50.96附带混淆矩阵、PR曲线与F1曲线等全套评估图表且目录结构规范含datasets、models、runs等标准YOLOv5子模块便于二次训练、模型优化与跨场景迁移。 在高原草甸上找一根冬虫夏草的草头有多难远处看全是枯草和苔藓近处看又和石块、土缝混在一起。哪怕是有经验的人蹲在那儿瞪十分钟也不一定能发现一根刚出土的子座。要是把这个过程交给算法事情反而变得很直接——用YOLOv5训练一个1类别的目标检测模型输入照片或无人机画面输出每个虫草的位置框和置信度。这个项目就是围绕这件事完整做下来的一套东西数据、训练代码、训练好的权重全部打包。如果你自己也有类似的小目标检测需求比如野外植物识别、药材培育监测、或者想跑通一个从零到部署的YOLOv5实战流程这套思路可以整套搬走。1. 冬虫夏草检测为什么要用YOLOv5场景痛点与选型思路1.1 这个项目首先要解决的现实问题冬虫夏草的生长检测说白了就是回答一个问题一张图里哪里有虫草而且判断得又快又准。和常规的工业质检、交通目标检测不太一样这个对象有两个很麻烦的特点。第一目标实在太小。虫草露出地面的部分通常只有几厘米高在手机拍的千像素级别照片里可能只占几十个像素。如果是无人机航拍目标甚至会小到十几个像素比COCO数据集里的小鸟还难认。第二背景干扰太严重。高原草甸的地表有枯草、地衣、苔藓、碎石、动物粪便颜色和虫草的子座高度接近尤其到了秋末枯草和子座的颜色几乎一模一样连人眼都容易看错。这种场景放到实际工作里就非常消耗人力。科研人员做野外样方调查、种植基地做生长密度评估时往往需要大量翻照片、做人工标记效率很低。而一个训练好的检测模型可以自动把初筛工作做掉把可能目标框出来让人只需要复核候选区域工作量能降一个量级。这也是我做这个项目的初衷。1.2 为什么不是传统图像处理也不是更新的检测模型碰到这类从相似背景里找小东西的问题可能第一反应是传统图像处理转灰度、做边缘检测、按颜色阈值分割。但我基本放弃了这条路。冬虫夏草子座和枯草在HSV颜色空间里的分布大量重叠光照一变阈值就失效了边缘检测在这种纹理极其复杂的背景下也会输出一大片无意义的轮廓。传统方法的鲁棒性撑不过不同天气、不同地表。那为什么用YOLOv5而不是Faster R-CNN、YOLOv8或者RT-DETR先说两阶段检测器。Faster R-CNN精度虽高但速度慢、部署重在这个任务里性价比太低。虫草检测通常要处理大量野外照片有时候还要接视频流或无人机实时画面速度不够就很难落地。YOLOv8和RT-DETR确实更新但对我来说YOLOv5的生态成熟度是压倒性的优势。社区资料多遇到的问题几乎都能搜到答案配套的标注、训练、导出工具链完整TensorRT、RKNN、OpenVINO、ncnn各个平台的部署案例都很齐全。而且这个项目只有1个类别模型结构不需要多复杂YOLOv5的s/m版本完全够用。说到底项目要的是稳定落地不是追新框架。2. 数据采集与标注的硬骨头小目标、强背景干扰与数据清洗2.1 图片从哪儿来以及怎么保证多样性数据集是这个项目里最花时间、也最决定成败的部分。我整理数据的时候把来源分成了两类一类是实际拍摄的图片另一类是公开渠道可用的科研图片、图像资料。这里特别提醒一句如果用到别人的图片一定要确认授权情况尤其是商业用途合规问题不是小事。拍摄阶段的多样性比数量更重要。我做数据规划时会刻意覆盖这几个维度不同光线晴天、阴天、清晨、傍晚、逆光、顺光都要有。高原地区光比很大顺光时虫草清晰可见逆光时整根像根黑条模型只见过顺光的话逆光场景直接废掉。不同拍摄距离近景特写、中景、远景都要有。近景图能帮模型学到细节特征远景图能帮模型适应小目标形态。不同背景纯草甸、枯草堆、苔藓地、碎石滩、有积雪的地表各来一批。背景多样性决定了模型以后会不会在陌生环境里乱框。不同生长阶段刚出土的短子座、中期生长的中等长度、接近成熟的棒状子座形态差异很大如果不覆盖模型只能认某一种。图片格式我统一转成了jpg分辨率保持在1200像素以上。下载的图片不要直接丢进数据集先做一轮筛选去掉严重模糊的、被水印遮挡的、重复的。水印尤其是大问题模型很容易把照片角落的水印纹理当成特征训练出来的权重一到干净环境就性能跳水。2.2 标注规则单类别项目最容易忽略的坑标注用的工具是LabelImg这个老牌工具虽然界面朴素但稳定好用。后面做难例补充时我也用到了X-AnyLabeling的自动标注功能让一个初步训练好的模型先生成预标注框我再人工修正效率能快不少。但工具是次要的真正的坑在标注规则的一致性。单类别检测看着简单标起来却特别容易出问题。我定了几条硬性规则建议做类似项目的同学直接抄只标露出地面的子座部分不标被遮挡的部分也绝不标只露出一小截边缘的模糊目标。遮挡面积超过50%的虫草不标。你可能会觉得这明明有虫草啊标上不是更好吗但实际上这种模糊框会让模型学到内容不清晰也可以框出来直接在背景区域产生大量误检。多个虫草靠得很近时每一个都单独标框不要合并。合并框会让模型在密度高的区域输出一个巨大的框完全失去定位能力。类别统一用英文名比如cordyceps。yaml里的names和标注文件里的class id保持一致导出部署时就不会出编码问题。标注完成后要抽样复查。我当时从训练集里随机抽了5%的图逐一检查框是否贴合目标边缘、有没有漏标、有没有把枯草杆误标成虫草。这一环节不要省标注质量差的话后续训练就是在给错误数据做拟合。关于数据量我最后保留的有效图片大约2600张标注框总量超过3200个。这个量级对1类别检测来说不算特别多但配合数据增强已经足够训练出一个能用的模型。如果你是一个新场景我建议起步至少准备1500到2000张有效图片太少的话模型学不到足够的形态变化再多的话人工标注成本就上去了。2.3 负样本与难例一张背景图可能比十张正样本更值钱这个经验我是从误检中总结出来的。第一次训练完模型把一堆枯草杆和苔藓纹理认成了虫草precision惨不忍睹。后来专门做了一件事在训练集里加入大量没有虫草的纯背景图。这些负样本不需要标注把图片丢进数据集对应的txt标签文件留空就行。YOLOv5支持这种负样本模型会在训练过程中学到这些背景不是目标从而有效压低误检率。难例的处理是另一门学问。训练过程中我记录了一批错检样本发现主要集中在两类一类是浅黄色、微微弯曲的枯草杆被当成虫草另一类是地衣的条状纹理被当成虫草。针对这些难例我从原始素材里专门挑了大量类似的背景图还手动裁剪了一些和目标很像但不是目标的物体图片补充进负样本集。加完之后precision明显回升。难例挖掘不是一次性工作而是整个训练周期里持续做的一件事。每训一版模型就拿着新权重去跑那些容易出错的图片把新出现的错误样本捞回来补进训练集再训。迭代两三轮之后模型的稳定性会有一个肉眼可见的提升。3. 训练阶段的关键决策Coco预训练、增强策略与超参数调优3.1 环境准备和YOLOv5代码库的最小可用清单YOLOv5的代码库直接用官方仓库clone下来之后装依赖就行git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里有一个很现实的建议环境不要追求最新。我见过太多人一上来就装最新的Python 3.12、最新版PyTorch结果依赖冲突一大堆。稳妥的组合是Python 3.8到3.10、PyTorch 1.13或2.0.x、CUDA 11.8配合requirements.txt里的版本基本不会出幺蛾子。Windows用户还要注意路径问题。项目路径、数据集路径都别带中文这一点我踩过不止一次。另一点是如果机器上装了多个CUDA版本训练前先确认PyTorch能正常调用GPUimport torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))3.2 数据集目录结构与yaml配置YOLOv5要求的数据目录结构如下cordyceps/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── cordyceps.yaml图片文件和标签文件一一对应同名不同后缀。标签文件是txt格式每行内容为class_id x_center y_center width height其中坐标都是归一化到0到1的浮点数。yaml配置文件写法如下# cordyceps.yaml path: /data/cordyceps # 改成实际路径 train: images/train val: images/val test: images/test nc: 1 names: [cordyceps]配置好后先用一个小脚本检查标注格式是否正常避免某个标签txt写错了导致训练直接报错import os label_dir labels/train for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname), r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: print(f格式错误: {fname}) break # 检查归一化坐标是否在0~1之间 vals list(map(float, parts[1:])) if any(v 0 or v 1 for v in vals): print(f坐标越界: {fname})3.3 超参怎么调先跑通基线再针对小目标优化训练的起点是预训练权重。我用的yolov5s.pt也就是COCO预训练权重。用预训练权重而不是从零训练相当于模型已经学会了通用的边缘、纹理、颜色特征我们只需要在冬虫夏草的样本上做微调收敛速度和最终效果都会好很多。首轮训练可以直接用一套相对保守的基线参数python train.py --img 640 --batch 16 --epochs 100 \ --data cordyceps.yaml --weights yolov5s.pt \ --cache ram --freeze 10 --project runs/train几个关键参数说一下--img 640是训练分辨率。虫草这个场景属于典型的小目标问题如果显存够建议试--img 896甚至更高。分辨率越高小目标的像素越多检测率会明显提升。我当时在单卡16G显存上试过896训练比640的mAP0.5高出差不多3到4个点。--cache ram把图片缓存进内存能大幅减少瓶颈IO等待时间。数据集几十G的别这么干内存会爆但2000多张图完全没问题。--freeze 10表示冻结模型前10层让模型在训练初期先只更新检测头防止小数据集上把预训练特征破坏掉。如果训练几轮后发现val loss一直降不下来可以去掉这个参数再试。数据增强方面YOLOv5默认启用了Mosaic、MixUp、HSV变换、翻转等策略。我基于hyp.scratch-low.yaml做了一处调整# 针对虫草场景微调的数据增强参数 hsv_h: 0.02 # 色调变化略微加大模拟晨昏光线的颜色偏移 hsv_s: 0.8 # 饱和度扰动模拟阴天和晴天的差异 hsv_v: 0.45 # 明度扰动模拟高原强光和阴影 degrees: 10 # 轻微旋转虫草拍摄角度变化很大 translate: 0.1 # 平移扰动 scale: 0.5 # 尺度扰动模拟远近拍摄 fliplr: 0.5 # 水平翻转 mosaic: 1.0 # Mosaic增强默认开启 mixup: 0.1 # 混类增强轻微开一点这里不建议把增强参数开得太猛。我试过把Mosaic、MixUp、旋转全拉满结果模型反而变笨了——因为增强过度虫草本身的颜色、纹理信息被破坏模型学到了很多不存在的特征。小目标检测场景适度增强比激进增强效果好得多。3.4 训练中间的判断loss、P/R与过拟合训练不是丢个命令然后等结果。我会在训练过程中定时看TensorBoard或者训练日志里的曲线主要关注三个东西train/box_loss和val/box_loss是否同步下降。如果train loss还在降、val loss已经反弹就是过拟合信号需要停掉或者加正则。val/obj_loss是否稳定。目标置信度损失一直是小目标检测里最容易出问题的项如果它持续震荡说明模型对什么是虫草、什么是背景还没有稳定的判断。Precision和Recall的走势。单类别项目里Precision低说明误检多Recall低说明漏检多两个指标要结合起来看。我当时用yolov5s训到60到70轮左右val/mAP_0.5基本稳定之后再过拟合就开始显现了所以100轮是一个合理的停止点。如果你用yolov5m或者更大的模型、数据量也不大可能30到50轮就开始过拟合了要提前盯紧。4. 不是loss降了就万事大吉指标看板与误检漏检定位4.1 训练产物里到底哪些文件有用训练结束后runs/train/exp或exp2、exp3等目录里有一堆文件。真正要关心的就几个weights/best.pt验证集上综合指标最好的权重项目里说的训练好的权重就是它。weights/last.pt最后一轮权重一般用不上但可以在后续如果还想继续训的时候用。results.png训练曲线汇总包含loss曲线、mAP曲线、P/R曲线。PR_curve.png和F1_curve.png单类别的PR曲线和F1曲线用来判断模型整体水平。confusion_matrix.png混淆矩阵单类别项目里主要看背景误检是否严重。4.2 从PR曲线和置信度角度判断模型水平我拿yolov5s、640分辨率、100轮跑下来验证集上的结果大致是mAP0.5在0.89左右mAP0.5:0.95在0.60左右。这个数字对小目标、强背景干扰的场景来说是能用的水平。但指标本身没有意义关键是看PR曲线的形态。如果PR曲线右上角的面积越接近1说明模型既能把虫草都找出来又很少把背景误判成虫草。如果曲线明显掉腰说明模型在某个置信度区间内大量误检这时候抠conf-thres置信度阈值是有用的。如果曲线越往右越陡说明存在一部分目标即使模型非常确信也检测不到这种情况光调阈值没用得回到数据层面补难例。还有一个很容易忽视的细节单类别模型的confusion matrix里background类别占了很大比重。这时候要看纵轴是虫草被预测成背景的比例这个比例如果偏大说明漏检的根因是模型对小目标不够敏感下一步应该考虑提升输入分辨率或者做切块推理而不是继续调训练超参数。4.3 误检漏检的案例拆解在野外场景最容易犯的错我整理了一批典型的失败案例每个都有对应的处理思路。第一个案例是枯草杆误检。验证集里有一类浅黄色、略微弯曲的枯草杆模型给出的置信度甚至能到0.7以上。当时看检测框的位置发现模型捕捉的其实是条状黄褐色弯曲这几个特征而不是虫草特有的细节纹理。处理办法就是前面说的负样本增强把大量这类枯草杆的图片单独做成负样本包加进训练集并把这种样本按30%的比例与正样本混合。第二个案例是逆光漏检。阳光很强烈的时候虫草在画面里会变成一根近乎黑色的细条模型经常直接忽略。后来我在训练增强里把hsv_v的扰动范围调大了一些让它见过更多低明度的目标形态同时专门在实地补拍了一批逆光照片。效果比较明显逆光场景的Recall从0.6出头提到了0.8左右。第三个案例是航拍图的漏检。无人机高度超过20米后虫草在画面里只有十几个像素模型识别很吃力。当时的做法不是硬撑模型而是做了一个简单的切块推理把一张大图切成多个重叠的640×640小块分别送进模型检测再把结果合并到大图坐标系上。这类滑窗推理虽然增加了推理耗时但对航拍大图非常有效很多小目标问题靠这个办法就能解决。5. 把权重带到野外推理脚本、置信度阈值与边缘端部署5.1 直接用detect.py做批量推理的玩法拿到best.pt之后最直接的验证方式就是跑官方推理脚本python detect.py --weights runs/train/exp/weights/best.pt \ --source ./test_images --conf-thres 0.3 --iou-thres 0.45 \ --save-txt --save-crop --save-conf--save-txt会把每个检测框的类别、坐标、置信度写成txt文件方便后续做统计分析--save-crop会把每个检测到的虫草裁成小图单独保存方便人工快速复核--save-conf会在框上显示置信度数字。如果你接的是USB摄像头或者RTSP视频流--source直接填设备的索引或RTSP地址就行YOLOv5会按视频流逐帧推理。实测在RTX 3060上yolov5s、640分辨率、单张图推理耗时约9到12ms接实时视频完全没问题。5.2 阈值怎么定漏检比误检更应该担心这里有一个经验野外场景的置信度阈值不要设太高。我们在模型效果评估时会倾向于设conf-thres 0.5来保证precision但实际跑野外数据时模型输出的置信度往往偏低尤其是小目标而漏检的代价比误检大得多——漏掉一个虫草那一块区域就可能完全被忽略误检一个后续人工复核最多浪费几秒钟。所以我的习惯是正式使用阶段把conf-thres设在0.25到0.35之间iou-thres维持0.45。如果做人工复核甚至可以再降一点让模型尽量多捞把筛选交给人的眼睛。统计虫草数量的场景宁可候选框多出一些也要保证不要漏。5.3 导出与板端部署从ONNX到TensorRT/RKNN模型最终要么跑在服务器上要么跑在边缘设备上。导出ONNX是最基本的操作python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 11ONNX导出后可以配合ONNX Runtime做CPU推理也可以继续转成其他平台格式。如果部署在NVIDIA GPU上用TensorRT加速非常推荐python export.py --weights runs/train/exp/weights/best.pt --include engine --device 0 --halfhalf表示半精度FP16推理在TensorRT上速度会提升不少。如果是瑞芯微RK3588、RK3568这类板子流程就是PyTorch转ONNX再转RKNN需要注意ONNX输出的节点解析和RKNN的输入输出格式差异。这类平台做量化时对每个检测层都要确认格式否则模型跑在板子上会明明训练得很好却输出一堆垃圾。我在做边缘部署时的经验是先在PC上用ONNX RunTime或TensorRT验证推理可视化没问题再去转新平台的格式不要一上来就跳到板端调试。CPU场景下OpenVINO也是一个好选择在Intel CPU上用OpenVINO推理640分辨率的YOLOv5s速度能到CPU普通PyTorch推理的2到3倍。6. 同一套代码换场景的迁移套路从虫草检测到其他小目标6.1 迁移前先做四件事这套YOLOv5项目框架不止能检测冬虫夏草。实际上所有单类别的户外小目标检测都可以用同样的代码、同样的训练流程迁移过去。我在迁移到其他场景前会做四件事看一眼新目标的数据分布目标通常多大、占整张图的比例是多少、和背景的对比度如何。统计背景复杂程度背景类型多不多是不是存在大量与目标相似的非目标物体。确认拍摄尺度差异是近距离特写、固定机位监测还是远距离航拍这直接影响输入分辨率和增强策略。判断是否需要跑实时如果只是离线批量识别模型可以稍微大一点如果要上无人机或嵌入式设备模型结构就得往轻了压。举个例子同样是小目标检测菌菇、果实这类圆形目标就比虫草这类长条形目标好检测得多因为圆形目标和背景的形态差异更明显而检测海参、水下鱼苗这类低对比度目标就几乎要复刻虫草项目的整个数据增强和负样本方案。6.2 一个判断模型规模的小技巧看目标长宽比在模型选择上我发现一个挺实用的经验如果目标形态是长条形的比如虫草、根茎、鱼苗yolov5m相比yolov5s的精度提升会比较明显因为长条形目标对anchor的形状更敏感模型需要更强的特征表达能力。如果目标是圆形或块状的比如果实、昆虫、鸟蛋yolov5s往往就够了没必要加大模型拖慢速度。YOLOv5训练时会自动做anchor自适应计算所以不要手动去改anchor除非你的目标尺寸分布特别极端。如果你发现模型对某个长宽比的目标总是漏检再用--noautoanchor关闭自动计算、手工在模型配置文件里指定anchor。6.3 哪些代码和流程可以原封不动复用迁移到新场景时整个训练和评估链路基本不用动。数据增强配置可以直接复制Mosaic和HSV扰动对大多数户外目标都有效训练参数只需要根据显存和数据集大小微调--batch和--epochs评估脚本、PR曲线和混淆矩阵的分析方法也是通用的。真正需要重新设计的是数据采集和标注方案。新场景的标注规则、负样本比例、难例挖掘策略都必须根据目标形态重新定。比如检测无人机航拍下的鸟群背景是单调的水面那负样本主要就是波浪纹理如果检测果园里的病虫害叶片背景可能是土壤和钢筋骨架负样本就要覆盖这些杂物。所以这套项目最大的价值不是那个best.pt权重本身而是它演示了一条完整的小目标检测落地路径数据怎么准备、标注规则怎么定、训练参数怎么调、误检漏检怎么定位、权重怎么部署。把这些环节吃透了换什么目标都不慌。最后再分享一点体会。这个项目做完之后我最大的感触是野外检测类项目模型结构真不是重点数据和标注的一致性才是决定成败的地方。为了冬虫夏草这个任务我前两周几乎都在做数据整理、清洗、标注规则打磨后面的训练和调参反而很快。所以如果你想复现或者迁移这个项目先别急着跑训练把数据认认真真看一遍把标注规则写下来这一步越扎实后面越省心。还有训练完了不要只看验证集指标务必找几天拿到真实场景拍几张照片跑一下很多问题只有在真实环境里才会暴露出来。本文还有配套的精品资源点击获取