
简介这份茶叶与杂草检测数据集面向农业AI开发者、计算机视觉研究者及农业机器人团队用于构建茶园杂草自动识别与精准除草模型。数据采集自真实农业场景包含maleza杂草与te茶两个关键类别标注采用YOLO格式可直接用于目标检测或实例分割任务兼容主流深度学习框架便于快速集成与部署。压缩包共656个文件以327张jpg图像和327个同名txt标注文件为主另附1个yaml配置文件与1份docx说明文档整体约25.71MB训练集288张、验证集25张、测试集14张划分清晰。目前已有54人学习下载。借助精准的类别标签与真实场景样本读者可快速搭建农业智能监测系统、训练杂草识别模型或将其作为教学案例用于农业AI实践培训提升模型在实际环境中的泛化能力。1. 茶叶与杂草检测数据集从标注文件到可训练模型的落地路径茶园里最头疼的不是采茶而是分辨哪些是茶、哪些是草。嫩芽和杂草在早期生长阶段颜色接近、纹理相似人工巡检一亩地要花大半天漏检率还高。这个标题指向的是一份已经打包好的茶叶与杂草检测数据集文件名里的时间戳说明它是某个时间点导出的快照。它解决的核心问题是让你跳过最耗时的野外拍摄和标注环节直接拿到一批带边界框的图片用来训练目标检测模型最终部署到无人机或边缘设备上做自动识别。适合谁用做农业AI的算法工程师、想验证检测方案的学生、以及需要快速出demo的植保团队。但拿到压缩包只是起点真正决定成败的是标注格式、类别定义和划分策略——这三件事没搞对后面训出来的模型就是玄学。2. 拆开压缩包先看什么目录结构与标注格式判定2.1 解压后的典型目录布局与文件类型拿到一个检测数据集压缩包我一般不会急着写训练脚本而是先看目录树。常见的组织方式有两种一种是VOC风格的AnnotationsJPEGImagesImageSets另一种是YOLO风格的imageslabels加一个data.yaml。茶叶与杂草这类农业数据集因为标注人员习惯不同两种都可能出现。先执行解压和目录扫描unzip 茶叶与杂草检测数据集_20251116_211936.zip -d tea_weed_dataset cd tea_weed_dataset find . -maxdepth 2 -type d | sort find . -name *.xml | head -5 find . -name *.txt | head -5 find . -name *.json | head -5这段命令做三件事解压到指定目录、列出两层目录结构、分别探测XML、TXT、JSON三种标注文件是否存在。参数-maxdepth 2避免递归太深刷屏head -5只取样看格式。如果输出里XML多基本是VOC如果TXT和图片同名成对出现大概率是YOLO格式JSON则可能是COCO或LabelMe导出。2.2 判定标注格式的三个检查点判定格式不能只看后缀要打开文件看内容。第一个检查点XML里是否有object节点每个节点下name就是类别名。第二个检查点TXT文件每行是不是class_id x_center y_center width height五个值且数值在0到1之间——这是YOLO归一化坐标。第三个检查点JSON里是否有images、annotations、categories三个顶层键这是COCO标准。import json, os, xml.etree.ElementTree as ET def detect_format(root): xmls [f for f in os.listdir(root) if f.endswith(.xml)] txts [f for f in os.listdir(root) if f.endswith(.txt)] jsons [f for f in os.listdir(root) if f.endswith(.json)] if xmls: tree ET.parse(os.path.join(root, xmls[0])) names [obj.find(name).text for obj in tree.findall(.//object)] return VOC, names if txts: with open(os.path.join(root, txts[0])) as f: line f.readline().strip().split() return YOLO, line if jsons: with open(os.path.join(root, jsons[0])) as f: data json.load(f) return COCO, list(data.keys()) return UNKNOWN, []逻辑说明优先检查XML解析第一个文件的所有object节点取出类别名没有XML则看TXT首行返回五个字段再没有则读JSON顶层键。参数上root传标注文件所在目录。这一步的输出直接决定你后面用哪套转换脚本。2.3 类别名清洗与ID映射表茶叶与杂草数据集的类别名经常有坑有的写成中文“茶叶”“杂草”有的写成“tea”“weed”还有的混用“tea_leaf”“weed_grass”。训练框架通常要求类别是连续的整数ID所以必须建映射表。我一般会统计所有出现的类别名去重后排序再手动确认有没有同义项需要合并。from collections import Counter def build_class_map(ann_dir, fmtVOC): counter Counter() if fmt VOC: for f in os.listdir(ann_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, f)) for obj in tree.findall(.//object): counter[obj.find(name).text.strip()] 1 elif fmt YOLO: for f in os.listdir(ann_dir): if not f.endswith(.txt): continue with open(os.path.join(ann_dir, f)) as fh: for line in fh: counter[line.split()[0]] 1 names sorted(counter.keys()) return {name: idx for idx, name in enumerate(names)}, counter这段代码统计每个类别出现的次数排序后生成name-id字典。counter的用处是发现长尾类别——如果某个类别只有个位数标注训练时基本学不到考虑合并或丢弃。参数ann_dir是标注目录fmt根据上一步判定结果传入。3. 从原始标注到训练格式转换脚本与划分策略3.1 VOC转YOLO的完整转换脚本如果你拿到的是VOC格式而想用YOLO系列训练转换是绕不开的。核心是把XML里的绝对坐标xmin,ymin,xmax,ymax转成归一化的中心点加宽高。这里有个容易翻车的地方图片尺寸要从对应的图片文件读不能假设所有图都是同一分辨率。from PIL import Image import os def voc_to_yolo(xml_dir, img_dir, out_dir, class_map): os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if not f.endswith(.xml): continue stem os.path.splitext(f)[0] img_path None for ext in [.jpg, .jpeg, .png]: p os.path.join(img_dir, stem ext) if os.path.exists(p): img_path p; break if not img_path: continue w, h Image.open(img_path).size tree ET.parse(os.path.join(xml_dir, f)) lines [] for obj in tree.findall(.//object): name obj.find(name).text.strip() if name not in class_map: 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) xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_map[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, stem .txt), w) as out: out.write(\n.join(lines))逻辑上先根据XML文件名找到同名图片读尺寸再逐object转换。参数class_map来自上一节的映射表。注意坐标要裁剪到0到1之间有些标注框会超出图片边界不裁剪会导致训练时loss异常。3.2 训练集验证集划分的两种策略划分不是随机切就完事。农业数据集有个特点同一块地、同一天拍的照片高度相似如果随机划分验证集里会出现和训练集几乎一样的图指标虚高。我一般用两种策略一是按拍摄批次分如果文件名里带时间或地块编号按编号切二是按图片哈希去重后再随机分。import hashlib, random, shutil def split_dataset(img_dir, label_dir, out_root, ratio0.8): files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg,.png,.jpeg))] # 按文件内容哈希去重避免近似重复图跨集 seen {} for f in files: h hashlib.md5(open(os.path.join(img_dir, f),rb).read()).hexdigest() seen.setdefault(h, []).append(f) unique [v[0] for v in seen.values()] random.shuffle(unique) n int(len(unique) * ratio) for split, subset in [(train, unique[:n]), (val, unique[n:])]: os.makedirs(f{out_root}/images/{split}, exist_okTrue) os.makedirs(f{out_root}/labels/{split}, exist_okTrue) for f in subset: shutil.copy(os.path.join(img_dir, f), f{out_root}/images/{split}/{f}) stem os.path.splitext(f)[0] lbl os.path.join(label_dir, stem .txt) if os.path.exists(lbl): shutil.copy(lbl, f{out_root}/labels/{split}/{stem}.txt)哈希去重那步是关键参数ratio控制训练集比例农业数据集我通常给到0.85因为样本本身就不多。输出目录结构直接对齐YOLO训练要求。3.3 data.yaml的字段含义与常见填错YOLO训练需要一个data.yaml里面path、train、val、nc、names五个字段。nc是类别数必须和names长度一致多一个少一个都报错。names的顺序必须和转换时的class_map完全对应否则模型学出来的类别是错位的。path: /data/tea_weed train: images/train val: images/val nc: 2 names: 0: tea 1: weed注意path写绝对路径train和val写相对路径。如果训练时提示找不到图片九成是这两个路径拼接后不对。4. 训练参数怎么设茶叶与杂草场景的调参经验4.1 输入分辨率与批量大小的取舍茶叶和杂草在图像里往往只占几十个像素分辨率太低直接丢特征。我一般把imgsz设到640起步如果显存够就上到1024。批量大小batch和分辨率是跷跷板640下能跑161024下可能只能跑4。用YOLOv8的话命令行里直接指定。yolo detect train datadata.yaml modelyolov8n.pt imgsz640 batch16 epochs100 patience20patience20表示20轮指标不升就早停农业数据集容易过拟合这个参数能省不少时间。model选n还是s看你的部署端边缘设备选n服务器可以上m。4.2 学习率与数据增强的农业场景适配默认学习率lr00.01对小数据集偏大我一般降到0.001。数据增强里mosaic对茶叶杂草有用能增加小目标在不同背景下的出现次数但mixup要慎用它把两张图线性叠加茶叶和杂草叠在一起后标注框语义就乱了。degrees旋转可以开因为无人机拍摄角度本来就不固定。yolo detect train datadata.yaml modelyolov8s.pt imgsz640 batch8 epochs150 lr00.001 mosaic1.0 mixup0.0 degrees15.0参数说明mosaic1.0表示100%概率启用mixup0.0关闭degrees15.0限制旋转角度避免引入不真实样本。4.3 类别不平衡时的损失权重调整如果统计发现茶叶标注框远多于杂草模型会偏向茶叶。YOLO本身没有直接的类别权重参数但可以通过复制杂草样本或使用focal loss变体来缓解。更简单的做法是在数据集层面过采样杂草图片让两个类别的实例数接近。import os, shutil, random def oversample_minority(label_dir, img_dir, out_img, out_lbl, target_cls1, times3): os.makedirs(out_img, exist_okTrue); os.makedirs(out_lbl, exist_okTrue) for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fh: lines fh.readlines() has_minority any(l.split()[0] str(target_cls) for l in lines if l.strip()) stem os.path.splitext(f)[0] for ext in [.jpg,.png,.jpeg]: src os.path.join(img_dir, stemext) if os.path.exists(src): break copies times if has_minority else 1 for i in range(copies): suffix f_os{i} if i 0 else shutil.copy(src, os.path.join(out_img, stemsuffixos.path.splitext(src)[1])) shutil.copy(os.path.join(label_dir, f), os.path.join(out_lbl, stemsuffix.txt))这段把含杂草的图复制多份文件名加后缀避免冲突。参数times控制复制倍数一般2到3倍就够太多会导致过拟合。5. 避坑与排查标注和训练里最容易翻车的五件事5.1 标注框越界导致loss变NaN现象训练几个epoch后loss突然变成nan或者框的回归损失异常大。原因VOC转YOLO时没有裁剪坐标某些框的xmax超过图片宽度归一化后大于1。解决在转换脚本里加xc min(max(xc, 0), 1)这类裁剪或者转换后统一扫描所有label文件发现值大于1或小于0就修正。5.2 类别名大小写不一致导致类别数翻倍现象data.yaml里写nc: 2但训练日志显示模型输出3类甚至更多。原因标注里同时存在Tea和tea建映射表时被当成两个类。解决在build_class_map里统一转小写再统计或者手动合并同义项。5.3 图片和标注文件名不匹配现象训练时警告“image not found”或“label missing”大量样本被跳过。原因图片是.JPG大写后缀标注是.txt小写或者文件名里有空格、中文括号。解决写一个配对检查脚本遍历图片目录检查同名.txt是否存在不存在的列出来人工处理。5.4 验证集指标虚高但实际推理很差现象mAP0.5跑到0.9但拿新拍的茶园图测试漏检严重。原因划分时没有按地块或批次分验证集和训练集图片高度相似。解决回到3.2节的哈希去重加按批次划分确保验证集来自不同拍摄条件。5.5 小目标被下采样吃掉现象茶叶嫩芽这种小目标检测不到但大棵杂草能检出。原因imgsz设得太小或者模型下采样倍数太高。解决提高输入分辨率到1024或者换用带P2层的检测头如YOLOv8-p2变体P2层保留更高分辨率的特征图。6. 验证模型是否真的学到茶叶与杂草三个进阶检查技巧训练完看mAP只是第一步我习惯再做三件事确认模型不是靠背景作弊。第一件用yolo detect predict跑一批验证集图片把预测框画出来存盘肉眼扫一遍有没有把茶垄阴影当成杂草。第二件做类别激活可视化看模型关注区域是否落在叶片上而不是土壤。第三件构造负样本测试——拿纯土壤、纯天空、纯茶垄的图喂进去如果模型在这些图上输出高置信度框说明它学的是背景捷径。yolo detect predict modelruns/detect/train/weights/best.pt sourceval_images saveTrue conf0.25conf0.25是置信度阈值调低能看到更多候选框方便排查漏检调高则看误检。我一般会跑两遍一遍0.25看召回一遍0.5看精度。还有一个技巧是检查标注框的宽高分布。如果茶叶框的宽高比集中在某个窄区间而杂草框分布很散模型可能只靠形状就能分类换个品种就崩。用pandas统计一下import pandas as pd, os records [] for f in os.listdir(labels/train): with open(flabels/train/{f}) as fh: for line in fh: c, x, y, w, h line.split() records.append({cls: int(c), w: float(w), h: float(h), ar: float(w)/float(h)}) df pd.DataFrame(records) print(df.groupby(cls)[ar].describe())如果两个类别的宽高比均值差不到0.1说明形状区分度低得靠纹理和颜色特征这时候数据增强里的色彩抖动hsv_v可以适当调大。我踩过最深的坑是拿到数据集直接开训结果类别映射错了茶叶和杂草的ID反了模型把茶叶当杂草除。后来养成习惯转换完先拿一张图手动把label画回图上确认框和类别都对得上再开始训练。这个检查花五分钟能省一整天返工。希望帮到你。本文还有配套的精品资源点击获取