YOLO训练从数据开始:杂草与作物数据集的全流程实操

YOLO训练从数据开始:杂草与作物数据集的全流程实操 简介面向yolo系列算法目标检测任务的杂草与作物数据集涵盖杂草和作物两大类目标适用于农业场景下的模型训练、算法验证与精准作业可配合yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流框架使用为精确除草、作物计数等任务提供标注基础。数据集已预先划分出训练集与验证集并附带data.yaml配置文件下载后即可直接执行训练和测试显著降低数据预处理门槛。标注格式同时提供yolo txt与VOC xml两套版本分别存储于独立文件夹其中yolo格式规范为类别索引加归一化框中心点坐标与宽高结构简洁便于不同框架无缝迁移。压缩包共2000个文件以xml标注文件为主整体大小约137.26MB。目前已有102人学习下载适合需要快速获取高质量农业目标检测数据集的初学者和科研人员。1. 拿到杂草与作物数据集先把 YOLO 训练的地基打牢2722 张图像、带标签、已经分好类这几个条件放在一起对准备用 YOLO 做农业目标检测的工程师来说等于把最花时间的标注环节压缩到了最低。杂草与作物检测是精准农业里的典型视觉任务无人机或田间相机拍回的图像里叶片层层交叠杂草幼苗和作物幼苗长得非常接近光照变化、土壤反光、生长阶段差异都会让模型失效。这也是为什么不少团队拿通用目标检测权重直接开始训练损失函数一路下降验证集 mAP 却卡在低点——问题通常不在算法而在数据集本身的标注格式、类名顺序和数据划分。这篇从 yolo算法-杂草和作物数据集 这个压缩包展开先把文件结构、YOLO 标注格式和检查工具讲清楚再给一条从预处理、训练到评估的完整流程。适合刚拿到数据、正在确认标注是否符合训练要求的人也适合跑过若干轮 YOLO、但没认真做过数据体检的工程师。2. 2722 张图像的构成与 YOLO 标注格式拆解拿到压缩包的第一步不是解压后立刻训练而是确认内部结构是否符合 YOLO 训练脚本的预期。大多数带标签数据集会按 images 与 labels 两个顶层目录组织里面再按 train/val/test 分。少见的情况是把标注全部放在一个 annots 目录里或者根目录下只有一个 classes.txt。先用最基础的 shell 命令把目录树和标签样本拉出来看一眼能省掉后面非常多定位问题的环节。find . -maxdepth 2 -type d | sort head -20 labels/train/*.txt cat classes.txt 2/dev/null || cat data.yaml 2/dev/null第一条命令列出两层以内的所有子目录重点看 images 和 labels 是否存在同名子目录。第二条命令随便挑几个标注文件看前 20 行确认里面不是空的。第三条命令用于找到类别清单。知道类别顺序非常关键——txt 里第一列写的是整数 id模型训练时只会按顺序把 id 对应到 data.yaml 的 names 列表上不对齐的话模型会把杂草学成作物。2.1 YOLO 标注的五个数字分别代表什么任何一个 YOLO 格式的标注 txt每一行都由 5 个数字组成class_id x_center y_center width height。后四个坐标不是像素值而是相对整张图的归一化值范围通常在 0 到 1 之间。x_center 是目标中心点在图像宽度上的比例y_center 同理width 和 height 是目标框相对整张图宽高的比例。这种归一化格式的好处是它不绑定原始图片分辨率。同一份标注文件放入 640×640 或 1280×1280 的训练尺寸里都能正确换算网络输出层的特征图尺度不管怎么变边界框都能被还原到输入图像上。常见错误是把 x_center 写成左上角 x 坐标或者把 width 写成像素宽度这两种错法都会让训练时 IoU 计算异常轻则 loss 不降重则目标框整体偏移到图像边缘。字段含义取值范围与像素坐标的关系cls类别整型 id0 到 nc-1对应 data.yaml 的 names 顺序x_center目标中心点的横向比例0 到 1像素 x 坐标除以图像宽度y_center目标中心点的纵向比例0 到 1像素 y 坐标除以图像高度width目标框宽度比例大于 0 且小于等于 1像素宽度除以图像宽度height目标框高度比例大于 0 且小于等于 1像素高度除以图像高度对这个数据集来说更值得注意的字段是第一列。杂草和作物如果分别对应 0 和 1那 classes.txt 里的书写顺序必须和训练配置一致。很多现成数据集的发布者会直接给出 classes.txt但训练前仍要人工确认一次因为有的标注工具导出时会按标注顺序自动排序类别比如把先标注的作物排在 0杂草排在 1。2.2 用脚本体检所有标签文件只靠 head 命令抽查几个文件不够2722 张图的标签应该整批检查一遍。下面这段 Python 遍历所有 txt逐行检查列数和坐标范围发现异常直接打印文件名和内容。from pathlib import Path bad_count 0 for label_file in Path(labels).rglob(*.txt): for line in label_file.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: print(f列数错误: {label_file}: {line}) bad_count 1 continue cls, x, y, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f坐标越界: {label_file}: {line}) bad_count 1 else: # 面积占全图比例过大的框可能是标注错误 if w * h 0.9: print(f可疑大框: {label_file}: {line}) print(f异常行数: {bad_count})脚本先按空格切分一行列数不等于 5 说明标注工具导出格式不对坐标范围检查则能抓出负坐标、x_center 越界这类问题。最后的面积检查是针对农业图像加的杂草和作物的目标框面积占比通常不会超过 0.9如果出现近乎铺满整张图的框基本是标注时把背景或大片土壤选成了目标。遇到异常行不要直接删先定位对应原图确认是标注错误还是数据增强脚本写错了。2.3 类别计数与框面积分布决定训练策略体检完坐标格式还要统计两个分布每个类别的目标框数量以及框面积在整张图像中的占比。这直接决定后续要不要处理类别不平衡、要不要调整 anchor 或增加小目标层。from pathlib import Path from collections import Counter counts Counter() areas {weed: [], crop: []} for label_file in Path(labels).rglob(*.txt): for line in label_file.read_text().strip().splitlines(): parts line.split() cls, w, h int(parts[0]), float(parts[3]), float(parts[4]) counts[cls] 1 name weed if cls 0 else crop areas[name].append(w * h) print(各类别框数量:, dict(counts)) for k, v in areas.items(): v.sort() median v[len(v) // 2] if v else 0 print(f{k}: 框数量{len(v)}, 中位面积占比{median:.4f})如果 weed 和 crop 的框数量差距超过 5 倍训练时就要考虑给少样本类别增加复制粘贴增强或者在后处理里对不同类别使用不同的置信度阈值。如果中位面积占比普遍低于 0.01说明大量目标是 640×640 图像里的几十像素小框这个信号比任何超参数都重要小目标密集的场景应该把 imgsz 拉到 768 或 1024或者在 YOLOv8 基础上单独给 P2 检测头增加权重。3. 训练前用脚本把数据集过一遍完整性检查、划分与可视化格式体检只能说明标注文件语法正确不能说明图片与标签真的能一一对应。训练前还要做三件事双向核对文件配对、按固定随机种子划分数据集、把标签画回原图做视觉确认。这三步做完数据侧的大坑基本都会暴露。3.1 双向核对图像与标签文件图像文件多出来训练时会被静默忽略你可能根本不知道有图没参与训练标签文件多出来训练会直接报找不到对应图像的错。两种问题都要主动排查不要等训练日志提醒。cd dataset_root ls images/train | sed s/\.[^.]*$// | sort img_ids.txt ls labels/train | sed s/\.[^.]*$// | sort lab_ids.txt comm -23 img_ids.txt lab_ids.txt # 有图无标签 comm -13 img_ids.txt lab_ids.txt # 有标签无图sed 的作用是去掉文件扩展名sort 保证两边按同样的字典序输出comm 再比较两个文件。有图无标签时不要自己补一个空 txt空标签文件在训练中同样会产生警告并拖慢 epoch 速度正确做法是回到标注工具里确认是否漏标。有标签无图时检查原始采集卡是否丢帧确认后把对应 txt 移出目录即可。3.2 按固定随机种子划分 train/val/test2722 张图属于中小规模数据集随机划分很容易把同一块田在不同光照条件下的照片拆到训练集和验证集里导致验证指标虚高。更稳的做法是按照文件名前缀或采集批次划分但如果文件名里没有批次信息就只能用随机划分同时给随机种子一个固定值保证后面每次实验之间的数据划分一致。python - EOF import random from pathlib import Path random.seed(2024) images sorted(Path(images/train_all).glob(*.jpg)) random.shuffle(images) n len(images) train_end, val_end int(n * 0.8), int(n * 0.9) def move_to(part, files): img_dst Path(images) / part lab_dst Path(labels) / part img_dst.mkdir(parentsTrue, exist_okTrue) lab_dst.mkdir(parentsTrue, exist_okTrue) for f in files: f.rename(img_dst / f.name) lab Path(labels/train_all) / (f.stem .txt) if lab.exists(): lab.rename(lab_dst / lab.name) else: print(f缺失标注: {f}) move_to(train, images[:train_end]) move_to(val, images[train_end:val_end]) move_to(test, images[val_end:]) EOF随机种子设置为 2024 是为了让 shuffle 的结果可复现。8:1:1 的划分比例对这个体量比较合适验证集保留约 272 张能稳定评估 mAP测试集额外留 272 张专门用于最终部署前的模拟推理。没有单独测试集直接 train/val 二分的做法也可以但那样你最后评估的 val 精度很可能是在划分时已经接触过的图像上测出来的不能代表未见过的新地块。3.3 用 OpenCV 画框让标注错误直接显形坐标值合法不等于标注真的贴着目标。最常见的问题是标注框比目标大一圈、框的中心点在叶尖而不是叶心、以及类别标反。这些必须画出来看。下面这段脚本把归一化坐标还原成像素坐标再用 OpenCV 画到原图上。import cv2 from pathlib import Path def draw_boxes(image_path, label_path, class_names): img cv2.imread(str(image_path)) h, w img.shape[:2] for line in Path(label_path).read_text().strip().splitlines(): cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color (0, 255, 0) if int(cls) 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[int(cls)], (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return img img draw_boxes(Path(images/train/0001.jpg), Path(labels/train/0001.txt), [weed, crop]) cv2.imwrite(check_0001.jpg, img)坐标还原的关键在 x1 (xc - bw / 2) * w中心点减去半宽再乘以图像宽度得到左上角像素坐标。YOLO 格式里存的是中心点和宽高不是左上角和右下角这里最容易写错。验证时看框是否紧贴目标外边缘如果每个框都大一圈说明标注者的框定习惯是偏保守的这类数据训练出的模型在推理时倾向于输出更大的预测框需要后续调低 NMS 的置信度阈值来缓解。3.4 其他标注格式转 YOLO 的通用路径有的发布者会同时提供 COCO json 或 VOC xml这时要做一次格式转换。COCO 的 bbox 字段是[x1, y1, width, height]像素值转成 YOLO 需要先读原图像尺寸再按公式换算出中心点和归一化宽高。kitti 标注转 yolo 的流程也接近读取高度、宽度、类别名把目标框坐标从像素绝对值换算成相对值。转换后必须再做一次 2.2 节的范围检查因为像 COCO 里目标被截断时坐标可能落在图像外直接转出来的归一化值会小于 0 或大于 1。异常类型现象处理建议图像解码失败cv2.imread 返回 None删除或重新导出该图空标签文件txt 文件 0 字节检查标注源不要补空文件类别 id 越界cls 大于等于 nc按 names 顺序重新映射坐标越界x/w/y/h 超出 0-1按原始 bbox 裁剪或修复4. 用 YOLOv8 在杂草与作物数据集上跑通完整训练预处理做完之后进入真正训练环节。这里选择 YOLOv8 而不是更早的 YOLOv5主要原因是 ultralytics 仓库把训练、验证、导出和推理做成了统一命令行对 2722 张图这种中小规模项目能节省大量配置时间。模型体积先用 nano跑通流程后再考虑换 medium。4.1 把 data.yaml 按实际路径写对YOLOv8 的项目配置集中在 data.yaml。注意 path 字段的基准目录是当前工作目录不是 yaml 文件所在目录。写绝对路径最省事避免在不同机器上复制项目时出现找不到数据集的报错。path: /home/user/weed_crop_yolo train: images/train val: images/val test: images/test nc: 2 names: 0: weed 1: crop训练前用下面这段代码验证路径拼接是否正常不要直接启动训练再等报错import yaml from pathlib import Path cfg yaml.safe_load(Path(data.yaml).read_text()) base Path(cfg[path]) for split in [train, val, test]: p base / cfg[split] print(split, p, 存在 if p.exists() else 不存在)data.yaml 里的 names 顺序必须和标注文件里的类别 id 严格一致。这个数据集是二分类浏览器里打开是 0 和 1 两行顺序一旦反了训练不会报错但验证时 mAP 会一直上不去因为模型学到的“weed”其实是“crop”。这类错误最隐蔽因为 loss 曲线显示收敛正常。4.2 ultralytics 训练命令与关键参数数据路径检查通过后直接执行pip install ultralytics yolo detect train datadata.yaml modelyolov8n.pt epochs150 imgsz640 batch16 lr00.01 patience30yolov8n.pt 是 COCO 预训练权重加载它之后训练会从已有特征开始而不是随机初始化这对 2722 张图规模的数据集几乎必不可少。epochs 开到 150是因为验证指标通常在 80 轮之后才开始明显上升如果前期就配了 early stopping可能错过后面最好的权重。patience 设置为 30 意味着连续 30 轮 val 精度没有提升就自动停止这比盯日志人工判断可靠得多。参数建议初值调整信号epochs150val 仍在上升就继续imgsz640小目标多改为 768 或 1024batch16显存 OOM 降到 8lr00.01loss 震荡不降时减半patience30过拟合时降低到 20如果显存不够batch 降到 8 比降到 4 的收敛效果更稳不要直接 batch1batch 太小会让 BatchNorm 统计量不稳定。imgsz 从 640 提高到 768 对杂草这类小目标有明显收益但训练时间和显存占用会同步增加先用 640 跑通一个版本记录 baseline再横向对比。4.3 训练日志里重点看哪几个指标终端输出的指标很多不需要每一条都盯。重点看 val 子集上的 box_loss 和 cls_loss它们应该随 epoch 逐步下降并趋于平缓。如果 train loss 持续下降而 val loss 在第 30 轮左右开始反弹说明过拟合应优先增强数据而不是换更大模型。训练结束后在 runs/detect/train 目录下找这几个文件weights/best.pt、weights/last.pt、confusion_matrix.png、results.png。results.png 里能看到训练集和验证集在 loss、精度、召回率上的完整曲线这是判断收敛质量的第一手材料。confusion_matrix.png 对二分类更有用能直接看出模型把哪些杂草误判成作物或者把多少背景误检成杂草。4.4 训练完立刻用测试集做第一次推理训练结束后的第一件事不是画曲线而是用测试集里模型从未见过的图片跑一次预测yolo detect predict modelruns/detect/train/weights/best.pt sourceimages/test saveTrue推理结果会写到 runs/detect/predict 目录。打开每张图的带框预览如果大部分框的置信度集中在 0.3 以下说明模型在未见过的田块上泛化不足需要回到数据增强或检查训练集是否与测试集来自不同光照时段。如果框的位置飘忽不定多半是训练时 imgsz 与实际物体尺度不匹配小目标在 640×640 下特征已经退化应考虑加大训练分辨率。5. 让有限样本发挥更多价值增强边界、冻结层与错标修正2722 张图像终究不算大最后分享三个不换网络结构也能提升实际效果的做法。它们分别对应数据侧、训练侧和后处理侧按顺序做完通常能让 mAP 再涨 2 到 4 个点。5.1 数据增强不要把所有开关都打开YOLOv8 默认的增强参数还算克制但自定义调参时最容易犯的错是把 hsv_h 调高。杂草和作物的区别主要依赖叶片纹理和轮廓不是颜色偏黄还是偏绿。将 hsv_h 从默认 0.015 改成 0.1模型会花大量参数学习颜色扰动真实场景里光照变化引起的亮度差异却被忽略了。我会优先提高 hsv_v 亮度扰动再保持 fliplr 为 0.5mosaic 开 1.0。mosaic 增强对密集叶片场景帮助很大因为它把四张图拼接后模型被迫在更复杂的背景下定位目标。5.2 用 freeze 参数控制浅层特征的学习强度从 COCO 预训练权重起步时浅层特征图通常已经学会了边缘、纹理、颜色等通用表示。小数据集上让这些层继续大尺度更新很容易把通用特征带偏。ultralytics 提供 freeze 参数比如 freeze10 表示冻结前 10 层只训练检测头和中间部分。对 2722 张图的二分类任务freeze10 通常效果最好如果数据与 COCO 场景差距很大比如全是俯视的无人机图可以改成 freeze5让更多底层特征参与微调。from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( datadata.yaml, epochs150, imgsz640, batch16, freeze10, hsv_h0.015, hsv_v0.5, fliplr0.5, mosaic1.0, )代码里的 hsv_v 比默认值略高就是针对田间光照方向变化做的调整。freeze 层数不宜过度尝试先从 10 开始如果训练 loss 一开始就不降再减少冻结层数。5.3 用最好模型反向筛选标注错误最后一个技巧是把训练好的 best.pt 拿到训练集上重新预测一遍找出置信度极高但预测类别与原始标签不一致的样本。这些样本八成是标注错误而不是模型学得不好。yolo detect predict modelruns/detect/train/weights/best.pt sourceimages/train save_txtTrue然后写一个简单脚本把预测类别与 labels 里原始类别做对比只输出不一致且置信度大于 0.8 的样本。集中看这些图的标注框往往能发现类别标反或框选到了叶片缝隙。修正几十张图后重新训练比盲目调任何超参数都有效因为错误标签直接向反向传播注入了噪声模型必须花更多 epoch 才能覆盖掉这些错误信号。本文还有配套的精品资源点击获取