蘑菇识别实战:Faster-RCNN+RegNet+FPN目标检测训练全记录
干蘑菇识别这件事最麻烦的不是模型而是数据。网上公开的蘑菇图像数据集不少但分类体系乱、标注噪声大很多还只给整图标签根本不适合做定位。我接手这个项目的时候目标就很明确做一个能在一张野外照片里同时完成“找出蘑菇”和“判断能不能吃”的识别系统出框、给类别、给置信度而不是整图二分。这套方案的完整技术栈写出来是这么一串Faster-RCNN RegNetX-4GF FPN MS-3x COCO预训练。标题挂了“_1”说明这是个连载项目我打算把这套系统从数据、训练到部署踩过的坑分几篇记录。这篇先讲选型逻辑和训练方案后续再聊量化部署和端侧优化。如果你手里也有一批自定义目标检测数据或者正在纠结“到底是无脑上YOLO还是回头用两阶段模型”这篇应该能给你一些参考。1. 项目需求与技术路线为什么蘑菇识别不能简单用分类模型1.1 蘑菇识别场景的核心难点先说到底要解决什么问题。蘑菇可食用性识别并不是一个标准的图像分类任务。你在野外拍一张照片画面里往往有泥土、落叶、苔藓、树根蘑菇可能只有一朵也可能散落一片有的蘑菇在画面里占了大半张有的远远藏在草丛里只露一个伞盖。这种情况下如果直接拿整图做分类模型根本不知道该看哪里更别说多目标同时判定了。蘑菇本身在视觉上又有两个非常棘手的特性类间差异小和类内差异大。可食用的鸡油菌和有毒的某些牛肝菌颜色、形状、菌褶的疏密程度区别非常细微很多连采了几十年蘑菇的人都要靠闻气味、看菌柄剖面才能判断而同一种蘑菇在不同的生长阶段、不同的湿度光照条件下颜色和伞形又可能完全不一样。这意味着模型必须学会利用局部纹理、边缘轮廓、甚至菌柄与菌盖的连接方式来做判断这对特征提取能力的要求很高。还有一个工程上的难点数据量小且不均衡。可食用蘑菇和不可食用蘑菇的真实分布天然不均衡常见种多稀有种少而且标注这类数据必须由真菌学背景的人去复核单纯靠众包平台收集很难控制质量。我拿到的这批数据大概有三千多张经过清洗的野外照片分两个类别这在目标检测任务里属于“小数据量迁移学习”的典型场景。1.2 为什么选择 Faster-RCNN 而不是直接上 YOLO做目标检测最容易遇到的灵魂拷问就是现在YOLO不是又快又准吗为什么还要回头折腾Faster-RCNN先说结论蘑菇识别这个场景我更在乎的是“可控的误检”和“可靠的小目标召回”而不是视频流级别的速度。YOLO系模型通过grid-based单阶段设计把检测变成了回归问题推理确实快在通用物体、车辆、行人这些标注风格非常统一的数据上表现极好。但单阶段模型正样本分配、特征对齐、NMS后处理等方面对训练数据的噪声和小目标分布比较敏感。我在这类数据上试过不止一次YOLO系模型训练出来之后AP看起来不差但实际推理时经常出现把落叶、树皮、松果当蘑菇的高置信度误检而且小蘑菇漏检率偏高。Faster-RCNN这类两阶段模型的思路是“先粗筛再精修”。RPN阶段先扫一遍全图给出可能有目标的候选框ROIHead再对每个候选框做二次分类和边框回归。虽然推理速度比单阶段慢但在目标尺度变化大、背景复杂、正负样本天然不均衡的任务里两阶段的精修机制能大幅降低误检率。简单打个比方一个是在人堆里靠衣着颜色直接喊名字另一个是先圈出几个可疑的人、再走过去看脸确认身份后者慢一点但认错人的概率小很多。另外Faster-RCNN有成熟的开源实现detectron2、mmdetection训练配置、预训练权重的生态非常完整遇到问题时社区资料也多非常适合一个需要长期迭代维护的项目。1.3 RegNetX-4GF 骨干网络的计算量与精度权衡选定两阶段框架之后下一个关键决策是骨干网络。标题里写的RegNetX-4GF很多人第一次看到会觉得陌生这里展开说明。RegNet是FAIR团队提出的一类网络设计范式核心理念是不再人工设计深度和宽度的具体数值而是通过一个量化的搜索空间找出“网络宽度、深度、每组block数量”的最优组合规律。RegNetX表示X型变体带SE模块的版本叫RegNetY。RegNetX-4GF中的4GF指的是网络的整体计算量约为4 GFLOPs也就是一次前向推理完成约40亿次浮点运算。作为对比ResNet-50的计算量大约是4.1 GFLOPs所以RegNetX-4GF和ResNet-50属于同一个算力档次但它的精度在同等计算量下普遍高于ResNet-50。为什么不用更大更深的骨干因为数据量摆在那里。三千多张图像的训练集如果直接用RegNetX-16GF或者更重的Swin Transformer做骨干模型容量上去了但数据撑不住很容易陷入过拟合——训练集AP冲到0.98验证集AP反而掉下去。4GF这个量级在“特征表达能力”和“数据拟合风险”之间踩了一个比较稳的平衡点加上后续FPN的多尺度特征融合对蘑菇这种小目标密集出现的场景已经够用。2. 数据集准备与标注影响模型上限的核心环节2.1 数据采集与类别平衡策略很多项目栽在起步阶段不是因为模型不够好而是因为数据集本身就没整明白。蘑菇数据集的构建我分了三步走公开数据筛选、补充拍摄、专家复核。公开数据主要来自几个支持目标检测标注的开放图像平台以及一些蘑菇图鉴类的开源项目。这部分数据量大但质量参差不齐有的图带了水印、有的是室内棚拍、有的把同一朵蘑菇从不同角度拍了好多张但标注框明显偏移。筛数据的时候我给自己定了一个硬性标准只保留野外自然光线下拍摄、目标清晰、标注框能框住完整菌体的图像凡是背景占主导、蘑菇被大面积遮挡或者严重失焦的图直接丢弃。筛选完大概剩三千一百多张再用自己拍的补充了一部分林地场景的图尽量覆盖不同光照、不同腐烂程度、不同生长密度的情况。类别平衡比想象中难做。可食用类的图像数量天然比不可食用类多因为大众拍摄热情都集中在“这看起来能吃”的东西上。我在训练前统计了一下可食用类占了大头不可食用的样本明显偏少大概失衡比到了1:0.6。针对这个情况我没有简单靠加权采样硬拉而是在不可食用类里做了一个简单的过采样重复采样让每个batch里两类数量接近同时对不可食用类使用更强的颜色抖动和随机裁剪增广模拟更多生长环境。实测下来类别间的AP差距从最初的8个点缩小到不到2个点。2.2 标注规范与工具选择标注工具我用的是开源免费的CVAT比起最早的LabelImgCVAT在多人协作、标签体系管理、半自动追踪标注上的体验好很多而且在标注长序列图片时能用插值自动补框。当然如果你就几百张图单人操作LabelImg也完全够用没必要为了上工具增加学习成本。标注规范是我特别想强调的。蘑菇检测最容易犯的标注毛病是框得太随意——有些人喜欢把蘑菇连同旁边的松针、泥土一起框进去觉得这样模型“能学到更多上下文”。这个想法貌似合理实际操作中却会让模型非常困惑。因为目标检测头的监督信号来自框内区域的RoI特征如果背景杂物太多RoI特征会被稀释分类头学到的可能会是“绿色棕色纹理蘑菇”这种假相关。我这边定的标注规则是框体只包含菌盖和菌柄的完整外轮廓紧贴目标边缘留2到3个像素的余量当菌盖被遮挡时按可见部分的最大外接矩形框当同簇蘑菇相互重叠但伞面有明显分离时各标各的框不合并。所有标注完成后我请一位有野生菌鉴别经验的朋友帮忙抽检了20%的量重点看两类的边界案例有没有标错。2.3 数据划分与防泄漏处理划分数据集时最容易踩的坑是数据泄漏。网络来源的图像往往有多张连拍同一朵蘑菇可能在好多张图里出现只是角度不同。如果这些图同时被划到训练集和验证集验证指标会虚高模型部署到真实场景立刻现原形。我的做法是先做图片去重用感知哈希加人工抽样确认把所有相似度过高的连拍图归到同一组然后按组划分而不是按单张划分。最终划分结果是训练集约2400张、验证集约450张、测试集约300张。测试集完全隔离只允许在最终阶段跑一次用来评估真实泛化性能。这个习惯保住了后面所有实验结论的可信度强烈建议你也这么做。3. 模型结构拆解FPN、RPN 与检测头各自在解决什么问题3.1 FPN多尺度特征融合从“只认一层”到“融会贯通”蘑菇数据里目标尺度的跨度极大。我见过画面里一朵巨大的灵芝占掉三分之一图像也见过十米外的树根处一小丛蘑菇只有十几个像素大小。如果只用骨干网络的最后一层特征图去做检测小目标信息经过多层下采样后早就丢得差不多了。FPNFeature Pyramid Network解决的就是这个尺度的矛盾。骨干网络天然具备从底层到高层的金字塔结构浅层的特征图分辨率高、保留了轮廓和边缘细节但语义信息弱深层的特征图感受野大、语义信息强但分辨率低、小目标几乎消失。FPN做的事情不是简单把多层特征图拼起来而是从顶层开始逐层向下做侧向连接和上采样相加让每一层都同时拥有“高分辨率”和“强语义”两种能力。FPN输出的多尺度特征图P2到P5某些配置再到P6分别去匹配不同尺度的目标检测头会在每一层独立预测最后汇总结果。这样远处的小蘑菇和近处的大蘑菇各自都被分到了合适的特征层去做检测。实际效果直观可感加入FPN之后验证集上小目标面积小于图片面积1%的目标的召回率提升了接近6个点。3.2 RPN先回答“哪里可能有蘑菇”Faster-RCNN的两阶段设计里RPNRegion Proposal Network承担的是“粗筛”职责。RPN在FPN输出的特征图上滑动一组预定形状的anchor box每个anchor通过分类分支判断“有没有目标”通过回归分支修正box的粗略位置。这一步不区分蘑菇能不能吃只判断“像不像蘑菇”。RPN设计里有一个不容易注意但很重要的细节anchor的尺寸和比例。通用目标检测默认会用128、256、512这样偏大的anchor尺寸以及[1:1, 1:2, 2:1]三种比例。蘑菇的形状分布其实很有特点——伞盖宽、菌柄窄很多品种的宽高比集中在1:1到2:1之间而且真正占据图像面积的中小型目标占比更高。因此我把anchor尺寸缩小到了[32到256]的范围比例调整为[1:1, 1.5:1, 1:1.5]。这个改动对最终检测效果的影响非常显著训练收敛速度明显加快完全没有必要拿着默认参数硬跑。另外RPN阶段有一个被很多人忽略的“正负样本平衡参数”每张图采样256个候选框正样本比例要求不高于50%。如果你的数据集里一张图经常有十几朵蘑菇这个参数需要调大我当时调到了每张图512个候选框正样本上限放宽到64个不然小目标那部分候选框根本喂不到后面的ROIHead。3.3 ROIHead对候选框做二次精修ROIHead是两阶段模型和第二阶段的分水岭。RPN给出的proposal仍然带有大量噪声同一朵蘑菇可能被多个重叠框罩住有些框里只有一半蘑菇。ROIHead接收到每个proposal后先用RoIAlign把特征图上的区域池化成固定大小然后并行接两个分支一个做细分类可食用/不可食用/背景另一个做bounding box回归。在自定义分类任务里ROIHead的分类分支是从零开始训练的因为预训练模型是在COCO的80类上训的没见过“蘑菇”这个概念。这就是为什么迁移学习时一定要调整NUM_CLASSES而且分类头需要相对更高的学习率。我在实验里把ROIHead的head学习率设为主干网络的10倍loss下降速度肉眼可见地变快。4. 训练策略COCO预训练权重、MS-3x 多尺度训练与超参配置4.1 为什么用COCO预训练而不是从零训练小数据集目标检测从零训练几乎是自寻死路。骨干网络在ImageNet或COCO上学到的底层视觉特征——边缘、纹理、颜色渐变、局部形状模式——对你的蘑菇任务同样有效。预训练权重让模型起步时就站在一个“已经会看东西”的状态接下来只需要在蘑菇这个特定语义上微调而不需要重新学会识别光影和轮廓。我用的是COCO数据集预训练权重虽然COCO的类别里没有蘑菇但它的检测预训练让FPN、RPN的学习率分配都处在一个“更接近目标分布”的启动状态。相比只用ImageNet分类预训练权重的方案COCO检测权重的收敛曲线明显更平滑最终AP也略高。这里注意权重文件里包含RPN和FPN的参数加载时如果骨干网络换了RegNet配置文件里的权重映射需要做配对前缀不匹配的部分要正确处理。4.2 理解MS-3x多尺度训练与三倍训练时长标题里的MS-3x拆开是Multi-Scale和3x分别对应“输入尺度扰动”和“训练总迭代时长”。多尺度训练MS指的是在训练过程中每次迭代从预设的尺度列表里随机选一个尺度把输入图像缩放到这个尺度再送进网络。我设置的短边范围是640到800像素步长32每次随机选取。这样做的好处是模型在不同尺度上都见过蘑菇检测头被迫去学习尺度不变的特征表达。尺度扰动相当于一种极其自然的增广策略它在全局层面放缩目标而不是像裁剪那样局部改变目标形态。3x这个术语来自detectron2的调度体系。标准1x schedule在COCO上是90k次迭代约12个epoch3x就是270k次迭代对应约36个epoch并非真的要求你必须跑满36轮而是沿用它的“训练时长-学习率衰减节奏”。在蘑菇这种小数据集上我的做法是把这个节奏按数据量等比缩放初始训练设到25k次迭代学习率在第16k和第22k时分别衰减十倍。实际跑下来大概到了第8k次迭代AP曲线就趋于平缓了后面的迭代主要是在稳定边界框回归。4.3 实操配置detectron2 的蘑菇检测训练配置我用的框架是detectron2配置文件如下以yaml标准格式给出_BASE_: Base-RCNN-FPN.yaml MODEL: WEIGHTS: /path/to/regnetx_4gf_coco.pkl META_ARCHITECTURE: GeneralizedRCNN BACKBONE: NAME: build_regnet_fpn_backbone DEPTH: 4 # 具体结构由命名规则决定这里只声明使用RegNet类型 RPN: ASPECT_RATIOS: [0.67, 1.0, 1.5] SIZES: [16, 32, 64, 128, 256] ROI_HEADS: NUM_CLASSES: 3 # 背景 可食用 不可食用 SCORE_THRESH_TEST: 0.5 ROI_BOX_HEAD: CLS_AGNOSTIC_BBOX_REG: False DATASETS: TRAIN: (mushroom_train,) TEST: (mushroom_val,) SOLVER: IMS_PER_BATCH: 8 BASE_LR: 0.0025 WARMUP_ITERS: 500 STEPS: (16000, 22000) MAX_ITER: 25000 CHECKPOINT_PERIOD: 2500 INPUT: MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800) MIN_SIZE_TRAIN_SAMPLING: choice MIN_SIZE_TEST: 800 DATALOADER: NUM_WORKERS: 4几个关键参数的取值逻辑说明IMS_PER_BATCH这台机器上只跑了8batch size小的时候学习率不宜太大BASE_LR设0.0025大batch环境可以按比例往上调。STEPS和MAX_ITER对应当前数据量的衰减节奏我不建议直接抄COCO的270k迭代配置那个在小数据集上会浪费大量训练时间。类别数这里特别容易踩坑。Faster-RCNN的NUM_CLASSES包含背景类所以两个蘑菇类别要填3。填2的话模型会少一个输出类别维度训练直接报错。4.4 损失曲线与训练监控经验训练过程中我盯的主要是loss曲线里这三个分量rpn_cls_loss、rpn_loc_loss、roi_cls_loss。如果rpn_cls_loss快速收敛但roi_cls_loss下不去大概率是ROIHead的正样本太少或者标注框不紧。如果rpn_loc_loss和roi_loc_loss反复震荡先看基础学习率是不是太高再看anchor尺寸是否和目标尺度的交集太小。还有个容易被忽略的指标——显存占用。FPN加了P2层之后特征图分辨率上去了显存压力会大很多实测下来一张800x1000左右的图在batch size为8时显存吃到接近11GB如果你的卡只有8GB建议把MIN_SIZE_TRAIN上限调到736或者IMS_PER_BATCH降到4同时用梯度累积模拟更大的batch。5. 训练、验证与推理实操从命令行到部署前的完整流程5.1 环境搭建与数据注册环境的版本配套老生常谈了。我这里用的是CUDA 11.8 PyTorch 2.0 detectron2的预编译轮子版。如果你已经装了PyTorch安装detectron2最稳妥的方式是从源码编译直接pip装容易遇到二进制库和本地PyTorch版本不匹配的问题尤其是和RegNet预训练权重配合使用时前向推理的兼容性需要验证。数据集注册在detectron2里有固定的写法在训练脚本里用register_coco_instances把coco json标注文件注册进去from detectron2.data.datasets import register_coco_instances register_coco_instances(mushroom_train, {}, annotations/train.json, images/train) register_coco_instances(mushroom_val, {}, annotations/val.json, images/val)这里有一个坑图像路径建议用绝对路径或者做成相对路径后把config里的DATASETS根目录明确指出。我在项目迁移到另一台服务器时吃过相对路径的亏训练跑到一半找不到图片白白浪费时间。5.2 训练命令与指标评估训练命令很简单python tools/train_net.py \ --config-file configs/mushroom_faster_regnet.yaml \ --num-gpus 1验证评估在detectron2里只需要加一个flagpython tools/train_net.py \ --config-file configs/mushroom_faster_regnet.yaml \ --eval-only \ --eval-vis-only \ MODEL.WEIGHTS output/model_final.pth指标这里多说一句不要只看mAP这一个数字。蘑菇识别属于安全敏感场景我更关注每个类别单独算的AP以及recall曲线在置信度0.5到0.9之间的变化。如果不可食用类别的AP比可食用类低很多说明不可食用类的特征多样性覆盖不足需要补数据而不是盲目调参。我用训练过程中的checkpoint在验证集上做了对比第25k轮的模型比第10k轮在不可食用类别上的AP高了约3个点说明延长训练对难分类别是有效的。5.3 推理可视化与置信度阈值选择推理阶段用detectron2的DefaultPredictor即可from detectron2.engine import DefaultPredictor from detectron2.config import get_cfg cfg get_cfg() cfg.merge_from_file(configs/mushroom_faster_regnet.yaml) cfg.MODEL.WEIGHTS output/model_final.pth cfg.MODEL.ROI_HEADS.NUM_CLASSES 3 # 注意推理时也要设置类别数 predictor DefaultPredictor(cfg) image cv2.imread(test.jpg) outputs predictor(image)推理阈值我建议从默认的0.5往高调一些。蘑菇识别应用里误报的代价比漏报更高——把有毒蘑菇标成可食用是严重问题而把可食用蘑菇漏检最多让人少吃一顿。我在测试集上跑了几组阈值把不可食用类的推理阈值调高到了0.65可食用类保持0.5既保住了召回又压低了误检。6. 问题排查与避坑实录训练蘑菇检测模型常见故障速查这部分内容我直接整理成表格是实际训练过程中踩过和排查过的典型问题覆盖训练崩溃、指标诡异和部署翻车三类情况。问题现象可能的根因排查与解决办法训练启动就报shape mismatchNUM_CLASSES没有加背景类或自定义数据集的类别id从1开始确认类别数为3检查coco json里的category_id是否从0或1连续编号roi_cls_loss一直居高不下标注框过松、正样本iou质量低检查标注是否紧贴目标优先清一批边界模糊的proposal查看训练日志小目标全部漏检anchor尺寸范围过大或过小、FPN层没有检索到小目标缩小anchor尺寸下限开启P2特征层用小目标密集图单独做可视化诊断同一朵蘑菇输出多个重叠框不同FPN层同时命中NMS阈值不合适调低NMS的iou阈值到0.4或提高分数阈值过滤低置信度重复框把落叶和树皮高置信度误检成蘑菇负样本不充分背景类特征表达不足收集一批不含蘑菇的自然林地负样本图参与训练在推理时做裁边预处理训练中显存溢出多尺度训练的短边上限太大batch过大减小MIN_SIZE_TRAIN上限IMS_PER_BATCH降为4并开梯度累积验证mAP不错但实测翻车数据泄漏连拍图被拆开划到训练和验证集按图像相似度分组划分建立独立的完全没参与训练的测试集做最终评估换了RegNet之后权重加载报unexpected key预训练权重来自ResNet骨干或命名空间前缀不一致使用对应RegNet的预训练权重若只能拿到ResNet权重则需手动匹配backbone前缀6.1 几个值得单独讲的坑第一个坑是预训练权重的加载。RegNet在torchvision上也有对应实现但detectron2里的RegNet权重格式和torchvision导出的pkl并不完全一致。最稳妥的做法是直接用detectron2官方提供的RegNetX-4GF检测预训练权重不要自己去ImageNet模型转否则会在backbone的stage名称上卡住模型输出的结果怎么都不对。第二个坑是标注框的“过拟合”。有一段时间我发现验证集AP连续提升但新拍回来的照片检测效果特别差后来查出来是因为标注框习惯性框得偏大模型学到的是“蘑菇周围一圈背景也是蘑菇”。把标注规范改成紧贴外轮廓重新标注了一个子集去微调检测框精度立刻改善。第三个坑是负样本。目标检测的loss在训练时只对图像里存在的目标做正样本而所有背景区域都被当作负样本。如果你的训练集里每张图都有蘑菇模型几乎没有见过“完全没有目标的林地背景图”推理时就很容易把落叶阴影当成蘑菇。解决思路很简单往训练集里加一批没有蘑菇的图片纯背景让RPN学会抑制误检。6.2 可解释性检查定期积累bad case我养成了一个习惯每次训练结束后在测试集上跑推理把模型输出置信度前20的错误框累存成一个bad case文件夹。这个文件夹的价值很快就显现了——当你连续看了五六十张“模型为什么错”的图之后你会非常清楚你的数据缺什么、标注错在哪、增广该往哪个方向做。这比盯着mAP数字猜测要高效得多。比如我看到一批错误框都集中在“菌盖边缘反光”的蘑菇上于是对数据做了针对性的亮度扰动增强模型对高光场景的鲁棒性明显提升。这套配置能跑到什么水平按目前的实验配置在隔离测试集上的最终结果是可食用类AP 83.2不可食用类AP 78.4mAP约80.7。对于三千多张数据、两个分类、野外场景的检测任务来说这个数字属于“能用于辅助判断但还不能完全替代人工复核”的水平。我在这个项目里最大的体会是这类识别任务真正难的地方不是模型选型而是你对数据和场景的理解是否足够深。Faster-RCNN RegNetX-4GF FPN这套组合不是最新潮的方案但它在小数据、高误检成本、目标尺度跨度大的任务里是一个权衡下来非常稳健的选择。如果你也正在做类似的自定义目标检测项目建议不要太快追新骨架新框架先把anchor、正负样本、标注质量和loss曲线这四个基本功打扎实。后面我准备再写一篇关于模型压缩和端侧部署的内容——最终这个模型需要跑在离线环境下目标是做到推理时间不掉太多、AP不崩太多。那篇会涉及量化蒸馏和TensorRT/NCNN的实践过程等做完实验再来更新。