尧图精选

植物叶片分割数据集从零构建:标注、训练与实战避坑

🕒 发布时间:2026/9/26 18:05:58 📁 来源:尧图网络
简介面向植物图像分割与计算机视觉入门学习者数据集包含110组植物叶片原始图像与对应mask标注前景区域丰富、标注质量高可直接用于叶片分割模型的训练与评估。包内另附一个Python可视化脚本可随机抽取样本将原图、GT分割图及原图上的蒙版效果并排展示并保存便于快速检查标注质量与模型效果。资源共222个文件以png和jpg图像为主约158个png、62个jpg另有1个py脚本和1个jpeg示例整体压缩包约545.53MB数据规模适中适合个人学习与小规模实验。目前已有688人浏览学习适合需要高质量植物叶片分割数据作为练习或算法验证的读者。1. 植物叶片分割数据集才是真正要先解决的问题很多想入局植物图像分析的人最容易翻车的不是训练代码而是开头就卡在“没有数据”。网上能找到的植物图像分割模型不少但真正能直接拿去复现的叶片分割数据集往往要么类别太宽、要么背景太干净、要么没有带像素级掩码标注。也就是说当你搜“图像分割 数据集 叶片分割 植物图像”的时候你真正想要的是一个能直接喂给分割模型、又能在真实田间或实验室场景里扛得住干扰的标注集合。这篇文章就围绕“怎么把一个植物叶片分割数据集从零做出来、怎么标注、怎么训、怎么避坑”展开适合刚接触图像分割的学生、做植物表型分析的科研人员以及想用YOLOv8这类算法训练自己的数据集但被数据卡住的一线工程师。2. 从零做出植物叶片分割数据集采集、清理与标注工具选型2.1 先想清楚你的分割目标叶实例、叶语义还是病斑动手收集图片之前先确定你要做的是三种分割里的哪一种。实例分割要区分每一片叶子同一株植物上有两片重叠的叶子也得分开给不同的掩码语义分割不区分个体所有叶子算同一个类别适合做覆盖率、叶面积指数估算如果你关注的是病斑叶片分割那前景类别就变成了“病斑区域”健康叶肉反而成了背景。这个选择直接影响标注工作量实例分割单张图可能要花十分钟以上语义分割快一些但碰到叶片重叠严重时依然很头疼。常见做法是先用语义分割搭基线等业务上确实需要统计单叶数量时再升级实例分割。我一般会建议第一次做植物叶片分割数据集的人先挑一个主任务定住别想着做一个数据集同时支持两种任务。你后面训练时YOLOv8-Seg用的是实例分割标注格式而U-Net这类网络用的是语义分割掩码两种任务的数据组织方式完全不一样中途切换代价很大。2.2 图像采集的三个可执行路径采集植物图像数据的路径主要分三块。第一条是拍自己手里的真实样本拿手机或普通数码相机在自然光下拍盆栽、温室苗床、田间植株注意正打光、侧打光都要覆盖背景尽量包含土壤、石子、其他杂草。第二条是筛选已有公开数据集比如PlantVillage、LeafDisease相关公开集合虽然很多是为分类做的但里面的大田照片可以用来做分割的底图自己跑一下简单的颜色阈值或抠图预处理就能拿到原始图。第三条是用高光谱或多光谱设备采集的公开数据这类适合做更专业的分割研究但文件通常很大格式也往往是HDF5这类元数据和二进制数据分开存的前处理成本高新手不建议一上来就碰。筛选已有数据集时有一个实用原则看掩码质量。很多公开数据集是语义掩码但边缘处有大量锯齿和漏标这种图宁可不要。我会拿一部分图先人工瞄一眼如果十张里有三张掩码边缘是“糊”的直接放弃这批数据来源省得后面清洗模型输出时耗费更多时间。2.3 标注工具选型Labelme、CVAT还是Roboflow标注工具的选择没有绝对答案取决于你是单兵作战还是团队协作。单机标注小几百张图Labelme最省事格式是JSON每个标注对象带一个多边形坐标点列表后续转YOLO格式也好写脚本。团队做几百上千张图用CVAT这类在线工具大家登录同一套环境标注避免文件发来发去搞出版本冲突。Roboflow的优势是自带数据集增强和导出格式转换但免费额度有限图片多的时候容易卡。下面给一段我常用的Labelme标注转YOLO分割格式脚本转换后能直接喂给YOLOv8训练import json import os from glob import glob def labelme_to_yolo_seg(json_path, save_dir, class_dict): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_name os.path.basename(json_path).replace(.json, .txt) with open(os.path.join(save_dir, txt_name), w) as out: for shape in data[shapes]: label shape[label] if label not in class_dict: continue cls_id class_dict[label] points shape[points] norm_points [] for x, y in points: norm_x round(x / img_w, 6) norm_y round(y / img_h, 6) norm_points.append(f{norm_x} {norm_y}) line f{cls_id} .join(norm_points) out.write(line \n) if __name__ __main__: json_dir ./labelme_json out_dir ./yolo_seg_txt os.makedirs(out_dir, exist_okTrue) class_map {leaf: 0, stem: 1} for jf in glob(os.path.join(json_dir, *.json)): labelme_to_yolo_seg(jf, out_dir, class_map)这段脚本的逻辑是把Labelme里每个多边形的绝对像素坐标除以图像宽高归一化成0到1之间的相对坐标然后按YOLO分割格式写成类别ID 坐标点1x 坐标点1y 坐标点2x 坐标点2y ...的纯文本。关键参数是class_map你要确保它和后续训练时的类别顺序完全一致不然类别对不上。归一化这步必须要做YOLO系列训练读取标注时默认坐标是相对坐标直接给像素值不仅loss会异常推理时输出的掩码也会跑到图像外面去。2.4 数据清理模糊图、反光图、背景杂物的劝退原则采集回来的原始图至少有三类要筛出去。第一是失焦模糊的这种片的掩码边缘天然就是“抖”的模型学会了抖动反而对真实边缘不敏感。第二是叶片表面有强烈反光的卤素灯或正午直射光拍出的高光区域会让模型把高光当作一种新类别训练出来一遇高光就漏检。第三是背景里出现手、工具、花盆边缘的图很多人觉得分割模型会自动忽略背景实际不然模型会把“手”这种高频出现的形状也学进去结果推理时遇到手就割一块。我一般会写一个小脚本把图像过一遍清晰度筛选比如计算Laplacian方差低于阈值的直接移到discard目录。阈值需要根据你的相机设置试跑手机拍的图通常比单反拍的图整体方差低不要用同一个阈值套所有来源的图。这个环节看起来不产生标注价值但它决定了你的图像分割数据集上限在哪脏数据进去后面一切指标都是自欺欺人。3. 用叶片分割数据集训练首版模型YOLOv8-Seg完整流水线3.1 数据集目录结构与data.yaml组织先交代一下常规目录组织方式。假设你已经完成了清洗和标注接下来把图片和标注文本统一放到datasets/leaf_seg/下里面按images/train、images/val、labels/train、labels/val划分。图片文件和标注txt文件的文件名要一一对应比如IMG_001.jpg对IMG_001.txtYOLO训练脚本就是靠这个文件名对应关系去匹配图像与标签的少一个都会在训练时报错。对应的data.yaml文件是训练入口之一内容如下path: /your/absolute/path/datasets/leaf_seg train: images/train val: images/val names: 0: leaf 1: stem有几个字段需要解释一下。path建议写绝对路径训练脚本跑在别的机器上时改动最小。train和val相对于path来写注意不要写成./images/train这种前缀某些版本会拼接出错。names的索引顺序必须和标注文件里的类别ID一致这是用YOLOv8训练自己的数据集时最容易出错的点——标注脚本里定义了leaf0, stem1YAML里就必须保持同样顺序。3.2 从零训练而不是微调的指令模板训练命令我习惯写成一行方便改参数重跑yolo segment train modelyolov8n-seg.pt dataleaf_seg.yaml epochs200 imgsz640 batch16 device0 project./runs/segment nameleaf_exp1逐项说明关键参数。modelyolov8n-seg.pt用的是最轻量的分割预训练模型第一次跑通流程时它的速度最快显存占用也小等流程稳定后再换s或m型号提升精度。epochs200听起来多但分割任务收敛本来就慢叶片边缘回归需要足够多轮次我试过50轮mAP只能到0.6左右撑到150轮以后才明显改善。imgsz640对大多数植物叶片图够用但如果你的原图普遍是4000×3000这种高分辨率建议用imgsz960不然小叶片的边缘细节会被严重压缩。batch16取决于显存12GB卡跑yolov8n-seg开16没问题换yolov8m-seg就降到8。训练结束后看runs/segment/leaf_exp1/目录下的results.png、confusion_matrix.png和val_batch*_pred.jpg。不要只盯着loss曲线重点看预测掩码图里叶片边缘和真实标注的重合程度这一步就能直观看出数据标注质量问题。3.3 推理脚本从图片到掩码和可视化训练完模型后推荐写一个推理脚本做验证而不是反复用命令行跑from ultralytics import YOLO model YOLO(./runs/segment/leaf_exp1/weights/best.pt) results model.predict( source./test_images, imgsz640, conf0.25, saveTrue, save_txtTrue, save_confTrue, line_width2 ) for result in results: masks result.masks if masks is None: print(result.path, no mask) continue print(result.path, boxes:, len(result.boxes), masks:, len(masks.data))这段代码的核心逻辑是把一批测试图像喂给训练好的模型saveTrue会生成带掩码覆盖的可视化图save_txtTrue会把每个目标的类别、置信度和归一化坐标点写出来方便后续做后处理。conf0.25是一个起始阈值叶片分割里宁可多出几个低置信度候选框再滤除也别一开始就把阈值拉到0.5否则重叠叶片区域很容易被整块丢掉。line_width2只影响画图不影响掩码数据。第一次推理时如果发现mask数量远小于box数量优先检查是不是多个叶片被合并成了一个框这通常在重叠叶片场景里最常发生。4. 叶片分割常见踩坑6条真实翻车记录4.1 翻车一训练正常但val的mAP为0现象loss下降很漂亮train阶段一切正常但验证集mAP一直为零或者低得离谱。我一度以为是超参数问题反复调lr、调batch结果毫无变化。原因标签文本里出现了非法的类别ID。比如标注时不小心生成了3而data.yaml里只定义了0: leaf, 1: stem。YOLO训练时不会直接报错而是把这个样本当作无效目标跳过导致验证阶段模型预测出的目标永远无法匹配到有效真值框。解决训练前写一段脚本扫描所有txt标签检查每一行的第一个数字是否在合法类别范围内顺手把异常文件打出来。这个检查花不了两分钟比训练完再回头排查快得多。4.2 翻车二叶片边缘预测像锯齿和标注对不上现象掩码可视化时边缘毛刺明显和人工标注多边形差异很大尤其弧形叶缘处。原因标注时用的多边形点数太少尤其是在弧线区域只点了两三个点导致真值本身就失真。另一个原因是imgsz太小边缘被插值破坏。解决标注弧线部位时加密点工作量大但值得。训练参数里imgsz从640提到800或960边缘预测质量会有肉眼可见的提升。如果标注已经做完了、重新标代价高可以试试在预处理里对掩码做轻微膨胀腐蚀平滑但这是治标不治本。4.3 翻车三玻璃温室背景被误检成叶片现象模型把温室玻璃反光区域、透明塑料膜边缘也割出来置信度还不低。原因训练集里带了大量玻璃温室背景的图而这类背景的纹理和反光在特征上和叶片表面有重叠模型学到了“亮绿色且带反光的都算叶子”这个错误规律。解决清理训练集中背景占比过高、透光玻璃明显的图或者在标注时把玻璃、反射区域单独建一个背景类。没有时间重标的话数据增强里增加色调偏移和明度扰动帮助模型不再依赖“亮绿色”这种表面特征。4.4 翻车四多类别标注时类别错位现象明明标注的是茎训练出来的模型把所有茎都预测成叶。检查labelme转格式时类名没写错data.yaml也没写错。原因转换脚本的class_dict顺序和data.yaml不一致。比如脚本里leaf0, stem1但data.yaml里写了0: stem, 1: leaf模型不会报错就是全部错位。解决把class_dict和data.yaml放在同一个配置文件里读取比如统一用YAML管理类别映射而不是一处写死在脚本里一处写死在YAML里。两处来源就必然有一处会被忽略。4.5 翻车五val/loss正常但实拍视频里框乱跳现象单张图片推理效果不错一上视频或者连续帧推理掩码忽大忽小有的帧甚至漏掉整片叶子。原因训练数据来自静态图像单张画面里叶子的姿态和光照是固定的但视频帧里叶片随风晃动姿态变化幅度大模型没见过中间状态。另一个原因是逐帧推理没有做时序平滑。解决采集数据时不要只拍静态植株围绕叶片从正、侧、俯三个角度各拍一组并在不同光照时段重复拍。推理端可以做一个简单的跟踪平滑把相邻几帧的掩码做IoU匹配对轻微抖动做中值滤波。严格说这属于后处理范畴但实际部署中省掉这步根本没法用。4.6 翻车六训练到一半显存溢出现象batch调到16训练到第50个epoch直接OOM卡死。原因训练时验证集的imgsz和训练集不一致或者开了太大的rect模式导致验证阶段图像padding后尺寸暴涨。有些人训练集用小图跑通了验证集忽然来了一张超大图显存直接翻倍。解决在训练脚本里给验证集也显式指定imgsz或者用scale参数控制验证阶段的最长边。显存不够时优先降batch而不是降imgsz因为imgsz一降边缘质量损失立刻体现而batch降到4对精度影响不明显。5. 看指标也看效果用mAP和掩码可视化做双重验证很多人训练完只看一眼loss曲线就宣布完事实际上叶片分割的验收标准应该分两层。第一层是数值指标常见做法是记录mask mAP50和mask mAP50-95。mAP50是IoU阈值0.5下的平均精度适合快速判断有没有学对位置mAP50-95严格得多对边缘质量很敏感叶片分割要做到0.7以上才算比较稳。但数值指标只能告诉你“大致行不行”真正决定模型能不能上线的是掩码可视化。我习惯在验证集上挑出三类图重点看重叠叶片图、阴影下叶片图、与土壤颜色接近的枯叶图。这三类图如果掩码边缘稳定、没有大面积误检就算mAP略低一些也可以接受反之mAP再高只要重叠叶片图出现把两片叶子并成一块的情况落地时就是事故。可以做一个简单的对比表格来记录测试结果场景mask mAP50掩码主观质量是否通过单叶干净背景0.95边缘平滑通过多叶重叠0.72偶发合并待优化阴天室内0.81边缘稍欠通过枯叶与土混色0.58漏检明显不通过我一般还会跑一次逐类别的mAP输出检查是不是只有叶子这一类效果好、茎秆类别几乎没学到。如果某类指标明显偏低单独看看它的训练样本数量植物茎秆的标注往往比叶片少得多这会直接导致模型对细长目标的召回率偏低。优化手段不外乎两类给这类样本增加复制粘贴增强或者单独提高该类的loss权重但前者效果通常更直接。6. 高分辨率叶片图的tiles切图推理解决小叶片漏检的实用技巧最后讲一个实战里很管用的技巧遇到4000×3000以上大图时直接把整图缩放后推理往往会丢掉小叶片。像无人机俯拍的植物图像里单叶可能只有几十个像素缩放后变成几个像素彻底没法分割。我习惯的做法是tiles切图——把大图切成若干640×640的块每块单独推理得到掩码后再拼回原图坐标。这样既能保住细节又不需要重新训练高分辨率模型。import cv2 import numpy as np from ultralytics import YOLO model YOLO(./runs/segment/leaf_exp1/weights/best.pt) def infer_tiles(image_path, tile_size640, overlap64): img cv2.imread(image_path) h, w img.shape[:2] full_mask np.zeros((h, w), dtypenp.uint8) full_conf np.zeros((h, w), dtypenp.float32) for y in range(0, h, tile_size - overlap): for x in range(0, w, tile_size - overlap): x_end min(x tile_size, w) y_end min(y tile_size, h) tile img[y:y_end, x:x_end] result model.predict(tile, imgsztile_size, conf0.25)[0] if result.masks is not None: mask result.masks.data.cpu().numpy().sum(axis0) mask cv2.resize(mask, (x_end - x, y_end - y)) full_mask[y:y_end, x:x_end] np.maximum(full_mask[y:y_end, x:x_end], mask) full_conf[y:y_end, x:x_end] np.maximum(full_conf[y:y_end, x:x_end], mask) return full_mask mask infer_tiles(./large_field.jpg) cv2.imwrite(./large_field_mask.png, mask * 255)这段代码的核心逻辑是滑动窗口切图tile_size640对应训练时的输入尺寸overlap64让相邻tile有重叠区域避免叶片正好被切在边缘处导致截断。每个tile的mask先归一化到0到1之间再放大回原tile尺寸贴回大图对应坐标时用np.maximum而不是直接覆盖这样重叠区域的掩码能保留置信度更高的一侧。full_conf变量用来记录每个像素的最大置信度如果最后想过滤低频噪声可以直接按这个置信度做阈值。实际使用中overlap越大拼接越平滑但推理耗时也成倍增加64到128像素是速度和质量的平衡点。还有一个血泪经验是切图前一定先把原图尺寸打印出来看一眼有些航拍图是TIFF格式带多个波段直接用cv2.imread读进来可能只是单通道灰度图掩码结果会完全错掉。先统一转成RGB再喂模型避免这种低级翻车。这套方法我之前在处理温室巡检图像时反复用到最大的收获是叶片分割的精度上限不在模型结构而在数据集和推理策略的匹配。先把数据做干净再谈调参和切图路才走得通。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →