做槟榔、做瓜子、做花生坚果分选的朋友应该都有一个共同感受:传统人工挑拣花生霉变颗粒不仅费眼,而且标准非常不稳定。同一个工人上午和下午的判断标准都能差出一截,更不用说换班之后了。所以当有人问我能不能用深度学习快速做一个花生霉变检测系统时,我的第一反应不是直接甩一个训练脚本,而是先想清楚一个问题:我们到底要机器替代人工的哪一部分?是单纯把肉眼判断变成模型判断,还是要把从进料、拍照、识别到分选的全流程都串起来?
这次我基于YOLOv11做了一套完整的花生种子霉变检测系统,覆盖了数据采集、标注、训练、评估,再到带登录注册的UI界面部署。项目核心用到了YOLOv11深度学习模型,配合YOLO格式数据集,整体代码基于Python实现。下面把整个环节里踩过的坑、做过的取舍、以及一些容易被忽略的细节都写出来,希望给正在做农产品质检类项目的朋友一点参考。
1. 花生霉变检测的真实痛点:为什么一个二分类不够用
1.1 产线上人工分拣到底差在哪
花生霉变这件事,比想象中复杂。很多人以为霉变就是“长毛了、变黑了”,但实际在产线上遇到的样本千奇百怪:有的是表面出现黄褐色斑块,有的是胚芽部位发暗,有的是整体颜色发灰,还有的只是局部油光异常。更麻烦的是,有些花生表面看起来好好的,切开后内部已经霉变——当然,这类靠视觉本身就很难处理,项目初期就不纳入范围。
人工分拣的瓶颈在于持续注意力。一个工人坐在传送带旁边,八小时盯着花生看,前两个小时准确率还行,后面就开始麻木。按每分钟过手几百粒的量来算,漏检率会明显上升。而且花生大小、品种、表面纹理差异大,新工人和老工人的判断标准很难统一,质检记录也很难追溯。
这套系统想解决的核心问题,就是把“依靠人眼和经验”变成“依靠模型和阈值”,让检测结果可量化、可复现。模型输出的每一帧检测结果都可以落日志,任何一个批次的花生有多大的霉变比例,随时能查。
1.2 系统要做到什么程度才算能落地
做一个演示Demo很容易,难的是做成现场能用的东西。我给自己定的目标是:拿着笔记本接一个USB摄像头,对着托盘里的花生拍一下,就能在界面上看到每一个霉变颗粒被框出来,同时统计出霉变数量、霉变率,还能登录账号、记录检测历史。
从功能上看,这套系统需要四块内容:
- 能够识别“正常花生”和“霉变花生”的检测模型;
- 能够框出霉变颗粒位置并提供置信度的推理逻辑;
- 一个操作简单的图形界面,支持图片检测、摄像头实时检测、历史记录查看;
- 一个登录注册模块,区分不同操作员的使用权限。
也就是说,算法能力只是其中一环,工程化能力同样重要。如果模型精度再高,但UI难用、结果不可追溯,在产线上是推不下去的。
1.3 为什么选择目标检测而不是图像分类
一开始有人建议直接用图像分类,把整张图丢给网络判断“有没有霉变”。但对于实际场景,分类有两个硬伤:第一,如果托盘里同时有10颗正常花生和1颗霉变花生,分类模型只能告诉你“这盘有问题”,却没法告诉你是哪一颗有问题,后续根本没法做分选;第二,分类模型对目标大小很敏感,霉变区域在整张图中占比很小,模型很容易被背景干扰。
目标检测天然适合这类任务。它能在图中定位出每个目标的位置和类别,尤其在花生这类小目标密集、个体边界清晰的场景下,YOLO系模型是相当成熟的选择。我要做的就是在目标框的类别标签上做文章,把“花生”和“霉变花生”分开,甚至可以进一步细分“表面霉斑”“整粒霉变”等类别。
2. YOLOv11选型决策:v5、v8、v11之间的对比与适配性分析
2.1 不同版本的能力差距没有想象中大
开始动手之前,我把YOLOv5、YOLOv8和YOLOv11都跑了一轮简单的对比实验。惊讶的是,在花生霉变这种特征比较明显的任务上,三者的精度差距其实很小,真正拉开差距的是训练效率、推理速度和工程便利性。
YOLOv5胜在稳定,生态老,资料多,但代码风格相对陈旧;YOLOv8在工程结构上做了很多优化,训练和导出都很顺手,接口清晰;YOLOv11则在检测头结构上和训练策略上做了进一步优化,整体推理效率比前代更高,尤其是在边缘设备上,速度优势能体现出来。
我给一个初步结论:如果目标是快速上线一套单机系统,选YOLOv11没有毛病,尤其在CPU推理和低算力环境下,它的性价比更突出。但如果你有大量历史标注数据都是YOLOv5格式,继续用v5也完全没问题,模型选型的收益远小于数据质量的收益。
2.2 网络结构上值得关注的几个点
YOLOv11和v8直观的区别在于C3k2模块替换了一部分C2f结构,在保持特征提取能力的同时减少了计算量。另外,v11在骨干网络和颈部网络的连接方式上也做了调整,这种调整带来的实际效果是:在同等精度下模型更轻量,推理更快。
以我用的YOLOv11n为例,模型参数量很小,在笔记本的GPU上推理一帧640x640的图,延迟基本在十几毫秒量级,如果用CPU,也只要几百毫秒。对于静态图片检测这种场景,CPU完全顶得住;如果是实时摄像头视频流,建议至少用一张入门级独立显卡,或者把推理分辨率从640降到416。
实际项目里我选了YOLOv11n而不是更大的s或m,原因很直接:目标特征简单,不需要超大模型;系统最终可能部署在没有独立GPU的工控机上,模型越小越安全。n版本在精度上的损失,靠数据和训练策略完全能补回来。
2.3 预训练权重的迁移
YOLOv11预训练权重是在COCO数据集上训练的,包含80类常见物体。虽然COCO里没有花生霉变这个类别,但预训练权重学到的底层特征——边缘、纹理、颜色渐变、局部形状——对花生表面病变识别非常有用。
我选择在官方预训练权重基础上微调,而不是从头训练。这样收敛快很多,一般来说几十个epoch就能达到可以用的状态。如果从头训练,至少要多花一倍的时间,而且初期loss波动很大,不容易判断训练是否正常。
3. 数据集构建是真正的上限:采集、清洗与标注规范
3.1 现场采集:拍照背景、光照和角度都得固定
很多人觉得数据集构建就是“多拍点照片”,实际上采集环境不一致会让模型学到错误的东西。我第一次采集的时候,上午在窗边拍,下午在灯下拍,背景一会儿是白纸一会儿是黑色塑料托盘,结果训练出来的模型把“阴影”学成了“霉变”,换到现场环境直接没法用。
正确的做法是模拟实际部署环境:固定一块颜色单一的底板,固定光源位置和色温,固定摄像头高度和俯仰角。这样采集到的图像分布稳定,模型学到的是花生表面的真实差异,而不是环境变化。
采集时要注意几个细节:花生不能堆叠太厚,尽量铺成单层,让每一颗都清晰可见;拍照角度要接近垂直俯拍,避免侧面透视导致花生轮廓变形;每一批样本尽量覆盖不同品种、不同产地、不同含水量的花生,霉变表现会不一样。
3.2 标注环节:边界框和类别标签的规则
标注是枯燥但极其重要的环节。我用LabelImg工具,输出格式直接保存为YOLO格式的txt文件,每行对应一个目标:类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。
标注规则需要提前定死:
- 一颗花生如果只有局部霉斑,框住整颗花生,类别标为霉变;
- 如果一颗花生表面有黑点但不是霉变(比如泥土残留),不标;
- 如果花生已经严重皱缩、变色,框出来并标霉变;
- 正常花生也要框出来,类别为正常,如果不框正常花生,模型会认为背景里出现花生也无所谓。
这里强调一下:正常花生和霉变花生都要标注。有些项目只标霉变,然后把所有没标的东西都当背景,这样做的问题在于,模型无法通过对比学习区分“正常”和“霉变”,误检率会很高。只有同时提供正负样本,模型才能真正学到两类之间的边界。
3.3 数据增强与样本平衡:霉变样本少怎么处理
花生产线上霉变颗粒的比例通常不高,可能只有百分之几到十几。这意味着如果随机拍照,霉变样本会远远少于正常样本,训练出来的模型会对霉变类别严重不敏感。
解决思路有两个方向。第一,在采集阶段做富集:专门挑选霉变花生,摆成密集托盘拍照,让霉变样本在数据集中占到百分之三四十的比例。第二,在训练阶段用数据增强:YOLOv11自带马赛克增强、随机透视、色彩抖动、翻转等策略。对于霉变这种颜色敏感的特征,色彩抖动增强要注意幅度别太大,否则会把正常花生也抖动成“看起来霉变”的样子。
我最终的训练集比例大约是正常样本占60%、霉变样本占40%。这个比例在训练初期效果比较好,模型不会过度偏向某一边。
3.4 数据集划分:同一颗花生不能同时出现在训练集和验证集
数据集划分有一个容易忽略的坑:如果在拍照时同一颗花生被拍了好几张,或者同一盘花生被不同角度拍摄,这些图像之间高度相关。如果它们被随机打乱分到训练集和验证集,验证集损失会虚低,模型实际泛化能力被高估。
我的做法是:以“拍照批次”为单位划分数据。比如拍了30批花生,按批次而不是按单张图片划分,一批进入训练集,另一批才能进验证集。这样验证结果更接近真实场景。
划分比例方面,我用了训练集70%、验证集20%、测试集10%的比例。测试集是最后模型全部训练完之后才拿出来评估的,用来模拟真实场景。
4. 模型训练与评估:参数配置和结果判读
4.1 训练环境和基本命令
训练环境不需要太夸张。我用的是一台带有RTX 3060显卡的笔记本,显存12G,跑YOLOv11n毫无压力。如果显存只有6G,可以把batch size调小,或者把图像分辨率降到512,一样能训。
数据准备好之后,需要在项目目录下建一个data.yaml文件,内容大概长这样:
train: ./datasets/peanut/train/images val: ./datasets/peanut/val/images test: ./datasets/peanut/test/images nc: 2 names: ['normal', 'moldy']这里需要注意路径建议写绝对路径,避免训练时找不到数据集。类别顺序要和你标注时的类别ID保持一致,顺序错了模型就白训了。
训练命令相对简单:
yolo detect train data=peanut.yaml model=yolo11n.pt epochs=100 imgsz=640 batch=16 device=0如果只想验证流程是否跑通,可以先跑10个epoch,确认loss在下降,再跑完整训练。
4.2 超参数设置的经验
Epochs我设了100,但其实在60轮左右就已经基本收敛。早停机制很有用,YOLO训练时可以直接配置早停参数,当验证集损失连续多轮不再下降时自动停止,省时间也防止过拟合。
Batch size受显存限制,16到32都可以。实测batch size从8提到32,收敛稳定性更好,精度也有小幅提升。学习率不需要手动调,YOLO默认的自动学习率调度策略表现就不错。
图像分辨率上我对比过640和1280两种,1280在密集小目标上确实有精度优势,但训练时间和推理时间都翻倍。对于花生这种目标尺寸比较大的场景,640完全够用,所以我最终选择了640。
4.3 训练结果怎么看
训练过程中最需要关注几个指标:box_loss、cls_loss、以及验证集上的mAP50和mAP50-95。
- mAP50看的是预测框和真实框的IoU超过0.5时算正确的平均精度,这个指标对工程落地最有参考价值;
- mAP50-95更严格,跨度从0.5到0.95,学术对比常用,但实际使用中目标框稍微偏一点并不影响识别结果;
- 如果训练集loss持续下降但验证集loss反而上升,那就是过拟合,应该早停或者增加数据增强。
我这个项目的最终结果,mAP50在0.96左右,mAP50-95在0.78左右。这个成绩对产线筛选来说已经足够用了,漏检率控制在可接受范围内。
4.4 置信度阈值怎么定
模型输出的是一个置信度分数,一般默认0.25,表示只有置信度超过25%的框才显示。但这个默认值不一定适合实际场景。
如果把阈值调低(比如0.1),召回率会提高,漏检变少,但误检也会增加;把阈值调高(比如0.5),误检减少,但可能会漏掉一些表现不明显、模型信心不足的霉变花生。我的做法是在验证集上画PR曲线,找到精度和召回率平衡的拐点,再根据现场容忍度微调。一般来说,花生霉变检测更怕漏检而不是误检,所以阈值可以适当偏低。
5. 系统集成:登录注册、UI界面与实时推理
5.1 为什么需要登录注册
很多做模型的人会把登录注册当成“多余的界面需求”,但在实际产线部署中,这个模块是有实际意义的。工厂里操作员、质检员、管理员职责不同,有的只需要运行检测,有的需要查看历史记录,有的需要调整检测参数,用账号体系把权限分开是合理的。
另外,检测记录需要对应到人。如果一批花生被判定霉变率超标,工厂需要知道是谁操作的、什么时候检测的,方便追溯。没有登录机制就没有操作留痕,出了问题说不清楚。
实现上我用的比较简单的方式:注册时把用户名和加密后的密码写入本地SQLite数据库,登录时读取比对,再用一个会话变量记录当前登录用户。对单机系统来说,SQLite足够,不需要额外部署数据库服务。
5.2 UI界面功能划分
整个界面我用了PySide6来做,分左中右布局:左边是功能面板和登录信息,中间是图像显示区域,右边是检测结果和统计信息。
功能面板里有三个主要入口:
- 图片检测:点击上传图片,跑一次推理,显示检测结果图和检测统计;
- 摄像头检测:打开本地摄像头,实时显示检测画面,每隔几帧执行一次推理;
- 历史记录:查看当前账号的历史检测记录,包括时间、图片路径、检测到的霉变数量。
界面底部还有一个大号的统计栏,实时显示霉变数量、正常数量、霉变率,方便产线工人一眼看到结果。
5.3 推理后处理:框、计数和标记
模型推理返回的是每个目标的边界框坐标、类别ID和置信度。拿到这些数据之后,不能只是画个框,还要做几件事:
- 过滤置信度低于阈值的框;
- 按照类别ID分别计数,统计正常数量和霉变数量;
- 在霉变框旁边标注置信度百分比,方便人工复核;
- 对霉变目标按坐标排序,输出它们在托盘中的大致分布位置。
另外,实时视频流推理时,模型输出的坐标是相对于当前帧的,如果画面有抖动,检测框会跟着晃。这种情况下可以用相邻帧之间的检测结果做简单的平滑,或者直接保持固定摄像头位置,避免画面频繁移动。
5.4 项目代码结构
整个Python项目的目录结构大致如下:
peanut_detection/ ├── models/ # 存放训练好的模型权重 ├── datasets/ # 数据集和data.yaml ├── utils/ # 数据库操作、图像处理等工具函数 ├── ui/ # 登录界面、主界面代码 ├── detect.py # 模型推理封装 ├── train.py # 训练脚本 ├── login_window.py # 登录注册窗口 └── main_window.py # 主界面入口把代码拆开而不是写成一个几百行的脚本,后面维护会省很多事。比如摄像头检测逻辑和图片检测逻辑可以共用detect.py里的推理函数,界面部分只负责展示结果,互不干扰。
6. 部署过程中的真实踩坑记录
6.1 检测框抖动和置信度波动
第一次接上USB摄像头做实时检测时,发现连续帧之间检测结果很不稳定:同一颗花生一会儿被框出来,一会儿没被框出来,置信度在0.2到0.6之间跳。
排查下来有几个原因:首先是摄像头自动白平衡导致颜色在漂移,花生表面的颜色特征跟着变;其次是图像压缩噪声影响了小目标的特征;最后是推理分辨率如果不够高,模型对边缘模糊的目标信心不足。
解决办法是把摄像头的自动曝光和自动白平衡关掉,固定一组手动参数;推理分辨率保持在640不变;对相同位置的检测框做时间上的平滑,比如连续三帧都检测到才确定是霉变。
6.2 CPU推理速度不够怎么办
如果在没有独立显卡的电脑上部署,实时摄像头检测会明显卡顿。YOLOv11n在CPU上推理一帧640x640的图大约需要300到600毫秒,换成摄像头30帧每秒的输入,根本跑不过来。
我的优化思路是“降帧率 + 降分辨率 + 跳帧处理”。摄像头采集帧率保持30帧,但只对每3帧执行一次推理,其他帧直接显示上一帧的检测结果,画面看起来是平滑的,实际推理压力一下子降了下来。如果还不够,可以把推理分辨率从640降到416,速度能提升将近一倍,精度损失很小。
6.3 温度和湿度变化带来的误检
项目做到后期,现场反馈说“今天误检变多了”。我跑到现场一看,是因为当天湿度大,花生表面凝结了一层水汽,反光特征和干燥状态下差别很大,模型的判断受到了影响。
这个坑很难完全靠模型解决,因为训练数据里没有覆盖这种环境变化。可行的方案是在部署机台上增加固定除湿或恒温手段,或者在数据采集阶段就考虑加入不同湿度条件下的花生样本。工程上永远是数据和环境的问题,模型只是映射了数据中的规律。
7. 扩展思路与个人经验总结
7.1 从二分类扩展到多分级
目前系统只区分“正常”和“霉变”两类。实际产线上,很多工厂还想区分“轻度霉变”和“严重霉变”,因为轻度霉变可能经过处理后还能用,严重霉变必须剔除。这个需求可以通过增加类别标签实现,在标注时把霉变分成轻度和重度两类,重新训练模型即可。
如果还想更精细,可以增加“破损”“发芽”等类别,让一条产线同时完成多项质检。数据集标注工作量会增加不少,但YOLOv11的多标签分类能力完全能支撑。
7.2 模型导出的几种形式
训练的模型除了在Python界面里使用,还可以导出成ONNX格式,部署到其他平台。用YOLO自带的导出命令就能完成:
yolo export model=best.pt format=onnxONNX模型可以被很多推理框架加载,比如在C++程序里用ONNX Runtime推理,或者部署到嵌入式设备上。如果以后有人要求脱离Python环境运行,导出ONNX是一条必经之路。
7.3 做这类项目的一些体会
整套系统做下来,我最想强调的一个观点是:模型训练只占整个项目不到三成的工作量,数据采集、标注规范和系统集成才是大头。很多初学者把重心放在调参上,反复试各种学习率、各种模型版本,收获反而有限。把数据的采集环境固定好、把标注规范定清楚、把界面逻辑做顺畅,这三件事做好,项目已经成功了一大半。
另外,做农产品检测类项目,一定要给自己预留现场调试的时间。实验室里效果再好,到了现场也会有各种意外状况,光源、反光、湿度、传送带速度、花生品种差异,任何一个因素都能让模型表现明显下降。提前和数据采集方、设备方沟通好环境条件,能省掉大量返工时间。
如果后面有条件,我计划把系统再往前推一步,在界面上增加批次管理功能,每次检测自动生成一份包含图片、检测结果、霉变率和操作员信息的报告,直接导出成表格。这样整个系统就不只是“一个识别工具”,而是能融入工厂现有质检流程的一个完整环节。