
简介本资源为基于Python与PyTorch的CNN深度学习玉米粒品质检测完整代码包面向具备一定深度学习基础、希望实践图像分类项目的学生与开发者可帮助快速搭建从数据预处理到模型训练再到可视化界面的全流程方案。压缩包共256个文件以250张jpg图像样本为主另含3个txt说明与3个py脚本整体约5.58MB涵盖数据集、环境依赖说明及核心代码。资源对图片进行了短边补灰边成正方形与旋转等增强处理并依次提供数据集文本生成、模型训练与PyQt界面三个脚本训练过程会保存模型与逐epoch日志便于观察验证集损失与准确率变化。目前已有127人学习下载适合作为课程设计、毕业设计或入门图像分类的参考案例。1. 玉米粒品质检测为什么值得用 CNN 重做一遍收粮季的质检窗口前一簸箕玉米粒倒进料斗老师傅抓一把、看色泽、掐胚芽十秒内给出等级。这套流程在小型收粮点够用但一旦日处理量上到几十吨人工分选的漏检率和疲劳误差就压不住了。基于python-CNN深度学习的玉米粒品质检测要解决的正是这件事把「好粒 / 霉变 / 破损 / 虫蛀」这类肉眼判断交给一个能在普通笔记本上跑起来的卷积神经网络。它适合三类人——做农业智能装备的嵌入式工程师、想拿一个完整数据集练手的深度学习入门者、以及需要给现有色选机加一层软件复检的产线技术员。标题里的 python 是工程胶水CNN 是判断核心玉米粒品质检测是落地场景三者缺一不可。这一篇不讲空泛概念只讲怎么从零把这条链路跑通以及我在复现同类方案时踩过的那些坑。2. 玉米粒品质检测的数据集怎么攒、怎么标、怎么切2.1 先想清楚分类粒度再决定拍多少张很多人一上来就问「需要多少张图」这个问题问反了。应该先定分类粒度是只分「合格 / 不合格」二分类还是分「完好 / 霉变 / 破损 / 虫蛀」四分类甚至要定位霉变区域做分割。粒度直接决定标注成本和模型头结构。我的经验是产线复检场景优先做四分类因为霉变和破损的处置动作不同——霉变要整批隔离破损可以降级。四分类每类至少准备 800 到 1200 张有效样本二分类可以压到每类 400 张。样本来源要覆盖不同品种郑单958、先玉335 这类常见品种的粒色差异明显、不同光照自然光、环形灯、背光、不同含水率状态。别只用一批烘干过的干净样本那样训出来的模型一到现场就翻车。拍摄环节有个容易被忽略的点背景。用纯色亚光背景板深灰或墨绿避免反光托盘。每颗玉米粒单独摆放粒间距至少半个粒宽防止粘连导致标注框重叠。相机用手机主摄就够但必须固定高度和角度建议做一个简易支架镜头距料面 15 到 20 厘米。光源用两侧 45 度柔光灯避免单侧硬光造成胚芽阴影被误判为霉斑。2.2 标注规范与数据增强的边界标注用 LabelImg 或 CVAT 都行分类任务其实只需要按文件夹归类但如果你后续想升级到检测建议一开始就存 YOLO 格式的框。分类目录结构按数据集根目录/类别名/图片.jpg组织这是torchvision.datasets.ImageFolder能直接吃的格式。标注时定一条硬规则霉变面积超过粒面 5% 才归为霉变类边缘轻微变色归为完好这条规则要写进标注手册否则不同人标出来的标准不一致模型学到的就是噪声。数据增强不能乱用。玉米粒品质检测里水平翻转、±15 度旋转、亮度 ±20% 是安全的垂直翻转要慎用因为胚芽位置有方向性色彩抖动幅度过大会把浅色霉斑洗掉。我一般用 torchvision 的 transforms 组合训练集做增强验证集和测试集只做 resize 和归一化。切分比例按 7:1.5:1.5且必须按「拍摄批次」切分不能随机打散——同一批次里相似度极高随机切分会让验证集精度虚高这是血泪经验。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader, random_split # 训练集增强翻转、小角度旋转、亮度微调 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证/测试集只做确定性变换 val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) full_set datasets.ImageFolder(corn_dataset/train, transformtrain_tf) n len(full_set) n_train, n_val int(n * 0.85), n - int(n * 0.85) train_set, val_set random_split(full_set, [n_train, n_val], generatortorch.Generator().manual_seed(42)) # 注意验证集应换成 val_tf这里用 Subset 包一层更严谨 train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers4)这段代码的关键参数有三个Resize((224,224))对齐主流骨干网的输入Normalize用的 ImageNet 均值方差因为后面要加载预训练权重manual_seed(42)保证切分可复现。num_workers在 Windows 上建议设 0 或 2设 4 以上容易在 DataLoader 初始化时报管道错误这是 python 在 Windows 下的老问题。验证集务必单独用val_tf重新构建一个 Dataset不要图省事共用train_tf否则验证指标会因增强随机性而抖动。3. 用 python 搭一个能落地的 CNN 分类网络3.1 骨干网选型从 ResNet18 起步别一上来就上大模型玉米粒品质检测的判别特征主要是颜色纹理和局部霉斑不是 ImageNet 那种复杂语义。ResNet18 在 224×224 输入下参数量约 1100 万单张推理在 CPU 上约 30 到 50 毫秒完全够用。我对比过 ResNet18、MobileNetV3-Small 和自建四层卷积自建网络在样本少于 3000 张时容易过拟合MobileNetV3 精度略低但速度快一倍ResNet18 是精度和速度的平衡点。如果你要部署到树莓派这类边缘设备再考虑 MobileNetV3 或量化后的 ResNet18。迁移学习是必须的。加载 torchvision 的预训练权重把最后的全连接层换成你的类别数。冻结前两个 stage 还是全部微调样本少于 2000 张时先冻结 backbone 只训分类头 10 个 epoch再解冻全部微调 20 个 epoch这个两段式策略比直接全量微调稳定得多。学习率用 1e-3 起步解冻后降到 1e-4。import torch.nn as nn from torchvision import models def build_model(num_classes4, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) if freeze_backbone: for name, param in model.named_parameters(): if layer3 not in name and layer4 not in name and fc not in name: param.requires_grad False in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model model build_model(num_classes4, freeze_backboneTrue) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4 )freeze_backboneTrue时只训练 layer3、layer4 和分类头这是两段式训练的第一阶段。Dropout(0.3)放在全连接前对小数据集防过拟合很有效。优化器选 AdamW 而不是 SGD是因为小数据集上 AdamW 收敛更快weight_decay1e-4抑制权重膨胀。注意filter(lambda p: p.requires_grad, ...)这行它保证优化器只更新解冻的参数漏掉这行会把冻结层也更新等于白冻。3.2 训练循环里必须盯住的三个量训练循环本身不复杂但有三处必须记录训练损失、验证损失、验证准确率。训练损失降但验证损失升是过拟合的典型信号此时要么加数据增强要么提前停止。验证准确率连续 5 个 epoch 不提升就停这是最省时间的策略。另外玉米粒四分类里如果某一类样本偏少要算每类的召回率别只看总体准确率——霉变类召回率低于 0.9 的模型不能上产线漏检一颗霉变粒的代价远大于误判一颗好粒。def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total # 验证阶段务必 model.eval() 并关闭梯度 torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) loss criterion(outputs, labels) total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / totalmodel.eval()和torch.no_grad()是验证阶段的两道保险前者关掉 Dropout 和 BatchNorm 的训练行为后者省显存。loss.item() * imgs.size(0)这个写法是为了按样本数加权平均直接累加loss.item()会在最后一个不满 batch 上产生偏差。设备选择上有 GPU 用cuda没有就用cpuResNet18 在 CPU 上训 3000 张图约 20 分钟一个 epoch能接受。4. 玉米粒品质检测的避坑与排查清单4.1 验证集精度 99%上线就废现象本地验证准确率冲到 0.99拿到新一批玉米粒上测只有 0.6。原因验证集和训练集来自同一拍摄批次图像高度相似模型记住了背景和光照而非玉米粒本身特征。解决按拍摄批次切分数据集验证集必须包含训练时没见过的光照条件和品种。如果条件允许留一个「跨批次测试集」只在最终评估时用一次。4.2 霉变类召回率始终上不去现象总体准确率 0.92但霉变类召回率只有 0.75。原因霉变样本少且霉斑面积小模型被完好类主导。解决对霉变类做过采样或在损失函数里给霉变类更高权重比如CrossEntropyLoss(weighttorch.tensor([1.0, 3.0, 1.5, 2.0]))权重按类别频率的倒数设置。同时检查标注把边缘轻微变色的样本重新归入霉变类。4.3 训练损失震荡不收敛现象loss 在 0.5 到 2.0 之间来回跳。原因学习率过大或 batch size 太小导致梯度噪声大。解决先把学习率降到 1e-4 试如果还震荡就把 batch size 从 16 提到 32 或 64。另外检查数据归一化是否和预训练权重匹配用错均值方差会让输入分布偏移收敛极慢。4.4 DataLoader 在 Windows 上报管道错误现象RuntimeError: DataLoader worker (pid xxx) is killed by signal。原因Windows 下多进程 DataLoader 与某些库不兼容。解决把num_workers设为 0或者把训练代码包进if __name__ __main__:保护块。这是 python 在 Windows 上的经典坑跟模型本身无关。4.5 推理时单张图预测结果和批量预测不一致现象单张推理输出某一类凑成 batch 后结果变了。原因忘了model.eval()BatchNorm 在训练模式下用当前 batch 统计量。解决推理前无条件调用model.eval()并用torch.no_grad()包住。如果还不对检查输入是否做了和训练一致的归一化。5. 把模型压到能上产线的三个进阶技巧5.1 用 TorchScript 导出并做 CPU 推理加速训练完的模型要脱离 python 训练环境部署TorchScript 是最省事的路径。用torch.jit.trace把模型和示例输入一起导出得到一个不依赖源码的.pt文件C 也能加载。导出前务必model.eval()否则 trace 会记录训练行为。import torch model.eval() example torch.randn(1, 3, 224, 224) traced torch.jit.trace(model, example) traced.save(corn_quality_resnet18.pt) # 加载验证 loaded torch.jit.load(corn_quality_resnet18.pt) loaded.eval() with torch.no_grad(): out loaded(example) print(out.argmax(1))torch.randn(1,3,224,224)的 shape 必须和实际推理输入一致动态 batch 可以用torch.jit.trace配合strictFalse但产线上固定 batch 更稳。导出后对比一下原模型和 TorchScript 模型的输出误差应在 1e-5 以内超过说明 trace 过程中有控制流没被正确捕获。5.2 量化到 INT8 把模型体积砍掉四分之三ResNet18 的 fp32 权重约 45MB量化到 INT8 后约 11MBCPU 推理速度提升约 2 倍。用 PyTorch 的动态量化最省事不需要校准数据集。import torch.quantization as tq model.eval() quantized tq.quantize_dynamic( model, {torch.nn.Linear, torch.nn.Conv2d}, dtypetorch.qint8 ) torch.jit.save(torch.jit.script(quantized), corn_quality_int8.pt)动态量化只量化权重激活值在推理时动态量化精度损失通常在 1% 以内。如果精度掉得多改用静态量化并准备 200 张校准图。注意量化后的模型在 ARM 设备上收益最大x86 上提升有限。5.3 一个我常用的验证习惯每次训完模型我会固定做三件事第一用混淆矩阵看每一类的误判方向特别关注霉变被误判成完好的数量第二从验证集里挑 20 张预测错误的图逐张看判断是标注错了还是模型真的学不会第三把模型对同一张图做 10 次随机增强推理看预测稳定性如果 10 次里有 3 次结果不同说明模型对这个样本的判别边界太模糊这类样本要补进训练集。这套习惯帮我省掉了至少三次「指标好看但上线翻车」的后悔药。玉米粒品质检测这件事模型结构从来不是瓶颈数据质量和验证方式才是。希望帮到你。本文还有配套的精品资源点击获取