
简介面向水稻田杂草检测与精准农业场景这份数据集包含221张水稻慈姑类杂草实拍图片标注类别为慈姑sagittaria与慈姑花sagittaria_flower累计1264个目标框。资源同时提供Pascal VOC和YOLO两种标注格式并附有对应的xml与txt文件可直接用于训练YOLO系列、Faster R-CNN等目标检测模型适用于智慧农业、除草机器人视觉等研究方向。整个压缩包共665个文件以221张jpg原图、221个xml标注和223个txt标签文件为主大小约129.55MB解压后目录结构清晰便于按图像与标注一一对应地开展训练与验证。目前已有144人学习下载适合农业AI领域入门或需要现成杂草标注数据的开发者快速上手。1. 水稻慈姑类杂草检测221张图、1264个框能做什么做农业视觉检测的同行应该都有体会公开数据集里大田作物多、杂草数据少能用的杂草数据又普遍存在标注类别过粗、背景单一的问题。这份水稻慈姑类杂草检测数据集一共221张jpg图Pascal VOC和YOLO格式双份标注齐全覆盖两个类别——sagittaria(慈姑植株)和sagittaria_flower(慈姑花)总框数1264个。对想快速验证杂草检测方案、做迁移学习或者跑YOLO系列模型的人来说它最大的价值是开箱即用不用花时间自己转格式、重新划分训练验证集。尤其适合两类人一是在校学生做毕设需要真实农业场景数据支撑二是刚接触目标检测的工程师想用一份小规模但完整的标注数据把YOLOv8训练全流程跑通。2. 数据盘点与目录结构拿到手先做三件事这份数据集虽然只有221张图但直接打开压缩包就开训很容易翻车。我先说拿到手之后应该怎么盘它你再决定怎么划分训练集。2.1 文件构成和标注格式体检解压之后目录里是三种文件jpg图片、VOC格式的xml标注、YOLO格式的txt标注。文件名一对一对应没有多余的README或者分割路径txt这点对新手反而友好——不用去解析复杂的目录层级。建议先跑一个脚本做体检确认图片、xml、txt三个文件数量一致并且xml里的标注对象数和txt里的行数能对上。常见做法是用Python的os模块遍历目录统计文件数再抽查几个xml文件看标注内容是否为空或者缺类别。import os from xml.etree import ElementTree as ET img_dir images xml_dir annotations_xml txt_dir annotations_txt imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] xmls [f for f in os.listdir(xml_dir) if f.endswith(.xml)] txts [f for f in os.listdir(txt_dir) if f.endswith(.txt)] print(f图片数量: {len(imgs)}, xml数量: {len(xmls)}, txt数量: {len(txts)}) # 抽查前3个xml确认标注框数量和类别名称 for xml_name in sorted(xmls)[:3]: tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() objects root.findall(object) print(f{xml_name}: {len(objects)}个目标) for obj in objects: name obj.find(name).text 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) w xmax - xmin h ymax - ymin print(f 类别: {name}, 宽: {w:.1f}, 高: {h:.1f})这段脚本做的事情很简单统计三个目录的文件数量是否一致再读xml里每个框的坐标和类别。重点看宽高比如果大量框的宽高比集中在某个区间说明数据里的植株形态相对一致训练出来的模型对极端形态的泛化能力有限。如果xml和txt文件数量对不上多半是漏标或者导出异常这种数据不能直接进训练。2.2 类别分布和框尺寸的统计学观察从摘要数据看sagittaria有744个框sagittaria_flower有520个框总框数1264。两类比例大约是6:4对二分类检测来说不算严重失衡不需要刻意做类别权重补偿。但我一般还会看一眼框尺寸分布因为小目标和大目标的训练策略完全不同。import os from xml.etree import ElementTree as ET import matplotlib.pyplot as plt xml_dir annotations_xml sizes {sagittaria: [], sagittaria_flower: []} for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() # 从filename标签读对应的图片尺寸 img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) w float(bndbox.find(xmax).text) - float(bndbox.find(xmin).text) h float(bndbox.find(ymax).text) - float(bndbox.find(ymin).text) # 计算框面积占图片面积的比例 ratio (w * h) / (img_width * img_height) sizes[name].append(ratio) fig, ax plt.subplots(1, 2, figsize(12, 4)) for idx, (cls, data) in enumerate(sizes.items()): ax[idx].hist(data, bins30) ax[idx].set_title(cls) plt.show()这个可视化脚本会画出两个类别的目标占比直方图。如果发现大量小框训练时需要调小anchors或者开启多尺度训练如果框占比普遍偏大说明相机离植株近模型更容易学到大尺度特征。这里有个经验sagittaria是植株本体框通常比花朵大可以预见到两类框的尺度分布有明显差异做数据增强时对不同类别要区别处理比如花朵小目标用mosaic增强更容易提升召回。2.3 图片质量审核不能省221张图里难免有个别模糊、过曝或者被遮挡严重的。直接用这些脏数据训练模型精度会莫名掉一两个点而且很难定位原因。我一般会写个批量脚本把每张图的尺寸、亮度均值、清晰度拉普拉斯方差列出来快速筛出异常样本。import cv2 import numpy as np img_dir images results [] for img_name in sorted(os.listdir(img_dir)): if not img_name.endswith(.jpg): continue img cv2.imread(os.path.join(img_dir, img_name)) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 拉普拉斯方差值越小说明图片越模糊 laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var() brightness gray.mean() h, w img.shape[:2] results.append((img_name, w, h, brightness, laplacian_var)) # 按清晰度升序排列看最模糊的10张 results.sort(keylambda x: x[4]) for item in results[:10]: print(f{item[0]}: 尺寸{item[1]}x{item[2]}, 亮度{item[3]:.1f}, 拉普拉斯方差{item[4]:.2f})拉普拉斯方差是最快的模糊检测手段低于某个阈值比如50的图片我会手动过一遍确认是否影响标注框的可见性。亮度异常偏低或偏高的图也可能导致训练初期loss震荡尤其是使用预训练权重时输入分布和ImageNet差异太大前几个epoch的损失会很不稳定。3. 从VOC到YOLO再到训练集划分格式转换与比例分配摘要里已经说明这份数据同时提供了VOC和YOLO两种格式的标注省了转换这一步。但训练之前还有个关键问题怎么划分train/val/test。3.1 类别配置文件和数据划分脚本YOLO格式的txt文件里每行是「class x_center y_center width height」坐标是归一化后的值。VOC的xml里是像素坐标。两份数据我都建议核对一遍坐标是否一致防的是转换工具出bug导致坐标偏移这种问题肉眼很难发现但影响非常大。import os import random from xml.etree import ElementTree as ET import shutil # 设置随机种子保证每次划分结果一致 random.seed(42) img_dir images xml_dir annotations_xml txt_dir annotations_txt data [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(data) train_count int(len(data) * 0.7) val_count int(len(data) * 0.2) # 剩余作为测试集 train_imgs data[:train_count] val_imgs data[train_count:train_count val_count] test_imgs data[train_count val_count:] print(ftrain: {len(train_imgs)}, val: {len(val_imgs)}, test: {len(test_imgs)}) # 验证xml里归一化坐标和txt是否一致 def verify_xml_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) xml_boxes [] for obj in root.findall(object): 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) xml_boxes.append((xmin / img_w, ymin / img_h, xmax / img_w, ymax / img_h)) txt_boxes [] with open(txt_path) as f: for line in f: parts line.strip().split() x_center float(parts[1]) y_center float(parts[2]) w float(parts[3]) h float(parts[4]) txt_boxes.append((x_center - w/2, y_center - h/2, x_center w/2, y_center h/2)) return xml_boxes txt_boxes # 直接比较坐标是否一致 # 抽查5个文件 for img_name in random.sample(data, 5): xml_path os.path.join(xml_dir, img_name.replace(.jpg, .xml)) txt_path os.path.join(txt_dir, img_name.replace(.jpg, .txt)) ok verify_xml_txt(xml_path, txt_path) print(f{img_name}: {通过 if ok else 不一致!})坐标一致性核对这一步就是花钱买后悔药。我实际见过VOC转YOLO时中心点坐标公式写错的情况最终表现是loss能收敛但mAP一直在0.5左右上不去排查了两天才发现问题出在标注坐标错位。注意这里浮点数坐标直接用比较虽然简单但实际中可能会遇到精度差异更稳妥的做法是允许一个极小的误差范围。3.2 划分策略70/20/10还是K折交叉验证221张图这个体量怎么划分直接影响你最终汇报的精度指标。我的建议是正式训练用70/20/10或者80/20但做实验对比时强烈建议用K折交叉验证。为什么因为221张图本身就少随机划分一次的结果方差非常大。有可能某次划分里验证集恰好全是密集场景mAP就掉到0.6以下换个划分方式同样的数据可能到0.75。你无法判断是模型问题还是划分运气问题。from sklearn.model_selection import KFold img_files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] kf KFold(n_splits5, shuffleTrue, random_state42) fold_index 0 for train_idx, val_idx in kf.split(img_files): fold_index 1 train_imgs [img_files[i] for i in train_idx] val_imgs [img_files[i] for i in val_idx] # 生成YOLOv8需要的目录结构 train_dir ffold{fold_index}/train val_dir ffold{fold_index}/val os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) # 复制文件到对应目录 for img_name in train_imgs: shutil.copy(os.path.join(img_dir, img_name), train_dir) shutil.copy(os.path.join(txt_dir, img_name.replace(.jpg, .txt)), train_dir) for img_name in val_imgs: shutil.copy(os.path.join(img_dir, img_name), val_dir) shutil.copy(os.path.join(txt_dir, img_name.replace(.jpg, .txt)), val_dir)分成5折后每折大约44张验证图你拿5个fold的mAP均值±标准差来汇报审稿人和导师都会觉得靠谱得多。这个习惯在数据量小的场景下尤其重要它能帮你区分「模型确实提升了」和「这次随机划分对模型更友好」。3.3 YOLOv8训练时数据配置文件的写法YOLOv8的数据配置文件是yaml里面指定train/val路径、类别数和类别名。这个文件写错是新手最常见报错来源尤其是路径用了相对路径但工作目录不对。# data.yaml train: /home/user/rice_weed_dataset/images/train val: /home/user/rice_weed_dataset/images/val nc: 2 names: [sagittaria, sagittaria_flower]路径尽量写绝对路径避免YOLO从当前工作目录解析相对路径时找不到文件。如果用的是K折划分就对应修改train和val指向当前fold的路径。这里有个小提醒txt标注文件和jpg图片建议放在同一个目录下因为YOLO训练时默认找同名的txt文件分开目录需要额外配置。4. YOLOv8训练实操参数配置与命令行解析数据准备好了接下来就是把训练跑起来。YOLOv8是目前最稳的选择对221张这样的小数据集也能有不错的表现。4.1 ultralytics包安装和环境验证这一步对初次接触YOLO的读者来说很简单但还是值得说仔细。因为版本差异会导致API变化直接pip install latest版本遇到坑的概率不小。# 创建虚拟环境推荐 python -m venv weed_env source weed_env/bin/activate # 安装ultralytics pip install ultralytics # 验证安装并查看版本 python -c import ultralytics; print(ultralytics.__version__)装完之后建议用官方自带的yolov8n.pt做一个冒烟测试确认当前环境能正常跑通推理和训练流程再切换到自己的数据集。这一步能隔离环境问题和数据问题排查起来更快。from ultralytics import YOLO # 冒烟测试用预训练模型跑一次推理 model YOLO(yolov8n.pt) result model.predict(https://ultralytics.com/images/bus.jpg) print(result[0].boxes)4.2 预训练权重选择和训练命令详解对221张小数据集预训练权重的作用非常大。很多人纠结选n/s/m/l哪个版本我的建议是先用nano版本把端到端流程跑通再根据精度需求升级到small。因为nano训练快、显存占用低适合快速迭代超参数small在数据量小时提升未必明显但推理速度和精度会更均衡。yolo detect train datarice_weed.yaml modelyolov8n.pt epochs200 batch16 imgsz640 patience20 projectrice_weed_exp nameexp1逐项说明data数据配置文件的路径model预训练权重路径可选yolov8n.pt/yolov8s.pt等epochs训练轮次。221张图建议150-300轮小数据学得慢早停机制别开太激进batch如果显存不够就调小到8或4imgsz输入尺寸。默认640对大多数情况够用小目标多的话可以试896patience早停阈值。验证集loss连续20轮不下降就停project/name训练输出目录4.3 训练过程中的监控loss曲线和mAP指标怎么看训练日志里最核心的指标是box_loss、cls_loss、mAP50和mAP50-95。这里有个常见误区很多人只看final收敛值不关注曲线形状。# 训练结束后自动生成results.png直接查看 open rice_weed_exp/exp1/results.png # 训练过程中实时查看日志 tail -f rice_weed_exp/exp1/train.log正常的小数据集训练曲线应该长这样前50轮loss快速下降后100轮缓慢波动下行mAP50在150轮之后趋于平稳。如果你的cosmetic loss一直在0.05以下但mAP就是上不去多半是数据问题如果loss降到一定程度开始反弹注意是不是学习率没降下来或者过拟合了。5. 标注质量与训练避坑五条常见问题记录这一章是把前面所有环节里最容易翻车的地方集中说一下每条都是我实际踩过或者见别人踩过的问题。5.1 标注框类别混淆花和植株分不清现象训练结束后查看混淆矩阵发现sagittaria和sagittaria_flower互相误检的比例很高。原因这份数据的两个类别本身语义就有重叠——花是长在植株上的如果标注时花的位置被框得很大把周围叶片也框进去了模型学到的是「带花的植株」而不是「花」。解决先检查所有xml里sagittaria_flower的框宽高比和框中心点位置分布。如果花框普遍比植株框小很多那么在推理时可以按置信度和框尺寸做一个后处理规则大框Low置信度时优先判为sagittaria小框Low置信度时优先判为flower。另外训练时可以在loss权重上做文章YOLOv8支持通过cls_loss_multiplier参数调整类别损失的权重。# 后处理示例置信度低时用先验尺寸辅助判断 import numpy as np def post_process(boxes, scores, class_ids, img_w, img_h): for i in range(len(boxes)): w (boxes[i][2] - boxes[i][0]) / img_w h (boxes[i][3] - boxes[i][1]) / img_h area_ratio w * h # 面积占比小于5%的目标如果置信度在0.3-0.5之间倾向于判为flower if area_ratio 0.05 and scores[i] 0.5: class_ids[i] 1 # sagittaria_flower return class_ids5.2 验证集mAP高但实测效果差现象验证集mAP50到了0.85但拿到田间新图像检测漏检一片。原因221张图可能全部来自同一块田或同一时段背景、光照、植株密度高度相似。模型学到的其实是「这块田的背景特征」而不是「慈姑类别特征」。解决这是小数据集的固有问题缓解手段是训练时加大数据增强强度。把scale、flipud、mosaic这些参数调大一点多多少少能增强泛化性。更根本的办法是采集不同地块、不同光照的数据补进去哪怕多50张差异大的图效果都比加200张相似图好。5.3 训练时数据加载报错FileNotFoundError现象训练一开始就报错提示找不到图片或label。原因大部分时候是data.yaml里路径写错了或者图片目录下没有对应的txt文件。YOLOv8对label文件缺失会直接报错。解决先把data.yaml里的路径用python验证一遍import os from pathlib import Path data_cfg data.yaml # 检查目录是否存在 with open(data_cfg) as f: content f.read() # 提取路径字段 for line in content.splitlines(): if line.strip().startswith(train:) or line.strip().startswith(val:): path line.split(:, 1)[1].strip() print(f{path}: {os.path.exists(path)})5.4 训练正常收敛但txt坐标偏移导致检测框错位现象loss正常下降mAP也正常但可视化检测结果时发现框整体偏离目标一个固定偏移量。原因VOC转YOLO时坐标归一化公式有误或者xml里的坐标读取顺序和YOLO要求的顺序不一致。这类bug的可怕之处在于loss和mAP都可能正常因为框和ground truth是同一套坐标「错在一起」。解决训练前用3.1的验证函数批量对比xml和txt坐标别省这一步。另外推理时如果发现框的位置整体偏向某个方向检查预处理是否对图像做了缩放但没有对应缩放坐标。5.5 单张图inline推理正常但视频/批量推理OOM现象单张图预测没问题一跑视频或者大量图片批量推理显存直接爆掉。原因batch size没设置默认1时单张图OK批量推理时没限制batch或者视频帧率太高导致积压。解决推理时显式设置batch和half精度yolo predict modelbest.pt sourcetest_video.mp4 batch4 halfTrue imgsz640halfTrue用FP16推理显存减半对精度影响很小。如果还爆把batch降到2或者1。6. 验证集之外的校验与进阶用真实场景反推数据集短板训练跑完不等于交付完成。我习惯把模型拿到训练分布之外的场景做「对抗测试」用几个固定角度来暴露数据集短板。第一个角度是光照变化。把同一张图做亮度增减测试模型在暗光和过曝条件下的鲁棒性。我用Python的PIL库批量生成亮度梯度图然后跑批量推理统计mAP变化曲线。如果mAP随亮度下降很快说明训练数据的光照多样性不足。from PIL import Image, ImageEnhance import numpy as np img Image.open(test_field.jpg) results {} for factor in [0.5, 0.7, 0.9, 1.0, 1.2, 1.5, 2.0]: enhancer ImageEnhance.Brightness(img) bright_img enhancer.enhance(factor) bright_img.save(ftemp_bright_{factor}.jpg) # 这里调YOLO推理并记录mAP # results[factor] metrics第二个角度是密集区域切图测试。田间杂草往往成片生长直接用整图推理时小目标容易漏检。我的做法是从原图上切成512×512的小块做推理对比整图推理和切图推理在召回率上的差异。如果切图推理明显更好说明模型对大图上的小目标不敏感这可以作为后续改进方向——调大imgsz或者用tiling推理策略。第三个做法是留出式验证的「痛点分析」。把数据集中sagittaria_flower的框按尺寸从小到大排序分别计算不同尺寸区间内的召回率。如果最小组区间召回率明显低于平均水平说明小目标漏检是当前模型的主要矛盾。后续改进方向就非常明确了要么换带P2检测层的模型结构要么单独对小目标做过采样训练。说到底221张图的小数据集在深度学习时代能发挥的价值上限取决于你怎么用它——是直接开训出个结果跟老师说做完了还是用K折交叉验证加对抗测试把模型的边界摸清楚这之间的差距远大于数据量本身。从那以后我做任何小数据集项目都强制走一遍固定的校验流程先做坐标一致性检查再跑5折交叉验证看方差最后用亮度梯度和切图测试把模型的真实水平摸到底。数据量小不代表论文不能出把工程细节做实精度照样可以写得扎实。希望这份水稻慈姑类杂草数据集的实战拆解能帮到你少走我走过的弯路。本文还有配套的精品资源点击获取