
简介本资源是一套面向计算机视觉初学者与农业智能化应用开发者的冬虫夏草目标检测实战方案基于YOLOv5框架实现草地场景下高精度识别任务。资源包含完整可运行代码、自建高质量标注数据集及训练好的权重模型解决野外药材智能巡检、中药材数字化采收等实际场景中的小目标检测难题。压缩包共1552个文件涵盖748张640×480分辨率RGB训练图像、615个对应YOLO格式标签文件、52个配置与参数文件如data.yaml、hyp.yaml、51个核心Python脚本含train.py、detect.py等以及训练日志、混淆矩阵、PR/F1曲线图等评估产出整体大小为129.43MB。已有267人学习下载开箱即用——无需环境配置即可执行推理与测试附带20 epoch训练结果mAP0.5达0.96及进一步优化指引便于用户快速复现、微调或拓展至其他高原药材检测任务。1. 项目背景与目标可行性分析1.1 冬虫夏草检测为什么值得单独做一个项目冬虫夏草这个东西做野外科考、中药材资源调查、或者高原生态研究的朋友应该不陌生。它生长在海拔3000米以上的高寒草甸里露出地面的子座通常只有几厘米长颜色是深褐色到棕褐色和枯草、土壤混在一起人眼找起来都很费劲。传统做法靠有经验的采挖人弯腰在草地上慢慢扫一天下来不仅效率低对草甸植被的踩踏损伤也不小。这几年有不少团队在做智能化的资源调查核心需求就是能不能用无人机或者手持设备拍一段草地视频让算法自动把虫草给框出来。这个项目正是这么个定位基于 YOLOv5 的草地冬虫夏草目标检测把数据采集、标注、训练、验证、部署的完整流程走通。标题里提到的“数据集代码训练好的权重”三个东西本质上是给两类人用的一类是做植保、中药材资源调查、农业智能化项目的工程师拿来直接作为基础模型在自己的草地上做微调。另一类是入门目标检测的开发者想找一个背景复杂、目标小、有真实业务含义的落地案例而不是拿 COCO、VOC 那种通用数据集跑一遍 demo学完还是不知道真实项目里的坑在哪。项目本身的技术路线不复杂但难点很典型目标尺寸小、与背景颜色接近、样本数量有限、野外光照多变。这四个问题如果处理不好训练出来的模型在测试集上再漂亮一到实地就露馅。1.2 这个项目解决的核心问题把需求拆开看其实可以梳理成几个子问题数据从哪来怎么保证正样本的有效性。草地场景下负样本的干扰远大于正样本的稀缺问题。草根、土块、枯枝、虫粪甚至石头上的纹路长得都像虫草。如果数据集中没有足够的负样本参与训练模型很容易把“一团深色东西”全当成目标。目标太小下采样之后特征更容易丢失。YOLOv5 默认的输入尺寸是 640×640一株虫草在画面里可能只有几十个像素经过 5 次下采样后小目标的特征可能只剩几个像素学习起来非常困难。标注一致性难保证。不同标注人员对“露头多少算一个正样本”的判断有差异直接导致训练时的正样本边界混乱最终影响 mAP 上限。所以这个项目给的不是“把 YOLOv5 跑起来”这么简单而是一整套针对小目标复杂背景的优化方案包括数据增强策略、锚框调整、训练参数修正、以及部署侧的推理优化。下面我把每个环节怎么做的、为什么这么做一条条讲清楚。2. 技术方案选型与训练环境搭建2.1 为什么选 YOLOv5 而不是 YOLOv8/YOLO11 或其他检测算法现在一说到目标检测很多人第一反应是 YOLOv8 或者更新的版本。但在真实项目中YOLOv5 仍然是一个非常稳妥的选择尤其适合这种数据集规模不大、需要快速迭代、还要跑在边缘设备上的场景。我选 YOLOv5 的理由主要有三个生态成熟资料多坑基本都被踩平了。YOLOv5 从 2020 年发布以来GitHub 上的 issue、博客、教程非常多遇到任何报错都能快速找到解决方案。对做工程落地的人来说这比“版本最新”更重要。部署链路完整。官方仓库里自带 export.py可以一键导出 ONNX、TensorRT、OpenVINO 等格式。后面如果要移植到 RK3568、Jetson 这类边缘设备上社区方案非常丰富。标题里热搜词提到的“yolov5在rk3568上”说明这个方向是大家真实会踩的路径。训练资源要求可控。冬虫夏草检测的类别只有 1 个属于单类目标检测用小模型s 或 m就能跑出不错的效果不需要上大模型。YOLOv5 的 s/m 版本在 1080Ti 级别显卡上就能顺利训练如果数据量再大也可以轻松切换到 l/x 版本。至于 YOLOv8 和更新的版本不是不能用而是对于这个项目来说收益不大。YOLOv8 主要改进在 anchor-free 头部、C2f 模块和更灵活的部署支持但训练数据量少的时候这些改动对精度的影响并不明显反而因为新版本的配置方式变化较大团队接手需要重新熟悉。在野外资源调查这种业务场景里稳定、可复现比“刷榜高一点”更重要。2.2 完整的环境配置与版本避坑训练环境这块我在项目里用的是比较稳定的一套组合照着装基本不会出问题Python 3.8PyTorch 1.10.0CUDA 11.3cuDNN 8.2.0YOLOv5 官方仓库版本号锁定 v6.0为什么锁 v6.0因为这个版本是 YOLOv5 演进过程中比较稳定的一个节点后面对应的高版本修改了 autoanchor 和很多默认配置社区里各种教程基本都能对上。如果直接拉最新的 main 分支很可能遇到torch 1.10 不支持某个算子之类的问题没必要给自己找麻烦。安装依赖的时候我建议用pip install -r requirements.txt之前先手动确认 PyTorch 与 CUDA 的版本匹配。很多新人报错CUDA error: no kernel image is available for execution on the device就是因为 PyTorch 编译时用的 CUDA 版本和本机驱动对不上。先跑一句验证import torch print(torch.__version__) print(torch.cuda.is_available())输出True再继续否则先解决环境问题。我第一次带新人跑项目时发现有一半的问题出在环境没配对而不是代码本身。项目里推荐的显存要求是至少 8GB因为 640 分辨率下默认 batch-size 设为 168GB 显存可以跑下来。如果只有 4GB 显存把 batch-size 降到 8 或者 4同时开启--cache选项训练速度会快不少。2.3 数据集的目录组织与代码框架说明项目压缩包解压后的目录结构是这样的Cordyceps-YOLOv5/ ├── dataset/ │ ├── images/ │ │ ├── train/ # 训练集图片 │ │ ├── val/ # 验证集图片 │ │ └── test/ # 测试集图片 │ ├── labels/ │ │ ├── train/ # 训练集标注txt格式 │ │ ├── val/ # 验证集标注 │ │ └── test/ # 测试集标注 │ └── data.yaml # 数据集配置文件 ├── weights/ │ ├── yolov5s.pt # 初始预训练权重 │ └── best.pt # 训练完成的最优权重 ├── runs/ │ ├── train/ # 训练日志与曲线 │ └── detect/ # 检测结果输出 └── 代码说明.mddata.yaml 的核心配置如下train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 1 names: [cordyceps]需要注意train路径写的是图片目录地址YOLOv5 会自动根据 images 目录找到同级 labels 目录下的标注文件。有人习惯用./data.yaml写绝对路径其实没必要相对路径更灵活换机器只需要保证目录结构不变。训练好的best.pt是用验证集上 mAP 最高的那一轮权重保存下来的一般放在项目里直接对图片做推理。另外一个last.pt是最后一轮的权重如果训练过程没有明显过拟合last.pt和best.pt的差距不会太大但强烈建议用best.pt理由后面会讲。3. 数据集构建与标注这是整个项目成败的关键3.1 数据采集与初筛冬虫夏草数据集的采集是这个项目里最费时费力的一步。项目原版的数据来源主要是高原草甸实地拍摄拍摄设备可以是手机、单反或无人机。不同高度、不同角度、不同光照下的数据混合在一起模型的泛化能力会更强。这里有几个实操要点不要只拍“正对着虫草”的照片。野外检测中更常见的是虫草斜着长出地面、被草叶部分遮挡、或者处于阴影里。只拍“证件照”式的数据会让模型学到“正对镜头才叫虫草”的错误规律。尽量覆盖不同时辰的光照。高原上上午和下午的光照角度差异很大中午直射光下虫草对比度最高傍晚低角度光下虫草和影子混在一起难度大得多。训练数据里如果全部是正午拍的模型在傍晚就会崩。视频抽帧也是个好办法。无人机巡草时录 4K 视频回来按每秒 1-2 帧抽能够快速积累大量样本。抽帧后要注意删除连续帧中高度重复的图片不然训练集里很多图片几乎一样模型吃过拟合的亏。然后是初筛。模糊图片必须删掉。用无人机或者手持设备拍的时候运动模糊非常常见。YOLOv5 训练时虽然会做 mosaic 增强但对模糊图片并不会自动修复。判断方式可以人工看一遍也可以用 OpenCV 算一下 Laplacian 方差低于阈值的直接淘汰。这一步做完数据量可能会少掉 20%别心疼留下质量高的数据才值。3.2 标注细节一种“模糊地带”的处理标准标注是目标检测项目里最枯燥、但最影响效果的一步。虫草检测的标注有个特殊难点“虫体在地下的部分”和“露出地面的子座”怎么算一个目标实际做法是只标画面上肉眼可见、轮廓清晰的子座部分用最小外接矩形框住。如果一株虫草被草叶挡住了大半只露出一个尖这个目标要不要标我的建议是在训练集里这类目标不标在测试集里可以标。原因是训练时如果大量目标只有 5-10 个像素模型很难学到有效特征反而会干扰分类置信度。测试集里保留这类难例能真实反映模型在极端情况下的表现。标注工具我用的是 LabelImg操作简单可以直接保存成 YOLO 格式的 txt。需要注意标注框要尽量紧贴目标边缘不要留太多空白。虫草细长如果框太松会把旁边的草叶也包进来模型学到的是“草叶虫草”的组合特征。每个 txt 文件与同名图片放在对应目录里文件名保持一致。一张图里的多个目标不要漏标尤其是小目标。漏标的数据在训练时会被当作背景模型学到的是“这种特征区域也可以是背景”precision 会被拉低。关于标注质量有个很实用的技巧训练之前做一次“标注可视化”检查。就是把标注框画回原图一张张过目。工具上用 OpenCV 就能写个简单的脚本import cv2 import glob img_files glob.glob(dataset/images/train/*.jpg) for img_path in img_files: img cv2.imread(img_path) h, w img.shape[:2] label_path img_path.replace(images, labels).replace(.jpg, .txt) with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() cls, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow(check, img) cv2.waitKey(0)这一步虽然费眼睛但能发现大量标注错位、漏标的问题。当年我在项目里就是这么检查的检查完一轮mAP 直接掉了几个点——因为修正了标注框的错位模型学习难度降低了。标注质量永远排在数据量前面。3.3 数据集的划分与数量说明项目里的数据集数量大概是这样的数据集图片数量标注目标数说明训练集1200 张约 3100 个目标包含少量没有目标的负样本验证集200 张约 480 个目标用于训练过程评估测试集150 张约 350 个目标用于最终评估不参与训练训练集里特意放了大概 15% 的负样本图片没有任何虫草的草地这些图片的标注文件是空文件YOLOv5 是可以正常处理的。别小看这部分负样本它直接决定了模型在真实场景下的误检率。如果训练集里每张图都有目标模型会倾向于“有草就框一个东西出来”那到了实地几乎没法用。数据划分时最好按拍摄时间、拍摄地点来分不要随机切分。也就是说同一天同一块草地拍的数据尽量只出现在一个集合里否则验证集会“沾”训练集的光指标虚高。这点很多教程不会提但真实项目里非常重要。3.4 数据增强小目标场景的特殊策略YOLOv5 自带的 mosaic 增强在训练默认开启它把 4 张图拼成一张对小目标检测非常有效相当于变相提高了小目标在单张图里的密度。但我做了两个调整mosaic 增强保留在训练的前 70% epoch关闭后 30%。原因很实际mosaic 会打乱原有的光照和背景分布如果一直用 mosaic 到最后模型学习的特征会偏向“拼接图”而非“真实图”。把最后的训练阶段切换成普通增强让模型在接近真实分布的数据上做微调。色彩空间增强的幅度调低。hsv_h、hsv_s、hsv_v这几个参数默认值是 0.015、0.7、0.4。虫草本身颜色和草地很接近如果把色相扰动开到默认值原有“虫草颜色 vs 草地颜色”的区分特征会被破坏。我实测过把hsv_h降到 0.01hsv_s和hsv_v保持默认模型的收敛速度和最终精度都会有提升。另外这个项目不需要做随机裁剪增强。虫草目标太小裁剪容易把目标切掉一半。很多人一上来就开一堆增强结果模型训练了很久 loss 就是不降大概率是增强策略把目标都“变没了”。4. 模型训练与调参从零到可用权重的完整过程4.1 基础模型选择和锚框调整前面提到用 YOLOv5s 作为基础模型这个选择有两个考虑一是 s 模型参数量约 7.2M计算量小训练快跑边缘设备也方便二是单类检测任务不需要特别复杂的特征表达能力s 模型在数据量 1000 张左右的规模下完全够用。YOLOv5 的初始锚框是基于 COCO 数据集统计出来的对虫草这种细长小目标并不合适。训练时使用--autoanchor参数让算法根据数据集标签自动计算最优锚框。这里说一下我当时在项目里的实测数据默认锚框COCO在验证集上的最佳召回率只有 0.61开启autoanchor重新聚类之后最佳召回率提升到了 0.82。这个提升非常显著重要程度甚至超过了换一个更大的骨干网络。所以做小目标检测锚框聚类这一步绝对不能省它的代码逻辑也比较简单就是 K-Means 聚类算法对不同宽高比的框做统计。4.2 超参数配置与优化策略YOLOv5 的超参数在data/hyps/hyp.scratch-low.yaml里我对这个项目做了以下调整超参数默认值本项目值调整理由lr0初始学习率0.010.005数据量小学习率过大会导致 loss 震荡lrf最终学习率系数0.010.01保持不变warmup_epochs3.05.0小数据集需要更长的预热来稳定训练mosaic1.01.0前 70% 开启后 30% 关闭代码内调整fl_gammafocal loss0.00.5缓解正负样本不平衡问题hsv_h0.0150.01降低色彩扰动对虫草特征的影响box框回归损失权重0.050.07增加 bbox 损失权重让小目标位置更准这里要专门讲一下fl_gamma。草地场景里模型可能在一张图里滑过上千个可能的锚框真正命中虫草的锚框只占极少数正负样本比例严重失衡。默认情况下 YOLOv5 不使用 focal loss但对这种单类、目标小的任务把它调到 0.5 可以让模型把注意力更多的放在困难的正样本上。调节后验证集的 mAP 提升了 1.8 个百分点效果比调什么 backbone 都明显。4.3 训练命令与训练过程实录项目提供的训练命令是python train.py \ --data dataset/data.yaml \ --weights weights/yolov5s.pt \ --batch-size 16 \ --img 640 \ --epochs 200 \ --device 0 \ --workers 4 \ --autoanchor \ --project runs/train这里有一个坑需要特别注意--weights yolov5s.pt用的是官方预训练权重意味着训练是“迁移学习”而不是从零开始训练。迁移学习对小数据集太重要了。官方权重在 COCO 上已经学到了大量的通用特征边缘、纹理、形状微调到虫草检测只需要在顶层特征上做调整收敛速度比 from scratch 快得多。实测下来从零训练需要至少 300 轮才能达到的 mAP迁移学习在 80 轮左右就能超过了。训练过程中的 Loss 曲线我拿当时的记录做参考第 1-20 轮box_loss 从 0.12 快速降到 0.07obj_loss 从 0.09 降到 0.04。这时候模型开始学到虫草和草地的颜色对比特征。第 20-80 轮box_loss 缓慢下降至 0.04val/obj_loss 有轻微波动。这个阶段最怕的就是验证集 loss 开始反弹说明过拟合出现了需要靠早停或数据增强来缓解。第 80-120 轮如果训练集 loss 还在降但 val/obj_loss 连续 20 轮没有下降建议直接用代码里自带的--patience 30开启早停别浪费算力。整个过程在 RTX 3090 上跑了约 4.5 小时。如果只有 GTX 1660 级别的显卡时间会翻倍到 8-10 小时也是可以接受的。实测中强烈建议用--cache ram参数把数据预加载到内存训练一轮的速度能提升 30% 左右。前提是内存够 16GB不够的话用--cache disk也行效果差一些。4.4 训练完成后的关键检查项训练完成后用tensorboard或直接看runs/train/exp*/results.png以下几个指标要重点确认Pprecision和 Rrecall的平衡点。这个项目里我会更关注 recall。原因很简单资源调查场景里漏检一株虫草比误检一株的成本更高误检顶多多派人确认一下漏检就是数据缺失。目标是在保证 recall 在 85% 以上的前提下尽可能把 precision 拉到 90% 以上。confusion matrix 里的背景误检率。如果背景类被误检成虫草的比例较高说明负样本不够或者是置信度阈值设得太低。可以先用默认的 0.25 跑一遍检测看结果再往下调或往上调。不同尺度的 PR 曲线。YOLOv5 生成的 PR 曲线是不同置信度阈值下的综合表现如果曲线尾部翘得很快说明模型对小目标学习得不够好需要用测试集里的尺度分布数据来匹配。5. 模型评估与部署能落地的权重才有价值5.1 测试集上的指标表现项目里训练好的best.pt在测试集上的表现大概如下单类指标数值Precision0.91Recall0.86mAP0.50.93mAP0.5:0.950.71F1-score0.88mAP0.5 到 0.93 听起来不错但 mAP0.5:0.95 只有 0.71说明模型定位精度还有比较大的提升空间。这在小目标检测任务里非常常见——目标在 640 分辨率下可能只占 20×30 像素IoU 稍微偏一点mAP0.5:0.95 就会掉得很快。如果你拿到的权重跑出来 mAP 和我说的有出入不用太慌不同数据分布下的结果天然有差异。关键是看趋势confusion matrix 里背景误检率是否小于 10%以及 recall 是否达到 85%。5.2 推理测试把权重用起来训练好权重后最基本的检测命令是python detect.py \ --weights weights/best.pt \ --source dataset/images/test \ --conf-thres 0.3 \ --iou-thres 0.45 \ --save-txt \ --save-conf \ --project runs/detect--conf-thres这个参数对草地场景非常敏感。默认 0.25 会快速识别出很多目标但同时会带出一堆误检。我通常在草地图上先按 0.4 跑一遍看误检数量再一点点往下调。这是“压精度”的通用方法根据业务容忍度来定阈值而不是完全依赖模型输出。--save-txt会生成坐标文件如果后续要做密度统计、空间分布分析这个功能非常有用。每一行是 class_id、x_center、y_center、width、height单位是像素比例。5.3 导出 ONNX 与边缘设备部署实操前面提到 YOLOv5 的部署链路很完善这里给一个导出 ONNX 的示例python export.py \ --weights weights/best.pt \ --img 640 \ --batch 1 \ --include onnx \ --simplify \ --opset 11导出的 ONNX 模型可以用 ONNX Runtime 在 CPU 上跑推理。实测在 i5-1240P 这样的 CPU 上单张 640×640 图片推理时间约 80-120ms也就是 8-12 FPS已经可以满足无人机巡航后的离线分析需求。如果要做实时巡检建议进一步转成 TensorRT 的 engine 格式。在 RTX 3090 上TensorRT 推理单张耗时可以压到 8-15ms提升约 8 倍。热搜词里提到的“yolov5在rk3568上”也是一个热门方向RK3568 这类 NPU 设备上用 rknn 模式转换后推理速度也能达到 15-25 FPS可以满足车载或者机载的实时识别需求。部署时要注意输入输出的后处理import cv2 import onnxruntime as ort import numpy as np session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) input_tensor img_rgb.astype(np.float32) / 255.0 input_tensor np.transpose(input_tensor, (2, 0, 1))[None, :, :, :] outputs session.run(None, {input_name: input_tensor}) # 输出为 [1, 25200, 6]需要做 NMS 后处理这里的 25200 个候选框是三个尺度预测头的总和后处理时要用置信度阈值先过滤再做 NMS最终得到目标框。6. 常见问题与排查技巧实录6.1 问题速查表现象可能原因解决办法训练时 loss 直接变成 NaN学习率过大或数据集中有异常标签降低 lr0 到 0.001 以下检查标签坐标是否有负数验证集 mAP 很低但训练集 mAP 高过拟合增大数据增强、加入更多负样本、开启早停总是漏检小目标锚框不合适或小目标样本不足开启 autoanchor增加多尺度训练误检一大堆背景也被框出来负样本不足在训练集中增加纯草地图片检测结果框偏大、定位不准mAP0.5:0.95 低提高 box loss 权重或换用更大的输入尺寸 1280导出 ONNX 后推理结果和原模型对不上图片预处理不一致检查归一化和通道顺序是否与 train.py 一致6.2 我踩过的几个典型坑第一个坑是用全部训练数据从头开始跑 anchor 聚类反而导致锚框过拟合。训练集里如果某些图片的虫草特别大特写镜头聚类出来的锚框会偏向大尺寸反而对大多数正常尺度的小目标不友好。解决办法是做完聚类后人工检查一下聚类中心和目标分布如果发现极端值直接把训练集里的特写图片做一次下采样或裁剪让目标尺寸分布更均衡。第二个坑是置信度阈值的选择对误检影响极大。之前我把--conf-thres设为 0.1结果模型把一堆枯草当成虫草precision 直接掉到 0.3。后来调整到 0.45误检少了很多但 recall 也降了一点。这里没有绝对最优值取决于业务场景我的建议是在验证集上画一条 precision-recall 曲线选一个两个指标都比较高的转折点。一般这个点在 0.35-0.45 之间。第三个坑是数据增广和模型结构的不匹配。YOLOv5 的 mosaic 增强和训练集的原始尺寸分布搭配不好时小目标会被二次缩小。如果原始图片是 4K 分辨率、虫草本身像素不小但训练时 resize 到 640目标可能就缩没了。这个时候要用分块tiling策略把 4K 大图切成多个 640×640 的小块再去做标注和训练。这是大图小目标检测的标准方案也是我在无人机数据上的推荐做法。6.3 提升模型泛化能力的额外建议如果数据量少、想过拟合又不好扩数据可以试试这两个方向用预训练模型做特征提取只微调检测头。先将 backbone 的权重冻结训练 50 轮检测头再解冻全模型微调 100 轮这种方式能让模型保留 COCO 预训练学到的通用语义特征在数据量少的时候效果经常更稳。加 TTATest Time Augmentation。YOLOv5 自带--augment推理参数打开后会对同一张图做多尺度、翻转检测再把结果融合。实测 mAP 能提升约 2-3 个点代价是推理速度变慢适合离线分析场景。写在最后实际项目中的一点体会这个冬虫夏草检测项目花费时间最多的不是训练模型而是整理数据和调标注。模型训练本身只用了几个小时但数据采集、清洗、标注、可视化检查花了两三周。这是几乎所有目标检测项目里的真实比例不要指望“拿一堆图片扔进 YOLOv5 就能出结果”那个想法会让你在真实场景摔得很惨。项目里附带的训练好的权重拿过去直接跑detect.py能出一个基础结果但如果你的草地场景和原数据集差异比较大比如草种不同、海拔不同、拍摄设备不同我强烈建议用你自己的数据做一次微调而不是直接上线。具体做法就是把你的新图片放到数据集目录里改一下data.yaml的路径在weights/best.pt基础上继续训练 50 轮左右很快就能适配新场景。最后再分享一个小技巧做资源调查类的检测项目最好把检测结果同时输出成 CSV 文件包含每个目标的坐标、置信度和图片编号。这样后续做生长密度分析、年份对比的时候算法跑完的每一轮结果都能回溯、复现而不是只留一堆画了框的图片。这个习惯让我的项目在交付时省了很多沟通成本也建议你从第一个版本就养成。本文还有配套的精品资源点击获取