
简介面向智慧农业中水稻田杂草检测的目标检测数据集包含 1373 张由田间行走拍摄的视频截取而来的清晰图像类别仅标注 weed矩形框总计 2394 个。压缩包按 JPEGImages、Annotations、labels 三个文件夹组织分别存放 jpg 图像、xml 标注与 txt 标注同时兼顾 VOC 与 YOLO 两种格式可直接接入 YOLO、Faster R-CNN 等主流目标检测框架进行训练或评估。资源共 2000 个文件以 1373 个 xml 标注文件与 627 个 txt 标注文件为主压缩后约 122.61MB目录结构直观、便于检索。图像未做数据增强保留了真实稻田环境中的光照变化与杂草遮挡情况对训练鲁棒模型具有实际参考价值也适合用来验证标注流程、完成格式转换练习或作为农业视觉项目的数据基础。目前已有 187 人学习使用。对于智慧农业、计算机视觉方向的研究者与开发者该数据集可用于模型微调、精度对比和算法验证同时也能帮助快速理解 VOC 标注与 YOLO 标注的组织方式与相互转换逻辑以更好地开展实际项目落地。1. 先从标题看这本数据集的实际用途1373 张水稻田图像能训出什么模型拿到“水稻中杂草数据集检测 yolovoc 格式 1373 张”这个压缩包第一反应不是解压而是先想清楚一件事这个量级的图像数据在目标检测里到底能跑多远。1373 张不是 ImageNet 那种万级规模也不是只有三五十张的“玩具数据集”它处在一个很微妙的区间——配合预训练权重做迁移学习足够训出一个在固定地块、固定季节下可用的杂草检测模型但如果你打算从零开始训一个深层网络或者指望它覆盖全国各地的稻田场景那必然会翻车。这类数据集在农业视觉项目里非常常见甲方通常只关心“这块田里的稗草、千金子能不能被框出来”并不关心你是否用上了最前沿的网络结构。所以这篇笔记的核心就围绕这套数据展开先弄清楚里面的目录和标注格式再跑通训练、做格式转换最后把踩过的坑一次说干净。2. 数据集里到底有什么先看目录和标注格式再决定要不要换格式2.1 1373 张在目标检测里是什么量级先定训练策略再打开 zip做目标检测的人对数据量容易有两个极端误解。一种认为“只要数据够多网络随便选”另一种觉得“一千多张什么都训不出来”。实际经验是1373 张如果是单类杂草检测配合 YOLOv8n 或 YOLOv5s 这类轻量骨架在迁移学习下能跑到一个可用的 mAP如果类别超过五种、且部分类别样本只有几十张那就要考虑数据增强和类别重采样。打开压缩包前先盘一下自己的训练策略。我一般会在一个空目录里放好 data.yaml 和预训练权重然后想清楚三件事第一backbone 用冻结还是全量微调小数据量下我倾向冻结前几层第二输入分辨率取 640 还是 512这直接决定显存和小目标召回第三要不要开 mosaic 增强水稻田里的杂草往往很小过度拼接会把目标缩小到难以学习。这些决策先定下来再动手解压数据否则很容易被数据带着走。2.2 YOLO 和 VOC 两种格式的核心差异坐标、归一化与类别索引标题里写着“yolovoc 格式”这其实是在说同一批图像提供了两套标注文件。VOC 格式是 2008 年前后 PASCAL VOC 竞赛带起来的每张图对应一个同名 xml里面用bndbox标签记录目标的 xmin、ymin、xmax、ymax单位是像素绝对坐标类别名直接写在name里是可读的字符串比如barnyardgrass。而 YOLO 格式每张图对应一个同名 txt每行五个数字class_id、x_center、y_center、width、height坐标和宽高都除以了图像宽高归一化到 0~1 区间class_id 是从 0 开始的整数索引。这俩格式本身都不复杂但混用起来非常容易出错。VOC 转 YOLO 时像素坐标要转成归一化中心点坐标YOLO 转 VOC 时则要反算回整数像素框。更隐蔽的是类别索引问题VOC 里稗草这个名字排在第几个完全取决于你写的 classes.txt 顺序而 YOLO 的 class_id 就依赖这个顺序。很多人在这一步把类别排错了导致训练时 loss 在降但 mAP 一直是 0——这个问题后面专门讲。2.3 打开压缩包前先做三件事哈希校验、类目盘点、标注可视化第一步是校验压缩包完整性。下载的数据集如果文件缺失训练到一半报错会非常难受。在命令行里直接算一下 SHA-256sha256sum 水稻杂草数据集.zipWindows 系统可以用certutil -hashfile 水稻杂草数据集.zip SHA256。算出来的值和你拿到的原始文件摘要比对一致再继续。这一步不是玄学我遇到过解压时文件损坏、xml 截断的情况哈希校验是最早的后悔药。第二步是看类别名和样本分布。解压后先打开classes.txt或labels/classes.txt把类别数量记下来。然后快速统计每个类别在训练集中的数量for f in labels/train/*.txt; do awk {print $1} $f; done | sort | uniq -c这一步能看出类别是否失衡。如果某个类是只有几十张的“尾巴类”后面训练时必须做类别加权或复制增强否则模型会直接把这类漏掉。第三步也是最重要的一步可视化和原始图像对比。只信 txt 和 xml 里的数字不行我习惯先写一个 20 行的小脚本把标注画出来看真实情况import cv2 # 将 YOLO 归一化坐标转换回像素并画框 def draw_yolo_boxes(img_path, label_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path, r, encodingutf-8) as fp: for line in fp: parts line.strip().split() cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imshow(check, img) cv2.waitKey(0)这里有两个参数值得注意x_c - bw / 2是中心点坐标换算左边界的标准做法int()强转会丢失小数画图时无所谓但保存为 txt 时就要保留六位小数。抽十张图看一遍主要确认框有没有整体偏移、有没有把相邻的草框进去、有没有漏标。如果图像里明明有大片杂草但标注框很少说明这份数据的标注密度不均匀训练前要做筛选。3. 用这份数据跑通一次最小训练目录划分、data.yaml 和训练命令3.1 先把 1373 张按 train/val 拆开别让同一片田出现在两端很多初学者直接拿train_test_split随机切 8:2这在目标检测里不是最优做法。水稻田图像往往是一组一组拍的同一块田的连续帧背景、光线、杂草形态高度相似按单张随机切验证集里全是“训练时见过的田”指标虚高下地一测就打回原形。正确的做法是按采集组划分如果文件名带序号就先提取出拍摄组标识比如按前缀或采集批次再按组拆分。import os import random import shutil random.seed(20240807) src_img images train_dir dataset/images/train val_dir dataset/images/val os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) # 提取文件名的批次前缀IMG_20240801_01.jpg 这类命名按前缀归组 files os.listdir(src_img) groups {} for f in files: if not f.endswith(.jpg): continue prefix f.split(_)[0] _ f.split(_)[1] groups.setdefault(prefix, []).append(f) group_names list(groups.keys()) random.shuffle(group_names) split int(len(group_names) * 0.8) for g in group_names[:split]: for f in groups[g]: shutil.copy(os.path.join(src_img, f), train_dir) for g in group_names[split:]: for f in groups[g]: shutil.copy(os.path.join(src_img, f), val_dir)这段脚本的关键不是复制文件而是groups.setdefault(prefix, ...)这一步把同批次的图片锁在一起。如果你的文件名不是按批次的那就优先按地块、按行、按拍摄时间等已知维度分组实在没有分组信息才退回到纯随机切分并在心里给指标打个折扣。注意脚本里的split比例 0.8 是经验值数据量越小验证集占比越要少防止验证集只剩几十张导致评估波动大。3.2 写一份 data.yaml注意中文路径、类别顺序和编码YOLOv8 系列训练时读取的是 data.yaml网上下载的数据集一般会带一份现成的但我不建议直接拿来用。原因很简单压缩包里的path字段往往是作者的绝对路径换到你的机器上必炸。# data.yaml 示例路径全部用相对路径训练时统一复制到项目目录 path: ./rice_weed train: images/train val: images/val nc: 3 names: 0: barnyardgrass 1: leptochloa 2: monochoria两个细节容易踩坑。第一names的索引必须和训练集 txt 里的 class_id 一致0之后不能跳号索引从 0 开始YOLO 对类别索引严格按列表顺序解释。第二如果你的数据放在中文路径下配置path时尽量改成英文目录别名否则 Windows 下 OpenCV 读图偶尔会出现编码异常cv2.imread返回 None训练时 loss 直接是 NaN。虽然path写成./rice_weed能规避一部分问题但更稳妥的做法是把整个数据集复制到纯英文路径下再开训。3.3 最小训练命令和 4 个必调参数目录拆好了data.yaml 也就位跑一次最小训练只需要一条命令yolo train modelyolov8n.pt datadata.yaml epochs100 imgsz640 batch16 device0 project./run namerice_weed_first执行完不用急着看结果先理解其中 4 个直接决定能不能收敛的参数。modelyolov8n.ptn 是 nano参数量最小小数据集的起步配置换 s 或 m 会明显增加显存和过拟合风险。imgsz640这个值不是越大越好水稻杂草属于中小目标640 是精度和速度的平衡点盲目上 1280 需要 batch 降到 4 以下反而容易训出震荡的 loss。batch16如果显存只有 8Gbatch 先按 8 试跑起来看显存占用再往上加batch 太小BN 层的统计量不稳也就是社区里常说的“yolo 训练中 BN 崩溃”的诱因之一。epochs100配合早停 patience20这个值在小数据集上通常够用不用一上来就三百轮。另外两个细节也值得调。第一mosaic 增强在小数据集上建议关小mosaic0.5或干脆设 0因为目标本身小拼接后目标更小模型容易学偏第二workers4记得加上不然数据加载会成为瓶颈GPU 一直吃不满。训练跑完看results.png重点看val/box_loss和metrics/mAP50两条曲线如果 mAP50 在 20 轮后还在爬升说明模型还在学可以延长时间。4. 常见格式转换脚本VOC 转 YOLO 的坐标换算和四个边界坑4.1 坐标换算的公式先立住再写完整转换脚本标题里提供的是 yolovoc 双格式但实际工作中拿到手的数据集往往只有一种格式或者标注版本和作者描述对不上需要自己转一次。VOC 转 YOLO 的核心就是把bndbox里的像素坐标换算成归一化中心点加宽高x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height注意分子是xmax - xmin不是加。很多人在这一步写反训练时 loss 直接飞掉。下面是一份可以直接跑的脚本import os import xml.etree.ElementTree as ET # 按 classes.txt 里的顺序定义映射顺序错一个全盘皆错 class_map {barnyardgrass: 0, leptochloa: 1, monochoria: 2} def voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: print(f[skip] 未在 classes.txt 中的类别: {name}) continue cls_id class_map[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界值约束防止裁剪后的坐标越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w, encodingutf-8) as fp: fp.write(\n.join(lines)) xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue voc_to_yolo(os.path.join(xml_dir, xml_file), os.path.join(out_dir, xml_file.replace(.xml, .txt)))这段脚本有三个点要特别解释。root.iter(object)用 iter 是为了兼容 xml 里 object 节点嵌套的情况比findall更稳边界约束那段是防止标注工具把框画到图像外导致归一化后坐标超过 1训练时 loss 出现 NaN.6f保留六位小数是 YOLO 官方训练的常见数值精度太短会造成框偏移交割。4.2 边界坑一xml 里有没有difficult和truncated别擅自当成负样本丢弃PASCAL VOC 早期标注里有difficult标签表示该目标难以辨认不算模型错误。水稻杂草数据集中这类目标不少幼苗期稗草和水稻本身形态极其接近标注员可能标了 difficult1。转 YOLO 时如果直接把这类 object 跳过会造成“漏检”的假象。我的建议是保留这些目标让模型去学如果你发现它对模型干扰太大再做一轮人工清洗而不是在转换脚本里一删了之。与其丢掉难例不如留着看模型边界在哪。4.3 边界坑二类别名的排序不一致导致整份标注类别错位VOC 格式里写的是类别字符串比如barnyardgrassYOLO 里只认整数索引。如果你从网上找的 classes.txt 顺序和数据集作者的顺序不一样那么同一张图的同一个框转出来的 class_id 就完全不同。表现是训练时 loss 正常下降但 val 的混淆矩阵一片乱。解决思路很简单转格式前先用脚本打印两张表的类别清单逐一比对别靠肉眼记。# 从所有 xml 中提取出现的类别名 grep -h name Annotations/*.xml | sort | uniq -c再看你的 classes.txt 顺序两边一致才继续。这个检查 10 秒做完能救回一整天训练时间。4.4 边界坑三标注里出现旋转框或四点坐标别硬转成水平框如果你解压后看到robndbox或polygon标签说明这份数据集的作者用的是旋转框标注目标是细长倾斜的杂草叶片水平框会框进大量背景学习效果很差。此时强行套用上面的脚本会漏读坐标导致输出空 txt。处理方式要分情况如果你的任务是常规 HBB 检测就用 OpenCV 的minAreaRect算出旋转框的外接水平矩形再转但注意斜长条目标的水平外接框面积过大训练出来的模型会框偏。更合理的方向是换用 mmrotate 这类旋转框检测框架把标注转成 DOTA 格式而不是硬塞给 YOLO。这个决策在项目初期就要定不要等训练完 mAP 上不去才回头排查。4.5 边界坑四重复标注和空 xml转换时不做体检就会埋雷压缩包数据最常见的问题是一张图像配了两个同名的 xml一个在根目录一个在子目录或者某个 xml 里根本没有 object 节点。转换脚本通常不报错但会输出一个 0 字节的空 txtYOLO 训练时读到无目标文件会自动跳过这张图导致你实际训练图片数少了而不自知。所以转换后要做一次文件数量核对find Annotations -name *.xml | wc -l find labels -name *.txt | wc -l # 再找出所有 0 字节的 txt find labels -name *.txt -empty如果两者数量不一致就用上面 4.1 的脚本加一个 try-except 定位坏文件把对应的 xml 单独移出来修。5. 训练中常见的几个翻车点先看现象再定排查顺序5.1 现象一loss 卡住不降或不断跳动水稻杂草数据集训练时最常见的曲线是 loss 前 20 轮降得很快到 30 轮开始上下抖动val loss 甚至反弹。先不要急着换模型按顺序排查三件事。第一学习率过大YOLOv8 默认 lr00.01 在 1373 张这种小数据上往往偏激进改到lr00.001或者加warmup_epochs5第二BN 层不稳定也就是常说的“BN 崩溃”表现为 loss 突然跳到很大再回不来原因是 batch 太小或某个 batch 里图像差异过大把 batch 调大一点点或把mosaic0试一轮作对照第三类别极度不均衡模型学不到少数类val loss 被多数类带低查上一章的类别分布给少数类单独加权。记住排错顺序永远是数据分布、超参数、网络结构。5.2 现象二mAP 一直为 0但训练曲线很“正常”这是格式转换最容易埋下的雷。训练 loss 降得很漂亮结果验证集 mAP50 是 0.000说明模型从头到尾没学到任何正样本。最常见的原因是类别索引错位你的 txt 文件里 class_id 从 1 开始作者用了 1-based 索引而 data.yaml 的names列表是从 0 开始解释等于所有标签被整体平移了一位。另一个隐蔽原因是坐标写成像素值而不是归一化导致框比整张图大出几十倍。排查时先抽查第一张训练图head -5 labels/train/IMG_0001.txt看到 class_id 是 0 且后面四个数都在 0~1 之间才符合 YOLO 正常格式。这条检查脚本应该在你做完格式转换后就跑不要等到训练完再查。5.3 现象三显存 OOM训练直接中断torch.cuda.OutOfMemoryError: CUDA out of memory.根本原因是 batch 和 imgsz 的乘积超过了显存容量。一个直观参数参考8G 显存下640 输入、yolov8nbatch 最多 16换 1080 输入batch 必须降到 6 以下。不要直接改模型结构来省显存优先降batch然后降imgsz。如果你用的是双卡或四卡注意 YOLO 的batch参数是每张卡的 batch总 batch batch × 卡数四卡设 batch8 实际是 32显存占用要按 32 估。还有一个容易被忽略的点workers设置过高也会吃掉内存但一般不至于 OOM先排除显存问题再查内存。5.4 现象四验证集指标虚高实地拍几张就漏检很多人在这一步直接怀疑模型不行其实先看一下 train/val 是“按帧切”还是“按组切”。如果按图像随机切分同一田块的不同帧大概率同时出现在 train 和 val 里模型相当于开卷考试val mAP 0.8 到了新田地直接跌到 0.4。解决办法是回到 3.1 按批次分组重新划分。另一个造成虚高的因素是验证集图片本身太简单全是晴天正午、杂草长大后的清晰照片没有包含苗期、阴天、逆光这类难例。所以我会在验证时额外留出一批“hard set”专门挑那些连人眼都犹豫的图单独测混淆矩阵里的这一块才是最值得看的。6. 验证模型是否真的可用用混淆矩阵和实际推理来收尾训练完拿到 results.csv 只是第一步真正决定模型能不能交给田里用的是三个验证动作。先看混淆矩阵。YOLOv8 训练完会在run/rice_weed_first/下生成confusion_matrix.png重点读对角线之外的位置如果稗草和千金子经常互混说明这两类在幼苗期形态太接近不要试图靠加深网络解决反而是回到数据层面补样本更有效。如果某个类别大量被分到背景类就是样本不足或标注框太小模型学不到特征。我习惯顺手算一遍每类的 mAP50 而不是只看整体值因为整体值会被优势类拉高。再抽出模型没见过的图片做实际推理而不是依赖 val 指标from ultralytics import YOLO model YOLO(run/rice_weed_first/weights/best.pt) result model.predict(hard_samples/IMG_0915.jpg, conf0.25, saveTrue, imgsz640)对比 val 指标和这组hard_samples的检出结果如果漏检集中在画面边缘的小目标把conf降到 0.15 试试而不是改模型如果出现大量重叠框再去查 NMS 参数。这个流程比反复调网络结构更能解决实际问题。最后一个动作是端侧部署前的导出验证。无论最终交给嵌入式设备还是服务器先统一导出成 ONNXyolo export modelrun/rice_weed_first/weights/best.pt formatonnx imgsz640导出后用onnxruntime加载跑一遍检查输出张量的 shape 是不是[1, 84, 8400]nc3 时为 84如果对不上多半是导出时模型配置和训练时不一致。我在这类小数据集项目上吃过最大的亏是花了大量时间挑网络结构最后发现卡在数据划分和类别索引上——模型本身反而是最省心的部分。每次训练前把“划分方式、类别顺序、文件完整性”三件事先在 10 分钟内查完能省掉后面两三天的无效训练。这是我做农业检测数据集训练两年下来最值的一条习惯希望帮到你。本文还有配套的精品资源点击获取