简介:面向小麦田间杂草精准识别与目标检测任务,这份数据集包含约一千四百张已标注图像及配套YOLO格式标注文件,覆盖八个杂草类别(如RAD1、RD2、RAD23等),并已按训练集和验证集划分,可直接用于YOLO系列模型训练,适合目标检测方向的研究者、农业智能化开发人员和学生在模型训练、算法验证及课程设计中使用。包内共有两千个文件,包含一千四百一十四份文本格式标注文件、五百八十五张原始图像和一个可运行的可视化脚本,压缩包整体约二百七十六点一九兆字节,文件组织清晰,便于快速检索与加载;目前已有一百零一人学习使用。借助包内自带的可视化脚本可一键核对标注效果,省去人工排查标签的繁琐过程;资源同时提供关于YOLOv5改进实战的系列博文及更多目标检测、图像分割项目参考,便于进一步拓展模型优化思路、实现农业场景下的定制化检测方案。
1. 小麦田杂草检测数据集:1400 张图、8 类杂草、YOLO 格式直接开训
做过农业视觉项目的人都知道,找一套「标签干净、类别明确、能直接扔进 YOLO 训练」的杂草数据有多难。这份小麦田地杂草图像目标检测数据,约 1400 张已标注图片,8 个杂草类别,全部采用 YOLO 的 txt 标注格式,还做好了训练集和验证集划分,拿到手就能开始训练。适合正在做精准农业、变量喷药、杂草识别这类方向的开发者,也适合刚接触目标检测、想拿一份真实农业数据练手的人。相比从零采集标注,这份数据省掉的不是一点半点时间。接下来我就从数据格式、可视化脚本、训练流程到二次改造,完整拆一遍。
2. 数据集结构拆解:文件名字段、8 类标签与划分逻辑
2.1 文件名字段:一眼看懂采集元信息
这份数据的文件名不是随手起的,里面藏着采集侧的大量信息。取一条典型的样本出来拆:
camden_wheat_radish1_45_20210421_11-33-07_crop.jpg
按下划线切分,各字段含义如下:
| 字段 | 示例值 | 含义 |
|---|---|---|
| 场地 | camden | 采集地块分区代号 |
| 作物 | wheat | 田中作物为小麦 |
| 对象 | radish1 | 采集目标为野萝卜类杂草群 |
| 角度 | 45 | 拍摄视角或云台倾斜角 |
| 日期 | 20210421 | 采集日期 |
| 时间 | 11-33-07 | 采集时刻 |
| 裁剪 | crop | 是否为裁剪后的子图 |
这个字段设计非常实用。同一时刻同一角度会连续拍多张,文件名里能明显看到时间戳只有几十秒的间隔;角度字段只有 0 和 45 两种取值,对应不同拍摄视角。crop 后缀说明这批图是从大图中裁剪出来的子图,意味着做推理时可能还需要把检测框映射回原图坐标。
2.2 YOLO 标注格式:txt 文件里存什么
每张 jpg 同目录或 labels 目录下存在同名 txt 文件,这就是 YOLO 格式的标注本体。每一行代表一个目标,格式固定为:
class_id cx cy w h
其中 cx、cy 是目标中心点的归一化坐标,w、h 是归一化框宽高,全部除以图片宽高换算到 0~1 区间,class_id 是从 0 开始的整数。下面是一条真实标签行的形状:
0 0.438672 0.309318 0.105469 0.152273
这条数据表示类别 id 为 0 的杂草,中心点在图片横向 43.87%、纵向 30.93% 的位置,框宽占 10.55%、高占 15.23%。后四位数值全部以小数形式存,训练时模型直接把框回归到这几个值上,不需要再做坐标换算。
2.3 类别编码与 classes.txt
摘要里提到类别个数为 8,具体名称为:RAD2、RAD3、RAD4、RAD1、RD2、RAD23、RDA3、RA1。YOLO 训练要求类别 id 从 0 开始连续排列,所以 classes.txt 中通常按这样的顺序定义 id 与类名的映射:
0: RAD1 1: RAD2 2: RAD3 3: RAD4 4: RD2 5: RAD23 6: RDA3 7: RA1
从命名规律看,这套编码不是随手写的。RAD、RD、RA 可以理解为不同杂草物种的缩写前缀,后面的数字大概率代表不同生长期或亚种。RAD1 到 RAD4 属于同一大类下的不同形态学阶段,RAD23 可能是某两个阶段的共现编码,RDA3 和 RA1 则是独立类目。使用这份数据做训练时,务必先打开 classes.txt 确认 id 顺序,再在 data.yaml 里同步 names 列表,顺序错一位整个模型就废了。
2.4 训练集与验证集的划分逻辑
数据已经做了训练集、验证集划分,这是这份资源比普通标注集更省事的地方。常见的划分目录结构如下:
wheat_weed/ ├── images/ │ ├── train/ # 约 1200 张 │ └── val/ # 约 200 张 ├── labels/ │ ├── train/ # 对应 txt │ └── val/ # 对应 txt └── classes.txt中间文件数据量不大,但尽量保持 train 和 val 两边都覆盖所有类别。我的建议是拿到数据后先跑一遍类别统计,确认验证集里每个类都有样本,避免出现某个杂草类只在训练集出现、验证时 mAP 虚高或直接报错的情况。
3. 核心脚本实战:show 可视化与标签体检两个 Python 脚本
3.1 show 脚本:把 YOLO 标签画回图片
数据好不好,肉眼看一眼最直观。摘要里提到的 show 脚本,核心逻辑就是把 txt 里的归一化坐标反算回像素坐标,用 OpenCV 画框并标注类别名。下面是我按这套思路重写的完整版本:
import os import cv2 IMG_DIR = "images/train" LABEL_DIR = "labels/train" CLASS_NAMES = ["RAD1", "RAD2", "RAD3", "RAD4", "RD2", "RAD23", "RDA3", "RA1"] def draw_yolo_box(img_path, label_path, out_path=None): img = cv2.imread(img_path) if img is None: print(f"[skip] cannot read image: {img_path}") return h, w = img.shape[:2] if not os.path.exists(label_path): print(f"[warn] no label: {label_path}") return with open(label_path, "r", encoding="utf-8") as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) # 反归一化,还原到像素坐标 x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) # 坐标钳制到图像范围内 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, CLASS_NAMES[cls_id], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) if out_path: cv2.imwrite(out_path, img) print(f"saved: {out_path}") else: cv2.imshow("result", img) cv2.waitKey(0) if __name__ == "__main__": draw_yolo_box( "images/train/camden_wheat_radish1_45_20210421_11-33-07_crop.jpg", "labels/train/camden_wheat_radish1_45_20210421_11-33-07_crop.txt", out_path="vis_result.jpg" )这段代码把归一化框还原成像素框时,用 (cx - bw / 2) 计算左上角、用 cx + bw / 2 计算右下角,这是 YOLO 格式转像素坐标的标准做法,不能直接用 cx 当左上角。坐标钳制那两行是防止某些框角点轻微越界导致 OpenCV 画矩形时报错。CLASS_NAMES 的顺序必须和训练时 classes.txt 的 id 严格一致,否则框是对的但标签名字会错位,这是这类脚本最容易踩的坑。
3.2 标签体检:训练前必跑的统计脚本
可视化只能看几张图,检测整套数据里有没有坐标越界、空标签、类别分布失衡,需要写一个统计脚本。我一般会在拿到任何 YOLO 数据后,先跑一遍下面的体检逻辑:
import os import glob def check_labels(img_dir, label_dir): label_files = glob.glob(os.path.join(label_dir, "*.txt")) class_stats = {} bad_files = [] empty_label_files = [] for lp in label_files: img_name = os.path.basename(lp)[:-4] + ".jpg" img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): bad_files.append((lp, "missing image")) continue with open(lp, "r", encoding="utf-8") as f: boxes = f.readlines() if len(boxes) == 0: empty_label_files.append(lp) for box in boxes: parts = box.strip().split() if len(parts) != 5: bad_files.append((lp, f"bad format: {box.strip()}")) continue cls_id = int(parts[0]) class_stats[cls_id] = class_stats.get(cls_id, 0) + 1 cx, cy, bw, bh = map(float, parts[1:]) if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < bw <= 1 and 0 < bh <= 1): bad_files.append((lp, f"out of range: {box.strip()}")) return class_stats, bad_files, empty_label_files if __name__ == "__main__": stats, bad, empty = check_labels("images/train", "labels/train") print("类别实例数统计:", stats) print("问题文件数:", len(bad)) for item in bad[:10]: print(item) print("空标签文件数:", len(empty))这个脚本输出三块信息:各类别实例数量、坐标越界等异常文件列表、空标签文件数量。归一化坐标理论上必须落在 0~1 之间,w 和 h 为正数,一旦出现大于 1 的 cx 或负数宽高,说明标注工具导出时出了偏差,训练时轻则 Loss 不降,重则训练直接崩溃。空标签文件则要单独判断——如果图片里确实没有杂草,空标签是合理的背景图;如果图片里肉眼可见杂草但 txt 为空,就是漏标,需要补标。
3.3 批量可视化与体检的组合用法
实际用的时候,我习惯先跑体检脚本,把有问题的文件过滤掉,再对随机抽样的几十张图跑 show 脚本做人工确认。两个脚本可以组合成一条命令链:先体检输出坏文件名单,再写个批量脚本跳过坏文件逐张可视化。1400 张图全看一遍不现实,但每类抽 5 张看标注框贴不贴目标边界,基本能判断整体标注质量。
4. 跑通训练流程:YOLOv5 配置、命令与指标解读
4.1 数据目录重组与 data.yaml 配置
数据自带的划分如果和 YOLO 训练框架的目录预期不一致,需要做一次目录软链或拷贝。YOLOv5 和 YOLOv8 系都期望数据按 images 和 labels 分家存放,且 train、val 各自成目录。重组后的目录结构:
wheat_weed/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── weed.yaml注意 labels 目录里不要出现 classes.txt,YOLO 家族框架只认识同名的 txt 标签文件。data.yaml 的写法如下:
path: /absolute/path/to/wheat_weed train: images/train val: images/val nc: 8 names: ["RAD1", "RAD2", "RAD3", "RAD4", "RD2", "RAD23", "RDA3", "RA1"]path 字段在 YOLOv5 里推荐写绝对路径,避免相对路径引发找不到图片的问题。train 和 val 写的是相对于 path 的目录;nc 是类别数,必须与 names 列表长度一致,写错会导致类别标签错乱。names 顺序要和 classes.txt 保持一致,这是老生常谈但永远有人犯错的点。
4.2 训练命令与超参解析
基于这份杂草数据,我给出的训练基准命令如下,适用于 C 类 GPU 最低 8G 显存的机器:
python train.py \ --data weed.yaml \ --cfg yolov5s.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --imgsz 1280 \ --project runs/weed \ --name baseline逐项说明关键参数的含义。--weights 传 COCO 预训练权重,YOLOv5 会自动把输出层替换成 nc=8 的结构,预训练 backbone 能显著加速收敛,比从零训练少花一半时间。--imgsz 这里我推荐 1280 而不是默认的 640,原因后面避坑章节会展开:小麦田里的杂草在整幅图中占比很小,不少目标只有几十个像素,分辨率翻倍对小目标检测的提升立竿见影。代价是显存占用变大,如果 OOM 就降到 640 并把 batch-size 调到 32。
epochs 设 200 是因为农业数据的类内形态差异较大,RAD1 到 RAD4 可能对应同一杂草的不同生育期,训练轮数不够模型学不出阶段特征。如果验证集 mAP 在 150 epoch 后基本不再上升,可以提前用早停。
4.3 训练产物与评估指标怎么看
训练完在 runs/weed/baseline 下会看到 weights/best.pt 和 weights/last.pt,还有一堆评测图。重点看三样:results.png 里的验证 mAP 曲线、confusion_matrix.png 里的混淆矩阵、以及各类别的 PR 曲线。
python val.py --data weed.yaml --weights runs/weed/baseline/weights/best.pt --img 1280单独跑一次验证可以得到每类的 Precision、Recall、mAP@0.5,这是判断哪类杂草拖后腿的最直接依据。如果某类 mAP 明显低于平均值,多半是样本量太少或者形态多变,回到体检脚本看该类实例数。混淆矩阵里重点看杂草之间的互相误判——RAD1 和 RAD2 如果频繁互认,说明两者的形态学边界在标注时就比较模糊,单靠检测模型很难分开。
5. 避坑指南:杂草检测数据集上的四个翻车现场
5.1 现象一:Loss 不降反升,标签坐标越界
有次我拿一份类似数据直接开训,发现训练 Loss 前 20 个 epoch 不但没降,还在轻微上升,验证集 mAP 始终是 0。排查时抽查标签,发现少量 txt 里出现了 cx=1.23 这种越界数值,模型对这部分回归目标产生了梯度冲突。
原因在于标注工具的导出环节没有做边界归一化,某些框的坐标是按原始大图尺寸算的,裁切子图后标签没同步重新计算。解决方法是训练前用体检脚本把越界标签全部过滤,或者写一个修正函数把 cx、cy 超界的框裁剪回图像范围内。从那以后任何数据进训练前都会先跑一遍坐标范围检查,这条已经被我写进团队的默认流程。
5.2 现象二:小目标漏检严重,mAP 很高但地里找不到草
验证集 mAP@0.5 能到 0.75 以上,看起来效果不错,但拉到真实地块的无人机画面上推理,漏检率立刻暴露。原因很典型:这批 crop 子图经过裁切后,目标在图中占比其实不小,模型学到的是中尺度目标特征;而实际无人机原始画面中杂草占全图比例极小,直接推理自然全部漏掉。
解决方向有两个。一是训练时把输入分辨率从 640 提到 1280,配合 --multi-scale 开关让模型适应多变尺度。二是推理阶段用切片策略,把原图切成 640 重叠子图分别推理,再按裁剪坐标把框拼回大图,这部分可以用现成工具实现。训练集里带 crop 后缀的影子就是给这个思路准备的。
5.3 现象三:类别不均衡导致某类 AP 为 0
体检脚本打印出类别实例数后,常见情况是 RAD 系列占比过半,RA1 这类只有几十个实例。YOLO 的默认分类损失对高频类更友好,少数类很容易在训练后期被抑制,PR 曲线上表现为 AP 为 0。
解决方法是双层下功夫:数据侧对少样本类做复制粘贴增强,把标注框连同图像块复制到其他空白区域,生成新训练样本;训练侧调整 cls loss 的权重或使用采样策略让每个 batch 都包含所有类别。更简单粗暴的方案是在数据增强里对少数类做水平翻转加 HSV 扰动,先让样本量过百再谈训练。
5.4 现象四:随机划分导致验证集虚高,指标失去参考价值
这是这类农业时序数据最容易踩的坑。文件名里时间戳只差几十秒的连续帧,内容高度相似,如果直接随机划分,训练集和验证集会混入同一场景的邻近帧,模型等于开卷考试,验证 mAP 虚高到失真。拿到真实地块新数据时,性能断崖下跌的锅往往就在这。
正确做法是按文件名中的时间戳做 group 划分,把同一采集日期或连续时间段的图片整体划入同一侧。稳定性优先时,建议按日期分组并拆出跨日期验证集,才能测出模型对新地块日期组合的泛化能力。比如拿所有 20210421 拍的图做验证、其余做训练,这样才能真实还原跨时相应用场景。
6. 二次开发路径:类别合并、跨角度泛化与边缘部署
6.1 按生育期合并类别,降低分类难度
RAD1 到 RAD4 以及 RAD23 如果代表同一物种的不同生育期,实际植保场景中并不需要区分阶段,只需要知道是不是这种杂草。可以写一个类别映射脚本,把标签里的 0、1、2、3 统一改写成新 id 0,同时更新 data.yaml 的 names 为合并后的类别列表,这样把 8 类任务降为 5 类甚至 4 类,单类样本量翻倍,检测精度通常会有明显提升。对已训练好的模型也可以用验证集重新统计,确认合并后各类的 AP 是否均衡。
6.2 利用文件名角度字段做跨视角验证
文件名里的 0 和 45 两个角度值是一个天然实验变量。可以把 0 度视角的图全部划入训练集、45 度全部划入验证集,观察模型在训练中没见过的视角上表现如何。如果 mAP 掉得厉害,说明模型对视角敏感,应该在数据增强时加入随机旋转和透视变换来模拟视角变化,或者直接按角度分组做交叉验证。这种利用文件名元信息设计实验的能力,就是普通使用者与高阶开发者的分水岭。
6.3 导出 ONNX 做边缘端部署
训练好的 best.pt 落地成实际农药喷洒或监测设备时,需要转成轻量格式。在 YOLOv5 工程内执行:
python export.py --weights runs/weed/baseline/weights/best.pt --include onnx --imgsz 1280导出的 onnx 文件可以用推理引擎在 Jetson 或树莓派等边缘设备上运行。注意导出时的 imgsz 要和训练时一致,否则输出 tensor 形状不匹配;输入图片预处理时也要对齐归一化方式。
从拿到这份数据集到落地推理,完整链路大约是:体检脚本过滤坏标签,show 脚本人工抽检,划分目录并配置 data.yaml,开启训练评估,最后导出部署。我个人的习惯是每个项目开工前强制走一遍数据体检,这个习惯帮我省掉了好几轮无效训练,希望帮到你。
本文还有配套的精品资源,点击获取