简介:面向YOLOv5、v6、v7、v8等版本的目标检测训练,这套农作物杂草数据集专为农业智能化与精准除草场景设计,适合目标检测初学者、算法工程师以及农业AI项目开发者。压缩包共2000个文件,内含500张JPG原始图像,以及500个XML、500个TXT、500个JSON标注文件,分别对应VOC、YOLO、COCO三种常见标注格式,整包大小约774MB。目前已有1859人学习下载,是农业目标检测资源中较受关注的数据集之一。图像内容以玉米作物和杂草为主,由专业人员细致标注目标边界,覆盖多种田间拍摄角度与光照条件,可直接用于模型训练、验证与测试。借助YOLO系列模型训练流程,读者可以完成数据预处理、标注格式转换、训练集划分与参数调优,并最终构建适用于农田场景的杂草识别系统,为降低除草成本、提升作物产量提供有力的数据支撑。
1. 先说结论:一套能喂给 YOLOv5/v6/v7/v8 的农作物杂草数据集应该长什么样
如果你手头有一套可用于YOLOv5/v6/v7/v8训练的农作物杂草数据集,那么恭喜你,已经跨过了农业视觉项目里最容易被低估的一道门槛。杂草检测和通用行人检测不一样,目标小、背景杂、类别之间长得像,数据集的格式、标注颗粒度、场景分布,直接决定你后续训练是“跑通即可”还是“能落地”。这套数据集的本质,不是一堆图片加几个txt,而是“图片目录、标签目录、类别定义、数据划分”四个部分严丝合缝地对齐。这篇文章就按我实际做杂草检测时沉淀下来的流程来写:从格式选型、转换脚本、训练参数,到最容易让mAP莫名下跌的坑,最后给你一套难例挖掘的收尾方法。新手可以照着把公开数据转成YOLO格式跑通训练,熟手可以直接跳到第5章和第6章对参数和评估做校准。
2. 从原始图片到 YOLO 训练集:格式、目录结构与选型依据
2.1 三种常见标注格式怎么选:VOC、COCO 还是 YOLO txt
农作物杂草相关的公开数据,最常见的原始标注格式是VOC XML和COCO JSON。VOC格式每个图片对应一个XML文件,标注框用xmin、ymin、xmax、ymax四个绝对像素值表示,一眼能看懂,适合抽查和人工修正。COCO格式把所有图片信息、类别表、标注信息写进一个JSON文件,字段多、ID体系复杂,但很多学术项目和多模态检测框架默认用这个格式。
而标题里明确写了“可用于YOLOv5/v6/v7/v8训练”,这里直接给结论:最终必须统一成YOLO txt格式。YOLO系列四个版本虽然训练脚本和模型结构各不相同,但数据集读取逻辑高度一致:每张图片对应一个同名txt,txt每一行是一个目标的类别ID和归一化后的中心点坐标、宽高。用YOLO txt的好处是跨版本迁移成本最低,YOLOv5/v6/v7/v8换一遍都不需要重新整理数据,坏处是纯文本肉眼很难检查,必须有可视化脚本兜底。
三个格式的取舍可以看这张表:
| 格式 | 视觉可读性 | 跨版本兼容性 | 转换成本 | 适合场景 |
|---|---|---|---|---|
| VOC XML | 高 | 低,需转换 | 低 | 原始数据积累阶段 |
| COCO JSON | 低 | 低,需转换 | 中 | 学术预训练或模型对比 |
| YOLO txt | 低 | 高,直接训练 | 高,需脚本保证 | 正式训练与落地上线 |
我的建议是:数据源头用什么格式不重要,重要的是进训练器之前,在本地维护一套“YOLO txt + 可视化脚本”的组合。你从VOC转成txt,再从txt画回图片做抽样检查,整个流程是闭环的,后面不管换YOLOv5还是v8,数据侧都不用再动。
2.2 最小可用的数据集目录长什么样
一份能被YOLO系列四个版本直接识别的数据集,目录结构必须长成这样:
weed_dataset/ ├── images/ │ ├── train/ │ │ ├── plot01_0001.jpg │ │ ├── plot01_0002.jpg │ │ └── ... │ └── val/ │ ├── plot02_0001.jpg │ └── ... └── labels/ ├── train/ │ ├── plot01_0001.txt │ ├── plot01_0002.txt │ └── ... └── val/ ├── plot02_0001.txt └── ...images和labels是平级兄弟目录,图片和标签只允许后缀不同。YOLO训练时会根据图片路径自动去labels同路径下找同名txt,所以绝对不能在images内部再建labels子目录,也不能把txt放在别的盘符下用软链接去指,路径错位是新手最高频的报错来源。
一个标准的labels txt内容如下:
0 0.4825 0.6732 0.1128 0.0965 1 0.7331 0.5148 0.0832 0.1421 0 0.2034 0.3889 0.0671 0.0513第一列是类别ID,从0开始;后面四列分别是归一化后的中心点x、中心点y、框宽、框高,取值范围在0到1之间。归一化就是用像素坐标除以图片宽度或高度。如果一张图上没有目标,txt可以留空文件,也可以直接删除对应图片,但必须整个数据集统一策略,我习惯的做法是保留空txt,这样训练时图片被读进来后不产生梯度,不会报错。
2.3 我自己惯用的 3 条选型准则
数据集的类别定义要不要和你的目标一致,这是第一准则。杂草不是一个类,马唐、牛筋草、反枝苋、藜是不同物种,甚至同一物种在不同生长阶段形态差异都很大。如果数据集只标了“杂草”一个类,模型只能输出“有没有草”的置信度,没法指导除草动作。反过来说,如果你只需要区分“作物/杂草”两类,拿到细颗粒度数据集后可以通过类别映射合并,成本很低。所以优先选类别颗粒度比你的需求更细的数据集。
第二准则是看采样视角和地块环境。无人机俯视大田和地面近摄的杂草,在目标尺度、遮挡程度、光照方向上差别巨大。如果你的落地场景是机器人近地行走拍摄,却用无人机远视角数据集训练,那imgsz调到1280也救不回来。选数据时先看图片EXIF或缩略图,没有EXIF就随机抽图看分辨率,判断视角是否接近。
第三准则是标注质量。很多公开数据集的标注是半自动生成的,框会整体偏移一个身位,或者把苗期两片子叶和土壤背景切一半进去。标注很脏的数据集,模型训练再久,在验证集上看起来不错,换个地块就原形毕露。我的习惯是随机抽200张图,把标注框画上去人工扫一遍,框不贴边的比例超过三成,直接放弃这份数据,没有后悔药可吃。
3. 把公开数据转成 YOLO 格式:转换脚本与四个边界坑
3.1 手写一个 VOC 转 YOLO 的最小脚本
假设你从公开渠道拿到的是VOC XML,最稳妥的做法是自己写脚本转格式,不依赖在线转换工具,因为本地脚本才能控制类别映射和异常过滤。下面这个脚本是我一直沿用的最小实现:
import xml.etree.ElementTree as ET import os def voc2yolo(xml_path, out_dir, class_list): tree = ET.parse(xml_path) root = tree.getroot() # 图片原始宽高 width = int(root.find('size/width').text) height = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_list: continue box = obj.find('bndbox') xmin = int(float(box.find('xmin').text)) ymin = int(float(box.find('ymin').text)) xmax = int(float(box.find('xmax').text)) ymax = int(float(box.find('ymax').text)) # 边界约束:防止负值和越界 xmin = max(xmin, 0) ymin = max(ymin, 0) xmax = min(xmax, width) ymax = min(ymax, height) if xmax <= xmin or ymax <= ymin: continue cx = ((xmin + xmax) / 2) / width cy = ((ymin + ymax) / 2) / height bw = (xmax - xmin) / width bh = (ymax - ymin) / height lines.append(f"{class_list.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: stem = os.path.splitext(os.path.basename(xml_path))[0] out_path = os.path.join(out_dir, stem + '.txt') with open(out_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) # 使用示例 class_list = ['amaranth', 'crabgrass', 'soybean'] os.makedirs('labels', exist_ok=True) for xml_file in os.listdir('xmls'): if xml_file.endswith('.xml'): voc2yolo(os.path.join('xmls', xml_file), 'labels', class_list)逻辑上,脚本先解析XML根节点,读取size字段下的width和height,然后遍历所有object子节点。每个object里最关键的是name和bndbox。注意我把xmin这些值都先转成int再运算,避免某些XML里标注出带小数点的浮点坐标导致字符串拼接错位。归一化公式里除的是原始图片宽高,不是标注值最大值,这个顺序写反会出现一堆大于1的坐标,训练时直接报错。
参数方面,class_list的顺序就是后续YOLO类别ID的顺序,绝不能随意变更。你定义了class_list = ['soybean', 'amaranth', 'crabgrass'],那么所有txt第一列数字就变成soybean是0,amaranth是1。data yaml里的names列表必须和这个class_list逐项对齐,否则模型学到的语义和数据对不上,可视化检查时看着像错标。
3.2 转换时最容易翻车的四个边界条件
第一个边界坑是空XML。某些标注工具会把没有目标的图片也生成一个XML文件,里面没有object节点。脚本里if lines判断就是为此准备的,没有目标就不写txt。但这里存在隐藏风险:如果原图确实有目标,只是标注员漏标了,脚本也会安静地跳过这个文件,后续训练时这张图被当成背景图。我的建议是转换后把“空label图片清单”输出到一个文本文件,人工扫一眼,确认这些图真的不用保留。
第二个边界坑是类别过滤。公开数据集经常只有部分类别是你需要的,脚本里if name not in class_list直接跳过,剩下不认识的类目不会进入txt。这里不要图省事把名字写错,比如类名是“Amaranthus”和“amaranth”差一个字母,脚本会把这类目标全过滤掉,造成类别缺失。转换完成后用类别计数脚本统计,一旦发现某个预期类别数量为0,大概率是名字没对齐。
第三个边界坑是重复坐标或畸形框。有的XML里xmax比xmin还小,或者ymax超出图片高度,脚本里的max/min约束和xmax<=xmin判断能挡住大部分。但有一种情况挡不住:坐标全都是0。这在VOC里代表标注员清空了框但没删对象,脚本会生成一个中心点在0.5、宽高为0的无意义框。这种目标在训练时不会报错,但会拉低正样本质量。
第四个边界坑是编码和换行符。Windows上生成的XML可能是UTF-8 with BOM,Python解析时第一行会多出隐藏字符,导致root.iter('object')遍历不到任何节点。如果你发现转换后txt数量比XML数量少很多,优先怀疑这个。解决办法是open时用encoding='utf-8-sig'读取,或者先转成纯UTF-8再跑脚本。
3.3 转换完成后的数据卫生检查
转完格式不代表能直接开训,我每次都会跑三个检查脚本。第一个检查是图片与标签一一对应。用代码遍历images目录下所有jpg,在labels里找同名txt,找不到就报错。
from pathlib import Path img_dir = Path('images/train') label_dir = Path('labels/train') img_files = list(img_dir.glob('*.jpg')) missing = [p.stem for p in img_files if not (label_dir / (p.stem + '.txt')).exists()] if missing: print(f"缺标签 {len(missing)} 张,例如:{missing[:5]}")第二个检查是类别分布统计。把所有txt读进来,按第一列计数,输出每个类别的目标数和平均框面积。平均框面积和图片总面积的比例如果低于1%,说明这一类的目标很小,后续训练必须考虑高分辨率输入。这个数字也会指导你是否需要增加小目标增强。
第三个检查是把标注画回图片上做视觉确认。代码也很简单:
from PIL import Image, ImageDraw def draw_yolo(img_path, txt_path): img = Image.open(img_path).convert('RGB') w, h = img.size draw = ImageDraw.Draw(img) with open(txt_path, 'r') as f: for line in f: cls, cx, cy, bw, bh = line.split() x1 = (float(cx) - float(bw) / 2) * w y1 = (float(cy) - float(bh) / 2) * h x2 = (float(cx) + float(bw) / 2) * w y2 = (float(cy) + float(bh) / 2) * h draw.rectangle([x1, y1, x2, y2], outline='red', width=2) img.show()这里的中心点还原公式是x1 = (cx - bw/2) * w,很多新手写成cx * w - bw * w,虽然结果一样,但容易在框宽计算时漏除2。我一般随机抽100张train和30张val做可视化,把图和框直接肉眼扫一遍。这一步看着费时间,实际上能省掉后面无数次无效训练。
4. 用 YOLOv5/v6/v7/v8 跑通杂草检测训练:命令、参数与调参顺序
4.1 各版本通用训练入口:以 YOLOv5 为例的最小命令
数据集整理好后,先在YOLOv5上跑通最小命令,确认全链路没问题,再切到其他版本。YOLOv5的data配置文件长这样:
# weed.yaml train: /home/user/weed_dataset/images/train val: /home/user/weed_dataset/images/val nc: 3 names: ['amaranth', 'crabgrass', 'soybean']注意train和val这里指向的是images目录,不是labels目录,YOLO会自己去找labels下的对应文件。nc必须和names列表长度相等,这个写错在启动阶段不会立刻报错,但训练时类别ID越界会直接中断。names顺序务必和yaml里的类别ID一致,和转换脚本的class_list一致,三个地方的顺序要保持同一个版本。
最小训练命令:
python train.py --data weed.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100--weights用yolov5s.pt可以从COCO预训练权重起步,迁移学习让杂草模型的收敛速度快很多。如果机器显存不够,把--batch降到8,或者把--weights设置为空字符串从零训练,但后者在小数据集上很容易欠拟合。--img 640是输入分辨率,杂草检测场景里如果目标普遍小于64像素,640可能不够,下一节专门说怎么调。
4.2 必调参数:imgsz、batch、epochs 与增强配置
杂草检测和常规目标检测最大的区别是目标尺度。刚出苗的杂草在1080p原图上可能只有30x30像素,即使缩放到640分辨率,也压到了不足20像素。imgsz直接决定小目标在输入图上的可辨识度。我的调参顺序是:先用640跑一轮baseline,统计每个类别平均框面积;若多数类别目标框面积占比低于1%,就试1280输入。
把imgsz从640涨到1280,显存消耗约是4倍,batch必须同步砍半甚至砍到四分之一。一个常见的组合是imgsz=1280、batch=8在16G显存上跑yolov5s,如果爆显存就退回960或800,不必一步到位。增大imgsz后不要忘记把图片增强里的scale参数适当调低,否则大图上的目标会被随机缩放剪裁得过于剧烈,造成漏检。
epochs的起点我建议100。跑完看训练末尾的val mAP曲线是否还在上升,如果最后10个epoch还在爬,就加50轮再跑;如果50轮后就震荡,说明增强调得过强或数据量太少,不是简单拉长训练能解决的。不要一开始就冲300轮,浪费时间在错误配置上。
数据增强参数在YOLOv5里通过hyp.yaml控制,最关键的是mosaic、hsv_h、hsv_s、hsv_v。杂草数据集里土壤颜色是最大的背景干扰源,把hsv_h从默认0.015调到0.05、hsv_s从0.7调到1.2,能让模型不再依赖“土色”这个捷径。mosaic默认开着,它对小目标友好,因为能把四张图拼成一张,等于变相缩小了目标尺度范围。但如果你在第五步避坑章节里看到mosaic导致的问题,记得先关掉它跑baseline。
4.3 在 v6/v7/v8 上要注意的差异点
YOLOv5/v6/v7/v8四个版本的数据集读取方式是一致的,所以同一份weed_dataset可以无缝切换。差异主要在训练入口命令和默认超参数上。
YOLOv6的训练入口和v5类似,但参数名略有不同,比如v6用--data-file而不是--data,用--epochs和--batch-size。如果误用v5的命令去启动v6,会在参数解析阶段报错,识别到这个错误后替换成对应参数名即可。YOLOv6的默认数据增强比v5激进,小数据集上首次训练时建议把--mosaic和--mixup关掉。
YOLOv7多数情况沿用v5风格,但它对预训练权重路径和类别数一致性要求更高。如果换用v7,把--weights设为v7对应的预训练权重,不要用v5的权重混搭。v7在batch=32、imgsz=1280时容易因为BN层统计量不稳定而发散,起步阶段先用batch=16更安全。
YOLOv8是uvltrlytics仓库生态,命令风格完全不同。它用统一的yolo命令入口:
yolo detect train data=weed.yaml model=yolov8s.yaml epochs=100 imgsz=640 batch=16v8对yaml路径要求必须是绝对路径或当前目录下的相对路径,如果data写错位置,它会静默创建空数据集并直接训练,跑出全零mAP。我碰过一次,翻来覆去找原因,结果只是data路径少写了一层目录。所以切到v8后,第一个动作不是调参,而是打印你的data配置让v8正常解析出图片数量。
5. 农作物杂草数据集避坑指南:5 个让 mAP 莫名下跌的典型问题
5.1 类别不平衡:杂草只占几十像素,模型一直在学空气
现象:训练总loss下降得很顺,但val mAP里杂草类不到0.1,作物类却有0.7。打开验证集预测结果,发现几乎把所有目标都预测成了作物,杂草全被当背景滤掉。
原因:农田场景里作物和杂草的占比天然失衡,有些地块杂草只出现在零星几行里,标注后杂草框数量可能只有作物的十分之一。YOLO系列默认对所有类别等权采样,少数类正样本在几十万候选锚框里占比太低,梯度被多数类淹没。
解决:最直接的办法是调整数据采样,把少数类图片多复制几份放进train集,不是简单改loss权重,因为图片复制能让模型看到更多上下文。复制时保留同样的标签文件名,但图片名加后缀区分。如果复制三轮后杂草mAP还不上涨,再看是否标注框本身太小,配合第4章的imgsz加大输入分辨率。我遇到过复制四轮后mAP从0.4涨到0.7,说明问题主要不在loss权重而在样本量。
5.2 验证集混入“隐性错误答案”:数据划分时机不对
现象:训练时val mAP一路涨到0.95,你觉得模型已经完美了,结果拿到新地块无人机照片上直接崩到0.3。反复检查代码和配置都没问题,最后发现数据划分时出了问题。
原因:很多公开数据集按文件名或拍摄时间排序整理,如果直接random_split,同一个地块连续拍摄的几十张图会被同时分进train和val。模型见过了这些图的背景纹理和光照条件,val评估等于开卷考试,看起来好得很。
解决:划分数据前先看图片路径前缀,同一拍摄地块的图按组切分。比如图片名是plot01_0001.jpg到plot01_0250.jpg,就把plot01整个分到train,plot02整个分到val,保证没有任何一个地块同时出现在两个集合。更严谨的做法是按拍摄日期切分,同一日期同一地块的图不跨越边界。切分后还要做一个文件级检查,确认train和val的图片文件名没有交集。
5.3 作物与杂草形态相似:标签语义没有对齐生长阶段
现象:模型把苗期的作物误检为杂草,尤其是双子叶作物刚出土时,子叶形状和很多阔叶杂草几乎一样,val mAP看着正常,实际错误集中在生长早期。
原因:标注员在标数据时按物种名称标注,但没按生长阶段区分。同一物种的三叶期和五叶期形态差异,可能比不同物种之间的差异还大。模型只能学到“叶片形状”的单一模式,遇到没见过的生长阶段就失效。
解决:把数据集里的目标按“苗期/成株期”拆分,或者直接在类别里增加“作物幼苗”“杂草幼苗”两个细分类。这个操作要在标注阶段就规划好,转换脚本里写死两个独立的类名。如果公开数据集没有这个颗粒度,可以单独补充幼苗期图片做增量训练。实操中我发现,即便最后推理时只需要“是否为杂草”的二值结果,训练时把幼苗单列成一个类,mAP也会更稳,因为模型不用在两类形态差异大的目标上强行学一个统一特征。
5.4 背景过拟合:模型靠土壤纹理猜答案
现象:模型在训练地块的val图上mAP很好,换到土壤颜色不同的相邻地块就暴跌。你把预测框画出来,发现模型把一些没有杂草的裸地也框成了杂草。
原因:杂草数据集往往在同一个季节集中采集,背景就是那一块地的土壤、秸秆和光照方向。模型学到的不只是目标特征,还有“这种纹理的土上大概率长草”的上下文捷径。数据增强里的HSV扰动幅度太小,压不住这个捷径。
解决:加大hsv_h、hsv_s、hsv_v的扰动范围,尤其把饱和度和亮度扰动拉大。再用随机裁剪、旋转和尺度增强模拟不同角度拍摄。如果条件允许,混入其他地块或不同天气下拍摄的背景图,让模型看到更多负样本。注意这里负样本不是空图,而是“没有杂草但有相似纹理”的农田图,这些图能显著降低误检率。
5.5 增强参数与数据规模不匹配:几百张图扛不住全套增强
现象:数据集只有400张图,前20个epoch的loss稳定,第30轮后val mAP开始乱跳,重复训练两次结果差异很大,像是玄学。
原因:增强开太猛,尤其mosaic、mixup、旋转、尺度叠加在小数据集上,会让同一个目标每次迭代都长得不像自己,模型学不到稳定的语义特征,训练过程变成在多个“伪样本分布”间摇摆。
解决:小数据集先把mosaic关掉,把旋转和缩放幅度也降到一个温和档位,比如rotation只开±10度,scale只开±20%。先跑一个baseline,确认收敛稳定后,再逐步加入mosaic和mixup。每次只加一项增强,跑一轮看val mAP,如果下跌就回退。这个流程虽然慢,但能定位到具体是哪项增强把你的数据“洗碎了”。我见过200张图全开增强直接不收敛的情况,关掉一半增强后反而完美。数据量小于500张时,不要迷信增强,先让模型好好记住目标长什么样。
6. 数据基础上的最后一公里:负样本清洗和难例挖掘把 mAP 再抬一截
6.1 难例挖掘的具体做法
第一轮训练完成后,不要直接换模型结构调超参数,先做一次难例挖掘。拿训练好的模型去预测验证集中它没见过的图片,尤其是那些在val上被漏检或误检的图。把置信度在0.3到0.7之间的预测框单独导出,画出来人工看一遍。你会发现大量“模型不确定”的框其实是你标注时漏掉的真实目标,或者反过来是背景误检。把漏检的目标补进train集,把误检的框当作该图的负样本上下文,再跑第二轮训练。这一步的效果往往比把epochs翻倍还要明显,因为模型刻意学了你之前标错的地方。
难例挖掘的操作不需要写复杂代码,用YOLO推理的txt输出加一张可视化脚本就能跑通。把第二轮训练后的val mAP和第一轮对比,重点看难例类别上涨幅度。如果某个类别涨得明显,说明第一轮缺的就是这类数据。我习惯用这种方式确认“是数据不够还是模型不够”,如果难例挖掘后mAP上涨超过3个点,那模型和超参数都不用动,继续扩数据就行了。
6.2 验证集的正确打开方式
走到这一步,你还应该改掉一个坏习惯:不要把训练集里的增强应用到验证集。val上永远用原图、原分辨率、无mosaic做推理,这样才能看出模型真实能力。对比不同模型的性能时,保证imgsz和conf阈值一致,否则你看到的差异可能只是阈值不同造成的假象。记录指标时不要只看mAP@0.5,把mAP@0.5:0.95、精确率、召回率、推理耗时四列一起写进表格,杂草场景里召回率往往比精确率更重要,因为漏一次草可能就意味着这株草会长大变成下一季的杂草种子。
最后说一个我自己的习惯:拿到任何一套可用于YOLOv5/v6/v7/v8训练的农作物杂草数据集,不管别人告诉我标注得多干净,我都先可视化抽查50张再做训练,从来不复用旧的划分结果。很多数据集的坑,不是靠看文件和统计数字能发现的,而是得把框画到图上亲眼扫一遍。希望这套流程能帮你把数据侧的问题在训练前就排干净,省下去,报错重来的时间。希望帮到你。
本文还有配套的精品资源,点击获取