ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

苹果叶片病害图像识别:1700张标注数据实战与调优

苹果叶片病害图像识别:1700张标注数据实战与调优 简介苹果叶片病害图像识别数据集面向计算机视觉与农业智能识别方向的学习者和研究者用于训练和评估叶片病害分类模型。数据已由专业人员标注共约1700张图片划分为健康、生锈、痂三个类别并预先切分为训练集与验证集便于直接开展模型训练与泛化性能评估。压缩包共1733个文件以1730张jpg图像为主体另含1个py可视化脚本、1个png与1个json标注说明文件整体约354.45MB目录按训练集与验证集分门别类存放结构清晰。运行包内show脚本可快速查看样本分布与图像质量帮助排查类别不均衡或标注异常。该资源适合作为CNN分类任务的练手数据也可结合YOLOv5分类方案进行迁移实验已有340人学习下载对入门图像分类与农业病害识别具有较高参考价值。1. 苹果叶片病害图像识别数据集1700 张已标注数据能跑出什么结果拿到一个约 1700 张、已标注的苹果叶片病害图像识别数据集第一反应不该是“数据量够不够”而是“这批标注到底能不能直接喂给检测或分类网络”。苹果叶片病害识别在农业视觉里属于典型的小样本、细粒度、强背景干扰任务黑星病、锈病、褐斑病、健康叶片这四类在早期症状上差异极小田间拍摄又常带逆光、遮挡、土壤背景。1700 张听起来不多但如果类别均衡、标注框贴合、划分合理跑一个轻量分类网络做到 90% 以上验证准确率是完全可行的。这个方向适合两类人一是想入门农业 AI 的算法工程师二是需要快速验证植保识别方案的落地团队。下面按“先看清数据、再跑通基线、最后调优避坑”的顺序拆开讲。2. 先看清数据1700 张苹果叶片病害标注的类别分布与质量核查2.1 类别体系与标注格式的对应关系苹果叶片病害数据集常见的标注体系分两种分类标注每张图一个类别标签和检测标注每张图带边界框和类别。约 1700 张的规模如果做分类任务单类平均 400 张左右如果做检测任务单类目标数可能只有几百个属于典型小样本检测。拿到数据后第一件事是确认标注格式是 VOC 的 XML、YOLO 的 txt还是 COCO 的 json。不同格式决定了后续训练脚本的写法也决定了你能不能直接用现成框架。常见做法是先统计每个类别的样本数再看标注框的宽高分布。如果某类样本数低于总样本的 15%训练时就要考虑重采样或类别权重。苹果叶片病害里健康叶片往往占比偏高黑星病和锈病偏少这个不均衡必须在划分数据集之前就处理否则验证集准确率会虚高。import os import xml.etree.ElementTree as ET from collections import Counter # 统计 VOC 格式标注的类别分布 def count_voc_classes(annotation_dir): class_counter Counter() for xml_file in os.listdir(annotation_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(annotation_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] 1 return class_counter # 替换为你的标注目录 counter count_voc_classes(./annotations) for cls, num in counter.most_common(): print(f{cls}: {num})这段代码遍历 VOC 格式的 XML 文件统计每个类别出现的次数。annotation_dir指向存放 XML 的目录class_counter用 Counter 累加。输出结果直接告诉你类别是否均衡。如果发现某类只有几十个实例后续训练必须加类别权重或做数据增强。注意统计的是实例数不是图片数一张图可能包含多个病害目标。2.2 图像质量筛查模糊、重复与标注框越界1700 张数据里通常混着三类脏数据运动模糊、近重复帧、标注框超出图像边界。模糊图像会拉低模型对早期病斑的敏感度近重复帧会导致训练集和验证集泄漏越界框会让检测损失计算异常。筛查顺序建议先查重复再查模糊因为重复帧往往成对出现先去掉能减少后续工作量。查重可以用感知哈希查模糊用拉普拉斯方差。拉普拉斯方差低于 100 通常认为模糊但这个阈值和图像分辨率有关1700 张的田间数据建议阈值设在 80 到 120 之间先看分布再定。标注框越界检查直接读 XML 的 bndbox和图像宽高比对即可。import cv2 import numpy as np from imutils import paths def check_blur_and_dup(image_dir, blur_threshold100): seen_hashes {} for img_path in paths.list_images(image_dir): img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 拉普拉斯方差判断模糊 blur_score cv2.Laplacian(gray, cv2.CV_64F).var() if blur_score blur_threshold: print(f模糊: {img_path}, score{blur_score:.1f}) # 感知哈希查重 img_small cv2.resize(gray, (32, 32)) avg img_small.mean() hash_str .join(1 if p avg else 0 for p in img_small.flatten()) if hash_str in seen_hashes: print(f重复: {img_path} 与 {seen_hashes[hash_str]}) else: seen_hashes[hash_str] img_path check_blur_and_dup(./images)blur_threshold控制模糊判定灵敏度值越高越严格。感知哈希把图像缩到 32x32 再二值化适合快速查近重复。实际跑的时候先把模糊和重复的图单独移到一个待定目录人工过一遍再决定删不删别直接批量删除血泪经验是有些“模糊”图其实是早期病斑的特写删了就丢样本。2.3 训练集验证集划分别让同一片叶子跨集划分数据集最容易被忽略的坑是按图片随机划分导致同一片叶子的不同角度照片同时出现在训练集和验证集。苹果叶片数据采集时往往对同一片叶子拍多张随机划分会让验证准确率虚高十几个百分点。正确做法是按“叶片编号”或“拍摄批次”分组划分保证同一片叶子的所有图像只出现在一个集合里。如果数据里没有叶片编号可以用图像相似度聚类把相似度高的图分到同一组再划分。常见比例是训练集 70%、验证集 15%、测试集 15%。1700 张的话测试集留 250 张左右就够评估用。划分完一定要再跑一次查重确认训练集和验证集之间没有近重复。3. 跑通基线用迁移学习在 1700 张苹果叶片数据上做分类3.1 为什么选 EfficientNet-B0 而不是 ResNet501700 张属于小样本直接训练大模型必然过拟合。ResNet50 参数量 2500 万在这种规模上即使冻结主干也容易在验证集上震荡。EfficientNet-B0 参数量约 530 万在 ImageNet 上的特征迁移能力足够输入 224x224 时单卡显存占用低训练速度快。苹果叶片病害的判别特征主要是病斑颜色、形状和纹理EfficientNet 的复合缩放策略对这种细粒度纹理比较友好。如果要做检测而不是分类YOLOv8n 或 YOLOv8s 是更实际的选择参数量小对 1700 张的检测任务来说比 Faster R-CNN 更容易收敛。选型原则就一条数据量在 2000 张以下优先选参数量低于 1000 万的模型别跟自己的显卡和耐心过不去。3.2 训练脚本冻结主干加分类头微调import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder # 数据增强田间图像需要颜色抖动和随机裁剪 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_dataset ImageFolder(./train, transformtrain_transform) val_dataset ImageFolder(./val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4) # 加载预训练 EfficientNet-B0 model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1) # 冻结主干参数 for param in model.parameters(): param.requires_grad False # 替换分类头类别数按你的数据集改 num_classes len(train_dataset.classes) model.classifier[1] nn.Linear(model.classifier[1].in_features, num_classes) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() # 只优化分类头参数 optimizer torch.optim.Adam(model.classifier.parameters(), lr1e-3) for epoch in range(15): 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() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f})RandomResizedCrop的scale(0.7, 1.0)控制裁剪范围田间图像背景杂乱裁剪太狠会丢病斑0.7 是保守值。ColorJitter的亮度、对比度、饱和度都设 0.3模拟不同光照条件。冻结主干后只训练分类头学习率可以设 1e-3如果解冻部分主干层学习率要降到 1e-4 以下。batch_size32在 8GB 显存上跑 224x224 没问题显存小就降到 16。3.3 训练过程看什么损失曲线和混淆矩阵训练时别只盯准确率。1700 张数据在冻结主干的情况下通常 5 个 epoch 内验证准确率就能到 85% 以上如果 10 个 epoch 还在 70% 徘徊大概率是数据划分有问题或者类别标签错了。损失曲线如果训练损失下降但验证损失上升说明过拟合需要加 dropout 或减小分类头学习率。混淆矩阵比准确率更能暴露问题。苹果叶片病害里黑星病和褐斑病容易混如果这两个类互相误判率高说明模型没学到区分性特征需要针对性补充这两类的训练样本或加注意力模块。验证集准确率高于训练集准确率也是常见现象因为验证集用了 CenterCrop图像更“标准”不代表模型泛化好。4. 避坑与排查苹果叶片病害数据集训练中最容易翻车的 5 个点4.1 验证准确率 99% 但实际预测一塌糊涂现象训练完验证集准确率接近满分拿田间新图测试却大量误判。原因训练集和验证集存在近重复图像模型记住了样本而不是学到特征。解决按叶片编号分组划分数据集划分后跑感知哈希查重确认训练集和验证集之间没有相似度超过阈值的图像对。如果已经训练完才发现重新划分后重训别在旧模型上调参。4.2 损失变成 NaN现象训练几个 batch 后损失突然变成 NaN。原因学习率过大或者数据里有损坏图像。EfficientNet 的分类头用 1e-3 学习率一般安全但如果解冻了主干层还用这个学习率梯度爆炸概率很高。解决先把学习率降到 1e-4加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。同时检查图像文件是否能正常读取损坏图像会产生异常值。4.3 某类病害召回率极低现象健康叶片识别很准但锈病召回率不到 50%。原因类别不均衡锈病样本太少模型偏向多数类。解决在损失函数里加类别权重nn.CrossEntropyLoss(weighttorch.tensor([1.0, 3.0, 2.5, 1.5]))权重按类别样本数的倒数设置。同时对该类做额外增强比如随机旋转、裁剪、颜色扰动。如果样本数低于 100考虑用 SMOTE 做图像层面的过采样但要注意别生成不真实的病斑纹理。4.4 推理时输入尺寸和训练不一致现象训练时验证准确率正常部署推理时结果全乱。原因推理预处理用了和训练不同的 resize 或 normalize 参数。解决把训练时的 val_transform 完整保存下来推理时严格复用。常见错误是推理时用了Resize(224)而训练用的是Resize(256) CenterCrop(224)两者对病斑的缩放比例不同模型看到的特征分布就变了。4.5 标注框越界导致检测训练崩溃现象做检测任务时训练初期 loss 就异常大或者直接报错。原因XML 里的 bndbox 坐标超出图像宽高YOLO 格式转换后产生负宽度或负高度。解决转换前先遍历所有标注检查xmin xmax、ymin ymax、xmax width、ymax height。越界框要么裁剪到边界内要么直接丢弃。丢弃比例超过 5% 就要回头检查标注流程是不是有问题。5. 把 1700 张用到极致半监督伪标签与测试时增强的实操技巧1700 张数据想再往上提点靠调参空间有限更实际的路子是半监督伪标签。具体做法先用训练集训一个基线模型对测试集或未标注的田间图像做预测把置信度高于 0.9 的样本连同预测标签加入训练集重新训练。置信度阈值别设太低0.9 是保守值低于这个值的伪标签噪声太大反而拉低模型。伪标签迭代一到两轮就够了再多容易过拟合到模型自己的错误上。测试时增强是另一个几乎零成本的技巧。推理时对同一张图做多次变换水平翻转、不同裁剪把多次预测概率平均后取最大值。苹果叶片病害识别里水平翻转和轻微旋转对结果影响不大但多裁剪平均能降低单次裁剪位置带来的随机性。实测在 1700 张训练的 EfficientNet-B0 上测试时增强通常能带来 1 到 2 个百分点的准确率提升代价只是推理时间翻几倍。def tta_predict(model, image, n_crops5): model.eval() probs [] # 原图 with torch.no_grad(): probs.append(torch.softmax(model(image.unsqueeze(0)), dim1)) # 水平翻转 with torch.no_grad(): probs.append(torch.softmax(model(torch.flip(image, [2]).unsqueeze(0)), dim1)) # 多位置裁剪 _, h, w image.shape for i in range(n_crops - 2): top int((h - 224) * i / max(n_crops - 3, 1)) left int((w - 224) * i / max(n_crops - 3, 1)) crop image[:, top:top224, left:left224] with torch.no_grad(): probs.append(torch.softmax(model(crop.unsqueeze(0)), dim1)) # 平均概率 avg_prob torch.mean(torch.stack(probs), dim0) return torch.argmax(avg_prob, dim1)n_crops控制裁剪次数5 次是精度和速度的折中。torch.flip(image, [2])对宽度维度翻转对应水平镜像。多位置裁剪从左上到右下均匀取点避免只裁到背景。这个函数返回预测类别索引实际部署时把avg_prob也返回方便设置置信度阈值做拒识。最后说个习惯每次拿到新数据集先花半天做数据核查和可视化别急着写训练脚本。我见过太多人直接拿 1700 张跑 YOLO训了两天发现标注格式不对或者类别标签错位返工成本远大于前期核查。苹果叶片病害识别这个方向数据质量比模型结构重要得多1700 张干净标注的数据比 5000 张脏数据能跑出更好的结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表