
简介面向 YOLO 系列算法学习与实战的目标检测数据集适合正在开展咖啡叶片识别、农业病害检测或目标检测课程实践的开发者。资源按 YOLO 格式组织标签文件与图像一一对应并已划分好训练与验证集可直接用于 yolov5、yolov8、yolov10 等主流版本训练及验证。包内共 2000 个文件包括 999 张带叶片目标的 jpg 图像、1000 个 txt 标签文件及 1 个模型配置文件 yaml压缩包整体约 24.53MB便于快速下载与本地部署。标签采用归一化坐标表示适用于多种检测框架也可按需转为 VOC 格式方便后续扩展或迁移学习。该资源已有 57 人学习浏览可帮助初学者省去数据采集与标注流程直接进入模型训练和效果验证阶段也可作为实际农业检测项目中算法选型与调参的参考数据。1. 咖啡叶片检测一套 1000 张带标签图像正好拿来练手 YOLO 的完整闭环把 YOLO 算法从入门到跑通最卡人的不是模型选型而是缺一套干净、格式统一、量级刚好的训练数据。咖啡叶片检测数据集正好卡在这个点上1000 张带标签图像压缩包文件名里写着“叶子 testing-detection-zsmzd.zip”说明作者已经把检测目标限定在叶片本身标签也做好了。你拿到手要做的不是从零标数据而是把它喂进 YOLO训练出一个能在田间照片上框出病斑或健康叶的模型。这套流程适合刚学 YOLO、想完整走一遍数据准备、训练、评估、推理的从业者也适合做农业视觉项目的工程师做前期验证。量级不算大但足够你把损失函数、超参、数据划分这几个环节全部踩一遍再爬出来。2. 先别急着训练数据集的目录、标签格式与类别分布核对2.1 解压后先查这两样图片元信息与标签格式拿到压缩包第一步不是解压完就跑训练而是先摸清内部结构。常见的坑是解压出来图片在根目录标签却在子目录再或者标签是 VOC 的 XML不是 YOLO 的 TXT——这两种情况在真实数据集里出现频率非常高。我一般会先列目录再看标签文件的后缀名unzip 叶子testing-detection-zsmzd.zip -d coffee_leaf cd coffee_leaf find . -maxdepth 2 -type f | head -20如果看到jpg和txt成对出现大概率是 YOLO 格式可以直接用如果看到xml那就要先做一步格式转换。再看一眼图片尺寸用 Python 批量获取from PIL import Image import glob imgs glob.glob(**/*.jpg, recursiveTrue) for p in imgs[:10]: with Image.open(p) as im: print(p, im.size, im.mode)这一步的用处是确认图像分辨率是否统一。很多公开数据集混着 640×640 和 1280×960 两种尺寸如果不做统一处理训练时 YOLO 会自动缩放短边压到 640 后长边的目标会变小小病斑很容易在缩放中丢失。如果你发现尺寸混排建议先统一缩放到一个常见分辨率再做训练而不是把问题丢给模型自己去适应。遥感图像标注、车辆检测数据集那套流程也一样解压后第一件事永远是查元信息。别相信压缩包文件名里的“带标签”三个字标签格式、类别名、是否漏标才是真正要确认的东西。2.2 标签格式核对从 TXT 坐标反推类别与归一化范围YOLO 的标签格式是每个目标一行class_id x_center y_center width height其中四个坐标值都是相对于图片宽高的归一化数值范围在 0 到 1 之间。很多新手拿到坐标系直接当像素坐标用训练出来的模型 loss 一直不降就是这个原因。我打开一个标签文件来验证cat labels/0001.txt如果看到类似0 0.5123 0.4876 0.2314 0.1987的内容说明坐标已经归一化过。如果看到512 390 231 198这种几百像素的坐标那就是还没归一化或者有人把像素坐标写进了 YOLO 标签这种情况模型会直接“翻车”——边界框全跑到画面右上角检测结果乱套。我建议写一个简单的坐标范围检查脚本import glob for txt in glob.glob(labels/*.txt): with open(txt) as f: for line in f: parts line.strip().split() cls int(parts[0]) x, y, w, h map(float, parts[1:]) assert 0 x 1 and 0 y 1, (txt, line) assert w 0 and h 0, (txt, line) print(fchecked: {txt})字段含义合法范围cls类别 ID从 0 开始0 ≤ cls 类别数x_center目标中心点 X 坐标 / 图片宽度0 ~ 1y_center目标中心点 Y 坐标 / 图片高度0 ~ 1w目标宽度 / 图片宽度0 ~ 1h目标高度 / 图片高度0 ~ 1边界情况也要留意坐标出现负值通常说明标注框起始点画到了图像外w 或 h 为 0 说明退化成了点线框YOLO 训练时会因为正样本区域为空而丢失该目标的梯度。这两类坏样本不用改直接从标签文件里删掉即可比你去手工修坐标快得多。2.3 用数据分布脚本看一眼类别平衡、宽高比与坏图在训练之前值得花十分钟统计一下类别的分布情况。咖啡叶片数据集常见的类别设计有健康叶、锈病叶、褐斑病叶、缺素叶等但不同来源的数据集分类粒度不一样有的把所有病斑统一标成一类有的细分成好几种。你需要根据自己的落地需求决定要不要合并类别。一个统计类别分布和图像坏图的脚本from collections import Counter from PIL import Image import glob cls_counter Counter() bad_imgs [] for txt in glob.glob(labels/*.txt): for line in open(txt): cls_counter[int(line.split()[0])] 1 for img in glob.glob(images/*.jpg): try: with Image.open(img) as im: if im.size[0] 32 or im.size[1] 32: bad_imgs.append(img) except Exception as e: bad_imgs.append((img, str(e))) print(类别分布:, cls_counter.most_common()) print(可疑小图/坏图数量:, len(bad_imgs)) print(bad_imgs[:10])类别严重不平衡是这类数据集最常见的毛病比如健康叶 900 张、锈病叶 100 张模型训出来的结果就是健康叶的 mAP 能到 0.9锈病叶只有 0.2。解决办法在后面的训练章节里会细说但这里先给你一个判断标准如果占比最小的类别不到最大类别的十分之一你就要提前做数据增强或类别加权不信等着踩坑。3. 把数据集装进 YOLO 的目录结构划分、yaml 与两种组织方式3.1 从原目录到 YOLO 目录三行命令搞定训练/验证划分YOLO 系列对目录结构的预期是images/train、images/val、labels/train、labels/val四个文件夹图片和同名标签一一对应。数据集作者给的目录可能不是这个布局你不用手工整理写个小脚本一次性完成划分。我常用的划分脚本import os, random, shutil, glob random.seed(42) images glob.glob(images/*.jpg) random.shuffle(images) split int(len(images) * 0.8) train_imgs images[:split] val_imgs images[split:] for subset, img_list in [(train, train_imgs), (val, val_imgs)]: os.makedirs(fdataset/{subset}/images, exist_okTrue) os.makedirs(fdataset/{subset}/labels, exist_okTrue) for img_path in img_list: shutil.copy(img_path, fdataset/{subset}/images/) label_path img_path.replace(images, labels).replace(.jpg, .txt) if os.path.exists(label_path): shutil.copy(label_path, fdataset/{subset}/labels/)用 8:2 的比例做训练验证划分是目标检测数据集里最常见的做法。两个细节要注意第一random.seed(42)固定随机种子保证每次运行划分结果一致这样前后两次训练的结果才有可比性不至于因为数据分法变了导致 mAP 忽高忽低第二图片和标签要按同名关系配对复制只复制图片不复制标签是新手最容易翻车的地方。如果你的原始目录里图片和标签不在同一级就把replace(images, labels)改成实际目录名比如replace(JPEGImages, Annotations)。3.2 data.yaml 怎么写路径、类别名与一个常见坑YOLO 训练需要一份 YAML 配置文件告诉框架数据在哪、有哪些类别。写法非常简单# coffee_leaf.yaml path: /absolute/path/to/dataset train: images/train val: images/val names: 0: healthy 1: rust 2: brown_spot这份文件里最大的坑是路径写法。path一定要写绝对路径或者确保你执行训练命令时的工作目录和path相对路径对齐。很多人报dataset not found错排查到最后发现是path写成了相对路径而命令是从别的目录执行的。另外一个隐蔽的问题是names里的类别顺序必须和标注文件里的class_id完全对应。如果你发现训练时 loss 正常但预测结果张冠李戴比如把锈病框标成了健康叶八成是 YAML 里类别名顺序和标签顺序错位了。这类错误训练脚本不会报错只有部署到现场才会暴露很阴。3.3 目录结构不对时的报错与快速修正YOLO 训练的报错信息不一定直白。常见的几种现象是报AssertionError: train: No labels in ...说明 labels 目录里没有 txt 文件或者图片目录和标签目录的上级目录名配对不上报FileNotFoundError且路径和你的实际目录对不上说明 yaml 里path写错了还有一种更隐蔽的训练能启动但每个 epoch 的images数量为 0这是图片扩展名不匹配导致的比如标签是.jpg、但实际文件是.pngYOLO 扫描时找不到匹配项。遇到这类问题我一般先用一条命令做快速体检find dataset -type f | awk -F. {print $NF} | sort | uniq -c这条命令会把目录里所有文件的扩展名统计出来。如果看到images/train里是jpglabels/train里是txt数量能对得上基本排除结构问题对不上就去检查是不是有图片没标签、有标签没图片。修配对的脚本很简单import glob, os imgs {os.path.splitext(os.path.basename(p))[0]: p for p in glob.glob(images/*.jpg)} txts {os.path.splitext(os.path.basename(p))[0]: p for p in glob.glob(labels/*.txt)} orphan_imgs set(imgs) - set(txts) orphan_txts set(txts) - set(imgs) print(缺标签的图片:, orphan_imgs) print(缺图片的标签:, orphan_txts)孤儿文件不影响别人但会拖慢训练启动时的数据加载还可能在你做类别统计时算错分布。稳妥做法是把它们移到一个_backup目录里既不删除原始素材也不干扰训练。4. 咖啡叶片检测训练超参、损失函数与第一次跑通4.1 用 YOLOv8 跑最小训练命令数据集结构整理好、yaml 写完后训练本身只需要一条命令。这里以目前应用最广的 YOLOv8 为例yolo detect train datacoffee_leaf.yaml modelyolov8s.pt imgsz640 epochs100 batch16 device0这是最小可用命令。modelyolov8s.pt的含义是加载 COCO 预训练权重作为起点在你自己数据集上做迁移学习。不要用yolov8s.yaml从零初始化训练1000 张图的数据量远远不够让模型从随机权重收敛到可用的程度预训练权重是真正的“后悔药”能帮你省掉几十轮 epoch 的挣扎。imgsz640是输入分辨率。如果原始照片大多是 1280×960把 imgsz 调到 960 或 1280 可以保留更多小目标细节。代价是显存占用和训练时间成倍上涨。一开始先用 640 跑通流程确认没有报错再回头调分辨率。4.2 必调的 4 个训练超参imgsz、epochs、batch 与 patience第一次跑通之后需要认真调的超参基本就四个imgsz、epochs、batch和patience。imgsz直接决定小目标能不能被检测到。咖啡叶片上的锈病斑往往只有十几到几十像素640 分辨率下这些斑点在特征图里可能只剩 1 到 2 个像素模型很难学到稳定特征。我的经验是如果你的标注框大多数面积小于图片面积的 5%优先把 imgsz 调到 960 以上。epochs不是越大越好。1000 张图的小数据集100 轮足够看到收敛趋势。重点观察训练集 loss 和验证集 mAP 的曲线训练 loss 一直在降、验证 mAP 稳着不动或往下掉就是过拟合信号此时加大 epochs 没有意义反而浪费时间。batch受显存限制。8GB 显存跑 yolov8s imgsz640batch 一般在 16 附近显存不够就减半但注意 batch 太小时 BN 层的统计量不稳定loss 曲线会抖得厉害最低建议不低于 8。patience是早停参数默认 100 轮不涨就自动停。小数据集上建议调成 30 到 50免得你出门吃个饭回来发现模型在 80 轮时就已经过拟合后面的 20 轮全在空转。4.3 训练日志怎么看loss、mAP 与那张玄学 P 曲线训练跑到一半终端会滚动输出一堆指标box_loss、cls_loss、dfl_loss和mAP50、mAP50-95。其中 box_loss 是边界框回归损失cls_loss 是分类损失dfl_loss 是分布焦点损失——YOLOv8 损失函数的三件套。你要盯的核心指标是mAP50它表示 IoU 阈值为 0.5 时的平均精度是判断模型“框得准不准”的第一道标准。很多新手有个误解认为 mAP 越高越好于是不停加训练轮数。实际上在小数据集上mAP50 涨到 0.85 以上之后每涨 0.01 都需要消耗大量训练时间而真实场景里 0.85 和 0.87 的区别微乎其微。我的习惯是先把 mAP50 跑到 0.85 以上然后转去看 precision-recall 曲线确认每个类别没有明显短板再决定要不要继续压榨指标。还有个玄学问题训练集 mAP 几乎每次都比验证集高。这是正常的因为模型在训练集上见过这些图像相当于开卷考试。如果训练集和验证集 mAP 差距过大比如差了 0.3 以上回到第 2 章检查数据泄露——是不是划分脚本没有固定随机种子验证集里混进了训练图片的增强变体。5. 踩坑记录咖啡叶片训练与推理中的 5 个高频问题5.1 显存不够 OOMbatch 从 16 降到 4 还是崩问题在 imgsz现象训练启动时报CUDA out of memory把batch从 16 一路降到 4 仍然崩。原因OOM 的直接变量是batch × imgsz × imgsz你把 batch 减到 4 只砍掉了一部分但 imgsz 还是 640 甚至 960显存占用依然居高不下。解决先确认显卡显存再按比例调整8GB 显存跑 imgsz640 时 batch 不超过 16跑 imgsz1280 时 batch 直接降到 4。如果你必须用大分辨率可以启用cacheTrue之外把模型换成yolov8n.pt或干脆用ampTrue混合精度训练后者通常能省 30% 以上的显存。5.2 类别不平衡健康叶 900 张、病斑只有 100 张怎么办现象训练完之后健康叶 mAP 0.9病斑 mAP 0.15输出里病斑几乎全部漏检。原因YOLO 默认每个样本权重相同占比高的类别主导梯度更新。解决分三层最省事的是对少类别做离线增强把 100 张病斑图做随机旋转、亮度扰动、镜像扩到 400 张再训练第二层是给数据加载器配fraction1.0之外的类别采样权重让少类别每轮被采样到的概率更高第三层才是改 loss 加权这一步涉及改源码不推荐新手做。5.3 验证集 mAP 高、田间大图一个都检测不到分辨率陷阱现象在验证集上 mAP 0.87把模型拿到单反拍的 4000×3000 田间原图上推理啥都框不出来。原因训练图和推理图的尺度差距过大。模型在 640 分辨率下学到的目标尺度是几百像素以内的病斑到了 4000 像素大图上病斑对应的像素面积被放大了好几倍模型从没见过这种尺度的目标。解决不要直接把大图整张喂进去用滑窗切成 640×640 的小块再推理最后拼接结果。这个技巧我会在下一章展开。5.4 标签数量对但坐标全 0数据增强与归一化的锅现象训练时偶尔报all class id in labels are negative或 loss 突然变成nan。原因最常见的是标签 txt 里的坐标已经归一化但你的数据增强管线里又叠加了一个“自动归一化”导致坐标被多次缩放到接近 0或者某个目标的宽高算出来为负值被增强逻辑当成无效样本丢弃。解决逐个打开报错的 txt如果坐标都在 0.0001 以下说明是被二次归一化了删掉增强管线里多余的一层即可如果坐标出现负值把增强的scale参数调回 0.5 以下。5.5 断点续训的后悔药resume 与 last.pt 的正确用法现象训练到第 70 轮机器断电重启后从第 0 轮重跑白白损失一晚上。原因YOLO 默认只在训练结束时保存best.pt中断时只有last.pt可能来得及落盘而你没有指定从断点恢复。解决养成好习惯从第一次训练开始就在命令里加projectcoffee runs nameexp1中断后执行yolo detect train resume modelruns/detect/coffee/weights/last.pt注意一个细节resume会自动读取上次训练的 epoch 数、优化器状态和学习率所以恢复后的学习率策略是连贯的不会出现学习率跳变导致的 loss 震荡。如果你手动改了 epochs 再恢复YOLO 会以新的总轮次为准继续跑这其实是特性可以用来“后悔”——比如上次设置 epochs100 但只跑到 70恢复时把 epochs 改成 150剩下的 80 轮会继续不会重头开始。6. 最后一步用滑窗推理把模型放回真实田间照片训练集里的 1000 张图大多是单叶或离体叶片照片模型学到的尺度非常固定。到了真实场景拿着手机或无人机拍一张 4000×3000 的整株咖啡树照片直接把大图喂给模型大概率什么都框不到。这不是模型没学会特征而是尺度不匹配——训练时目标占画面 30%推理时目标只占画面 1%对检测器来说等于在百倍缩小的图里找针。常规解法是缩小输入图吗正好相反应该把大图切成小图再推理。我常用的做法是写一个叠加滑窗函数固定窗口 640×640步长 512让相邻窗口有 20% 重叠避免目标正好卡在窗口边界被切碎。import cv2 import numpy as np def sliding_infer(model, img_path, window640, stride512): img cv2.imread(img_path) h, w img.shape[:2] results [] for y in range(0, h - window 1, stride): for x in range(0, w - window 1, stride): crop img[y:ywindow, x:xwindow] preds model(crop, verboseFalse)[0] for box in preds.boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) conf float(box.conf[0]) cls int(box.cls[0]) results.append((x x1, y y1, x x2, y y2, conf, cls)) return results这段代码的两个关键参数是window和stride。window 尽量和训练时的 imgsz 保持一致这样特征尺度不漂移stride 越接近 window重叠越多漏检越少但推理次数也越多。如果算力够stride window * 0.8 是比较均衡的选择。重叠区域的目标会被重复检测最后做一次 NMS 合并即可——把结果扔给torchvision.ops.nms一句话的事。滑窗推理的代价是推理时间线性增长。一张 4000×3000 的图片在 640 窗口 512 步长下大约要切 40 块单卡推理大概半秒到一秒。如果现场需要实时处理有两个方向一是把窗口调到 960 同时把 stride 拉大减少切片数量二是把模型导出成 TensorRT 做加速量级能再降一个档次。先别急着上 TensorRT滑窗这块的检测逻辑和结果拼接才是真正决定项目能不能用的核心。这套流程我从咖啡叶片的案例里跑通后回头做其他作物病害检测时直接复用只改类别名和窗口参数。最有价值的一次经验是把 stride 调太大导致漏检了边缘目标后来加上重叠窗口才解决——从此我闭着眼都知道检测类项目里数据集的尺度管理永远比调模型结构值钱。希望这些踩过的坑和验证过的流程能帮你在自己的数据集上少走一段弯路。本文还有配套的精品资源点击获取