ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

苹果果实目标检测数据集自制实战:从labelme标注到YOLO训练

2026/8/27 1:39:50 拓冰建站 浏览量
苹果果实目标检测数据集自制实战:从labelme标注到YOLO训练 简介在计算机视觉领域目标检测模型的性能高度依赖训练数据的质量与场景匹配度。对于农业场景中的果实检测任务公开数据集常存在环境差异大、标注不规范等问题导致模型在实际果园中泛化能力不足。数据集构建涉及图像采集、标注规范、格式转换与质量校验等关键环节而Labelme作为灵活的标注工具支持多边形与矩形标注可有效处理果实重叠、枝叶遮挡等复杂情况。通过将标注结果转换为YOLO格式并合理划分训练集、验证集与测试集结合数据增强策略能够训练出适应真实光照与遮挡条件的检测模型。这类数据集可支撑果园采摘机器人、产量预估等智能农业应用本文围绕苹果果实检测系统梳理了一套从零构建高可用数据集的完整实践路径。 做苹果果实目标检测第一步不是选模型而是搞数据集。我前后试过不少公开数据集要么是国外果园的苹果生长环境和国内密植栽培模式差异很大要么标注格式乱得头疼label和bbox对不上训练起来全是噪声。后来干脆自己动手用labelme从零开始采集、标注、整理最后沉淀出一份4430张图片的苹果果实检测数据集。这篇文章就把整个流程拆开讲清楚包括采集设计、标注规范、格式转换、质检流程和踩过的坑给准备自己造数据集做农业方向视觉项目的朋友一个完整参考。这份数据集的核心价值在于图片全部来自真实果园环境囊括了顺光、逆光、阴影、枝叶遮挡、果实重叠、不同成熟度等复杂情况标注内容也只有“苹果果实”这一个类别干净利落。适合做YOLO系列、Faster R-CNN、SSD等目标检测模型的训练和评估尤其适合果园采摘机器人、产量预估、疏果作业辅助这类落地场景。1. 数据集整体设计与方案选型1.1 为什么选择自建苹果果实数据集公开数据集的问题在于“场景不对口”。比如一些通用目标检测数据集里虽然有水果类别但主要拍的是超市货架、厨房案板上的苹果背景干净、光线均匀、果实完整和田间地头那种枝繁叶茂、果实相互遮挡、阳光忽明忽暗的环境完全是两码事。模型在这种数据上训练出来到了实际果园根本跑不动漏检率能高到让人怀疑人生。自建数据集的优势在于可以精确控制场景分布。我在设计阶段就确定了几个必须覆盖的维度光照角度顺光、逆光、侧光、天气条件晴天、阴天、拍摄距离近景、中景、远景、果实状态红色成熟果、偏绿未熟果、半红半绿过渡果以及遮挡程度无遮挡、枝叶遮挡、果实相互重叠。这些维度直接决定模型在真实场景中的泛化能力。另外4430张这个规模对农业目标检测来说是一个比较合理的选择。太少的话模型容易过拟合在复杂背景下稳不住太多的话标注成本急剧上升尤其是苹果这种果实密集、边界不清晰的标注对象单张图可能要框几十个目标人力消耗非常夸张。4430张配合适当的数据增强足以训练出一个mAP50在85%以上的可用模型性价比很高。1.2 标注工具选型labelme为什么够用且好用现在可用的标注工具不少有开源的LabelImg、labelme、CVAT也有商业的X-AnyLabeling等。我最后选了labelme核心原因是两点。第一labelme对多边形标注的支持非常完善。虽然我最终发给训练脚本的是矩形框但标注过程中偶尔需要先用多边形精确定位小而密集的果实因为矩形框在这种场景下很容易把相邻果实框到一起导致标注边界含糊。labelme可以先用多边形画再转成外接矩形两个能力都有灵活性最好。第二labelme的标注结果是有损可读的JSON格式和COCO、YOLO等格式之间可以通过脚本互转。这意味着数据集的后续使用路径非常宽无论是用YOLOv5/YOLOv8训练还是做SSD训练都能方便地转换不会被工具绑死。LabelImg同样能用但它默认输出的Pascal VOC XML格式在转YOLO时需要额外解析多一道工序。提示labelme支持Windows、macOS、Linux三平台直接pip install labelme就能装依赖项也不复杂。如果图像数量很大推荐在台式机上接显示器标注笔记本屏幕看密集小果实容易漏标。1.3 数据集的类别定义与标注边界苹果果实目标检测看起来只有“苹果”一个类别但实际操作中边界问题非常多。如果不提前定义清楚标注员今天按这个标准框明天按那个标准框最后数据集就废了。我把标注规则收敛为以下几条只标“苹果果实”这一个类别不标花朵、花萼、叶片、枝干。果实可见面积超过30%的才标注低于30%视为背景。果实被枝叶遮挡时按可见部分的外轮廓做矩形框如果能够根据轮廓合理推断完整果实的范围可以适当向外扩到推断边界。多个果实紧贴或重叠时每个果实各标各的框允许框之间有重叠。远景图像中果实像素小于15×15的跳过不纳入标注因为这类目标即使标注了也是噪声对模型训练没有正向帮助。这几条规则看起来简单但在标注过程中能减少大量纠结。我后面在质检环节发现最开始没有严格执行“低于30%不标”规则时模型训练出来在遮挡严重的果树上会疯狂误检——因为它把很多树叶间透出的红色小斑块也当成苹果了。后来清理掉这些不规范的标注误检率明显下降。2. 数据采集与labelme标注实操2.1 现场采集策略与筛选逻辑数据采集是整个项目的地基。我是用手机加微单双机拍摄手机负责快速扫场景微单负责拍高清细节。拍摄时固定在果园行间行走模拟采摘机器人或者人工巡检的视角避免只拍A面导致的模型视角单一。采集回来的原图数量大概是8000多张最终只留下4430张中间经历了两道筛选。第一道是清晰度筛选凡是模糊、过曝、严重抖动的直接删除这类图就算标注了也对训练没帮助反而会让模型学到错误纹理。第二道是重复度筛选同一个果树、同一个角度下连拍的多张近乎相同的照片只保留一张防止数据冗余导致训练集和验证集之间出现近似重复图像人为拉高验证指标。筛选之后我按场景做了一次均衡确保顺光、逆光、阴天、晴天、近景、中景的比例大致合理。这里有一个容易忽略的点逆光和阴影下的图片不要因为“难标注”就故意少留恰恰相反这类困难样本是提升模型鲁棒性的关键只能多不能少。2.2 labelme安装与界面操作流程labelme的安装很简单依赖Python环境就行pip install labelme装好之后在终端输入labelme即可启动。我这里用的是基于Python的版本界面是英文但操作逻辑很直接。打开图片后左侧工具栏有创建多边形、创建矩形、编辑、移动、删除等工具。实际标注流程是这样的打开一个图片文件或者通过“Open Dir”批量加载一个图片目录。用“Create Rectangle”工具框选苹果果实。如果是密集小果场景先用“Create Polygons”勾勒果实轮廓后续转矩形框精度更高。每个框弹出提示时输入类别名apple统一大小写不要一会儿Apple一会儿apple。一张图标注完成后按CtrlS保存labelme会自动生成一个与图片同名的.json文件。标注过程中随时可以用Edit Polygons微调边界或者Delete掉标错的框。提示labelme的标注数据默认保存在图片同目录下的.json文件里这个文件很小但里面包含了图片的Base64编码数据所以单文件可能达到几MB。批量标注后建议及时把这些.json文件单独归档不要和原图混在一起后续处理起来更清晰。我自己统计过一张有20~30个果实的密集果园图第一次标注需要3~5分钟熟练之后能压缩到2分钟左右。4430张图如果一个人标大概需要150小时以上所以这个项目我是分了四个人来做每人负责不同批次的图片最后统一合并质检。如果你们也是团队标注一定要在第2.1节的标注规则基础上再做一套示例图发给每个人确保标准同步。2.3 标注过程中的细节控制与常见错误第一个高发错误是漏标小果实。人眼在密集枝叶中识别小苹果本来就容易疲劳一旦漏标模型就会学到“小苹果不需要检测”这种错误信号。我用的办法是强制要求标注员对每张图放大到100%比例检查一遍确认视野内没有遗漏再保存。第二个是边界框过大。很多新手喜欢留白让框完全包住果实的“外接矩形一点余量”这会导致标注框和果实实际轮廓之间出现大量背景像素模型学到的是“框里有一部分是叶子也OK”定位精度会明显下降。正确做法是让矩形框尽量紧贴果实可见轮廓宁可稍微紧一点不要松。第三个是类别名不统一。这个离谱但确实发生过标到后面有人开始用Apple、苹果、apple_fruit等各种写法。一旦混入YOLO训练时类别索引就会错位或者直接报错检查起来非常折磨。我后来用脚本对全部JSON做了扫描把非apple的label统一替换掉才把问题解决。第四个是重叠果实处理分歧大。两个紧贴的苹果到底是一个框还是两个框我的规则很明确如果两个果实在视觉上是分离的只是挨得近就各标一个如果其中一个被另一个遮挡超过50%就只标露出来的那个。这类情况如果不统一数据集的标注一致性会非常差训练时边界框回归会不稳定。3. 标注格式解析与数据集构建细节3.1 labelme的JSON文件到底长什么样要玩转这个数据集必须先理解labelme的输出结构。每张图生成一个同名的.json文件内容大致如下{ version: 5.2.1, flags: {}, shapes: [ { label: apple, points: [[x1, y1], [x2, y2]], group_id: null, shape_type: rectangle, flags: {} } ], imagePath: IMG_20231012_143521.jpg, imageData: base64编码的图片数据, imageHeight: 1080, imageWidth: 1920 }shapes数组里的每个元素对应一个标注目标。points字段在shape_type为rectangle时是矩形的两个对角点坐标我统一存左上和右下点。imageData字段默认会把原图以Base64形式嵌入这对归档不友好转换的时候可以忽略不用直接从原图目录读取图片即可。理解这个结构之后做格式转换就简单了。核心就是读取shapes数组把每个目标的矩形坐标提取出来换算成YOLO需要的归一化中心坐标和宽高。3.2 标注格式转换一份可以直接跑的Python脚本labelme原生的JSON格式不能直接喂给YOLOv5/YOLOv8训练需要转换成YOLO的txt格式。YOLO的标注格式是类别ID 中心点x归一化 中心点y归一化 框宽归一化 框高归一化举个例子一张1920×1080的图上有一个苹果矩形框左上角在(100, 200)右下角在(300, 400)那么转换后的YOLO结果是0 0.1042 0.2778 0.1042 0.1852其中中心x(100300)/2 /1920200/1920≈0.1042中心y(200400)/2 /1080300/1080≈0.2778宽(300-100)/1920≈0.1042高(400-200)/1080≈0.1852。我给你一份我实际用过的转换脚本已做脱敏整理可以直接抄import json import os from pathlib import Path def convert_labelme_json_to_yolo(json_path, save_dir, classes_dict): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in classes_dict: continue class_id classes_dict[label] points shape[points] if shape[shape_type] rectangle: (x1, y1), (x2, y2) points[0], points[1] elif shape[shape_type] polygon: xs [p[0] for p in points] ys [p[1] for p in points] x1, x2 min(xs), max(xs) y1, y2 min(ys), max(ys) else: continue # 边界裁剪防止出现负数或超出图像范围 x1 max(0, min(x1, img_w)) y1 max(0, min(y1, img_h)) x2 max(0, min(x2, img_w)) y2 max(0, min(y2, img_h)) if x2 x1 or y2 y1: continue # 计算归一化中心坐标和宽高 box_w x2 - x1 box_h y2 - y1 center_x (x1 x2) / 2 / img_w center_y (y1 y2) / 2 / img_h norm_w box_w / img_w norm_h box_h / img_h # 防止归一化值出现越界 center_x min(0.999999, max(0.000001, center_x)) center_y min(0.999999, max(0.000001, center_y)) norm_w min(0.999999, max(0.000001, norm_w)) norm_h min(0.999999, max(0.000001, norm_h)) lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {norm_w:.6f} {norm_h:.6f}) # 将txt保存到目标目录 txt_name Path(json_path).stem .txt txt_path os.path.join(save_dir, txt_name) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: # 1表示apple这里只一个类别 classes {apple: 0} json_dir path/to/labelme_json save_dir path/to/yolo_labels os.makedirs(save_dir, exist_okTrue) for json_file in os.listdir(json_dir): if json_file.endswith(.json): convert_labelme_json_to_yolo( os.path.join(json_dir, json_file), save_dir, classes ) print(转换完成)这个脚本有几个细节值得注意。一是直接按rectangle和polygon两种类型做兼容因为标注过程中难免会混着画。二是做了边界裁剪和越界防止这在标注框靠近图像边缘时尤其重要YOLO对归一化超出0~1的值非常敏感可能导致训练时出现NaN。三是保留六位小数足够用不需要更高精度。3.3 数据划分策略训练集验证集测试集怎么分数据集划分看起来只是按比例随机切但背后有个容易踩的坑图片之间如果存在场景关联性直接随机划分会造成数据泄漏。比如同一棵果树在连续几张照片里出现随机划分后可能一张进了训练集另一张进了验证集那验证指标就会虚高因为模型在训练时已经见过高度相似的画面。我采用的办法是基于文件名的场景前缀做分组划分。采集时每一组连续照片的命名前缀相同比如Orchard_A_001.jpg、Orchard_A_002.jpg代表A果园第一组的连续拍摄。划分时以“组”为单位而不是“张”为单位整组进入到训练集、验证集或测试集保证同一个场景不会被拆散。最终划分如下数据集图片数量占比用途训练集354480%模型训练验证集66515%训练中调参和模型选择测试集2215%最终效果评估提示验证集和测试集的比例不用太大但前提是场景覆盖要完整。如果测试集里全是近景模型在远景上的表现你就完全不知道等于白测。划分之后把图片和对应的txt标注文件按照YOLO要求的目录结构放好datasets/apple/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── dataset.yamldataset.yaml文件里写上路径和类别配置path: /path/to/datasets/apple train: images/train val: images/val test: images/test nc: 1 names: [apple]到这里一份可以直接拿去训练的数据集就搭好了。3.4 数据增强与类别平衡的实操思路苹果果实检测的场景特殊性在于成熟苹果是红色的在绿色枝叶背景下对比度非常高模型很容易学到“红色团块就是苹果”这种偷懒特征。一旦遇到青苹果品种或者未成熟果检测效果就会断崖式下跌。为了缓解这个问题我在标注环节就刻意保留了相当比例的偏绿、半红果实样本同时在训练阶段引入针对性数据增强。HSV通道的随机扰动是必须的特别是色调(Hue)偏移和饱和度(Saturation)调整可以让模型对颜色的敏感度降低更多地学习形状和纹理特征。YOLOv8自带的HSV增强参数hsv_h、hsv_s、hsv_v默认值就可以直接使用。另一个值得推荐的是Mosaic增强。它把四张图拼成一张训练相当于同时增加图像多样性还变相提高了每张训练图的上下文复杂度。在果实密集场景下Mosaic能有效提升模型对小目标果实的召回率。类别平衡方面苹果数据集只有一个类别不存在多类别数量失衡问题但存在尺度失衡问题近景大果和远景小果的目标数量差别很大。如果大量的框都是近景大果模型对小果的检测能力就会偏弱。我统计过当时的标注框面积分布小于32×32像素的框占比不到10%这直接导致最初的模型在远景图上的召回率只有不到60%。后来我通过补充远景图片、对小果区域做切图增强把训练集里小目标的占比拉高到15%以上有效改善了这个问题。如果你的数据集来源固定一定要额外关注这类场景结构性问题。4. 常见问题与排查技巧实录4.1 标注质量可视化检查与清洗流程数据集做完格式转换、目录划分之后还不能直接开训。我强烈建议先做一次可视化的全量检查把标注框画到原图上逐张看一遍。这一步看着枯燥但能在训练前发现问题避免训到一半才发现数据集是脏的白白浪费几天时间。检查脚本不复杂用OpenCV画框就行核心逻辑是遍历每张图片的txt标注文件把归一化坐标换算回像素坐标再用cv2.rectangle画出来。我筛出过几类典型问题边界框明显偏移标注对象是隔壁的苹果、框尺寸为0转换时出现坐标相同、框大面积超出图像范围裁剪逻辑有bug、同一张图漏标严重人工疲劳导致。检查的时候重点关注以下四类图密集果实图看是否漏标框之间是否大面积重叠。遮挡严重图看是否只标可见部分有没有把被遮挡果实也错误标出。逆光暗部图看是否标出肉眼都很难辨别的暗部果实。远景小果图看小目标框是否紧贴真实果实轮廓。标注质量差的数据清掉重新标注或者干脆从数据集中剔除宁可总张数少一点也要保证每张图标注准确。数据集的质量永远比数量重要。4.2 数据集中后期训练时代的三大典型问题第一批数据集放在YOLOv8s上训练了200个epoch之后暴露出了几个问题这里逐一说明。第一个是误检树枝和背景中的红色物体。原因是部分标注框过大包含了很多背景枝叶像素模型把这些像素当成了苹果的组成部分。解决办法是收紧标注边界把所有框尽可能贴紧果实然后重新训练。第二个是重叠果实的漏检。这个在手工标注时就有讨论模型在预测时对高度重叠的两个果实往往只输出一个框NMS会把置信度高的框保留、低的抑制掉。解决办法是不要删减正样本保留所有重叠果实各自的标注框同时可以将NMS的IoU阈值从默认的0.45适当调低到0.35但要注意实测后调整不能盲目照搬。第三个是晴天强光下的过曝区域漏检。强光下苹果高光部分过曝呈现白色而不是红色模型会认为那不是苹果。这个问题的根源是数据里过曝图片太少解决方案也很直接增加正午时分拍摄的过曝样本并配合图像增强把正常图片压暗或提亮模拟更多光照极端条件。4.3 数据集版本管理与协作标注心得多人协作标注最大的隐患是标准漂移。前两周大家还严格按照规则来两周后有人开始凭感觉标注质量和第一周明显不一样。我采用的办法是固定节奏抽检每完成500张图我随机抽30张做二次标注对比原标注计算IoU一致性。如果两个标注的IoU低于0.7就说明标准执行不到位需要重新培训或者调整规则。另外建议从一开始就用Git管理数据集文件的变动虽然图片文件很多但不需要把原图全放进去只管理标注文本、转换脚本和数据集说明文档就够了。每次对标注规范进行调整同步更新文档避免不同人手里的规则版本不一致。如果你想把这份数据集做得更通用后续还可以扩展出两个方向一是把“苹果果实”细分为成熟果、半熟果和未熟果三个类别让数据能直接支撑成熟度检测任务二是增加含花和幼果时期的图片让模型能适配整个生长周期。这两种扩展都能显著提高数据集的长期使用价值我个人很推荐在采集季节顺手把这个工作做了不然后面想补数据又得等一年。最后再分享一个小技巧标注完成后不要急着删掉labelme的JSON原始文件。YOLO的txt格式是减法丢了信息就回不来labelme的JSON格式是加法随时可以再转成COCO、Pascal VOC或者其他自定义格式。我后来有次想把数据集转成COCO格式做对比试验就是因为还留着原始JSON才省去了重新标注的麻烦。数据集的原始格式就是整个资产里最值钱的那个备份。本文还有配套的精品资源点击获取