棉花分拣车间的工位前,一袋散棉倒上操作台,老质检员两只手来回拨弄,眼睛盯着看有没有霉斑、杂质和没长开的棉桃,一个棉包少说也要两三分钟。这样的活儿干一整天,眼睛和颈椎都难受,而且不同质检员的标准不可能完全统一。所以当我把“基于深度学习YOLOv12的棉花分类检测系统”做完之后,第一感觉是这种项目真的把“算法、数据、界面、部署”全串起来了,它是一个典型的端到端视觉应用:模型的检测精度直接决定系统有没有用,但UI和登录注册决定了它能不能交给别人使用。这篇文章我准备把这套系统的完整实现过程记录下来,从数据集怎么标注、YOLOv12参数怎么调,到PyQt5界面和登录注册模块怎么和模型联动,全部展开讲清楚,给同样想从零复现这个项目的朋友一条可以直接走的路线。
1. 从棉花质检痛点看系统需求
1.1 传统人工检测的困境与项目目标
棉花等级评价有很多维度,色级、杂质含量、成熟度、回潮率这些都是专业术语,但对于普通场景来说,最直观的分类维度就是看棉花的完整性:正常棉洁白蓬松,霉变棉发黄发灰甚至结块,含杂棉里混有棉叶、铃壳、地膜残片,未成熟棉桃则是一团瘪瘪的、没有完全开絮的样子。这几个类别如果让机器自动识别,就可以大幅减少人工反复翻看的环节,尤其在收购站和初加工厂,快速先用机器筛一遍,再安排人工复核坏样本,效率能提一个量级。
当时设计这个系统的目标也很直接:用户登录后,选择一张图片或打开摄像头,系统自动框出画面里的棉花区域,并标出类别和置信度,同时统计当前图像里每个类别的数量。除了检测之外,还要有一个简洁的登录注册界面,只有注册过的人才能进入主系统。所以整个项目从功能上拆开就是三个模块:数据与模型、UI界面、账户管理。数据与模型解决“能不能检测出来”,UI界面解决“怎么让人方便地用”,账户管理解决“谁可以用”。
1.2 为什么选YOLOv12作为检测核心
目标检测模型现在可选方案非常多,传统两阶段检测器、单阶段检测器、基于Transformer的DETR系列,还有后来一系列改进版本。为什么我在这个项目里选了YOLOv12?最直接的原因是它在精度和速度的平衡上表现很出色,而且官方提供了在通用数据集上的预训练权重,迁移到棉花这种农业场景收敛很快。
YOLOv12提出的核心改动包括引入高效的注意力机制、可学习的区域到点卷积(Area-to-Point Convolution),以及替换了之前版本常用的上采样方式。这些改动让模型在深层特征提取上更有针对性,同时保持了一阶段的检测速度。对于棉花分类检测这种目标不算特别小、但背景纹理复杂的任务,YOLOv12的注意力机制能更好地抓住棉花团和杂质区域的纹理差异,而不是只依赖边框纹理。实践里我用同样的数据集对比过几个方案,YOLOv12在漏检率和误检率上的表现是最稳定的,尤其是伪装性较强的霉变棉,它比传统卷积结构更容易学到颜色和质地的细微异常。另外,它取消了锚框机制,后处理简单,输出结果直接画框也很方便,这一点对后面接UI界面非常友好。
2. 棉花数据集从零构建:采集、标注与增强
2.1 类别设定与图像采集方案
做任何检测任务,第一步不是急着跑模型,而是先把数据集定义清楚。我这次定义了五个类别:正常棉、霉变棉、含杂棉、未成熟棉桃、背景干扰物。前面四类是模型真正要学习的对象,第五类其实是为了让模型学会“有些东西不是棉花”,比如操作台上散落的线头、包装带碎片、灰尘块等,这样推理时不会把每块浅色物体都当成棉花。
采集图像时我用了手机和普通相机,在白色操作台上、自然光下、LED灯下、背光场景下分别拍摄,这主要是为了让模型在不同成像条件下都能保持稳定。每张图不要只拍一大团棉花,尽量模拟真实工作台的散落状态:有的图像里棉花面积大,有的棉花只占画面一小块,有的图像里同时存在正常棉和杂质,这能提高模型的泛化能力。起步阶段每类至少准备300张原始图像,总样本量在1200到1500张之间,如果类别分布不均衡,后续要补采。
2.2 YOLO格式标注规范与常见误区
标注我用的工具是常见的图形化标注工具,它可以直接导出与YOLO系列训练框架兼容的txt文件。每行格式固定为:
class_id x_center y_center width height这四个坐标值全部是归一化到0到1之间的相对值,用框的左上角和右下角坐标计算得到,中心坐标等于两个角坐标的平均值除以图片宽高,宽高等于两个角坐标的差值除以图片宽高。比如一张宽1000像素、高800像素的图片里,某个含杂棉区域的左上角是(200, 150),右下角是(600, 700),那么对应的归一化中心x就是(200+600)/2/1000=0.4,中心y就是(150+700)/2/800=0.53125,宽度就是(600-200)/1000=0.4,高度就是(700-150)/800=0.6875。
标注时有几个细节别踩坑。第一,边界框不要贪大,尽量紧贴目标外轮廓,尤其棉花纹理蓬松边缘不规则,手一抖框大了,训练时会把大量背景学进特征里,导致最后画出来的框明显偏大。第二,同一张图里如果有多个类别的目标,不要偷懒只标显眼的大目标,小杂质和不成熟的棉桃一定要单独标出来,否则模型只会关注大目标,小目标一样会漏。第三,雨滴、污渍、阴影这类因素会让框轻微偏移,但不要因为图像质量不高就把难以判断的目标删掉,训练数据里保留一定难例反而能提升模型鲁棒性。
2.3 数据增强与划分策略实操
标注完成之后,原始图像数量可能还是不够,尤其是霉变棉和未成熟棉桃这类难样本,数量一少模型容易过拟合。我的做法是引入数据增强:水平翻转、随机旋转、HSV色域扰动、随机裁剪、模拟光照变化。这些增强操作可以用常见的深度学习训练框架自带的数据增强模块实现,也可以自己写一个工具来做。
我实际测试下来,对这个任务最有效的是HSV扰动中的明度和饱和度调整,因为棉花颜色敏感度高,稍微调整一下明度,就能模拟不同光线条件下的真实场景。随机旋转对小目标检测帮助也大,幅度控制在正负30度以内,超过这个范围会出现大量无效边界。增强后的数据集我控制在2800张左右,太多了训练时间成倍增加,对精度提升却接近饱和。
数据集划分不能简单随机抽三份,因为同一场景连拍出来的图像高度相似,如果训练集和验证集里出现大量近亲图像,验证损失会很好看,但一到现场就原形毕露。我按图像来源进行分组,把同一批次、同一拍摄角度、同一光照条件下的图像尽量放进同一个集合里,再按8:1:1分成训练集、验证集、测试集。这样划分后验证集分数虽然有下降,但更接近真实部署效果。
3. YOLOv12训练配置全记录:参数、过程与结果
3.1 运行环境与项目结构准备
训练环境我建议直接准备一块中高端独立显卡,显存8GB以上跑起来会轻松很多,如果只有CPU也能完成小批量训练,但速度会慢几十倍,不太适合反复调参。软件环境需要Python 3.10及以上的解释器、当前主流深度学习框架、OpenCV图像处理库、以及对应的目标检测训练框架组件。
项目目录我会分成几个部分:
cotton_detect/ ├── weights/ 存放预训练权重和最终模型 ├── datasets/ 数据图片和标签文件 ├── ui/ 登录注册界面和主界面代码 ├── model/ 模型加载与推理封装 ├── utils/ 画框、数据预处理、用户管理 └── main.py 程序入口这样拆分的好处是训练代码、界面代码、模型推理代码互不干扰,后续换模型版本或给界面加功能时不用大面积改代码。
3.2 训练关键参数解读与完整命令
YOLOv12官方仓库提供的训练启动方式很简洁,一个命令就能跑完整个流程。核心训练参数我按以下配置来设:
python train.py --data cotton.yaml --weights yolov12.pt \ --img 640 --batch 16 --epochs 120 \ --optimizer AdamW --lr 0.01 --workers 4 \ --project cotton_run --name v12_expcotton.yaml是数据集描述文件,里面要写明train、val路径和nc类别总数,以及每个类别对应的名称列表。img设为640意味着输入图像会被统一缩放到640乘640,这个尺寸对棉花场景足够,训练速度也快。batch设为16受显存限制,如果显卡显存更大可以提高到24甚至32,梯度更新更稳。epochs我建议至少100轮,前期学习率大、收敛快,后面逐渐降到很低,让损失值进一步压住。
训练时优化器我尝试过SGD和AdamW。SGD配合余弦退火学习率在最终精度上略高,但前期收敛太慢;AdamW前期损失下降明显更快,总体精度差距不大,所以最后选了AdamW。初始学习率设为0.01不需要手动改,训练框架内置的调度器会自动做warmup,前3轮用较低学习率预热,防止一开始模型参数被大梯度冲坏。
3.3 训练过程监控与性能指标复盘
训练过程中盯着日志里的三个核心指标就够了:box_loss边界框损失、cls_loss分类损失、mAP50和mAP50-95。mAP50是预测框与真实框的IoU超过0.5时算正确的平均精度,mAP50-95则是在0.5到0.95区间内每隔0.05取一个IoU阈值再平均,后者更严格,更能反映定位质量。
我跑完120轮之后,验证集上的mAP50稳定在0.93左右,mAP50-95稳定在0.86左右。从PR曲线看,含杂棉和正常棉的召回率最高,霉变棉和未成熟棉桃有少量漏检,集中在目标太小或者被大片正常棉遮挡的时候。如果现场使用对漏检零容忍,可以再把输入尺寸提高到800乘800,或在融合时对难分类别增加类别权重。
训练结束后,权重目录里会出现best.pt和last.pt,best.pt是验证指标最好的那一轮参数,最终部署就选它。需要注意的是,训练框架有时会针对验证集做测试时增强,训练时如果开启了这项,最终模型推理速度会变慢,部署时记得关掉。
4. 交互层开发:登录注册与UI界面如何串起来
4.1 系统软件架构:从窗口到模型推理的调用链路
整个系统不是把模型代码和界面代码混在一个大文件里那么简单,我把调用链路理成四层。最外层是登录窗口,负责接收用户名和密码,校验通过后销毁登录窗口并创建主窗口;主窗口里是各种按钮、图片展示区、检测结果列表,用户点击选择图片后,程序读取图像文件并交给推理模块;推理模块加载训练好的模型,对图像做预处理、前向推理、后处理,返回检测框、类别和置信度;最后主窗口把这些结果渲染到图片上并更新统计信息。
这样的分层方式让模块之间只通过函数接口交互,后面如果要换模型,只需要改推理模块内部实现,界面代码一行不用动。
4.2 登录注册模块实现与安全要点
登录注册这里用的是常见界面开发库,数据库用了轻量级的本地数据库,不用额外安装数据库服务,部署时一个文件就能带走全部用户数据。注册表结构很直观:
CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, created_time TEXT DEFAULT CURRENT_TIMESTAMP );密码不能明文存储。注册时对密码做SHA256加盐哈希,盐值就是用户名本身拼接一段固定的盐字符串,虽然不如专用密码哈希函数那么严谨,但作为Python桌面应用项目足够挡住明文存储的尴尬。校验登录时,取出数据库里对应用户名的哈希值,对输入的密码做同样哈希后比对,一致才视为登录成功。
登录窗口上两个输入框都要把回显模式设为密码模式,这样输入内容会被掩码。用户名不能留空,密码长度至少6位,注册时要二次确认密码,两遍不一致直接弹出错误提示。用户名重复时数据库会报唯一性冲突,程序捕获这个异常后提示“该用户名已被注册”,而不是直接崩溃。
用户登录成功后,我用一个全局变量保存当前用户信息,主界面的标题栏会显示“当前用户:xxx”,这样多用户使用时也能知道是谁在进行检测操作。
4.3 UI主界面的功能设计与线程模型
检测系统主界面核心就三块区域:操作区、结果显示区、统计信息区。操作区左侧放“选择图片”、“摄像头检测”、“保存结果”三个按钮,中间一个大的图像展示区域,右侧放检测结果的类别统计饼图或文本列表,底部是日志输出框,记录模型调用耗时和检测数量。
在读图像和显示图像时有个关键细节:图像处理库读进来的图像默认是BGR通道顺序,而界面组件显示时要求RGB通道顺序,不做转换的话整个图像颜色会偏蓝偏红,棉花本来应该是白色,一偏色就很明显。另外图像缩放显示时要用Qt自带的平滑缩放模式,否则图上画完框再缩放会糊。显示时保持原图宽高比,不要在QLabel里强行拉伸,否则检测框坐标和图像内容对应不上。
“摄像头检测”功能需要单独开一个子线程,主线程只负责刷新界面,否则摄像头数据流会卡死窗口。我用的方案是设置一个定时器,每50毫秒从摄像头读一帧,把帧数据丢给推理线程做检测,推理完成后通过信号把结果传回主线程。界面事件循环不被模型推理阻塞,用户操作始终流畅。
5. 系统联调与部署避坑实录
5.1 模型导出与推理加速选项
训练完成的best.pt权重文件可以直接被Python加载推理,但做桌面应用部署时我倾向于先导出成ONNX格式,用专门的推理引擎加载。ONNX的好处是模型结构和权重打包成一个文件,离开训练环境也能稳定运行,而且推理引擎对ONNX图的执行计划做了很多算子融合优化,实测推理速度比直接跑原始模型快约20%到30%。
导出也很简单,训练框架官方提供了导出脚本:
model.export(format="onnx", imgsz=640, opset=12)导出之后本地会多一个best.onnx文件,加载方式就是创建推理引擎会话,输入和输出名字在推理引擎里可以动态查询。以一张640乘640的输入图为例,模型直接输出的张量形状是(1, 5*num_classes+5, 8400),需要做置信度过滤和NMS后处理才能得到最终框坐标。
如果机器上有支持的加速卡,还能进一步优化,但对棉花分类检测这种单类别数量不多的场景,CPU上跑ONNX已经能到每张图片100毫秒以内,用户体验完全够用。
5.2 打包发布时最容易踩的三个坑
这个项目最后要给别人用,不能要求对方装Python解释器和一堆依赖,所以一定要打包成独立的可执行文件。用打包工具把入口脚本、界面资源、模型权重打成单个文件夹发布,我建议发布时用文件夹形式,不要迷信单文件模式。单文件模式启动时会先解压到临时目录,路径处理不好模型和界面资源经常找不到,而且杀毒软件误报概率也高很多。
打包时通常踩这三个坑。第一是隐藏导入问题,界面组件和推理引擎的很多底层模块不会被打包工具自动识别,需要在spec文件里明确加上隐藏导入列表,否则运行时会报“模块不存在”。第二是路径问题,代码里任何文件路径都不要写绝对路径,全部改成相对路径,并且从入口文件所在目录去计算当前工作目录。我习惯在程序启动时先把工作目录切换到可执行文件所在目录:
import os, sys if getattr(sys, 'frozen', False): BASE_DIR = os.path.dirname(sys.executable) else: BASE_DIR = os.path.dirname(os.path.abspath(__file__)) os.chdir(BASE_DIR)第三是模型文件过大导致启动慢,把ONNX模型放到单独的子目录里,启动时先显示加载提示,不要做无输出的长时间等待。
5.3 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动后无法加载模型 | 模型路径写错或权重文件被移走 | 检查BASE_DIR路径拼接,确认best.onnx存在 |
| 图像显示颜色偏蓝 | 没有把BGR转RGB | 显示前调用颜色通道转换 |
| 检测窗口卡死无响应 | 把推理放到了主线程 | 将推理迁移到子线程并配合信号通知 |
| 登录数据库报“表不存在” | 数据库初始化逻辑放在登录窗口之后才执行 | 程序启动入口先初始化数据库表再创建窗口 |
| 摄像头画面黑屏 | 没有释放被占用的摄像头资源 | 关闭窗口时停止定时器并释放摄像头对象 |
| 检测精度远低于训练指标 | 部署时缩放尺寸与训练不一致 | 推理时imgsz参数保持和训练时一致 |
| 双击可执行文件闪退 | 缺少动态链接库或隐藏导入缺失 | 查看系统事件日志或运行控制台捕获Traceback |
我把这个排查表放在项目文档里,其他人接手时遇到问题基本能自己解决。
6. 写在最后:几点经验与可扩展方向
这个项目从数据准备到最终打包发布,完整做下来我最大的体会是“模型选型重要,但数据集质量更重要”。一开始我用YOLOv5快速跑了一个版本,验证集mAP50也就是0.88左右,后来换了YOLOv12并按本文的方法重新整理数据、调整标注边界、加入HSV增强后,同一个测试集上mAP50提升到0.93。如果只换模型不整理数据,这个提升幅度是绝对出不来的。另一个印象深刻的小技巧是训练时先用预训练权重冻结主干跑大约15轮,让新加的分类头和检测头先稳定下来,然后再解冻整个网络微调,能明显减少前期训练震荡,对棉花这种背景纹理复杂的数据集很有效。
这套架构的扩展性很强。当前系统做的是棉花分类检测,但采集其他农产品数据集再训练后,界面和登录模块完全不用改,换掉模型文件就能变成另一个质检工具。后续想要提高小目标检测能力,可以把输入尺寸提升到960并开启多尺度训练;想要服务很多用户同时用,可能要考虑把模型部署成后端服务,把当前保存检测结果和统计数据的功能进一步强化,对接产线报表。再往后,分类维度还可以扩展到棉包回潮率等级预测、异性纤维识别这些更细的方向。思路一旦跑通,即使后续换检测目标,也能很快落地。