智慧农业农业智能诊断、植物保护 葡萄叶片病害分割数据集 葡萄叶片病害分割数据集概览表项目指标详细数据/描述数据集名称葡萄叶片病害分割数据集 (Grape Leaf Disease Segmentation Dataset)图像总数15,034张 (含数据增强)图像分辨率1200 × 1600 像素标注格式支持YOLO和VOC(XML) 两种格式任务类型实例分割 / 语义分割— 数据集划分统计数据集子集图像数量 (Images)占比 (约)用途训练集 (Train)12,12180.6%模型训练与参数学习验证集 (Val)1,4559.7%超参数调整与模型选择测试集 (Test)1,4589.7%最终模型性能评估总计15,034100%- 病害类别与标注详情类别 ID英文名称中文名称图像数量(含该类的图片数)标注数量(实例/掩膜总数)平均每图标注数病害特征描述0Healthy健康叶片7,74654,070~6.98无病斑叶片状态良好 (通常指健康区域分割)1Birds_Eye_Rot鸟眼斑腐烂6,15016,084~2.62圆形斑点中心灰白边缘深褐形似鸟眼2Powdery_Mildew白粉病7,94724,593~3.09叶片表面覆盖白色粉状霉层影响光合作用-总计/平均-15,034*94,747~6.30-注图像数量总和超过15,034是因为单张图片可能同时包含多种病害或同时包含健康与病斑区域多标签/多实例情况。在这里插入图片描述 数据集特点分析高分辨率所有图像均为1200×1600高清分辨率有利于捕捉细微的病斑纹理如白粉病的粉末状细节、鸟眼斑的边缘特征。类别平衡性健康样本最多作为背景参照。白粉病图像覆盖面最广 (7,947张)标注实例丰富。鸟眼斑虽然图像数略少但标注清晰适合训练小目标检测能力。标注密度总标注数高达94,747个平均每张图约有6.3个标注对象数据密度大利于模型学习复杂的叶片遮挡和多病斑并发场景。格式兼容性同时提供YOLO(txt) 和VOC(xml) 格式可无缝对接 YOLOv8/v9/v10-seg、Mask R-CNN、DeepLabV3 等主流分割网络。训练需要特别注意以下几点数据格式转换DeepLabV3 通常使用 PNG 掩膜图Mask作为标签而您提供的是 YOLO (txt) 或 VOC (xml) 格式。需要编写脚本将其转换为单通道的索引色 PNG 图片。显存优化由于原图分辨率高达1200x1600直接训练会爆显存。必须使用Crop裁剪或Resize缩放策略。代码中将默认采用随机裁剪。类别映射需要将您的3个类别健康、鸟眼斑、白粉病映射为 0, 1, 2。数据预处理脚本和基于 PyTorch Torchvision 的 DeepLabV3 训练代码。 第一步项目目录结构准备建议将数据处理后的文件整理为以下结构Cityscapes/Pascal VOC 风格Grape_Dataset/ ├── images/ # 存放原始 jpg 图片 (train/val/test 子文件夹) ├── masks/ # 存放转换后的 png 掩膜 (train/val/test 子文件夹) ├── splits/ # 存放训练/验证列表 txt 文件 └── train_deeplab.py # 训练脚本️ 第二步数据预处理脚本 (VOC/YOLO - Mask PNG)DeepLabV3 需要标签是单通道图片像素值代表类别ID0背景/健康, 1鸟眼斑, 2白粉病。注意如果您的“Healthy”是指整张健康的叶子作为背景通常在分割任务中我们只标注病害区域1和2健康区域视为背景0。如果您的数据中“Healthy”也是被标注出来的多边形请根据实际需求调整下面的逻辑。以下代码假设0背景(含健康叶肉), 1鸟眼斑, 2白粉病。importosimportxml.etree.ElementTreeasETimportcv2importnumpyasnpfromPILimportImage# 配置路径DATASET_ROOTGrape_Dataset_Raw# 原始数据根目录OUTPUT_ROOTGrape_Dataset# 输出目录IMG_DIRos.path.join(OUTPUT_ROOT,images)MASK_DIRos.path.join(OUTPUT_ROOT,masks)# 类别映射 (根据实际标注调整)# 假设 VOC XML 中的 name 字段如下若为 YOLO 需解析 txt 坐标CLASS_MAP{Birds_Eye_Rot:1,Powdery_Mildew:2,# Healthy 通常不作为前景标注或者如果标注了设为 0 或其他这里假设只分割病害}defcreate_dirs():forsplitin[train,val,test]:os.makedirs(os.path.join(IMG_DIR,split),exist_okTrue)os.makedirs(os.path.join(MASK_DIR,split),exist_okTrue)defvoc_xml_to_mask(xml_path,img_size):将 VOC XML 转换为单通道 Maskmasknp.zeros(img_size[:2],dtypenp.uint8)treeET.parse(xml_path)roottree.getroot()forobjinroot.findall(object):nameobj.find(name).textifnamenotinCLASS_MAP:continuebboxobj.find(bndbox)xminint(float(bbox.find(xmin).text))yminint(float(bbox.find(ymin).text))xmaxint(float(bbox.find(xmax).text))ymaxint(float(bbox.find(ymax).text))# 简单处理将边界框内填充为类别ID# 如果是实例分割(YOLO Segmentation)需要解析多边形点坐标绘制 fillPoly# 此处以 VOC 检测框为例演示若有分割多边形数据请使用 cv2.fillPolymask[ymin:ymax,xmin:xmax]CLASS_MAP[name]returnmaskdefprocess_dataset():create_dirs()# 此处仅为示例逻辑实际需根据您的原始文件结构遍历# 假设原始数据在 Grape_Dataset_Raw/VOCAnnotations 和 Grape_Dataset_Raw/JPEGImages# 模拟划分 (实际请读取您的 train.txt, val.txt)splits{train:list(range(12121)),val:list(range(12121,121211455)),test:list(range(121211455,15034))}print(⚠️ 请根据实际文件路径修改此函数的遍历逻辑)print(核心逻辑读取原图 - 读取标注(XML/TXT) - 生成单通道PNG (0,1,2) - 保存)if__name____main__:process_dataset() 重要提示如果您拥有的是YOLO 分割格式 (txt containing polygons)需要使用cv2.fillPoly将多边形点画在 mask 上而不是画矩形框。 第三步DeepLabV3 训练代码 (PyTorch)此代码使用了torchvision内置的 DeepLabV3 (ResNet101 backbone)并针对高分辨率图像做了随机裁剪处理。importosimporttorchimporttorch.nnasnnimporttorch.utils.dataasdataimporttorchvisionimporttorchvision.transformsasTimporttorchvision.modelsasmodelsimporttorchvision.models.segmentationasmsfromPILimportImageimportnumpyasnpfromtqdmimporttqdmimportmatplotlib.pyplotasplt# 配置区 CONFIG{data_root:Grape_Dataset,num_classes:3,# 0:Background/Healthy, 1:Birds_Eye, 2:Powdery_Mildewbatch_size:4,# 根据显存调整1200x1600裁剪后建议 4-8epochs:50,lr:0.001,img_size:512,# 训练时裁剪大小解决 1200x1600 显存爆炸问题device:cudaiftorch.cuda.is_available()elsecpu,workers:4,}# 数据集类 classGrapeSegmentationDataset(data.Dataset):def__init__(self,root,split,img_size):self.rootroot self.splitsplit self.img_sizeimg_size self.images[]self.masks[]img_diros.path.join(root,images,split)mask_diros.path.join(root,masks,split)# 获取所有图片路径forfnameinsorted(os.listdir(img_dir)):iffname.endswith(.jpg)orfname.endswith(.png):self.images.append(os.path.join(img_dir,fname))# 假设 mask 文件名与图片一致只是后缀为 .pngmask_fnameos.path.splitext(fname)[0].pngself.masks.append(os.path.join(mask_dir,mask_fname))print(fLoaded{len(self.images)}images for{split})def__getitem__(self,index):# 加载图像imgImage.open(self.images[index]).convert(RGB)maskImage.open(self.masks[index])# 数据增强与变换# 1. 随机裁剪 (解决高分辨率问题)# 2. 随机水平翻转# 3. 转为 Tensor 并归一化transform_imgT.Compose([T.RandomCrop(self.img_size),T.RandomHorizontalFlip(0.5),T.ToTensor(),T.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225]),])transform_maskT.Compose([T.RandomCrop(self.img_size),T.RandomHorizontalFlip(0.5),])# 注意为了保持 img 和 mask 同步增强需要使用相同的随机种子或自定义变换# 这里简化处理实际生产建议使用 torchvision.transforms.v2 或自定义类# 简易同步裁剪实现i,j,h,wT.RandomCrop.get_params(img,output_size(self.img_size,self.img_size))imgT.functional.crop(img,i,j,h,w)maskT.functional.crop(mask,i,j,h,w)ifnp.random.rand()0.5:imgT.functional.hflip(img)maskT.functional.hflip(mask)imgT.ToTensor()(img)imgT.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225])(img)maskT.ToTensor()(mask).squeeze(0).long()# Mask 不需要归一化只需转为 Longreturnimg,maskdef__len__(self):returnlen(self.images)# 模型构建 defget_model(num_classes):# 加载预训练的 DeepLabV3 (ResNet101 backbone)modelms.deeplabv3_resnet101(pretrainedTrue,progressTrue)# 修改分类器头以适配我们的类别数 (3类)# 原来的辅助分类器也需修改model.classifier[4]nn.Conv2d(256,num_classes,kernel_size1)model.aux_classifier[4]nn.Conv2d(256,num_classes,kernel_size1)returnmodel# 训练函数 deftrain():deviceCONFIG[device]print(fUsing device:{device})# 1. 加载数据train_datasetGrapeSegmentationDataset(CONFIG[data_root],train,CONFIG[img_size])val_datasetGrapeSegmentationDataset(CONFIG[data_root],val,CONFIG[img_size])train_loaderdata.DataLoader(train_dataset,batch_sizeCONFIG[batch_size],shuffleTrue,num_workersCONFIG[workers])val_loaderdata.DataLoader(val_dataset,batch_sizeCONFIG[batch_size],shuffleFalse,num_workersCONFIG[workers])# 2. 初始化模型modelget_model(CONFIG[num_classes]).to(device)# 3. 损失函数 (忽略背景0如果不忽略则不要 ignore_index)# 通常分割任务中如果背景也是类别之一则不忽略。如果只想训练病害可设置 ignore_index0criterionnn.CrossEntropyLoss(ignore_index-1)# 这里假设所有像素都有标签不设忽略# 4. 优化器optimizertorch.optim.SGD(model.parameters(),lrCONFIG[lr],momentum0.9,weight_decay1e-4)schedulertorch.optim.lr_scheduler.StepLR(optimizer,step_size10,gamma0.1)best_miou0.0# 5. 训练循环forepochinrange(CONFIG[epochs]):model.train()running_loss0.0pbartqdm(train_loader,descfEpoch{epoch1}/{CONFIG[epochs]})forimages,masksinpbar:images,masksimages.to(device),masks.to(device)optimizer.zero_grad()outputsmodel(images)[out]# DeepLabV3 输出字典losscriterion(outputs,masks)loss.backward()optimizer.step()running_lossloss.item()pbar.set_postfix({loss:f{loss.item():.4f}})avg_lossrunning_loss/len(train_loader)scheduler.step()# 验证阶段 (简化版仅计算 Loss)model.eval()val_loss0.0withtorch.no_grad():forimages,masksinval_loader:images,masksimages.to(device),masks.to(device)outputsmodel(images)[out]losscriterion(outputs,masks)val_lossloss.item()print(fEpoch{epoch1}: Train Loss{avg_loss:.4f}, Val Loss{val_loss/len(val_loader):.4f})# 保存最佳模型if(val_loss/len(val_loader))best_miouorepoch0:# 这里用 Loss 代替 mIoU 做简单判断best_miouval_loss/len(val_loader)torch.save(model.state_dict(),fdeeplabv3_grape_epoch{epoch1}.pth)print(f✅ Model saved at epoch{epoch1})print( Training Finished!)if__name____main__:train() 关键训练技巧与建议关于高分辨率 (1200x1600)不要直接将图片 Resize 到 512x512 进行训练这会导致细小的“鸟眼斑”丢失。推荐做法代码中使用了RandomCrop(512)。这样既保留了原始高分辨率的细节又适应了 GPU 显存限制。推理时可以使用Sliding Window Inference (滑动窗口推理)将大图切块预测后再拼接或者直接 Resize 后预测如果精度要求不高。类别不平衡葡萄叶片中背景健康部分通常占据 90% 以上的像素病害区域很小。解决方案在CrossEntropyLoss中添加weight参数。# 计算各类别频率后给少数类更高的权重class_weightstorch.tensor([0.1,1.0,1.0]).to(device)# 示例降低背景权重criterionnn.CrossEntropyLoss(weightclass_weights)评价指标 (mIoU)上面的代码为了简洁只计算了 Loss。正式训练建议引入torchmetrics库来计算Mean IoU这是分割任务最核心的指标。pipinstalltorchmetrics在代码中加入MeanIoU(num_classes3)并在验证集更新。数据增强除了随机裁剪和翻转还可以加入ColorJitter(亮度、对比度变化) 来模拟不同光照下的葡萄园环境提高模型鲁棒性。
智慧农业农业智能诊断、植物保护 葡萄叶片病害分割数据集 基于 PyTorch + Torchvision 的 DeepLabV3+ 训练葡萄叶片分割数据集
智慧农业农业智能诊断、植物保护 葡萄叶片病害分割数据集 葡萄叶片病害分割数据集概览表项目指标详细数据/描述数据集名称葡萄叶片病害分割数据集 (Grape Leaf Disease Segmentation Dataset)图像总数15,034张 (含数据增强)图像分辨率1200 × 1600 像素标注格式支持YOLO和VOC(XML) 两种格式任务类型实例分割 / 语义分割— 数据集划分统计数据集子集图像数量 (Images)占比 (约)用途训练集 (Train)12,12180.6%模型训练与参数学习验证集 (Val)1,4559.7%超参数调整与模型选择测试集 (Test)1,4589.7%最终模型性能评估总计15,034100%- 病害类别与标注详情类别 ID英文名称中文名称图像数量(含该类的图片数)标注数量(实例/掩膜总数)平均每图标注数病害特征描述0Healthy健康叶片7,74654,070~6.98无病斑叶片状态良好 (通常指健康区域分割)1Birds_Eye_Rot鸟眼斑腐烂6,15016,084~2.62圆形斑点中心灰白边缘深褐形似鸟眼2Powdery_Mildew白粉病7,94724,593~3.09叶片表面覆盖白色粉状霉层影响光合作用-总计/平均-15,034*94,747~6.30-注图像数量总和超过15,034是因为单张图片可能同时包含多种病害或同时包含健康与病斑区域多标签/多实例情况。在这里插入图片描述 数据集特点分析高分辨率所有图像均为1200×1600高清分辨率有利于捕捉细微的病斑纹理如白粉病的粉末状细节、鸟眼斑的边缘特征。类别平衡性健康样本最多作为背景参照。白粉病图像覆盖面最广 (7,947张)标注实例丰富。鸟眼斑虽然图像数略少但标注清晰适合训练小目标检测能力。标注密度总标注数高达94,747个平均每张图约有6.3个标注对象数据密度大利于模型学习复杂的叶片遮挡和多病斑并发场景。格式兼容性同时提供YOLO(txt) 和VOC(xml) 格式可无缝对接 YOLOv8/v9/v10-seg、Mask R-CNN、DeepLabV3 等主流分割网络。训练需要特别注意以下几点数据格式转换DeepLabV3 通常使用 PNG 掩膜图Mask作为标签而您提供的是 YOLO (txt) 或 VOC (xml) 格式。需要编写脚本将其转换为单通道的索引色 PNG 图片。显存优化由于原图分辨率高达1200x1600直接训练会爆显存。必须使用Crop裁剪或Resize缩放策略。代码中将默认采用随机裁剪。类别映射需要将您的3个类别健康、鸟眼斑、白粉病映射为 0, 1, 2。数据预处理脚本和基于 PyTorch Torchvision 的 DeepLabV3 训练代码。 第一步项目目录结构准备建议将数据处理后的文件整理为以下结构Cityscapes/Pascal VOC 风格Grape_Dataset/ ├── images/ # 存放原始 jpg 图片 (train/val/test 子文件夹) ├── masks/ # 存放转换后的 png 掩膜 (train/val/test 子文件夹) ├── splits/ # 存放训练/验证列表 txt 文件 └── train_deeplab.py # 训练脚本️ 第二步数据预处理脚本 (VOC/YOLO - Mask PNG)DeepLabV3 需要标签是单通道图片像素值代表类别ID0背景/健康, 1鸟眼斑, 2白粉病。注意如果您的“Healthy”是指整张健康的叶子作为背景通常在分割任务中我们只标注病害区域1和2健康区域视为背景0。如果您的数据中“Healthy”也是被标注出来的多边形请根据实际需求调整下面的逻辑。以下代码假设0背景(含健康叶肉), 1鸟眼斑, 2白粉病。importosimportxml.etree.ElementTreeasETimportcv2importnumpyasnpfromPILimportImage# 配置路径DATASET_ROOTGrape_Dataset_Raw# 原始数据根目录OUTPUT_ROOTGrape_Dataset# 输出目录IMG_DIRos.path.join(OUTPUT_ROOT,images)MASK_DIRos.path.join(OUTPUT_ROOT,masks)# 类别映射 (根据实际标注调整)# 假设 VOC XML 中的 name 字段如下若为 YOLO 需解析 txt 坐标CLASS_MAP{Birds_Eye_Rot:1,Powdery_Mildew:2,# Healthy 通常不作为前景标注或者如果标注了设为 0 或其他这里假设只分割病害}defcreate_dirs():forsplitin[train,val,test]:os.makedirs(os.path.join(IMG_DIR,split),exist_okTrue)os.makedirs(os.path.join(MASK_DIR,split),exist_okTrue)defvoc_xml_to_mask(xml_path,img_size):将 VOC XML 转换为单通道 Maskmasknp.zeros(img_size[:2],dtypenp.uint8)treeET.parse(xml_path)roottree.getroot()forobjinroot.findall(object):nameobj.find(name).textifnamenotinCLASS_MAP:continuebboxobj.find(bndbox)xminint(float(bbox.find(xmin).text))yminint(float(bbox.find(ymin).text))xmaxint(float(bbox.find(xmax).text))ymaxint(float(bbox.find(ymax).text))# 简单处理将边界框内填充为类别ID# 如果是实例分割(YOLO Segmentation)需要解析多边形点坐标绘制 fillPoly# 此处以 VOC 检测框为例演示若有分割多边形数据请使用 cv2.fillPolymask[ymin:ymax,xmin:xmax]CLASS_MAP[name]returnmaskdefprocess_dataset():create_dirs()# 此处仅为示例逻辑实际需根据您的原始文件结构遍历# 假设原始数据在 Grape_Dataset_Raw/VOCAnnotations 和 Grape_Dataset_Raw/JPEGImages# 模拟划分 (实际请读取您的 train.txt, val.txt)splits{train:list(range(12121)),val:list(range(12121,121211455)),test:list(range(121211455,15034))}print(⚠️ 请根据实际文件路径修改此函数的遍历逻辑)print(核心逻辑读取原图 - 读取标注(XML/TXT) - 生成单通道PNG (0,1,2) - 保存)if__name____main__:process_dataset() 重要提示如果您拥有的是YOLO 分割格式 (txt containing polygons)需要使用cv2.fillPoly将多边形点画在 mask 上而不是画矩形框。 第三步DeepLabV3 训练代码 (PyTorch)此代码使用了torchvision内置的 DeepLabV3 (ResNet101 backbone)并针对高分辨率图像做了随机裁剪处理。importosimporttorchimporttorch.nnasnnimporttorch.utils.dataasdataimporttorchvisionimporttorchvision.transformsasTimporttorchvision.modelsasmodelsimporttorchvision.models.segmentationasmsfromPILimportImageimportnumpyasnpfromtqdmimporttqdmimportmatplotlib.pyplotasplt# 配置区 CONFIG{data_root:Grape_Dataset,num_classes:3,# 0:Background/Healthy, 1:Birds_Eye, 2:Powdery_Mildewbatch_size:4,# 根据显存调整1200x1600裁剪后建议 4-8epochs:50,lr:0.001,img_size:512,# 训练时裁剪大小解决 1200x1600 显存爆炸问题device:cudaiftorch.cuda.is_available()elsecpu,workers:4,}# 数据集类 classGrapeSegmentationDataset(data.Dataset):def__init__(self,root,split,img_size):self.rootroot self.splitsplit self.img_sizeimg_size self.images[]self.masks[]img_diros.path.join(root,images,split)mask_diros.path.join(root,masks,split)# 获取所有图片路径forfnameinsorted(os.listdir(img_dir)):iffname.endswith(.jpg)orfname.endswith(.png):self.images.append(os.path.join(img_dir,fname))# 假设 mask 文件名与图片一致只是后缀为 .pngmask_fnameos.path.splitext(fname)[0].pngself.masks.append(os.path.join(mask_dir,mask_fname))print(fLoaded{len(self.images)}images for{split})def__getitem__(self,index):# 加载图像imgImage.open(self.images[index]).convert(RGB)maskImage.open(self.masks[index])# 数据增强与变换# 1. 随机裁剪 (解决高分辨率问题)# 2. 随机水平翻转# 3. 转为 Tensor 并归一化transform_imgT.Compose([T.RandomCrop(self.img_size),T.RandomHorizontalFlip(0.5),T.ToTensor(),T.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225]),])transform_maskT.Compose([T.RandomCrop(self.img_size),T.RandomHorizontalFlip(0.5),])# 注意为了保持 img 和 mask 同步增强需要使用相同的随机种子或自定义变换# 这里简化处理实际生产建议使用 torchvision.transforms.v2 或自定义类# 简易同步裁剪实现i,j,h,wT.RandomCrop.get_params(img,output_size(self.img_size,self.img_size))imgT.functional.crop(img,i,j,h,w)maskT.functional.crop(mask,i,j,h,w)ifnp.random.rand()0.5:imgT.functional.hflip(img)maskT.functional.hflip(mask)imgT.ToTensor()(img)imgT.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225])(img)maskT.ToTensor()(mask).squeeze(0).long()# Mask 不需要归一化只需转为 Longreturnimg,maskdef__len__(self):returnlen(self.images)# 模型构建 defget_model(num_classes):# 加载预训练的 DeepLabV3 (ResNet101 backbone)modelms.deeplabv3_resnet101(pretrainedTrue,progressTrue)# 修改分类器头以适配我们的类别数 (3类)# 原来的辅助分类器也需修改model.classifier[4]nn.Conv2d(256,num_classes,kernel_size1)model.aux_classifier[4]nn.Conv2d(256,num_classes,kernel_size1)returnmodel# 训练函数 deftrain():deviceCONFIG[device]print(fUsing device:{device})# 1. 加载数据train_datasetGrapeSegmentationDataset(CONFIG[data_root],train,CONFIG[img_size])val_datasetGrapeSegmentationDataset(CONFIG[data_root],val,CONFIG[img_size])train_loaderdata.DataLoader(train_dataset,batch_sizeCONFIG[batch_size],shuffleTrue,num_workersCONFIG[workers])val_loaderdata.DataLoader(val_dataset,batch_sizeCONFIG[batch_size],shuffleFalse,num_workersCONFIG[workers])# 2. 初始化模型modelget_model(CONFIG[num_classes]).to(device)# 3. 损失函数 (忽略背景0如果不忽略则不要 ignore_index)# 通常分割任务中如果背景也是类别之一则不忽略。如果只想训练病害可设置 ignore_index0criterionnn.CrossEntropyLoss(ignore_index-1)# 这里假设所有像素都有标签不设忽略# 4. 优化器optimizertorch.optim.SGD(model.parameters(),lrCONFIG[lr],momentum0.9,weight_decay1e-4)schedulertorch.optim.lr_scheduler.StepLR(optimizer,step_size10,gamma0.1)best_miou0.0# 5. 训练循环forepochinrange(CONFIG[epochs]):model.train()running_loss0.0pbartqdm(train_loader,descfEpoch{epoch1}/{CONFIG[epochs]})forimages,masksinpbar:images,masksimages.to(device),masks.to(device)optimizer.zero_grad()outputsmodel(images)[out]# DeepLabV3 输出字典losscriterion(outputs,masks)loss.backward()optimizer.step()running_lossloss.item()pbar.set_postfix({loss:f{loss.item():.4f}})avg_lossrunning_loss/len(train_loader)scheduler.step()# 验证阶段 (简化版仅计算 Loss)model.eval()val_loss0.0withtorch.no_grad():forimages,masksinval_loader:images,masksimages.to(device),masks.to(device)outputsmodel(images)[out]losscriterion(outputs,masks)val_lossloss.item()print(fEpoch{epoch1}: Train Loss{avg_loss:.4f}, Val Loss{val_loss/len(val_loader):.4f})# 保存最佳模型if(val_loss/len(val_loader))best_miouorepoch0:# 这里用 Loss 代替 mIoU 做简单判断best_miouval_loss/len(val_loader)torch.save(model.state_dict(),fdeeplabv3_grape_epoch{epoch1}.pth)print(f✅ Model saved at epoch{epoch1})print( Training Finished!)if__name____main__:train() 关键训练技巧与建议关于高分辨率 (1200x1600)不要直接将图片 Resize 到 512x512 进行训练这会导致细小的“鸟眼斑”丢失。推荐做法代码中使用了RandomCrop(512)。这样既保留了原始高分辨率的细节又适应了 GPU 显存限制。推理时可以使用Sliding Window Inference (滑动窗口推理)将大图切块预测后再拼接或者直接 Resize 后预测如果精度要求不高。类别不平衡葡萄叶片中背景健康部分通常占据 90% 以上的像素病害区域很小。解决方案在CrossEntropyLoss中添加weight参数。# 计算各类别频率后给少数类更高的权重class_weightstorch.tensor([0.1,1.0,1.0]).to(device)# 示例降低背景权重criterionnn.CrossEntropyLoss(weightclass_weights)评价指标 (mIoU)上面的代码为了简洁只计算了 Loss。正式训练建议引入torchmetrics库来计算Mean IoU这是分割任务最核心的指标。pipinstalltorchmetrics在代码中加入MeanIoU(num_classes3)并在验证集更新。数据增强除了随机裁剪和翻转还可以加入ColorJitter(亮度、对比度变化) 来模拟不同光照下的葡萄园环境提高模型鲁棒性。