其实我很早就想做一套能真正落地的深度学习质检系统了。目标检测的教程一搜一大把,但大多数都停在“训练完看个mAP”就结束了。真正放到实际场景里,用户要的不是一个权重文件,而是一个能登录、能选图、能出结果、能保存记录的完整工具。这个花生种子霉变检测系统就是这么来的,基于YOLOv11做目标检测,配合自建的花生种子数据集,外层用Python把模型推理、UI界面、登录注册全部串起来,最终交付的是一个开箱即用的桌面应用。
这套东西适合谁?一是做农业生产智能化或食品质量检测相关课题的朋友,需要一个完整的项目范式做参考;二是刚学完YOLO系列、想知道怎么把模型封装成产品的新手;三是需要快速搭建“深度学习+界面”演示系统的开发者。整个项目从数据准备、模型训练再到界面封装,链路完整,踩坑不少,这篇就把每一环的细节和教训都写出来。
1. 项目整体思路与选型分析
1.1 先理清需求:检测系统到底要做什么
很多人一上来就急着跑模型,其实第一步应该是把需求拆明白。这个系统的核心任务是:给定一张包含花生种子的图像,模型能自动找到每一颗花生籽粒的位置,并判断它是否发生霉变。注意,这里不是做图像分类——不是整张图给一个“好”或“坏”的结论,而是要定位出图像中每一个目标,标出类别和位置。
这就决定了技术路线必须走目标检测,不能简单套分类模型。再往下拆,系统需要有三个层次的功能:底层是训练好的检测模型;中间层是调用模型做推理的后端逻辑;最外层是用户能直接操作的可视化界面。再加上登录注册,整个系统就是一个mini版的SaaS形态——先管住用户身份,再提供服务。
这个拆解过程很重要。如果你在做一个项目或者毕设,先把你做的东西的功能边界画清楚,哪些是必要的、哪些是凑数的,想明白这个,后边的开发就是按图索骥。
1.2 为什么最终选了YOLOv11
选检测框架的时候,我认真对比过几代模型。老牌的Faster R-CNN精度是有保障的,但推理速度慢,而且部署复杂,尤其是往UI里集成的时候,检测一张图要一两秒,体验非常糟糕。SSD倒是快,但小目标检测能力偏弱,花生霉变通常表现为斑点状的局部色泽变化,很多时候目标面积并不大,SSD在这种场景下容易漏检。
YOLO系列是速度与精度的平衡点。从v5到v8到v11,迭代这么多版本,生态已经非常成熟了。到了v11这一代,C3k2模块替代了早期的C3结构,注意力机制也做了升级,整体在保持高帧率的同时,小目标检测能力有明显提升。对花生种子这种形态规则但细节差异很大的目标来说,YOLOv11的特征提取能力够用,而且它的训练工具链非常完善,从标注数据到训练再到导出的全流程都有成熟配套。
还有一个很现实的因素:社区资料丰富。遇到问题上网查,基本都能找到参考,这一点对于做项目的人来说太重要了。很多冷门框架精度再好,出了问题查一整天都找不到解决方案,分分钟劝退。
1.3 整体技术架构设计
系统整体分为三大块:模型端、服务端、客户端。
模型端就是YOLOv11的权重文件,这是整个系统的核心资产。训练好之后导出为适合推理的格式,保证加载速度快、内存占用可控。服务端指的是推理逻辑,也就是把图像传给模型、执行推理、解析输出结果、做后处理的这一段代码。客户端就是UI层,接受用户的操作指令,调用服务端逻辑,把结果展示出来。
登录注册模块独立出来,用本地文件存用户信息,不走网络数据库。好处是部署零依赖,坏处是只能本机使用、无法做真正的账号体系,但对于演示和课题来说完全够用,而且实现起来干净利落。
2. 数据集构建与标注规范
2.1 数据采集:比想象中费劲得多
做花生种子霉变检测,第一个卡住我的竟然不是模型,而是数据。网上公开的花生霉变检测数据集几乎没有,食品安全类的公开数据集本身就不多,更别说细到“花生种子的霉变目标检测”这种程度。所以整个数据集必须自己动手建。
采集过程分两路走。第一路是拍真实样品,去农贸市场买不同批次的带壳花生和花生米,在自然光和台灯下、白底和黑底背景下,用手机加微距镜头拍摄。霉变的花生有些是表面长绿毛或白毛的,有些是颜色变深发黄的,有些是局部有霉斑的,这些都要尽量覆盖。第二路是网络抓图补充,从公开图片平台搜花生、发霉花生、坏花生籽粒等关键词,人工筛选可用的图片。
最终我整理出原始图片大概1800多张。注意,这个数量不算多,但对于单类或双类检测来说已经能起步了。如果你要做更稳定的工业级模型,建议至少攒到5000张以上。
2.2 标注细节:类别的定义直接影响模型上限
标注工具我用的是LabelImg,输出PASCAL VOC格式的XML,再转成YOLO需要的txt格式。流程是先用LabelImg打开图片,框选目标,选择类别,保存。
这里有个很重要的经验:类别怎么定义,直接决定了模型的学习目标。我一开始图省事,只标了霉变花生(moldy)这一个类,正常花生不标,想着只检测异常目标就行了。结果训练出来的模型表现很飘,因为模型没有“正常”的概念,阈值稍低就把正常花生也框出来当成霉变,阈值一高又漏检。
后来我改成标注两个类:正常花生(normal)和霉变花生(moldy)。这样模型在训练时能明确区分两类特征的边界,推理时也可以同时输出两类目标。从实际效果看,mAP50从单类时的0.71涨到了双类时的0.89,提升非常明显。
2.3 数据增强与划分策略
YOLOv11训练框架里内置了丰富的数据增强方式,比如HSV色彩空间扰动、随机翻转、缩放、平移等。理论上开箱即用,但实际要把增强参数调对。
我的配置经验是:mosaic增强在训练前期打开,能大幅提升模型对多目标和小目标的适应能力,但训练最后几十个epoch建议关掉,因为mosaic生成的合成图跟真实分布有差异,一直开着会导致最终收敛不够精准。HSV增强适度即可,饱和度偏移不要超过0.5,否则花生的颜色会被拉得完全不自然,反而损害检测精度。
数据集划分上,我按8:1:1的比例拆成训练集、验证集、测试集。注意一定要打乱顺序,确保同一场景拍摄的相似图片不会集中出现在某一个集合里,否则验证集失真,训练出来的指标好看但实际泛化很差。
3. YOLOv11模型训练全流程
3.1 环境适配:版本匹配是第一个坑
YOLOv11对Python版本和依赖有明确要求。我的环境是Python 3.10,CUDA 11.8,PyTorch 2.0.1以上版本都兼容。这里提醒一句,如果你用的是Windows系统,安装PyTorch的时候一定要选对应CUDA版本的wheel包,用默认的CPU版本训起来会让人怀疑人生。
YOLOv11的源码在GitHub上直接克隆下来,安装依赖时用requirements.txt一步到位。除此之外,还需要单独安装ultralytics这个核心库,它统一了数据加载、模型构建、训练流程这些逻辑。
我训练用的是单张显卡,显存8G,batch size设为16,输入尺寸640x640。如果你是用的云端GPU,训练配置还可以往上推。先把预训练权重YOLOv11n.pt和yolov11s.pt下载好,训练时会自动加载通用特征,大幅减少收敛时间。
3.2 训练参数的认识与调优逻辑
很多人习惯直接跑默认参数,但我建议至少把这些关键参数搞清楚。以下几个参数基本决定了模型最终质量:epochs、batch、imgsz、lr0、patience。
我的训练设置是epochs=200,batch=16,imgsz=640。学习率初始值lr0=0.01,配合默认的余弦退火策略。patience设为30,意思是连续30个epoch验证集指标不提升就早停,防过拟合、省时间。训练过程中每10个epoch会在验证集上跑一次mAP评估,指标变化一目了然。
在调参的过程中,我试过把imgsz提到960,检测精度有小幅提升,但推理速度明显下降,帧率低了近一半。对于花生种子检测这种场景,640足够用了。结论是不要盲目追求高分辨率,要和你的实际使用环境匹配。
3.3 训练过程状态解读
训练日志里最有价值的三个指标:box_loss、cls_loss、mAP50。
box_loss是边界框回归损失,代表预测框和真实框的差距;cls_loss是分类损失,代表类别预测的准确程度。正常情况下这两者应该持续下降并趋于平稳。mAP50是IoU阈值取0.5时的平均精度均值,是衡量检测模型最直观的指标。
我吃了一次亏:第一次训练到第20轮的时候,box_loss不降反升,mAP一直趴着不动。排查了半天发现是数据集在标注的时候,很多霉变花生框得太紧,边界压住了霉变斑纹的边缘,导致完好的籽粒和霉变起始特征产生了重叠。后来重新过了一遍标注,把框往外扩了两三个像素,问题就解了。所以当你看到loss异常的时候,优先检查数据,不要急着调参数。
4. 检测效果评估与模型优化
4.1 精度判定的标准
训练完成后,评估模型光看loss曲线不够,要看验证集上的具体指标。我最终拿到的结果是:mAP50=0.89,mAP50-95=0.63,precision=0.91,recall=0.85。这个水平在当下的机器学习任务里属于中上,考虑到数据量和类别复杂度,已经能满足检测需求。
mAP50容易理解,就是所有类别的平均精度。mAP50-95是IoU从0.5变化到0.95取多个阈值后计算的平均值,这个指标更严苛,也能更真实反映模型定位的精细程度。一般来说mAP50和mAP50-95的差距如果超过0.3,说明定位还不够准,可以尝试增大输入分辨率或优化边框回归损失。
分类别看一眼:normal类的AP是0.93,moldy类的AP是0.85。霉变类AP偏低的原因是霉变花生形态多样,有些早期霉变颜色变化不明显,跟正常花生差异很小,误判率偏高。这也是当前整个检测系统的瓶颈。
4.2 优化方向与实践验证
针对霉变类AP偏低的问题,我尝试了几种优化方案。
第一,调整类别权重。给moldy类加更高的loss权重,相当于强行让模型对霉变类样本更敏感。做法是在数据配置文件中给class_weights参数赋值,例如normal=0.6、moldy=1.2。这样在分类损失中,霉变类贡献的梯度更大,模型会偏向提高对霉变特征的学习。
第二,增加带标签的难例数据。霉变早期阶段的样本单独标注了一批,补充进训练集。增加难例是最直接也最有效的方式,新数据训练后霉变类AP从0.85提到了0.88。
第三,使用训练时的TTA策略。推理时开启测试时增强,把原图、翻转图、缩放图分别推理再综合结果。TTA能稳定提升几个点的精度,但推理时间会成倍增加,所以我的建议是作为离线评估时的参考,线上实时检测还是关掉。
4.3 推理速度与模型体积
速度方面,用YOLOv11s作为主体模型时,GPU模式下推理一张640x640的图大约耗时12毫秒,CPU模式下大约150到250毫秒。放到桌面应用里,用户点选图片到出结果基本一秒内能完成,体验是可以接受的。
模型体积方面,YOLOv11s的权重文件约19MB,压缩后14MB左右,不管是打包还是分发都非常方便。如果你的硬件环境更差,可以用YOLOv11n,体积只有5MB左右,精度下降约3到4个点,但推理速度提升接近一倍。我甚至试过用v11n跑树莓派级别的设备,也能流畅运行。
5. UI界面与登录注册模块设计
5.1 UI框架选型与整体布局
UI层选型上,tkinter是Python自带库,语法简单、上手快,做小型工具类界面绰绰有余,而且打包体积小、跨平台。PyQt5功能更强、控件丰富、视觉效果更好,但引入的依赖体积大,打包后动辄一两百MB。考虑到这个系统的定位是检测演示与课题交付,最终选了PyQt5,因为用户对界面观感的要求往往比我们想象的高。
整体布局分三个区域。顶部是功能栏,包含打开图片、开始检测、保存结果等操作按钮。中间是主显示区,左边显示原始图片,右边显示检测结果图,两边的尺寸在窗口拉伸时能联动缩放。底部是信息栏,显示当前检测到的各类数量、单次推理耗时、以及模型加载状态。
5.2 登录注册界面的实现逻辑
登录注册模块看起来简单,但要做到账户安全性、代码结构清晰、扩展性好,还是需要仔细设计的。
用户信息存储我用了JSON文件。密码用hashlib库的sha256加盐处理,不存明文。注册时先判断用户名是否存在——存在则提示换一个;不存在则把用户名、加盐后的哈希值、注册时间写入用户文件。登录时读取用户传入的密码做同样的哈希处理,和存储的哈希值比对,一致则登录成功,进入主界面。
这里有两个细节值得说。第一是加盐必须做,恶意手段很擅长对常见密码做彩虹表碰撞,不处理哈希等于裸奔。第二是登录界面关闭时,要确保弹窗绑定的事件循环不会卡住主进程。用PyQt5的QDialog来做登录窗体,setWindowModality设为Qt.ApplicationModal,能确保登录窗口弹出时主界面不可操作,逻辑更严谨。
5.3 主界面核心检测功能实现
检测功能是主界面的核心。用户点击“打开图片”按钮,通过QFileDialog弹出文件选择器,支持常见的jpg、png、bmp格式。选中之后,用QPixmap将图片加载到左侧的QLabel中,原始图像不做任何压缩,方便后续在保存结果时保留原始分辨率。
点击“开始检测”后,程序先调用加载好的模型对图片做推理。这里需要注意,PyQt5的UI线程不能跑耗时的推理任务,否则界面会直接卡死、白屏,用户会以为程序崩了。解决办法是用QThread创建子线程,把推理逻辑放到worker线程里跑,推理结束后通过信号槽机制把结果传回主线程更新界面。
检测结果的绘制方面,模型输出的目标框坐标、类别和置信度都是归一化格式,要先乘回原图宽高。画框时normal类用绿色框显示,moldy类用红色框,线宽根据原图的尺寸自动缩放,避免大图上线条太细看不见。
界面右侧还会显示两类目标的统计数字,比如“正常花生:35颗,霉变花生:8颗,霉变率:18.6%”。这个额外功能虽然做起来不难,但对实际使用者的决策帮助很大,也对整个系统的实用程度有了直观提升。
6. 系统集成部署与避坑经验总结
6.1 模型加载与推理封装
系统实际交付时,模型加载的稳定性非常重要。我比较推荐把推理逻辑封装成一个独立的检测器类,启动界面时先实例化,避免每次检测都重新加载权重。
加载时用ultralytics库的YOLO类直接读取权重路径。推理时传入图片路径或ndarray数组,通过predict方法设置conf参数控制置信度阈值,这个阈值直接影响误报和漏报的平衡。从我的实践来看,阈值调到0.35到0.4之间比较合适,既能过滤掉大部分低置信度的误检框,又不会放过霉变特征不明显的早期样本。
推理完成后要从results对象中提取boxes数据和names映射。这个时候要小心:不同版本的ultralytics库,返回的数据接口可能有差异。因此,封装时一定要统一提取逻辑,保证后续界面层只依赖固定接口。
6.2 打包分发时的注意事项
Python项目交付,最绕不开的坑就是打包。用PyInstaller打出来的包,如果直接打包,往往缺少一些依赖文件,比如PyQt5的插件、ultralytics的运行配置文件、还有一些模型转换工具的动态库。
我的打包方案是用PyInstaller加spec文件的方法,重点在hidden imports里把torch、torchvision、ultralytics、cv2等动态导入的模块全部显式列上。还要把项目依赖的资源文件和权重文件一并加入datas,保证收到包的机器上不用手动去配模型路径。
打包产物是一个exe文件、权重文件、配置文件,再加上一个说明文档。第一次打包出来的exe我测试时发现新机器上打开就闪退,一查日志是缺少Visual C++ Redistributable运行库。后来打包时加了--add-data把对应运行库一并打进去,这个问题才算彻底解决。
6.3 实操中踩过的坑与排查技巧
这里把我在这个项目里踩过的几个坑集中整理一下,都是翻文档翻到头秃才找到的原因。
第一个坑是训练时显存溢出报错。解决方法是降低batch size,但减小batch会影响BatchNorm统计效果,所以更优的解法是开启gradient accumulation,模型优化器每积累多个batch的梯度再做一次更新,效果和用大batch几乎一致。
第二个坑是推理时中文标签乱码。这是因为Ultralytics库在画框时默认字体不支持中文。解决办法是手动指定中文字体路径,在画框前重置预测结果的names和字体设置。不要图省事全程用英文标签代替,做交付时用户看到中文界面更舒服。
第三个坑是打包后模型加载路径错乱。PyInstaller打包的路径跟源码运行路径不一样,直接用相对路径读权重很容易找不到文件。解决方法是运行时统一用sys._MEIPASS或者os.path.dirname(file)动态拼接路径,然后把权重文件放到约定目录,程序启动时主动检查文件是否存在并给出明确提示。
第四个坑是霉变类别在验证集上AP偏低但训练集很高,典型的过拟合信号。当时我数据量小、增强又保守,模型把训练集的细节当成通用特征硬记下来了。后来加大数据增强力度,同时补充了一些真实新样本,过拟合情况明显改善。
6.4 系统扩展方向建议
做完这一套花生种子霉变检测系统之后,我发现这个架构完全可以推广到其他农产品质检场景。比如大米发黄检测、玉米霉变检测、水果表面缺陷检测,只需要替换数据集和重新训练模型,UI层和登录注册模块几乎不用动。
还可以扩展的方向包括:把检测结果自动写入Excel报表并导出,做到检测记录的数字化管理;把模型部署到服务端,通过一个轻量级的接口让多个客户端同时检测;或者引入硬件,接入USB摄像头,做实时流式检测,这个功能离工厂产线的需求更近一步。
我在实际使用中发现,如果拿这套系统给一个小型收购站用,一个工作日要检测两千多张花生图片,同时还需要把每天霉变率数据留存下来。后续版本可以把统计模块做厚一点,把每日、每周的霉变率趋势以图表的形式画出来。这样的系统就从一个检测工具升级成了质检记录分析平台,实用性会再上一个台阶。