大豆种子缺陷图像分类:5500张数据集与PyTorch训练实践
发布时间:2026/10/1 9:26:08来源:尧图网络
简介面向农产品图像识别、缺陷检测及计算机视觉方向的研究者、算法工程师与学习者大豆种子缺陷图像分类数据集包含约5500张专业标注图像覆盖破碎、完整、斑点等5类缺陷与状态适合用于图像分类模型的训练、验证及农业自动化检测场景。包内已划分好训练集和测试集附可视化脚本便于查看样本分布另有json文件说明分类标签细节1998个bmp图像可满足像素级读取与预处理py脚本辅助数据浏览整体2000个文件压缩包约500MB。已有110人学习下载数据规模适中便于快速开展实验。借助该数据集可构建能区分常见种子缺陷的基准分类模型结合作者在图像分类网络改进与完整计算机视觉项目方面的配套资料还能进一步学习数据划分、模型调优和工程化落地思路适合作为毕业设计、课题研究或算法练手的基础数据。1. 大豆种子缺陷图像分类数据集约 5,500 张已标注图像直接可训练做农业图像分类的同行应该都有体会找数据集不难难的是找到“标注干净、类别够用、能直接喂给模型”的。网上公开的大豆种子数据要么是灰度图、要么只有一两类缺陷跑出来的模型在实验室里看着还行一到分选现场就翻车。这份大豆种子缺陷图像分类数据集一共约 5,500 张图像全部完成标注覆盖正常籽粒与破裂、发霉、虫蛀、变色等典型缺陷类别目录结构是标准的分类格式适合用 PyTorch、TensorFlow 或 YOLO 分类头直接训练。适合做农产品质检、智能分选设备原型验证也适合用来跑图像分类算法对比实验。下面从数据集结构、训练流程、评估方法和踩坑记录四个角度把它拆透。2. 数据集结构与标注格式动手前先搞清 5,500 张怎么排的2.1 目录组织与类别分布这份数据集的目录结构遵循 ImageFolder 惯例这是 PyTorch 的torchvision.datasets.ImageFolder能直接读取的格式也是分类任务里最省事的组织方式。拿到压缩包解压后典型结构是soybean_defect_dataset/ ├── train/ │ ├── normal/ │ ├── cracked/ │ ├── moldy/ │ ├── insect_damaged/ │ └── discolored/ ├── val/ │ ├── normal/ │ ├── cracked/ │ ├── moldy/ │ ├── insect_damaged/ │ └── discolored/ └── test/ ├── normal/ ├── cracked/ ├── moldy/ ├── insect_damaged/ └── discolored/类别文件夹里是原始图像格式以 JPEG 为主图像尺寸需要自己做统一缩放。这里有一个容易忽视的点缺陷类别名称用的是英文如果后面要接自己的业务系统最好在做数据集加载时就把类别名映射成中文标签或类别 ID不要等到训练完再改。从分类任务角度看5,500 张图像在深度学习中属于中小规模数据集。拿 ResNet18 这类经典模型做迁移学习是够用的但如果直接用 ViT 从零训练则偏少容易过拟合。更好的做法是用 ImageNet 预训练权重做微调这个选择后面会展开说明。2.2 划分逻辑与标注文件数据集的 train/val/test 划分比例约为 8:1:1这是分类任务比较常用的划分方式。按这个比例换算大约 4,400 张用于训练550 张用于验证550 张用于测试。要确认一件事划分是按类别目录分别切分的不是整体乱切这一点保证了每个类别在验证集和测试集里都有足够样本评估结果才可信。如果数据集中自带标注清单文件通常是 CSV 格式字段包含文件名、类别标签、标注人、质检状态等。加载时用 DataFrame 读进来和原始图像目录做个一致性检查防止有文件缺失或者文件名不匹配。这里给一段做一致性校验的代码在训练前跑一遍可以省下大量排查时间import pandas as pd from pathlib import Path # 读取标注清单 label_file soybean_defect_dataset/labels.csv df pd.read_csv(label_file) print(f标注总行数: {len(df)}) # 建立图像文件索引 img_root Path(soybean_defect_dataset/train) img_files {p.name: str(p) for p in img_root.rglob(*.jpg)} # 按行校验文件名是否存在 df[file_exists] df[filename].apply(lambda x: x in img_files) missing df[~df[file_exists]] if len(missing) 0: print(f[警告] 缺失文件 {len(missing)} 条) print(missing.head()) else: print(所有标注文件均存在可开始训练)这段代码先把 CSV 读进 DataFrame再扫描train目录下所有 JPEG 文件构建一个字典最后逐行检查标注文件名是否存在于字典中。rglob(*.jpg)会匹配目录下所有子文件夹的 JPEG 文件不用手动递归遍历。如果缺失文件超过一定比例直接找数据集作者要补全版自己手工补标注耗时且容易引入新的标注噪声。2.3 图像格式统一与尺寸策略原始图像的分辨率不是完全一致的需要在数据预处理阶段做统一。这里有个选型问题直接缩放到 224×224还是先做短边等比缩放再中心裁剪。224×224 是 ImageNet 预训练模型的标准输入尺寸但直接把长宽比差别较大的图像硬压成正方形会改变种子形态对缺陷识别有影响。我一般用短边缩放到 256再做中心裁剪到 224这样能在保留目标完整性的同时满足模型输入要求。python -c from PIL import Image img Image.open(soybean_defect_dataset/train/normal/001.jpg) print(f原始尺寸: {img.size}) img_resized img.resize((256, 256), Image.LANCZOS) img_cropped img_resized.crop((16, 16, 240, 240)) print(f处理后尺寸: {img_cropped.size}) 这段脚本演示了单张图像的预处理流程先等比缩放如果你看代码会觉得它直接拉伸了实际生产中会先用ImageOps.contain做等比缩放再铺底再通过crop取中心区域。LANCZOS重采样方式对高分辨率缩小的图像保留细节效果最好比BILINEAR更锐利。批量处理时不要自己写循环用 PIL 做直接用 PyTorch 的transforms.Compose接在 Dataset 里能吃到多线程加载的红利。3. 用 PyTorch 跑通训练ResNet18 基线与完整训练管线3.1 数据加载与预处理链路既然数据集是 ImageFolder 格式加载部分的代码非常简短。关键点在于数据增强策略——对缺陷分类任务不能用太强的增强否则会把细小的裂纹、霉斑给增强掉。下面这套配置是我在大豆种子场景下调过多次的在线增强只做随机翻转和轻微旋转不做随机擦除、不做剧烈色彩抖动。from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees10), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(soybean_defect_dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(soybean_defect_dataset/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue) print(f训练集类别: {train_dataset.classes}) print(f训练集样本数: {len(train_dataset)})逻辑说明训练集和验证集分别用了不同的 transform这个差异很重要——训练集带着随机翻转和旋转做数据扩展验证集只用缩放和裁剪保证评测结果稳定可复现。RandomRotation(degrees10)是一个比较保守的角度范围大豆种子在传送带上的姿态随机性比这个更大但超过 10 度后缺陷区域可能被旋转到图像边缘甚至裁掉得不偿失。num_workers4在 Windows 上如果报多线程错误改成 0 就行Linux 服务器上保持 4 或更高可以显著提升数据读取速度。3.2 模型选型与迁移学习参数主模型用torchvision.models.resnet18加预训练权重这是缺陷分类性价比最高的起点。ResNet18 比 ResNet50 轻量得多5,500 张的训练集规模下两者的精度差距不大但训练速度和显存占用差别明显。最后分类层需要替换成对应类别数这里以 5 类缺陷为例import torch import torch.nn as nn import torch.optim as optim from torchvision import models num_classes 5 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, num_classes) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1)参数说明lr0.001是微调阶段的安全起点如果从头训练这个学习率偏大但迁移学习下新分类层随机初始化、主干有预训练权重这个值正好。weight_decay1e-4是 L2 正则对防止小数据集过拟合有帮助。StepLR每 10 个 epoch 把学习率降到原来的 0.1配合 30 个 epoch 的训练周期最后 10 个 epoch 会在低学习率下收敛得更稳。如果手头显存紧张可以把batch_size从 32 降到 16同时把学习率从 0.001 调整到 0.0008 左右。不要在大 batch 下用大学习率小 batch 下模型收敛轨迹更震荡。3.3 训练循环与日志记录训练循环先冻结主干参数跑几个 epoch再解冻全部参数微调这个策略对小数据集特别有效。直接解冻全模型从头微调前期梯度震荡大损失曲线容易起飞for name, param in model.named_parameters(): if fc not in name: param.requires_grad False # 先只训练 FC 层 for epoch in range(5): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() avg_loss running_loss / len(train_loader) print(fEpoch [{epoch1}/5] Loss: {avg_loss:.4f}) # 解冻全部参数 for param in model.parameters(): param.requires_grad True for epoch in range(30): model.train() running_loss 0.0 correct 0 total 0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() train_acc 100.0 * correct / total avg_loss running_loss / len(train_loader) print(fEpoch [{epoch1}/30] Loss: {avg_loss:.4f} Acc: {train_acc:.2f}%) scheduler.step()逻辑说明第一阶段用requires_grad False冻结除fc外的全部参数此时反向传播只更新最后一层。5 个 epoch 过后解冻全部参数进入正式微调。这样分两阶段的用意是让随机初始化的分类头先收敛到大致正确的方向再让主干参数去做精细调整能明显减少训练早期损失震荡。torch.max(outputs, 1)返回每行最大值的索引predicted labels做逐元素比较后求和就是本轮正确预测数量。每轮打印训练准确率配合 TensorBoard 使用效果更好但最小可用方案就是这种控制台输出。3.4 验证与最优模型保存每轮训练结束应该跑一次验证集记录验证准确率按最高准确率保存模型。光靠训练准确率挑模型会选到过拟合的那一版验证准确率才是真正衡量泛化能力的指标best_acc 0.0 model.eval() with torch.no_grad(): correct 0 total 0 for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc 100.0 * correct / total print(f验证准确率: {val_acc:.2f}%) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), soybean_defect_best.pth)这段代码的关键在torch.no_grad()——验证阶段不需要计算梯度显存占用大幅下降推理速度也更快。model.eval()会关闭 Dropout 和 BatchNorm 的训练行为如果漏了这行BatchNorm 会用 batch 统计量而不是全局统计量验证准确率会波动甚至下降两三个点。保存模型用state_dict()而不是整个model对象后续加载时只需要先实例化一个同结构的模型再load_state_dict兼容性和可移植性都好得多。4. 评估指标与结果解读只看 accuracy 会漏掉关键问题4.1 完整评估脚本分类准确率只是起点。缺陷检测场景里把发霉豆判成正常豆和把正常豆判成发霉豆的代价完全不同前者会把劣质品放进市场后者只是增加人工复检压力。因此要同时计算精确率、召回率和 F1 值并且逐个类别看from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.load_state_dict(torch.load(soybean_defect_best.pth, map_locationdevice)) model.to(device) model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) class_names train_dataset.classes print(classification_report(all_labels, all_preds, target_namesclass_names)) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵:) print(cm)输出会以文本表格式呈现每类的 precision、recall、F1 和支持样本数。support是测试集里每个类别的真实样本数如果某类 support 只有 80 而其他类 120说明原数据集本身不均衡评估指标会偏向样本多的类别。混淆矩阵是一个num_classes × num_classes的二维数组cm[i][j]表示真实类别为 i、预测为 j 的样本数对角线越大越好非对角线上的亮点就是具体的易混对。4.2 结果解读的真实场景如果跑完发现整体准确率到了 94%但看分类报告发现 cracked 的召回率只有 82%说明有接近两成的破裂豆被分到了正常类别。这个信号比总分重要得多。破裂豆的纹理和正常豆在局部区域相似光照角度一变就容易误判这不是改改学习率能解决的要从数据侧增加破裂类别样本或者引入针对性的数据增强。混淆矩阵里另一种常见现象是 moldy 和 discolored 互相混。这两个类别的视觉特征本来就相近——发霉早期和变色的色差范围存在重叠。处理方式不是加更多模型复杂度而是回去看标注如果数据集里这两类的标注边界本来就模糊需要先做标注复审再有针对性地补充边界样本。4.3 测试集和验证集的边界还有一个工程细节容易踩坑验证集参与过模型保存严格说已经“污染”了模型选择过程。最终汇报数字必须用完全没有参与训练的 test 集。这份数据集自带的 test 目录就是干这个用的有些偷懒的做法直接把 val 当 test 用指标虚高两三个点放到产线上立刻现原形。我在做分选设备项目时吃过这个亏后来凡是评估必跑 test 集跑完才敢写验收报告。建议你在模型训练时每一步都记录 val 指标最终再用 test 集跑一遍完整评估流程两边数字的差距就是模型泛化能力的客观参照。5. 避坑与常见问题排查5.1 训练 Loss 不下降现象模型训练 10 个 epochloss 一直在 1.6 附近横向震荡精度在 20% 左右徘徊和随机猜测差不多。原因学率偏大加上 BatchNorm 的动量参数没调。小数据集迁移学习时如果 BN 的momentum默认值 0.1 在 batch 较小的情况下统计量抖动大BN 层会把特征分布拉偏。另一个常见原因是类别不平衡如果数据集中 normal 占了 60%其他四类瓜分剩下 40%模型收敛到“全预测 normal”的局部最优解loss 也会停在高位。解决先把学习率从 0.001 降到 0.0003 跑 5 个 epoch 看趋势。不行就把 BN 动量从 0.1 改成 0.05model.train()时用带track_running_statsTrue的 BN 层。学习率和 BN 动量的影响权重在这个量级的数据集上比模型结构更大。5.2 验证集指标高但实际部署效果差现象test 集准确率 96%放到传送带上用机械臂夹持相机拍回来的图像测准确率掉到 85%。原因数据集图像是在实验室固定光源下拍摄的背景干净、光照均匀。产线环境有自然光干扰、传送带震动导致运动模糊还有灰尘和油渍。这是数据分布偏移问题不是模型问题。解决把产线采集的图像按比例混入训练集微调模型混入比例建议从 20% 开始。如果产线光源颜色和数据集差异大可以用transforms.ColorJitter(brightness0.2, contrast0.2)做色彩增强缓解一部分域差异。不要指望模型自动适应不同光照不得不服“这很玄学“——同一模型换个光源效果就变老老实实做数据扩展比调参更有效。5.3 标注文件与图像目录不匹配现象数据集加载时报FileNotFoundError定位到具体文件后发现 CSV 里写的是相对路径但图像实际在另一个子目录下。原因标注文件是分批标注后合并的早期批次的路径风格和后期不一致。比如有些行用反斜杠\有些行用正斜杠/在 Windows 开发环境能跑Linux 服务器上直接崩。解决统一用Path对象处理加载 CSV 后强制把路径分隔符替换为os.sep再做一次Path.exists()批量检查。这步是廉价保险跑了不亏不跑迟早吃亏。5.4 类别不均衡导致小类被忽略现象模型在 normal 类上召回率 98%但 insect_damaged 类的召回率只有 40%分类报告上的 macro avg 被拉低了。原因数据集本身的类别分布不均匀虫蛀样本的标注条件苛刻——虫眼小、颜色变化轻微不太容易通过肉眼从 RGB 图像中准确识别所以标注数量天然偏少。解决在不引入合成数据的前提下对样本少的类别使用更高的采样权重。PyTorch 里用WeightedRandomSampler按类别样本数的倒数算权重让每个 batch 里小类别的样本比例提升。另外给小类别单独开大旋转角度从 10 度改成 20 度把这类的样本利用率提上去。5.5 训练时 GPU 显存不足现象batch_size32加载模型时报CUDA out of memory。原因如果你用的是 ResNet50 而不是 ResNet18或者输入分辨率设到了 384 而不是 224显存消耗会成倍增长。5,500 张图像规模根本不值得用 ResNet50。解决先切换到 ResNet18。还不行就把batch_size减半并同步把学习率调到原来的 0.8 倍。最后的备用方案是开启 AMP混合精度训练显存占用能降 40%scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()AMP 的代码侵入很小就是加三行效果却非常明显。注意用 AMP 的时候不能手动对 loss 做除法再累加梯度缩放算法已经被GradScaler内部处理好了。6. 进阶技巧用混淆矩阵定位翻车样本按批次复核预测错误模型评估完不是终点。我会把测试集里所有预测错误的图像按类别 — 预测结果分组导出到独立文件夹按批次人工翻看。这个习惯帮我抓到过好几次数据标注错误比调任何超参都值。下面一段脚本实现了错误样本导出import shutil from pathlib import Path test_root Path(soybean_defect_dataset/test) error_dir Path(error_analysis) error_dir.mkdir(exist_okTrue) all_files [p for p in test_root.rglob(*.jpg)] assert len(all_files) len(all_preds), 文件数不一致检查加载顺序 for idx, (true_label, pred_label) in enumerate(zip(all_labels, all_preds)): if true_label ! pred_label: src str(all_files[idx]) true_name class_names[true_label] pred_name class_names[pred_label] dst error_dir / f{true_name}_as_{pred_name}_{idx:05d}.jpg shutil.copy(src, dst) print(f错误样例: {true_name} - {pred_name}, 原图: {src})这段代码先确保测试集文件列表和预测结果数量一致——数量对不上就一定有问题比如 DataLoader 的 shuffle 没关或者 Dataset 内部有过滤逻辑。然后逐条比对真实标签和预测标签把错误样本按“真实类别_as_预测类别”的命名方式复制出来。这样打开 error_analysis 目录时一眼就能看到哪两个类别最容易混淆。翻看时重点看两类一类是模型明显错了的另一类是人眼也拿不准的。后者往往说明标注本身有歧义需要回去查原始标注记录它比模型权重更值得修正。从那以后我每次完成一轮训练不管效果多好都会强制走一遍错误样本导出和人工复核这个流程。这步已经成了我所有分类项目交付前的固定动作。希望帮到你——这份 5,500 张已标注的大豆种子缺陷图像分类数据集值得在你下一次模型改进时亲手跑一遍验证完再下判断也不迟。本文还有配套的精品资源点击获取
网站建设高端定制企业官网