YOLOv5实战:冬虫夏草小目标检测从训练到部署全流程
简介:目标检测是计算机视觉领域的核心任务之一,其本质是在图像中定位并识别感兴趣的目标。在实际工程中,针对小目标、复杂背景的检测场景,模型的选择与训练策略往往比网络结构本身更关键。YOLOv5作为成熟稳定的检测框架,凭借其轻量级设计、灵活的配置和丰富的生态资源,在诸多垂直领域中得到广泛应用。本文以冬虫夏草生长检测为实践案例,系统介绍从数据集构建、标注规范、数据增强到模型训练、超参调整、权重选择,再到推理优化与边缘设备部署的完整链路。通过合理调整置信度阈值、输入分辨率与锚框参数,可有效提升小目标的召回率。该技术路线同样适用于农作物监测、野生资源调查、工业缺陷检测等场景,为开发者提供了一套可复用的工程化解决方案。 这个项目其实挺有意思的。冬虫夏草这玩意儿,跟工业质检里那些规规矩矩的螺丝钉完全不一样,它长在高原草甸上,背景是枯草、泥土、碎石混在一起的复杂环境,子座从土里冒出来,个头小、颜色跟周围环境还特别接近,人眼找起来都得眯着眼睛仔细看。用YOLOv5来做单类别检测,听起来不算复杂,但真正跑通“数据准备→标注→训练→权重部署”这条链路,里头的坑一点不少。这篇就把整个实战过程掰开揉碎,从数据集怎么来、标注要注意什么,到训练参数怎么调、权重文件怎么选,再到推理和常见问题排查,一次性讲清楚。
1. 项目核心思路与方案选型
1.1 为什么选YOLOv5而不是YOLOv8或者更重的框架
先说结论:单类别目标检测,YOLOv5目前依然是性价比最高的选择,没有之一。虽然YOLOv8、YOLOv9这些新版本在COCO这类通用数据集上刷榜刷得很欢,但放到“冬虫夏草生长检测”这种垂直场景里,YOLOv5的生态成熟度、踩坑资料数量、部署资料完整度,都远超新版本。
具体原因有三点。第一,YOLOv5对硬件的要求足够友善。虫草检测这套任务,目标是小尺寸、低对比度,通常需要在实地设备上跑,可能是Jetson Nano,可能是RK3568,也可能是工厂或者实验室里一台普通的老GPU。YOLOv5s和YOLOv5n这两个轻量级版本,在GTX 1660这种入门级显卡上就能训练,推理时甚至CPU都能跑,只不过速度慢一些。而YOLOv8虽然也有n/s版本,但很多老设备上的推理库和工具链对它的支持还没有完全跟上。
第二,YOLOv5的代码结构清晰,配置文件简单,适合做二次改动。比如后面要讲的anchor尺寸调整、数据增强策略开关,都是几行配置的事。而且国内外关于YOLOv5的教程、踩坑记录、社区问答简直是海量,遇到问题搜一下基本都有答案。对这类偏应用型的项目来说,框架的“可救性”比“先进性”重要得多。
第三,YOLOv5的输出权重格式对后续部署非常友好。不管是转成onnx、TorchScript还是TensorRT,都有成熟稳定的转换脚本,这在后面做边缘设备部署时会省掉大量时间。
单类别任务尤其适合YOLOv5还有一个隐藏优势:它的Neck和Head设计本身是按通用物体检测优化的,在处理单类别、小目标、复杂背景这类场景时,可控的调节空间很大,不需要做网络结构层面的改动,只需要在数据增强、锚框、超参上做文章。
1.2 单类别“生长检测”的任务定位
这里需要先把“检测”和“分类”的概念理清楚。这个项目做的是“冬虫夏草生长检测”,本质上是在图像中回答两个问题:图中有没有冬虫夏草?如果有,它在哪里?
这类需求在实际业务中对应很多场景。比如高原产区做虫草资源的动态监测,通过无人机或地面巡拍采集图像,用模型自动框出每个虫草的位置,统计分布密度;再比如药材企业做收购分级前的初筛,把大量历史照片里的虫草快速标记出来,替代人工一张张翻找;还有科研单位做生长周期观测,通过连续图像中的虫草位置变化来分析出土时间、生长速度等指标。
“1类别”意味着不需要区分虫草的种类、等级、生长阶段,只要能找到“目标在哪”就行。这看起来比多类别任务简单,但实际做下来会发现,单类别任务的难点不在“区分度”,而在“查全率”,也就是漏检率。虫草的颜色和周围枯草接近,早期出土的子座非常小,几十像素到一百像素都很常见,如果不专门优化小目标检测能力,模型很容易漏掉大量真实目标。
所以这个项目的数据集设计和训练策略,一切都是围绕“少漏检、少误检”来做的,而不是追求把某个类别的AP刷到99%。
2. 数据集构建与预处理
2.1 数据来源与采集策略
冬虫夏草的数据集不像车牌、行人那么烂大街,公开的现成数据集很少,所以很大概率需要自己动手攒。根据我做类似农作物检测项目的经验,数据来源可以分三条路,按优先级排列。
第一条路是实地采集,也就是带着相机或手机到种植基地、产区草甸去拍。这是最理想的方式,因为拍到的图像和实际部署场景完全一致,没有域偏移问题。拍摄时要注意几个细节:尽量覆盖不同时间段,因为早晨和傍晚的光线角度、色温完全不一样;要拍不同天气条件,晴天、阴天、雨后(虫草出土后带着水珠的样子和干燥时差别很大);还要覆盖不同背景,纯草地、碎石地、枯草密集地都要有。拍摄高度和角度也要模拟实际使用场景,如果最终打算用无人机俯拍,那训练数据就不要大量使用平视角度拍摄的图。
第二条路是整理历史照片。很多产区的研究者、收购商手里都有大量历史照片,虽然可能没有针对检测任务做过标注,但作为检测任务的原始素材非常合适。我建议在动手标注之前,先建一个简单的素材池,把同角度、同光线、同背景的相似照片去重,避免模型过拟合到重复背景。
第三条路是网络公开图片。这个只能作为补充,因为公开图片的版权、分辨率、拍摄角度差异太大,而且能搜到的虫草特写图大多是大目标、清晰背景,跟实际野外场景差距很大。如果非要用,只能作为增加背景多样性的辅助,不能作为主力数据。
我以自己做的这套数据为例:总共1080张图,其中实地采集约700张,历史资料整理300张,网络补充80张。数量不算多,但单类别检测本身数据需求就不像多类别那么高,关键是质量。
2.2 标注工具与YOLO格式要点
标注工具我用的是LabelImg,开源免费,操作简单,安装后直接就能用。用pip安装就行:
pip install labelImg然后命令行启动:
labelImg在工具里把图片文件夹和预定义的类别文件路径配置好,类别文件就是一个txt,每一行写一个类别名。单类别项目就一行:
dongchongxiacao标注界面里用矩形框把虫草的子座框出来。这里有个非常关键的实操经验:框的范围宁小勿大。冬虫夏草的检测重点通常放在子座(也就是露出地面的那一段真菌结构)上,不要把周围的泥土、小草、杂物包进框里。因为框得太大,会让模型学到“虫草=一堆背景区域+一个小目标”的错误映射,推理时误检率会直线上升。
标注完成后,LabelImg默认会生成VOC格式的XML文件,需要转成YOLO格式的txt。YOLO格式的核心是:每张图对应一个txt文件,每一行代表一个目标,格式是:
class_id center_x center_y width height注意,这里的四个坐标值全部是归一化到0-1之间的,用像素值除以图像宽高得到。举个例子,一张1920x1080的图中,一个目标框左上角在(500, 300),右下角在(800, 700),那么:
img_w, img_h = 1920, 1080 x1, y1, x2, y2 = 500, 300, 800, 700 cx = ((x1 + x2) / 2) / img_w cy = ((y1 + y2) / 2) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h如果自己写转换脚本,这段逻辑是核心。还要注意,YOLO格式的txt文件名必须和图片文件名完全一致(包括扩展名前面的部分),且放在对应的labels目录下,否则训练时数据加载会报错。
2.3 数据增强策略与样本平衡
数据增强对于虫草这类小目标、复杂背景的任务极其重要。YOLOv5内置了Mosaic增强、随机翻转、HSV色域增强、随机仿射变换等,这些在训练时默认会启用。但对于虫草场景,我建议做几个针对性调整。
首先是Mosaic增强的拼图策略。Mosaic会把4张图缩放到小尺寸再拼接,好处是增加了小目标的数量,坏处是如果原图本身分辨率不高,缩放后目标会变得更加模糊。所以我建议在数据准备阶段就不要把原图压得太小,YOLOv5在训练时会把图resize到输入尺寸(比如640x640),如果原图本身分辨率不足,目标信噪比会急剧下降。我自己的经验是:原图分辨率至少保持1280以上,训练时imgsz可以设成640,让模型在缩放过程中学到更多细节。
其次是翻转增强。YOLOv5默认的翻转参数是随机水平翻转0.5,也就是一半概率左右翻转。虫草的生长方向虽然天然随机的,但如果你在实际部署时对方向有明确要求(比如明确要检测“从土里向上生长”的状态),翻转过狠会让模型对方向不敏感。我在这个项目里把水平翻转概率降到0.25,垂直翻转直接关闭,因为垂直翻转会让虫草变成“倒挂”状态,实际场景不会出现,学了反而干扰。
第三是HSV色域增强。虫草和背景的颜色差异很微妙,过度调整饱和度、色相会让模型学到错误的颜色特征。YOLOv5默认的hsv_h、hsv_s、hsv_v参数是0.015、0.7、0.4,对通用任务合适,但对虫草这种低对比度目标,我建议把hsv_s降到0.3左右,把hsv_v保持0.4,让模型更关注形状和纹理特征,而不是死记颜色。
数据集规模不够时,还可以用离线增强的方式扩充样本,比如旋转90度、180度、270度(对虫草这种无方向性目标其实没毛病),以及加入高斯噪声、轻微模糊,模拟不同天气条件下拍摄的效果。但要记住一个原则:增强是为了模拟真实分布,不是为了编造不存在的场景。
2.4 数据集目录结构
标准的YOLOv5数据集目录结构如下:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── dongchongxiacao.yaml其中images和labels必须严格对应,train、val的比例按8:1:1或9:1来分都可以。单类别任务数据量本身不大,我建议验证集比例稍微高一些,比如15%,这样评估指标更可信。划分的时候一定注意:同一次拍摄的连续帧照片不要同时出现在训练集和验证集里,不然验证集结果会虚高。我习惯按文件名前缀或目录来划分,而不是随机抽,这样更贴近真实场景。
yaml配置文件内容大概是这样的:
train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 1 names: ['dongchongxiacao']路径建议用相对路径,这样整个项目目录拷到别的机器上不用改配置。yaml文件里的换行、空格缩进要严格按YAML语法,一个空格错位都会报错。
3. 环境配置与训练关键步骤
3.1 YOLOv5环境安装
环境安装这块,网上教程五花八门,我直接说一套最稳的流程。先创建Python虚拟环境,推荐用conda:
conda create -n yolov5 python=3.8 conda activate yolov5然后克隆YOLOv5仓库并安装依赖:
git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt会装torch、torchvision、opencv-python、numpy、matplotlib等一堆依赖。需要注意,这里默认装的是CPU版torch,如果你有NVIDIA GPU,需要先装对应CUDA版本的torch再跑requirements。建议先单独装:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118再执行:
pip install -r requirements.txt这样能避免依赖顺序问题。装完之后先跑一个最简单的测试:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg如果顺利输出结果图,说明环境通了。如果卡在下载权重文件那一步,就手动去下载yolov5s.pt放到项目根目录。权重文件名必须严格匹配,大小写都不能错。
3.2 数据配置文件与训练启动
环境就绪后,把前面准备好的dataset目录和yaml配置文件放到YOLOv5项目同级目录下。训练命令是:
python train.py --img 640 --batch 16 --epochs 200 --data dongchongxiacao.yaml --weights yolov5s.pt --name dongchong_test逐项解释一下:--img是输入图像尺寸,640是默认值,如果你的显存够(比如12G以上)可以调成960,小目标检测效果会明显提升;--batch是批大小,16在8G显存下是安全的,如果爆显存就降到8;--epochs是训练轮数,单类别任务200轮完全够,再多了容易过拟合;--weights是预训练权重,这里用yolov5s.pt,如果你显存很小,可以用yolov5n.pt(更轻量,但精度会掉一点);--name是训练输出的文件夹名,YOLOv5会保存到runs/train/下。
训练过程中要盯几个东西。第一个是loss曲线,YOLOv5的输出里box_loss、obj_loss、cls_loss三条曲线,单类别任务主要关注box_loss和obj_loss。正常情况是前50轮快速下降,之后缓慢收敛,如果曲线一路走平或者反弹,说明有问题(下面排查章节详细说)。第二个是mAP曲线,每10轮会自动在验证集上评估一次,mAP@0.5一般到0.85以上就算能用,虫草这种难检测的小目标,0.8以上已经算不错了。
3.3 超参数调整与实战心得
YOLOv5自带一个超参数文件data/hyps/hyp.scratch-low.yaml,默认值对通用任务比较稳。但虫草检测有几个参数值得动一动。
一个是锚框(anchor)。YOLOv5默认的anchor是在COCO数据集聚类出来的,对虫草这种小目标来说偏大。如果你在训练日志里看到某些anchor匹配率很低(YOLOv5会自动重聚类,所以一般不用手动改),但为了保险,可以在训练前用YOLOv5自带的聚类脚本对标注框做一次分析。方法不复杂,本质是用K-means统计所有标注框的长宽分布。虫草的目标框特点是:细长、瘦高(子座是长的),跟COCO里那些方形目标差异很大。建议训练时开启--multi-scale参数,让模型适应不同尺度的目标。
另一个是正负样本匹配的iou阈值。YOLOv5低版本用的是single anchor匹配策略,新版本改成动态分配。如果想调,可以在模型配置文件里改anchor_t(默认4.0),这个值越大,允许预测框偏离真实框的程度越大,对召回率有帮助,但会牺牲一些精度。单类别任务我建议适度调高,比如设成4.5,因为虫草目标小、形状细长,太严格的正样本匹配会让模型学不到东西。
还有一个非常容易被忽略的参数:--workers。这个参数控制数据加载的线程数,Windows下默认0,Linux下可以设成4或8。数据加载速度跟不上GPU时,训练会一直等数据,GPU利用率上不去,看起来像模型坏了一样,实际是IO瓶颈。
3.4 权重文件解读与版本管理
训练结束后,runs/train/dongchong_test/weights/目录下会有两个文件:best.pt和last.pt。这两个文件看似差不多,实际用途完全不同。
best.pt是整个训练过程中在验证集上表现最好的权重,也是我们日常推理、部署要用的那个。last.pt是最后一个epoch的权重,主要用于中断后恢复训练,或者你想继续在前一次训练结果的基础上接着跑。千万别把它们搞混了,很多人图省事直接用last.pt部署,结果精度明显偏低。
权重文件用起来有几个细节要注意。第一,best.pt是包含完整的模型结构、优化器状态和训练配置的,文件比较大(yolov5s大约15M),如果只是做推理,可以在detect.py里正常加载,但如果是部署到生产环境,建议用export.py先转成onnx或TorchScript格式,体积更小、加载更快。第二,权重文件的版本和代码版本必须匹配。用YOLOv6.0的代码跑出来的权重,拿到YOLOv5v5.0的代码里直接推理,大概率报错或者结果异常。
我在本地会用一个简单的脚本管理多个项目的权重文件,目录结构按项目分:
weights/ ├── dongchong/ │ ├── best.pt │ ├── last.pt │ └── onnx/ │ └── best.onnx这样每次训练成果不会互相覆盖,也能快速回溯。
4. 模型评估与推理部署实践
4.1 评估指标怎么看
训练完之后,runs/train/dongchong_test/下会有results.png,里面画了所有指标曲线。单类别任务重点看四个:Precision(精确率)、Recall(召回率)、mAP@0.5、mAP@0.5:0.95。
在虫草检测场景里,Recall比Precision重要。这个逻辑跟工业质检刚好相反:质检里把良品误判成次品会增加人工复检成本,但虫草检测如果漏检(Recall低),意味着实际有虫草的地方没检测出来,统计密度或计数就会偏低,这直接导致数据失真。所以训练阶段如果发现Precision和Recall有矛盾,优先照顾Recall。
YOLOv5在验证时输出的PR曲线图会显示置信度阈值与Precision/Recall的平衡关系。默认置信度阈值是0.25,如果你发现推理时漏检多,可以把这个值降到0.15甚至0.1;如果误检多,就往上调到0.4。这个调参逻辑后面推理部分还会详细说。
一个容易踩的坑:mAP@0.5:0.95这个指标在虫草这种小目标、细长形状的任务上通常不会好看,可能只有0.3-0.4,这是正常的。因为这个IoU阈值范围太严格,虫草的框稍微偏一点IoU就掉得厉害。不要单纯因为mAP@0.5:0.95不高就认为模型不行,还是要看实际推理效果。
4.2 推理参数优化
推理用detect.py,基本命令:
python detect.py --weights runs/train/dongchong_test/weights/best.pt --source test_images/ --conf 0.25 --iou 0.45 --img 640--conf是置信度阈值,--iou是NMS的IoU阈值。实际调参经验是:虫草检测的置信度阈值不要设太高,因为小目标、低对比度目标的置信度天然偏低。我实测下来,0.15-0.2这个区间的置信度阈值,能在漏检和误检之间找到比较好的平衡点。NMS的iou阈值保持在0.45-0.5就行,虫草目标之间基本不会互相重叠。
如果检测的是视频或实时摄像头流,还要注意--vid-stride参数,默认是1,也就是逐帧检测。野外拍摄的视频如果帧率很高(比如30fps),相邻帧内容几乎不变,可以调成2或3,省一半算力还不漏目标。
代码层面,如果想在推理时顺便输出目标的数量,可以在detect.py的输出循环里加几行判断,统计每个检测结果里框的数量。这个功能在虫草密度统计场景特别实用。
4.3 边缘设备部署的思路
如果最终要部署在Jetson、RK3588这类边缘设备上做实时检测,建议提前把权重转成TensorRT或RKNN格式。YOLOv5自带的export.py支持onnx导出:
python export.py --weights best.pt --include onnx --opset 11转出来的onnx可以接着用TensorRT的trtexec工具优化,或者转成RKNN。这里有个重要提醒:边缘设备上的量化对细长小目标不友好。虫草目标本身的像素面积小,INT8量化后特征丢失严重,很多目标就检测不到了。如果边缘设备推理精度掉得厉害,优先考虑FP16而不是INT8,或者干脆用FP32,虽然慢一点,但保精度。
部署时还有一个很容易忽略的点:输入分辨率要和训练时保持一致。如果训练用640,推理时为了提高速度改成320,小目标会大量漏检。如果确实需要提速,建议改batch size或换轻量主干,而不是降低输入分辨率。
5. 常见问题与排查技巧实录
5.1 训练不收敛或loss震荡
这个现象在虫草项目里很常见,因为目标小、正负样本极度不平衡。首先要确认一个关键点:是不是数据标注出了问题。我遇到过好几次,训练loss一直降不下去,打开标注文件一看,坐标归一化做错了,有一半目标框的cx、cy超过1,模型根本学不到正确位置。
如果确认标注没问题,再检查训练集和验证集是不是有重复。前面说过,连续帧照片如果同时进训练集和验证集,验证集的loss会很低,但推理时表现差,这是典型的“假收敛”。另外,数据太少也会导致loss震荡,单类别任务至少要有500张有效图,再多就稳很多。
超参数方面,如果前50轮loss完全没有下降趋势,尝试把--lr0(初始学习率)从默认的0.01降到0.001。虫草这种难分类任务,学习率太大容易在loss曲面震荡,小一点反而收敛更稳。
5.2 CUDA显存不足
显存不足是新手遇到最多的报错,英文提示通常是CUDA out of memory。最直接的解决办法是降低--batch,从16降到8再降到4,直到能跑起来。但要注意,batch太小导致梯度估计不准,训练会不稳定。我建议优先降低--img尺寸,从640降到512或480,这样显存占用下降非常明显,同时小目标性能损失不算太大。
如果batch和img都不想动,就开梯度累积,可以通过在训练脚本里设置--accumulate参数(YOLOv5新版没有直接暴露的CLI参数,需要改代码或在hyp里调),相当于每多步积累一次梯度再更新,效果近似于变相扩大batch。
5.3 过拟合特征
虫草数据集如果采集场景单一,模型很容易过拟合。表现形式是:训练集loss很低,但验证集loss反弹,或者验证集mAP迟迟上不去。解决办法有两个方向。一是严格划分数据集,保证训练集和验证集的拍摄场景不重叠;二是加大数据增强强度,把Mosaic的缩放范围调大、开启copy_paste增强(YOLOv5新版支持),让模型见过更多样的输入。
5.4 推理误检、漏检调整思路
把常见问题整理成一张速查表,方便直接对着排查。
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 漏检多(该框的没框) | 置信度阈值偏高 | 把--conf从0.25降到0.15 |
| 漏检多且小目标完全看不见 | 输入分辨率太低 | 把--img从640提高到960 |
| 误检多(把草、石头框出来) | 置信度阈值偏低 | 把--conf从0.15升到0.35 |
| 误检多且框偏大 | 标注框画得太大 | 重新检查并修改部分标注后重训 |
| 验证集AP高,但新图效果差 | 训练集和部署场景差异大 | 补充部署场景的数据或做迁移微调 |
| 输出结果闪烁(视频检测) | 帧间置信度波动 | 结合跟踪逻辑或提高帧推理稳定性 |
5.5 几个容易忽视的细节
这些细节是做完整个项目之后回头看,最想让当初的自己提前知道的。
第一,不要迷信训练轮数多。单类别虫草检测,150-200轮足够,再多就是浪费算力和显存。如果发现100轮以后mAP已经开始走平,可以直接提前停(YOLOv5自带早停机制,默认patience是100,也可以手动设小一点)。
第二,权重文件损坏问题。从网上下载预训练权重时,如果文件下载不全,训练时可能不报错但loss乱跳。遇到训练异常,先把权重文件删掉重新下载,排除这个最简单的可能性。
第三,配置文件的YAML格式陷阱。YOLOv5的data yaml里,冒号后面一定要有空格,train: dataset/images/train这种写法如果写成train:dataset/images/train会直接报错,而且报错信息还很不直观,新人容易在这里卡一个小时。
第四,标签文件和图片数量必须一一对应。有些图是没有目标的,正确的做法是保留一个内容的txt文件(0字节),千万不要把没有目标的图直接删掉,或者不生成空txt。YOLOv5在加载数据时会校验,数量对不上训练就会中断。
第五,多尺度训练与推理分辨率的关系。如果训练时用了--multi-scale,模型在各尺度下都能工作,但推理时最好还是用训练时最常用的主尺度(通常是640),这样效果最稳定。
最后再分享一个小技巧:当我对一个权重文件的效果不确定时,会专门挑几张最难的图(比如目标极小、背景极乱、光线很差的)单独做测试,而不是只看验证集mAP。因为验证集是数据集划分出来的,跟训练集同分布,在这个数据集上效果好不代表真实现场效果好。用最难样本做一个“压力测试”,这个权重能不能用,心里马上就有数了。
这个项目做完,最大的感受是:目标检测项目的重心,往往不在模型本身,而在数据和细节上。YOLOv5的代码和权重都是现成的,真正决定虫草检测效果好坏的是有没有一张干净的、标注合理的、分布贴近真实场景的数据集,以及遇到了loss不降、显存爆掉、识别不准这些问题时,能不能快速定位到症结。把这套流程完整跑一遍,后续再换其他单类别检测项目,基本就是复制粘贴再微调的事。
本文还有配套的精品资源,点击获取
