简介:面向计算机视觉与智慧农业研究者的专业农业杂草识别数据集,可支撑稗草、马唐等多类常见恶性杂草的分类模型训练。数据包含近2000张真实农田与作物环境下的杂草高清图像,覆盖不同生长期、多种伴生场景与复杂田间背景,并经植保专家标注复核,图像统一为256×256像素,已按分类目录划分训练集与验证集。压缩包为7z格式,共2000个文件,以1998张jpg图像为主体,另含1个Python数据可视化脚本和1个json标签映射文件,整包约128.44MB,目录结构清晰,便于直接加载训练。附赠的可视化脚本支持一键生成类别分布图、样本示例网格、颜色纹理形态特征统计等图表,有助于快速理解数据特点、优化增强策略。已有52人浏览学习,适合学术研究、课程项目及智慧农业原型开发等场景。
1. 一个杂草数据集,为什么值得你花一整周去折腾
干农业视觉这行的人,遇到的第一道坎几乎都是同一个:数据从哪来。训练模型识别稗草、马唐等20+类常见恶性杂草的权威数据集,听起来像是一句话的事,实际上是把「杂草识别方案能不能落地」的答案提前锁死了。田间场景和公开数据集最大的差别在于:杂草长相随生长周期剧烈变化、光照和土壤背景杂乱、同类杂草在不同地区还有生态型差异。你要是拿一个只有几百张图的玩具数据集去训 YOLO,验证集上看着有 90 个点,下地一测直接跌回及格线以下,这是很多团队翻车的起点。
这个方向真正能解决的问题,是把「除草剂精准喷施」和「田间杂草调查」从人工目测变成模型自动判断。适合谁呢——做植保无人机的、做智能除草机器人的、搞农业科研需要定期统计草情的,以及想用视觉模型切入智慧农业但还没找到靠谱数据源的个人开发者。本篇不吹嘘某个现成数据集多牛,而是把这类数据集该长什么样、拿到手怎么处理、训练参数怎么定、坑在哪,一条线讲清楚。你照着走,至少能省下两周自己造轮子的时间。
2. 这类数据集到底装了什么:从相机到标注文件的全链路拆解
2.1 田间采集和公开数据集的本质区别
先纠正一个常见误解:很多人以为杂草识别数据集就是「拿手机拍一拍草,标个框」就行。真实情况是,田间采集的难度远高于公开数据集,因为杂草的生长环境极度不可控。以稗草为例,它在水稻田里刚出土时只有两片小叶,和水稻苗几乎长得一模一样,等它长到分蘖期,叶片形态又完全变了。一个合格的杂草数据集,必须在不同生育期分别采样,否则模型只能学会识别「某一种形态的稗草」,换个生长阶段就失灵。
另一个关键点是数据来源的混合性。权威数据集通常包含三种来源:无人机低空俯拍、地面机器人近景拍摄、手持设备田间抓拍。三种视角下同一株杂草的形态差异巨大——无人机视角下杂草是密集的团块,地面视角下则是单株的叶片纹理。我见过不少项目图省事只用单一视角,结果模型在另一种视角下直接报废。所以拿到数据集后先别急着训练,按视角做一次数据分布统计,比直接调参重要得多。
2.2 目录结构:一份能直接喂给训练框架的数据集长什么样
常见的杂草识别数据集目录结构一般是这样的,以你拿到的压缩包解压后为基准:
weed_dataset/ ├── images/ │ ├── train/ │ │ ├── barnyardgrass_001.jpg │ │ ├── barnyardgrass_002.jpg │ │ ├── crabgrass_001.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── barnyardgrass_001.txt │ │ └── ... │ ├── val/ │ └── test/ ├── classes.txt └── dataset.yamlimages和labels按 train/val/test 分好,classes.txt是类别清单,dataset.yaml是给 YOLO 系列训练框架读的配置文件。这套结构不是哪个数据集独有的,而是 YOLO 生态的事实标准,你后面换成 YOLOv5、YOLOv8、YOLOv11 都能直接复用。
讲一下 labels 里面的 .txt 文件格式。每一行代表一个标注框,五个数值分别是:类别序号、归一化后的中心点 x 坐标、中心点 y 坐标、框宽、框高。举个例子,一行内容是0 0.513 0.422 0.113 0.087,意思就是第 0 类(假设是稗草),框中心在图片横向 51.3%、纵向 42.2% 的位置,宽占整图的 11.3%,高占 8.7%。这套格式从 YOLOv5 沿用到现在,已经是目标检测数据交换的通用语言。
2.3 类别设计:20+ 类恶性杂草的构成逻辑
标题里「20+ 类常见恶性杂草」这个数字不是随便定的。以农田杂草防治的常见名录来看,数据集通常覆盖三大类群:禾本科(稗草、马唐、牛筋草、狗尾草等)、阔叶类(马齿苋、反枝苋、藜、苘麻等)、莎草科(香附子、碎米莎草等)。这三个类群在除草剂选择上完全不同——禾本科用芳氧苯氧丙酸类,阔叶类用激素类,莎草科往往需要专门药剂。所以类别划分不仅是视觉识别问题,还直接对接植保方案。
这里有一个值得注意的细节:不同数据集的「类」粒度不一样。有的把稗草细分为「稗草-幼苗期」「稗草-分蘖期」「稗草-成株期」三个类,有的则统一为一个类。前一种做法对识别精度有提升,但会牺牲训练数据的均衡性——一个类别被拆成三个,每个子类的样本量可能不足。我的建议是:如果数据集本身已经按生育期分了子类,优先保留;如果是自己重新整理,先按物种分,等模型 baseline 稳定后再考虑按生育期细拆。
| 类群 | 典型类别 | 识别难点 | 常见发生场景 |
|---|---|---|---|
| 禾本科 | 稗草、马唐、牛筋草、狗尾草 | 幼苗期与作物幼苗相似 | 水稻田、玉米田 |
| 阔叶类 | 马齿苋、反枝苋、藜、苘麻 | 叶片形态随生长变化大 | 大豆田、棉花田 |
| 莎草科 | 香附子、碎米莎草 | 茎横截面三棱形,难与禾本科区分 | 水稻田、果园 |
2.4 标注质量:权威数据集和网盘随意收集的最大分水岭
一个数据集敢自称「权威」,核心不是图片多,而是标注有严格的规范。常见的标注规范包括:框住杂草的地上部分(不含根部泥土)、相互遮挡的杂草各自独立标注而非合并、同一张图中处于不同生长时期的同种杂草都纳入、以及每张图附带采集地点和生育期元数据。这些东西在训练阶段看不见,但直接影响模型的泛化能力。
拿到数据集后,我建议你抽样 50 张图人工核对标注框,重点看两种情况:一是框是否紧贴目标边缘,太松会让模型学到多余的背景纹理,太紧会截断叶片;二是小目标是否被漏标,杂草幼苗期的标注框通常只有二三十个像素,漏标率高的数据集训练出来的模型会对小目标系统性失灵。这一步花半小时,能帮你提前判断这个数据集值不值得继续投入。
3. 把数据集变成能训的格式:格式转换与数据清洗实操
3.1 从 VOC 或 COCO 转成 YOLO 格式的脚本
不少杂草数据集最初公开时是 VOC 格式(XML 标注)或 COCO 格式(JSON 标注)。要喂给 YOLO 训练,得先转格式。下面这个脚本处理 VOC 转 YOLO,是这类项目里最常见的起步操作:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_list, output_dir, img_width, img_height): tree = ET.parse(xml_path) root = tree.getroot() yolo_lines = [] for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in class_list: continue cls_id = class_list.index(cls_name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) x_center = (xmin + xmax) / 2 / img_width y_center = (ymin + ymax) / 2 / img_height box_w = (xmax - xmin) / img_width box_h = (ymax - ymin) / img_height yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") if yolo_lines: txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' with open(os.path.join(output_dir, txt_name), 'w') as f: f.write('\n'.join(yolo_lines)) class_list = ['barnyardgrass', 'crabgrass', 'goosegrass', ...] # 按 classes.txt 顺序填 xml_dir = 'path/to/xmls' out_dir = 'path/to/labels' os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): voc_to_yolo(os.path.join(xml_dir, xml_file), class_list, out_dir, 1920, 1080)这段代码的思路是遍历 VOC 的 XML 文件,把每个目标的类别名映射成类别序号,再把边框坐标从绝对值转成相对于图宽的归一化值。核心参数是img_width和img_height,填错会让一整批标注框全部偏移。注意:如果数据集的图片尺寸不统一,直接传固定宽高会出问题,这时需要先读图片真实尺寸再传入,不能用变量名「欺骗」自己。
3.2 数据清洗三步:去重、查坏图、看类别分布
格式转完了,下一步是清洗。杂草数据集常见的脏数据问题比通用目标检测数据集更突出,因为田间采集经常连拍,会产生大量高度相似的连续帧。我一般按三步走:
第一步,去重。图片级别的 MD5 去重只能去掉完全相同的文件,对连拍产生的近似重复无能为力。一个实用的做法是抽帧计算感知哈希,把汉明距离小于阈值的图片剔除。这一步能砍掉 10% 到 20% 的训练量,模型不会变差,训练时间还能缩短。
第二步,查坏图。用 Python 遍历所有图片,尝试用 OpenCV 解码,解不出来的直接记录并移出数据集。这个操作很容易被忽略,但在真实采集数据里几乎必然遇到——相机的 SD 卡写了一半断电、传输中断,都会产生损坏图片。
import cv2 import os bad_images = [] for root, dirs, files in os.walk('images/train'): for f in files: if not f.lower().endswith(('.jpg', '.jpeg', '.png')): continue img_path = os.path.join(root, f) img = cv2.imread(img_path) if img is None: bad_images.append(img_path) else: h, w = img.shape[:2] if h < 32 or w < 32: bad_images.append(img_path) # 分辨率过低,标注失去意义 print(f"发现坏图 {len(bad_images)} 张") for p in bad_images: print(p)这段代码做了两件事:检查文件能否被 OpenCV 正常解码,以及过滤掉分辨率过小的图。阈值 32 像素是个经验值,小于这个尺寸的杂草目标即使有标注,模型也几乎学不到有效特征,反而会引入噪声。
第三步,统计类别分布。这一步我强烈建议画一个柱状图,看看 20+ 类里哪些类别样本量超过 2000 张、哪些只有两三百张。田间采集的数据天然存在长尾分布——稗草、马唐这种常见草样本量充足,但某些区域性杂草就少得可怜。长尾类别在训练时要么学不好,要么过拟合,后面专门讲对策。
3.3 数据增强:杂草场景千万别乱用
数据增强是深度学习里最容易被误用的环节。通用目标检测常用的随机裁剪、随机旋转,在杂草数据上要格外谨慎。原因在于:杂草识别的一个关键判别特征是叶片走向和纹理,过强的旋转会破坏这个特征。比如稗草的叶片有独特的纵向平行脉纹,你把它旋转 90 度,模型学到的东西就和实际不符了。
我常用的保守策略是:轻度水平翻转(概率 0.5)、小幅缩放和位移(±10% 以内)、亮度对比度调整(模拟早中晚光照差异),不做随机旋转和夸张的 HSV 扰动。HSV 扰动中色调偏移尤其危险——杂草的绿色是核心特征,把色调往黄色偏移 20 个色相值,模型可能就把枯草和活草搞混了。如果你用 YOLOv8 训练,它的默认增强配置就带有一定的旋转和透视,需要手动关掉或调低。
4. 用 YOLO 系模型训练杂草识别:配置文件与参数字典
4.1 dataset.yaml:20+ 类的配置写法
现在数据准备好了,进入训练环节。主流做法是用 YOLOv8 或 YOLOv11,这两个框架的配置逻辑几乎一致。先看 dataset.yaml 怎么写:
# dataset.yaml path: ./weed_dataset train: images/train val: images/val test: images/test names: 0: barnyardgrass 1: crabgrass 2: goosegrass 3: purslane 4: redroot_pigweed 5: lambsquarters 6: velvetleaf 7: nutsedge 8: green_foxtail 9: dandelion # 按你的 classes.txt 顺序继续写全这个文件的核心是names映射表,顺序必须和 labels 里的数字序号完全一致,差一个位置,整个训练就错位了。我自己的习惯是每次换数据集都对一遍 classes.txt 和 yaml,绝不依赖记忆。path建议写绝对路径,很多训练报错「图片找不到」的根源就是相对路径在换工作目录后失效了。
4.2 训练命令与必调参数详解
配置写好后,训练命令看起来不长,但每个参数背后的含义值得细说:
yolo train \ model=yolov8m.pt \ data=weed_dataset.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=15 \ augment=false \ project=./runs \ name=weed_v8m参数说明:
model=yolov8m.pt:预训练模型权重。m(medium)是杂草识别性价比最高的档位,比 s 精度高,比 l 训练快很多。如果算力紧张可以换 s,精度通常会掉 2 到 3 个点 mAP,但速度提升明显。imgsz=640:输入分辨率。田间杂草幼苗期目标很小,640 是底线,有条件可以上 960 或 1280,小目标召回率会有可观提升,代价是显存占用翻倍。epochs:首轮训练我建议固定 100 而不是追求训到底,因为要先把 baseline 的情况摸清楚。lr0=0.01:初始学习率。用预训练权重时 0.01 是通用安全值,数据集噪声大时可以降到 0.005。patience=15:早停机制,验证集指标连续 15 轮不改善就自动停止。田间数据噪声大,指标波动是常态,patience 设太小容易在局部最优附近提前停掉。augment=false:直接关闭内置增强。这一步很多人不理解,我解释一下:YOLOv8 默认开启马赛克增强,对通用目标效果极好,但杂草幼苗期的目标太小,马赛克拼接后单目标被大幅缩小,反而把最难的样本变得更难。建议第一轮关闭增强跑一个干净的 baseline,后续再逐步打开。
4.3 训练过程中的监控:看 loss 还是看 mAP
训练开始后,最容易犯的错误是死盯 mAP 曲线忽略了 loss。我自己的观察习惯是分两阶段:前 20 轮看训练 loss 是否平稳下降,如果 loss 出现剧烈震荡而不是平滑走低,大概率是学习率过高或者数据集里存在标注错乱;20 轮之后才关注验证集的 mAP 走势,这时候模型已经从预训练权重迁移到了杂草特征上,mAP 的变化才有参考意义。
另一个实用技巧是定期在验证集上做推理可视化。每训练 25 轮左右,随机抽一批验证集图片,把预测框画出来存成图。这一步的价值在于:mAP 是一个汇总数值,它不告诉你模型把稗草错认成了什么。可视化推理结果能直观暴露出类别混淆问题——比如模型把马唐幼苗认成了牛筋草,这在 mAP 上可能只是一两个点的损失,但实际除草方案完全不同,马唐用芽前封闭药剂,牛筋草需要茎叶处理,喷错就等于白喷。
4.4 显存不够怎么办:批大小与子图切分策略
个人开发者的显卡经常是 8G 甚至 6G 显存,训练 640 分辨率加 batch 16 基本跑不动。两个常规解决办法:一是把 batch 降到 4 或 2,配合梯度累积效果还能接受;二是做子图切分,把高分辨率田间图切成 640x640 的重叠子图再训练。切分要注意重叠率,我一般设 20% 重叠,避免目标恰好被切在边缘。
切分子图会带来一个标注同步问题,因为标注框是相对原图的坐标,切分后要重新计算。推荐的做法是写脚本对每张大图切出子图后,把原图标注中落入子图的框平移并裁剪,滤掉那些切掉超过一半的框。不建议手工处理,几十张还能忍,几百张手工弄就是在浪费生命。市面上的开源脚本大多能用,但是要仔细检查切分后漏框率,我自己第一次用就漏了大概 8% 的小目标。
5. 杂草数据集训练避坑指南:5 个让我掉过头发的问题
5.1 类别样本不平衡,长尾类别直接学废
现象:训练完看报告,马唐、稗草这类常见草的 mAP 有 0.85 以上,但香附子、苘麻这些样本少的类别 mAP 只有 0.3 左右,个别类甚至低于 0.1。
原因:田间采集天然偏向常见杂草,长尾类别样本量不足,模型把学习能力全花在了头部类别上。YOLO 的默认损失函数对各类别一视同仁,样本少的类别梯度贡献小,自然学不好。
解决:最直接的办法是针对性增强——对样本少的类别做过采样,把它们复制几份混进训练集,每轮能看到这些样本多次。配合类别权重调整,让少数类的 loss 权重放大,常见做法是把权重设为median(class_counts) / class_counts,中位数类别权重为 1,少的类别权重放大到 3 到 5。另外还可以去掉一部分头部类别的冗余样本,人工制造更均衡的分布。
5.2 苗期稗草和水稻苗长得太像,模型系统性混淆
现象:验证集里稗草-幼苗期的召回率特别低,而且错误样本大量集中在「稗草被预测为水稻」的方向上。可视化检查发现,模型对幼嫩叶片形态的判别力基本失效。
原因:稗草幼苗和水稻苗在颜色、叶形、纹理上高度相似,训练数据里如果苗期样本占比不够,模型就没机会学到区分这二者的细微特征。
解决:从两个方向入手。一是数据层面,单独统计苗期样本占比,如果低于 30%,优先补充苗期数据,而不是盲目增加总量。二是模型层面,把「稗草-幼苗期」单独拆成一个类别训练,让模型显式学习这个难分变体。实测这个操作通常能提升苗期召回率 10 个点以上。
5.3 验证集指标好看,下地一测全部翻车
现象:模型在验证集上 mAP 0.82,感觉稳了,拿到真实田间测试,发现预测框大量漂移,特别是逆光、土壤湿润反光、杂草密集簇生的场景,检测结果一塌糊涂。
原因:数据划分时偷了懒。很多数据集是按图片随机切分的,同一块田、同一天采的数据可能同时出现在训练集和验证集里,模型记住了场景背景而不是杂草本身。验证集指标虚高,部署时现出原形。
解决:权威数据集强调「按田块划分」——把一个田块的所有图片要么全放训练集,要么全放验证集。如果你是拿现成数据集,看它的划分逻辑是不是按采集会话(session)分的。实在没法重分,就在部署前专门跑一轮「异地数据体检」,拿另一块田的图片做测试,这个分数才是你真正要对外承诺的。
5.4 光照和湿度变化让模型不稳定
现象:晴天的验证集 mAP 正常,阴天或傍晚测试 mAP 掉 15 个点以上。水田场景积水反光时,模型疯了一样输出置信度很低的密集小框。
原因:田间光照是训练集里最大的隐变量,不同天气、不同时刻的色温和光照强度差异极大。水田反光会产生大量镜面高光区域,这些区域纹理混乱,模型容易把反光误判为叶片边缘,输出一堆假阳性。
解决:训练集里刻意混入不同天气和时段的数据,阴天和晴天的比例最好在 1:3 到 1:4 之间。数据增强里把亮度扰动幅度加大到 ±30%,让模型见过更宽的光照范围。另外一个实用技巧是推理阶段做简单的白平衡归一化:推理前用灰度世界假设对图片做一次颜色校正,能把跨天气的色温差异压掉一大半。
5.5 标注框框得太「完美」,模型反而不会泛化
现象:训练出来的模型在数据集的图片上表现很好,但换一个数据集测试,精度断崖式下降。仔细检查发现,原数据集的标注框紧贴叶片,边缘精确到像素级,而实际部署时目标边缘总是带着泥土、水珠或其他杂物。
原因:过度精确的标注让模型学到了「干净的叶片边缘」这一伪特征,真实场景中的遮挡和脏污让它无所适从。
解决:训练阶段引入轻微标注抖动——对标注框的坐标加 ±2% 到 ±3% 的随机偏移,相当于告诉模型「目标的边界不是一个精确的像素位置,而是一个区域」。这个方法听起来有点反直觉,但对田间场景的鲁棒性提升非常明显。另一个补充手段是在增强阶段随机给图片添加模拟泥点或水滴的噪声块,让模型提前适应杂草目标被部分遮挡的形态。
6. 验证模型的成色:混淆矩阵、逐类 mAP 和一张「信任清单」
模型训完后,先别急着看总 mAP,那是一个自我安慰的数字。我习惯把val集上的预测结果导出,手工画一个 20 类的混淆矩阵。重点关注两类错误:一是同类群内部的混淆(比如稗草和马唐同属禾本科,叶片形态本来就接近,混淆可以容忍),二是跨类群的混淆(比如禾本科被认成阔叶类,这直接导致施药方案错误,属于不可接受的错误)。如果后一种错误占比超过 5%,说明模型在特征提取层面存在系统缺陷,不是调参能救的。
逐类 mAP 的解读也要分场景。我一般按杂草的危害等级给每个类别标一个「可接受的最低 mAP」。对于稗草、马唐这种发生范围广、竞争能力强的恶性杂草,mAP 低于 0.75 就属于不合格;对个别分布局限的杂草,0.5 都能用。因为这类杂草即使漏检,也还有其他的防除手段兜底——别把模型当成唯一防线。
还有一个值得养成的习惯:每一轮训练结束,把模型在固定测试集上的混淆矩阵、逐类 mAP 和超参数配置存成一个纯文本报告存档。这相当于给每一次实验留了后悔药。你过了两三个月回头看,可能发现当时觉得「玄学」的参数组合其实是有效果的,但你已经找不到当时的配置了——这种事我经历过不止一次。这个报告不需要写得花哨,文件名带上日期和配置摘要就够用。
给新手的最终建议是:拿到任何杂草数据集,先花一天做数据体检(格式、分布、坏图、标注质量),再花两天跑第一个 baseline 和可视化推理,最后才谈调参。数据体检做扎实了,后面的训练和调参能省出一大堆时间。这个工作流我用了很多个杂草识别项目,翻车的次数在明显下降。希望帮到你。
本文还有配套的精品资源,点击获取