简介:这份资源面向从事农业智能识别、计算机视觉方向的研究者与开发者,提供一套可直接用于YOLO系列目标检测训练的杂草检测数据集,帮助解决田间杂草识别模型训练中样本不足、标注格式不统一的问题。压缩包共约2000个文件,以xml格式的VOC标注文件为主,同时配套txt格式标签,压缩包整体约128MB,目录已按train、val、test划分完毕,并附有data.yaml配置文件,yolov5、yolov7、yolov8、yolov9等主流算法可直接读取训练。数据集包含4000多张杂草图像,覆盖多种田间场景,标注信息完整,便于快速开展模型训练与效果验证。目前已有449人学习下载,适合需要搭建杂草检测基线、对比不同YOLO版本性能或进行数据增强实验的读者参考使用。
1. 杂草检测数据集与 YOLO 训练:4000 张图能跑出什么效果
田里那点事,最怕两样:草和苗长得太像,以及人工打药打不准。我最早接触杂草检测,是因为一个做植保的朋友抱怨,雇人拔草一天几百块,还分不清稗草和稻苗。后来我们试着用 YOLO 做了一版,核心资产就是一份 4000 多张的杂草检测数据集。这个量级不算大,但足够把 YOLO 从零训到能用的程度,前提是标注质量过关、类别定义清晰。
这份数据集通常覆盖禾本科杂草、阔叶杂草、作物幼苗几大类,标注格式以 YOLO 的 txt 为主,每张图对应一个同名 txt,内容是类别 x_center y_center w h的归一化坐标。4000 张听起来不少,但分到 5 到 8 个类别,每类也就几百张,属于典型的小样本目标检测场景。适合谁?适合想入门 YOLO 目标检测、手头没有工业级数据、又不想拿 COCO 那种通用数据集练手的从业者。杂草检测这个场景有个好处:背景相对固定,目标形态有规律,训出来的模型能直接下地试。
2. 杂草数据集怎么选、怎么拆、怎么标
2.1 4000 张杂草图的类别分布与采集边界
拿到一份杂草数据集,第一件事不是急着训,而是先看类别分布。4000 多张图,如果按常见做法分 6 类,平均每类 600 多张,但实际往往不均衡。我见过一份数据,马唐草占了 40%,其他几类加起来才 60%。这种长尾分布直接训,模型会对多数类过拟合,少数类召回率上不去。
采集边界也要看。杂草检测的图一般来自田间实拍,光照从清晨到正午都有,土壤湿度不同,背景有裸土、有残茬、有地膜。如果数据集里全是晴天正午的图,模型到了阴天就翻车。我一般会先统计一下图片的亮度分布和拍摄角度,用下面这段脚本快速过一遍:
import os import cv2 import numpy as np img_dir = "weed_dataset/images" brightness_list = [] for name in os.listdir(img_dir): if not name.lower().endswith((".jpg", ".png", ".jpeg")): continue img = cv2.imread(os.path.join(img_dir, name)) if img is None: continue gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) brightness_list.append(gray.mean()) brightness_list = np.array(brightness_list) print(f"图片总数: {len(brightness_list)}") print(f"平均亮度: {brightness_list.mean():.1f}") print(f"亮度标准差: {brightness_list.std():.1f}") print(f"最暗10%均值: {np.percentile(brightness_list, 10):.1f}") print(f"最亮10%均值: {np.percentile(brightness_list, 90):.1f}")这段代码遍历图片目录,转灰度后算平均亮度。逻辑很简单:亮度标准差小于 15,说明光照条件单一,训练时要做亮度增强;最暗 10% 和最亮 10% 的均值差距超过 80,说明数据里既有欠曝也有过曝,增强策略要覆盖两端。参数上,cv2.imread读进来是 BGR,转灰度用COLOR_BGR2GRAY,别用错。
2.2 训练集验证集划分:别让同一块田的图同时出现在两边
划分数据集有个血泪教训:如果同一块田、同一株草的不同角度图同时进了训练集和验证集,验证指标会虚高。模型记住了背景里的土壤纹理,而不是草的特征。正确做法是按拍摄批次或田块划分,同一批次的图只进一边。
常见做法是 8:1:1,训练 3200 张、验证 400 张、测试 400 张。如果数据量再小,可以 7:2:1。划分脚本我一般这么写:
import os import random import shutil random.seed(42) src_img = "weed_dataset/images" src_lbl = "weed_dataset/labels" dst_root = "weed_split" for split in ["train", "val", "test"]: os.makedirs(os.path.join(dst_root, "images", split), exist_ok=True) os.makedirs(os.path.join(dst_root, "labels", split), exist_ok=True) names = [f for f in os.listdir(src_img) if f.lower().endswith((".jpg", ".png"))] random.shuffle(names) n = len(names) n_train = int(n * 0.8) n_val = int(n * 0.1) split_map = {} for i, name in enumerate(names): if i < n_train: split_map[name] = "train" elif i < n_train + n_val: split_map[name] = "val" else: split_map[name] = "test" for name, split in split_map.items(): stem = os.path.splitext(name)[0] shutil.copy(os.path.join(src_img, name), os.path.join(dst_root, "images", split, name)) lbl_name = stem + ".txt" lbl_src = os.path.join(src_lbl, lbl_name) if os.path.exists(lbl_src): shutil.copy(lbl_src, os.path.join(dst_root, "labels", split, lbl_name)) print("划分完成")random.seed(42)保证每次划分结果一致,方便复现。split_map先记录每张图的归属,再统一拷贝,避免边遍历边移动导致混乱。注意标签文件和图片文件必须同名同目录结构,YOLO 训练时按路径自动配对,少一个标签就会报 warning。
2.3 标注格式转换与类别映射的坑
杂草数据集常见的标注来源有三种:LabelImg 的 XML、CVAT 的 JSON、以及已经转好的 YOLO txt。如果你拿到的是 VOC XML,需要转成 YOLO 格式。转换时最容易错的是坐标归一化:VOC 的xmin ymin xmax ymax是绝对像素,YOLO 要的是x_center y_center w h除以宽高。
import xml.etree.ElementTree as ET import os classes = ["grass", "sedge", "broadleaf", "crop", "soil", "other"] class_to_id = {c: i for i, c in enumerate(classes)} def voc_to_yolo(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text.strip() if cls_name not in class_to_id: continue bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_to_id[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return linesclasses列表的顺序就是类别 ID 的顺序,必须和训练时 data.yaml 里的names完全一致,否则模型学到的类别会错位。class_to_id过滤掉不在列表里的类别,避免未知类别导致训练崩溃。坐标保留 6 位小数足够,YOLO 内部会再处理。
3. 用 YOLOv8 在 4000 张杂草图上跑通训练
3.1 环境安装与 data.yaml 的最小配置
YOLOv8 用 ultralytics 包,安装就一行。我一般建个虚拟环境,避免和系统里的 torch 冲突:
conda create -n weed_yolo python=3.10 -y conda activate weed_yolo pip install ultralytics opencv-python装完验证一下:
yolo checks会输出环境信息,重点看 CUDA 是否可用。如果显示CPU,训练会慢到怀疑人生。杂草检测这种小数据集,GPU 上 100 轮大概十几分钟,CPU 上可能要几个小时。
data.yaml 是训练的数据入口,最小配置如下:
path: /home/user/weed_split train: images/train val: images/val test: images/test names: 0: grass 1: sedge 2: broadleaf 3: crop 4: soil 5: otherpath是数据集根目录,train/val/test是相对路径。names的键从 0 开始,和标注文件里的类别 ID 对应。如果类别数不是 6,改这里就行,但标注文件里的 ID 也要同步改。
3.2 训练命令与关键参数:imgsz、batch、epochs 怎么定
训练命令我一般用命令行,方便改参数:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=weed_runs \ name=exp1model=yolov8n.pt是最小的 nano 版本,参数量少,适合 4000 张的小数据集。如果显存够,可以换yolov8s.pt,精度会高一点。imgsz=640是 YOLO 的默认输入尺寸,杂草目标一般不大,640 够用;如果草苗特别小,可以上 1280,但显存翻倍。batch=16在 8G 显存上比较稳,12G 可以上 32。lr0=0.01是初始学习率,小数据集别设太大,否则 loss 震荡。patience=20表示 20 轮验证指标不提升就早停,省时间。
训练过程中重点看三个指标:box_loss是否稳定下降、mAP50是否上升、mAP50-95是否跟着涨。如果box_loss降但mAP不涨,大概率是过拟合,加数据增强或减模型容量。
3.3 训练日志怎么看:loss 曲线与 mAP 的对应关系
YOLOv8 训练完会在weed_runs/exp1下生成results.csv和若干曲线图。我习惯先看results.csv的最后几行:
tail -5 weed_runs/exp1/results.csv输出类似:
epoch, train/box_loss, train/cls_loss, metrics/mAP50, metrics/mAP50-95 96, 0.823, 0.412, 0.912, 0.634 97, 0.815, 0.405, 0.915, 0.641 98, 0.808, 0.398, 0.918, 0.648 99, 0.801, 0.392, 0.920, 0.652 100, 0.795, 0.388, 0.921, 0.655mAP50到 0.92 说明模型在 IoU 0.5 时基本能框住杂草,mAP50-950.65 说明框的精度还有提升空间。如果mAP50高但mAP50-95低,通常是框的位置不够准,可以试试加mosaic增强或调box损失权重。杂草检测里,mAP50到 0.85 以上就能下地试了,别死磕 0.95,田间光照一变,指标都会掉。
4. 杂草检测训练避坑:从标注到推理的 5 个翻车点
4.1 类别不均衡导致少数类召回率极低
现象:训练完看混淆矩阵,多数类grass召回 0.95,少数类sedge召回只有 0.3。
原因:4000 张图里sedge只有 200 个标注框,模型没见过多少正样本,倾向于全预测成背景或多数类。
解决:两种做法。一是过采样,把含sedge的图复制多份进训练集,但别复制到验证集。二是用 YOLOv8 的cls损失权重,在训练命令里加cls=1.5,让分类损失占更大比重。我一般先过采样,简单直接。
4.2 标注框贴边导致训练时被裁掉
现象:训练时 warning 提示ignoring corrupted label,或者某些目标在增强后消失。
原因:YOLO 的 mosaic 增强会随机裁剪拼接,如果标注框紧贴图片边缘,裁剪后框可能只剩一部分,宽高变成 0 或负数。
解决:标注时留 2 到 3 像素边距,别贴着边画。已经标好的,用脚本检查并修正:
import os lbl_dir = "weed_split/labels/train" for name in os.listdir(lbl_dir): path = os.path.join(lbl_dir, name) with open(path, "r") as f: lines = f.readlines() new_lines = [] for line in lines: parts = line.strip().split() if len(parts) != 5: continue cls, x, y, w, h = parts x, y, w, h = float(x), float(y), float(w), float(h) if w <= 0.001 or h <= 0.001: continue new_lines.append(f"{cls} {x:.6f} {y:.6f} {w:.6f} {h:.6f}\n") with open(path, "w") as f: f.writelines(new_lines)这段代码过滤掉宽高过小的框,避免训练时报错。0.001是经验阈值,归一化后小于这个值的框基本没意义。
4.3 验证集指标虚高:背景泄漏
现象:验证集mAP500.95,但拿新拍的田图一测,漏检一半。
原因:验证集和训练集来自同一块田的相邻区域,背景土壤颜色、光照几乎一样,模型记住了背景而不是草。
解决:划分时按田块或拍摄日期分。如果数据里没有田块信息,至少按图片文件名前缀分,同一批次的图只进一边。验证集最好包含不同光照、不同土壤湿度的图。
4.4 推理时置信度阈值设太高
现象:模型训练指标不错,但推理时框很少,漏检多。
原因:默认conf=0.25,杂草检测里有些目标置信度在 0.15 到 0.25 之间,被过滤掉了。
解决:推理时降低阈值,用conf=0.15试:
yolo detect predict \ model=weed_runs/exp1/weights/best.pt \ source=test_images \ conf=0.15 \ iou=0.5 \ save=Trueconf是置信度阈值,iou是 NMS 的 IoU 阈值。杂草密集时iou可以降到 0.4,避免相邻草被合并。
4.5 模型导出后精度掉点
现象:PyTorch 模型mAP500.92,导出 ONNX 后掉到 0.88。
原因:导出时输入尺寸、归一化方式或算子实现有差异。
解决:导出时固定imgsz和opset:
yolo export \ model=weed_runs/exp1/weights/best.pt \ format=onnx \ imgsz=640 \ opset=12 \ simplify=Trueopset=12兼容性较好,simplify=True会做图优化。导出后用 onnxruntime 跑一遍验证,对比输出差异。如果掉点超过 2 个点,检查预处理是否一致,YOLO 默认是 RGB、归一化到 0-1、letterbox 填充。
5. 小数据集涨点技巧:从 4000 张里榨出更多信息
4000 张图训 YOLO,瓶颈不在模型,在数据多样性。我一般从三个方向榨:增强、迁移、伪标签。
增强方面,YOLOv8 默认开了 mosaic、mixup、hsv。杂草检测里我额外加degrees=10做小角度旋转,translate=0.1做平移,scale=0.5做缩放。别开flipud,草不会倒着长,开了反而引入噪声。参数在训练命令里直接加:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ degrees=10 \ translate=0.1 \ scale=0.5 \ mosaic=1.0 \ mixup=0.1 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4hsv_h控制色调抖动,hsv_s饱和度,hsv_v亮度。杂草在不同光照下颜色差异大,这三个值可以适当调大,但hsv_h别超过 0.02,否则草的颜色会变得不像草。
迁移学习方面,yolov8n.pt本身就是在 COCO 上预训练的,已经学到了边缘、纹理等底层特征。杂草检测和 COCO 的差异主要在目标形态,所以冻结 backbone 前几层再训,小数据集上更稳。YOLOv8 没直接暴露冻结层数的参数,但可以用freeze参数:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ freeze=10 \ epochs=100freeze=10表示冻结前 10 层,只训后面的检测头。层数怎么定?我一般先跑一版不冻结的,看box_loss下降快不快。如果前 10 轮就降到 1.0 以下,说明预训练特征够用,可以冻结;如果降得慢,就别冻。
伪标签是最后的手段。先用训好的模型对未标注的田图做推理,挑置信度高于 0.7 的框作为伪标签,加入训练集再训一轮。注意伪标签的类别要和原数据集一致,且验证集不能用伪标签,否则指标失真。我试过一次,4000 张基础上加了 800 张伪标签图,mAP50从 0.91 涨到 0.93,但再往上加就过拟合了。
最后说个习惯:每次训完,我都会拿几张新拍的田图跑一遍,肉眼数漏检和误检。指标是给别人看的,田里的效果才是自己的。杂草检测这行,数据质量比模型结构重要得多,4000 张标得准的图,比 4 万张标得糙的图管用。希望帮到你。
本文还有配套的精品资源,点击获取