简介图像分割数据集植物图像叶片分割包含一百一十张真实拍摄的植物叶片图像及一一对应的像素级掩膜mask标签数据场景覆盖不同生长阶段、拍摄距离与叶片形态前景区域丰富、标注边缘连续精细既可用于植物叶片分割模型的训练与精度评估也适合作为课程设计或语义分割入门练习数据。整套资源共二百二十二个文件主体为PNG与JPG格式图像分别对应原始叶片照片与掩膜标注另附带一个Python可视化脚本可随机抽取一张图片将原始图像、GT掩膜以及GT叠加到原图上的蒙版效果同时展示并输出到当前目录便于核对标注质量与观察分割效果。压缩包整体大小约五百四十五兆字节目前已有六百八十八人学习对于需要真实植物叶片数据集的读者下载后即可同时获得配对图像、掩膜标签与可视化工具并可直接用于分割算法实验、叶片病害区域分割或植被表型分析等场景。1. 植物叶片分割数据集图像分割落地农业场景的第一道关卡图像分割在植物研究里不是炫技是刚需。叶片和背景的边界一旦糊掉后面算叶面积、数病斑、统计黄化率全部失真做出来的结果没人敢信。很多做农业AI、植物表型分析和园艺自动化的团队最初卡住的地方往往不在模型而是手上缺一套标注干净、类别明确、能直接喂进训练脚本的叶片分割数据集。这份资源正好补上这个缺口植物图像的叶片分割标注样本覆盖户外光照、温室背景、病斑叶片等常见情形。新手用它跑通从读数据到出 mask 的完整流程熟手拿它能做基于 U-Net 或 DeepLabV3 的迁移微调省掉找图、清洗、标注这些最耗人力的环节。2. 数据集内部长什么样目录结构、标注格式与读取方式这类资源能不能用好第一步永远是摸清目录和标注格式。这一步草率了后面训练出的模型再精致也是白搭。2.1 目录结构与三种典型标注格式拿到这份数据集最先看到的是一个整体目录。常见的组织方式是images和masks两个大文件夹再加上train.txt、val.txt这样的划分文件放在根目录。images 下面是原始植物照片masks 下面是和照片一一对应的分割标注图文件名前缀一致、后缀不同。train.txt 和 val.txt 里分别列出训练集和验证集对应的图片文件名一行一个不带扩展名。. ├── images/ │ ├── leaf_001.jpg │ ├── leaf_002.jpg │ └── ... ├── masks/ │ ├── leaf_001.png │ ├── leaf_002.png │ └── ... ├── train.txt └── val.txt这种组织方式和公开的植物分割数据集基本一致算是行业里通用约定。拿到手先别急着训练第一件事是写脚本扫描两个目录确认文件一一对应。常见做法是用 Python 对文件名取前缀做集合求差看看哪些图片缺了 mask或者哪些 mask 没有对应原图。mask 的标注格式需要特别留意。同一份资源里可能混着三种格式单通道二值图像素值为 0 和 255或 0 和 1背景是 0叶片是 1。这是最省内存的格式直接用灰度图读取就能训练。RGB 彩色标注图每个类别一个颜色健康叶片标成纯绿 (0, 255, 0)背景标黑病斑区域标成别的固定色。这种格式语义直观但转训练标签前要做颜色映射。JSON 多边形坐标叶片轮廓以点坐标形式存在 JSON 文件里需要先栅格化成 mask 才能用于训练。判断数据集属于哪种格式最稳的方式不是用眼睛看而是从 masks 目录里抽两三张图读进来打印 shape 和像素值范围。灰度图的 shape 是 (H, W)彩色标注图是 (H, W, 3)JSON 格式则根本不存在图片文件。三种格式的读取路径完全不同混着用大概率翻车。2.2 用 Python 读取 mask 并做类别统计文件读对了接下来做数据体检。所谓体检就是统计数据集全局的类别分布、单张 mask 的面积占比、是否有空标注和重叠标注。这一步很多人跳过结果训练到一半发现某些类别从未被模型见过。对于语义分割每个像素都属于一个类别。植物叶片分割通常先做二分类叶片与背景如果数据集中包含病斑标注就升级为多分类。类别统计的代码大致如下import cv2 import numpy as np def inspect_mask(mask): unique, counts np.unique(mask, return_countsTrue) total mask.size distribution {int(k): round(int(v) / total, 4) for k, v in zip(unique, counts)} return distribution mask cv2.imread(masks/leaf_001.png, cv2.IMREAD_GRAYSCALE) dist inspect_mask(mask) print(类别占比:, dist)关键点有两个。第一IMREAD_GRAYSCALE强制把 mask 转成单通道保证np.unique统计的是一维像素值。第二得到的分布字典里键是类别编号、值是占比如果某个类别占比只有 0.001 甚至为 0就得排查是不是大量图片的 mask 全黑或标注极不完整。后面如果选带权重的损失函数这份统计直接用来算类别权重。还有一种质量问题值得注意标注空洞和粘连。表现为 mask 内部像素值不连续叶片区域出现黑色空洞或者两个相邻叶片的标注区域粘在一起。这类问题肉眼逐张排查会疯掉建议写脚本按图像计算连通域数量。正常二值化后每个叶片应是一个独立连通域连通域数远少于实际叶数说明有粘连远多于实际叶数说明标注碎成渣了。2.3 多类别标注的重映射从 RGB 掩码到训练索引如果资源里的 mask 是 RGB 彩色标注图训练前必须做颜色到类别索引的映射。假设颜色约定是背景黑色、健康叶片纯绿、病斑棕红色常见做法是维护一张颜色表逐像素匹配color_map { (0, 0, 0): 0, # 背景 (0, 255, 0): 1, # 健康叶片 (128, 0, 0): 2, # 病斑区域 } def rgb_label_to_indices(rgb_mask, color_map): h, w rgb_mask.shape[:2] label np.zeros((h, w), dtypenp.uint8) for rgb, idx in color_map.items(): label[np.all(rgb_mask np.array(rgb), axis-1)] idx return label这段逻辑是遍历颜色表中的每一个 RGB 值用np.all在通道维上做匹配命中的像素位置直接写成对应类别索引。要注意label的初始值是 0背景里如果出现未登记的颜色会被悄悄归类为背景这是多分类分割里最常见的标签污染来源。跑完映射后建议立刻np.unique确认类别索引只有 0、1、2没有漏网的 3 或 255。这一章的核心只有一条目录结构决定怎么加载文件标注格式决定怎么转标签体检脚本决定模型能不能学对东西。这三步做干净后续训练才谈得上有意义。3. 从数据集到训练结果预处理、模型选型与评估指标数据集摸透了接下来就是把照片和 mask 变成可训练的模型。这一章按实际动手顺序讲先划分数据再搭训练脚本最后谈评估。3.1 数据划分与增强不能破坏图像和 mask 的对应关系资源里如果自带 train.txt 和 val.txt直接沿用没有就得自己划分。划分比例按 8:1:1 或 7:2:1 切成训练、验证、测试三份。这里最容易被忽略的是划分的单位是图片不是像素。同一张图片的原图和 mask 必须同时进同一个集合绝不能原图在训练集、mask 在验证集否则验证指标虚高模型等于开卷考试。我自己划分数据时习惯只操作文件名列表不复制图片文件。具体做法是扫出全部图片文件名shuffle 后按比例切片把三份文件名分别写入三个文本文件。训练脚本只依赖文本文件里列出的名字去两个目录找文件省磁盘也更直观。数据增强是分割任务里最容易写错的一环。旋转、翻转、缩放、裁剪这些操作必须对原图和 mask 施加完全相同的变换。很多初学者用 torchvision 的 RandomHorizontalFlip 单独处理图像mask 不翻转训练时 loss 降得很快一到真实场景预测的 mask 位置总是偏的。常见做法是直接使用双输入增强库代替手写。albumentations 天然支持 image 和 mask 同步变换不用自己维护随机种子。还有一个选择是在 PyTorch 的 Dataset 类__getitem__里做在线增强而不是预先离线生成增强图像。离线增强会成倍增加磁盘占用而且随机性无法跨 epoch 复用在线增强更灵活。3.2 最小可跑的 U-Net 训练代码分割模型的选择很多U-Net、DeepLabV3、SegFormer、Mask R-CNN。叶片分割这个场景背景相对简单、叶片形状不规则但边缘清晰U-Net 是性价比很高的起点。它参数量小单卡就能快速迭代特别适合几百到几千张的中小型数据集。只有当数据规模超过一万张且叶片尺寸差异极大时再考虑 DeepLabV3 的 ASPP 多尺度模块。下面是一段基于 PyTorch 的最小训练脚本核心模型用 segmentation_models_pytorch 库加载预训练 U-Netimport torch import torchvision.transforms as T from torch.utils.data import Dataset, DataLoader import segmentation_models_pytorch as smp import cv2 import numpy as np class LeafDataset(Dataset): def __init__(self, img_dir, mask_dir, file_list): self.img_paths [str(img_dir / line) for line in file_list] self.mask_paths [str(mask_dir / line) for line in file_list] def __getitem__(self, idx): image cv2.imread(self.img_paths[idx]) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) mask cv2.imread(self.mask_paths[idx], cv2.IMREAD_GRAYSCALE) mask (mask 127).astype(np.float32) image T.ToTensor()(image) mask torch.from_numpy(mask).unsqueeze(0) return image, mask def __len__(self): return len(self.img_paths) model smp.Unet(resnet34, encoder_weightsimagenet, classes1) criterion smp.losses.DiceLoss(modebinary) optimizer torch.optim.Adam(model.parameters(), lr1e-4) loader DataLoader(dataset, batch_size8, shuffleTrue, num_workers4) for epoch in range(30): model.train() for images, masks in loader: optimizer.zero_grad() logits model(images) loss criterion(logits, masks) loss.backward() optimizer.step() if epoch % 5 0: print(fepoch {epoch}, loss {loss.item():.4f})代码里有三个参数需要单独说明。第一个是mask 127的阈值原始 mask 像素值一般只有 0 和 255阈值取多少都行但如果是压缩过的 JPEG 标注边缘像素会有过渡值127 这个中间值能容忍一定压缩噪声。第二个是DiceLoss(modebinary)叶片像素在整张图中占比通常不高交叉熵容易被大面积背景主导DiceLoss 更稳。第三是学习率Adam 配 1e-4 起步是通用配置训练中期想提精度可以切 SGD 加 momentum但初学者直接 Adam 跑到底问题也不大。batch size 建议先给 8显存小就降到 4数据集小的时候大 batch 反而容易过拟合。epoch 数量按验证集早停来定固定 30 轮只是起步参考叶片数据集往往 15 轮左右就收敛了继续硬跑只会过拟合。3.3 验证指标IoU 和 Dice 到底怎么算分割验证不能只看准确率因为背景像素占大头模型全预测成背景都能有 95% 以上的准确率。必须看 IoU 和 Dice 系数。IoU 是交集除以并集Dice 是两倍交集除以两个集合面积之和。两者衡量区域重叠程度的侧重点略有不同Dice 对小目标更敏感。二分类场景直接用 torchmetrics 的 JaccardIndex 就能算from torchmetrics import JaccardIndex jaccard JaccardIndex(taskbinary, threshold0.5) model.eval() with torch.no_grad(): for images, masks in val_loader: logits model(images) probs torch.sigmoid(logits) jaccard.update(probs, masks.long()) print(mIoU:, jaccard.compute().item())这段验证代码的细节在于threshold0.5。模型输出的 logits 必须先经过 sigmoid 映射到 0 到 1 之间再和真实 mask 对比。如果直接把 logit 丢进指标函数结果通常会偏小而且阈值设在 0.5 意味着模型对叶片边界的位置已经很自信。多类别场景则要逐类算 IoU 再取平均不能只看一个 mean 值否则小类别表现会被大类别掩盖。提示验证指标建议加一次全背景预测的基线对比。如果模型 IoU 只有 0.4而全预测成背景的基线 IoU 是 0.38说明模型基本没学到东西只是蒙对了背景占多数。经验上叶片分割的 IoU 做到 0.85 以上预测出来的 mask 才有实用价值低于 0.7 的模型应用到下游叶片面积测量时误差会超过 10%这种结果拿出去很难让人信服。4. 避坑指南叶片分割数据集的六个实际坑以下六条都是从实际复现中踩过的记录每一条都按现象、原因、解决的顺序整理建议对着自己的数据集逐个排查。4.1 mask 格式混乱现象训练第一个 epoch loss 直接变成 nan或者 loss 正常收敛但预测出的 mask 全是黑的。原因数据集中部分 mask 是 RGB 三通道存储部分是单通道灰度图。统一用 IMREAD_GRAYSCALE 读取时RGB 图的三个通道被 OpenCV 加权平均原本整数值的类别标签被压成不对称的浮点值模型无法正确分类。解决加载 mask 前先统一格式。写脚本遍历全部 mask 文件按通道数和 dtype 分组只保留同一种格式作为 label 来源。最好的做法是在数据集准备阶段就把所有 mask 统一转成单通道 PNG。4.2 缩放时用了插值现象训练和验证指标都正常但预测出的叶片边界有一圈模糊的过渡带算面积时系统性偏大。原因预处理阶段对 image 和 mask 做 resize 时使用了相同的线性插值。mask 是类别标签线性插值会算出小数变成带过渡带的值二值化后边缘多了一圈伪影。解决mask 的缩放必须使用最近邻插值即cv2.INTER_NEAREST。线性插值只允许用在实际图像上。这一点在自定义 Dataset 或数据预处理脚本里要分开写不能图省事共用同一个 resize 函数。4.3 类别严重不平衡现象训练 loss 一直在降但验证集 IoU 停在 0.5 附近上不去尤其是病斑这类小目标几乎永远预测不出来。原因病斑或枯叶区域在整张图中占比常常只有百分之几。模型把所有像素预测成背景就能得到极低的 loss小目标类别在梯度更新中几乎没有贡献。解决换用 Focal Loss或者给 DiceLoss 加类别权重权重按上一章统计出的类别占比的反比计算。同时验证时要单独打印每个类别的 IoU别让 mIoU 的均值掩盖小类别塌方的事实。4.4 增强破坏了 mask 和图像的对齐现象引入 RandomCrop 后验证集指标突然暴涨但实际推理时预测 mask 和原图明显错位。原因实现增强时分别对 image 和 mask 调用随机变换两次调用使用的随机种子不同裁剪区域不一致训练时模型学了一堆错误的对应关系。解决使用 albumentations 这类双输入增强库自动同步变换参数。手写增强时务必在一次函数调用里生成所有随机参数再分别应用到 image 和 mask 上。4.5 训练验证集划分不当现象验证集指标每个 epoch 波动很大且最终指标虚高到不真实怀疑是过拟合。原因划分过程在文件复制阶段出问题。有些人先切分图片再单独切分 mask两边文件名排序不一致导致同一张照片的图像进了训练集、mask 进了验证集验证等于开卷。解决用文件名列表作为唯一划分依据。先 shuffle 文件名再按比例切片原图和 mask 共用同一份列表绝不分开生成切分逻辑。4.6 多类别 mask 的像素值不连续现象训练脚本用 np.unique 检查类别数时发现类别编号从 0 直接跳到 5中间缺了好几个值模型输出层的类别数怎么设置都对不上。原因部分标注工具导出时保留了原始调色板编号而数据读取脚本默认把像素值当连续索引使用。解决在数据加载前做一次类别重映射把实际出现的像素值映射到 0 到 N-1 的连续区间映射表保存成 JSON训练和验证阶段共用同一份映射表。5. 最后一步从 mask 到叶片面积与病斑占比分割结果本身不是终点落到测量才是价值。这一章给出从 mask 量化叶片表型的具体做法。当模型预测出每张图像的 mask 后最常用的下游指标是叶片总面积和病斑占比。像素数量到物理面积的换算需要标定。常见做法是在固定高度拍摄架上放一枚已知尺寸的圆形标定片例如直径 2 厘米。假设标定片在图像中直径占 80 像素则每像素对应实际面积约为 π×(1cm)² 除以 π×(40px)²得到 0.000625 平方厘米。叶片 mask 的像素总数乘以这个系数就是叶片面积。实际测量时mask 边缘粗糙会造成面积高估。预测出的 mask 如果是浮点概率图在算面积前需要做形态学处理先做闭运算填充内部孔洞再做开运算去掉边缘毛刺。闭运算用cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)kernel 大小按图片分辨率调一般 5×5 起步。病斑占比的计算思路类似如果数据集是多类别标注把 mask 按类别索引分区统计像素数用np.bincount(mask.ravel())一步算出每个类别的像素占比。如果病斑类别缺失模型仍可能预测出噪声区域需要过滤掉面积小于阈值的连通域。阈值记得按实际图片分辨率缩放——同一套代码在 3000×3000 的大图上和 512×512 的小图上连通域面积阈值差几十倍直接用固定值会误删真实病斑。我自己有一个固定习惯完成分割训练后专门留下 20 张左右覆盖极端光照的图片不参与任何训练。每次调整模型结构或预处理流程都强制用这 20 张跑一遍完整预测同时计算边界平均像素偏移和叶面积误差率两个指标边界偏移在 3 像素以内、面积误差在 5% 以内的模型才允许用于量化分析。从那以后我每次换模型、换预处理流程都会强制走一遍这个回归测试相当于给自己留了一份后悔药。希望帮到你。本文还有配套的精品资源点击获取