☰
农业害虫与植物病害目标检测数据集:从解压到训练的完整指南
2026/9/26 15:52:41 网站建设 项目流程

简介:针对农业害虫与植物病害检测需求,这份YOLO格式数据集覆盖13类高发性病虫害,包含非洲化蜜蜂、蚜虫、菜青虫、玉米螟、柑橘溃疡病等常见类别,共1294个txt标注文件、704张jpg图像、1个yaml配置及1个docx说明文档,压缩包整体62.95MB。数据集划分明确,训练集906张、验证集261张、测试集127张,适合作为智能农业监测、移动端病虫害识别及精准农业算法研究的训练输入。图像采自真实农田,涵盖叶片、茎秆、果实等多部位,含复杂背景、光照变化与多目标共存场景;尤其包含植物病害样本,支持作物病害早期发现。文件总数2000,以YOLO边界框标注为主,配套类别配置与文档说明,可直接用于YOLOv5/v7/v8等主流框架训练迁移,便于快速理解数据结构与训练流程。目前已有359人学习下载,适合学生、研究者及农业开发者用于模型调优与课题实验。

1. 农业害虫与植物病害目标检测数据集:一份能直接喂给检测模型的“带答案”底稿

拿到农业害虫与植物病害目标检测数据集.zip 的那一刻,很多人以为它只是一个图片压缩包,真正开始训练才发现,这份 zip 里装的不只是图像,而是一整套“问题的标准答案”。它通常包含原始田间照片、对应目标的标注框,以及类别名称文件;你要做的不是把图片摊开看一眼,而是把这份 zip 变成能被 YOLO 这类检测器直接消费的目录结构。做农业视觉落地的团队、做植保智能化项目的个人开发者,以及想用真实场景数据验证目标检测模型的学生,都会从这套数据里省下大量标图时间。可如果直接 unzip 后丢进训练脚本,翻车几乎是必然的。

2. 拆解农业害虫与植物病害目标检测数据集:目录结构、标注格式与类别表

拿到 zip 后先别急着训练,先用一条 tree 命令把目录结构打出来。常见做法是“图像文件夹 + 标注文件夹 + 类别清单”的三段式布局,解压后第一眼往往能看到下面这样的骨架:

unzip -o 农业害虫与植物病害目标检测数据集.zip -d agri_dataset tree agri_dataset -L 2

unzip -o表示覆盖已存在文件,-d agri_dataset指定解压目标目录;tree -L 2只展开两层,否则 images 下几百张图片会把终端刷屏。如果系统没有 tree,用ls -R agri_dataset | head -50也能看到大致结构。骨架通常长这样:

agri_dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── val/ ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── 002.txt │ └── val/ ├── classes.txt └── data.yaml

注意:有的打包者会把labels写成annotations,把classes.txt写成obj.names,data.yaml也可能叫dataset.yml;名字可以变,但三件事是固定的:图像、标注、类别名。

2.1 从解压后的目录结构读信息:JPEGImages、labels、classes 文件的含义

如果看到的目录名是JPEGImages和Annotations,说明这份数据集更接近 VOC 风格。JPEGImages放原始图片,Annotations放同名 XML 标注文件,ImageSets/Main里通常还有 train.txt、val.txt 两个清单。如果看到的是images/和labels/,则更接近 YOLO 风格,train 和 val 分别成对出现。

先别管风格,第一步要确认三件事:图片有多少张、标注有多少个、两者能否一一对应。一个快速判断方式是统计两个目录下的文件数:

find images/train -name "*.jpg" | wc -l find labels/train -name "*.txt" | wc -l

数量对不上时不要往下走,先找出缺失项。常见原因有三个:某张图没有标注文件、标注文件是空的(0 字节)、或者打包时把空目录也放进来了。这类问题在训练时不会立刻报错,但会让验证集的 mAP 指标虚高或者出现“一类都检测不到”的怪现象。

关于文件名:农业数据经常出现20240501_field01_001.jpg这种带日期、地块、序号的命名。这个信息很值钱——如果训练集和验证集按文件名随机分,同一地块相邻几帧的相似图像会同时出现在两边,指标会好看,但到了新地块立刻掉点。这一点在第 3 章的划分环节还会再谈,现在只需留意命名规律。

2.2 YOLO 与 VOC 两种标注格式的取舍:先确认格式再决定上游工具

标注格式决定了你能用哪些训练框架。YOLO 系的 txt 标注每一行代表一个目标:

0 0.5210 0.6342 0.1234 0.0871 1 0.8102 0.4220 0.0982 0.1556

第一个数字是类别编号,后面四个是归一化坐标:中心点 x、中心点 y、框宽 w、框高 h,数值都在 0 到 1 之间。这种格式省空间、读取快,训练脚本直接能用,但人眼几乎没法复核,你很难从一行数字判断“这个框是不是画歪了”。

VOC 风格的 XML 则把每个目标包在<object>节点里,用绝对像素坐标记录左上角和右下角:

<object> <name>rice_planthopper</name> <bndbox> <xmin>124</xmin> <ymin>89</ymin> <xmax>210</xmax> <ymax>145</ymax> </bndbox> </object>

VOC 格式的优势是能被 labelImg、LabelMe 等常用标注工具直接读取,方便你复核和二次修改;缺点是目录散、文件多,训练前多一道转换。大多数下载来的农业数据集不会只有一种格式,我经常在一个 zip 里同时看到 XML 和 txt,甚至还有.json。拿到手的第一件事应该是“格式识别”,而不是直接开训。

识别方法很简单:随机挑几个标注文件看一眼。第一行是<object>开头的就是 XML,第一行是数字 浮点 浮点 浮点 浮点的就是 YOLO txt。如果同一个数据集两种都有,要么写一个统一转换脚本把它们合成一套,要么干脆统一转成 YOLO——这个转换过程在第 4 章会展开。

2.3 类别表与 data.yaml:行家会先读这两个文件

classes.txt和data.yaml是这份数据的“目录”,直接决定类别编号怎么对。先把它们打开看一遍:

cat classes.txt cat data.yaml

如果 classes.txt 的顺序是rice_planthopper、rice_blast、leaf_hopper,那么这个顺序就是 txt 里第一个数字的编号基准。任何一次“调整顺序”的动作都会让现有标注整体错位——这是我在第 5 章要说的重点之一。

data.yaml在 YOLO 训练里会被直接引用,里面通常写着:

path: agri_dataset train: images/train val: images/val nc: 3 names: ['rice_planthopper', 'rice_blast', 'leaf_hopper']

很多人只改path,忘了核对nc和names的顺序。names的顺序必须和 classes.txt 一致,nc必须等于 names 元素个数;不一致时训练不会报错,但预测结果会“张冠李戴”。我会用一段小脚本把类别数和真实标注扫描一遍,顺便统计每个类别的标注框数量,这个脚本放在第 3 章。

3. 训练前先清洗:解压校验、坏图扫描与类别统计的落地脚本

数据集不是解压完就能直接进训练脚本的。田间照片的采集环境很杂:手机拍摄、无人机航拍、监控截图混在一起,经常出现损坏图片、全黑帧、重复帧。一份农业害虫与植物病害目标检测数据集虽然经过整理,但整理者不一定逐张看过。我这里习惯的做法是:先校验压缩包本身、再扫坏图、再统计类别分布,最后才划分数据集。

3.1 解压与完整性校验:unzip -t 的隐藏价值

常见的做法是下载后直接unzip -o,解压出来就开始看。如果包是网盘转存或者多次复制得到的,文件可能出现 CRC 错误。与其训练到一半报错再回头查,不如解压前先做一次完整性校验:

unzip -t 农业害虫与植物病害目标检测数据集.zip

-t参数只测试压缩包内每个文件的 CRC 校验值,不实际解出文件。输出末尾会有一行No errors detected in compressed data,看到这行再解压。如果报了某个文件的 CRC 错误,说明文件已损坏,不要试图“跳过坏文件解压剩余部分”,因为坏掉的可能是图片,也可能是标注;最稳的做法是重新下载。

如果这不是从下载站拿的,而是一个内部传阅包,又联系不上原作者,可以试试 Python 的zipfile模块做一次只读遍历:

import zipfile with zipfile.ZipFile("农业害虫与植物病害目标检测数据集.zip") as zf: bad = zf.testzip() print("OK" if bad is None else f"bad file: {bad}")

zf.testzip()返回第一个损坏文件的文件名,全部正常时返回 None。这一步治不了损坏文件,但能让你明确知道是哪张图坏了,决定是弃用这一张还是整包重下。注意这里的写法和unzip -t是重复校验,生产环境跑其中一条就够了。

3.2 坏图扫描:让脚本在训练前把脏数据找出来

校验完压缩包,接下来扫图片。田间数据集最常见的翻车之一是“黑图”和“半截图”:相机对焦失败、存储卡写坏、后期批量压缩出错,都会留下 RGB 值几乎不变的伪图片。这类图不一定会让 OpenCV 报错,因为文件头还是完整的 JPEG,但训练出来的特征会跑到纯色块上。

我用下面这个脚本扫一遍所有图片,同时做两件事:验证文件能否被正常解码,以及检查灰度对比度是否异常低:

from PIL import Image import sys from pathlib import Path def validate_image(path, min_contrast=1.0): try: im = Image.open(path) im.verify() # 只验证解码结构 except Exception as e: return False, f"verify_failed: {e}" try: with Image.open(path) as im: gray = im.convert("L") extrema = gray.getextrema() contrast = extrema[1] - extrema[0] if contrast < min_contrast: return False, f"low_contrast={contrast:.2f}" except Exception as e: return False, str(e) return True, "ok" img_dir = sys.argv[1] for p in Path(img_dir).rglob("*.jpg"): ok, reason = validate_image(p) if not ok: print(f"{p}: {reason}")

第一次im.verify()只检查 JPEG 文件结构是否完整,第二次用convert("L")转灰度后取像素极差,contrast < 1.0基本可以断定是纯色或接近纯色的废帧。这里的min_contrast参数对低对比度农业图像要谨慎:阴天田垄的对比度本来就不高,阈值设成 1.0 一般够用,继续调低容易误删。发现坏图后,连图带对应的标注文件一起移出目录,不要只删图片,不然后面训练时 txt 没有对应图,dataloader 也可能直接崩。

提示:这个脚本放在解压后、划分数据集之前跑,能省掉后续排查“模型不收敛却不知道是数据问题”的半天时间。

3.3 类别分布统计:不平衡往往藏在 txt 里

农业害虫与植物病害目标检测数据集最容易被忽略的问题不是标错框,而是类别极端不平衡。一个包含“稻飞虱、稻瘟病、二化螟、稻曲病”四个类的数据集,可能前两类占 90%,后两类加起来不到 5%,而且后两类恰恰是你最关心的早期病害。统计方式很简单,读每个 txt 的第一个数字,或者读 XML 里的<name>节点:

from collections import Counter from pathlib import Path import sys label_dir = Path(sys.argv[1]) counts = Counter() for txt in label_dir.rglob("*.txt"): for line in txt.read_text(encoding="utf-8").splitlines(): parts = line.strip().split() if len(parts) >= 1: counts[parts[0]] += 1 for cls_id, num in sorted(counts.items(), key=lambda x: -x[1]): print(f"class {cls_id}: {num} boxes")

如果数据集里同时有 XML,就换成解析<name>,逻辑一样。统计结果可以整理成一张表,我用 4 个常见类做个示意:

类别框数占总量比例可能的问题
稻飞虱183046.2%小目标多,需独立评估 AP-small
稻瘟病102325.8%病斑与叶片背景难分
二化螟3107.8%样本偏少,考虑合并或补充
稻曲病962.4%框太少,直接训练会学不到

看到“96 框”这种数字时,我的做法不是硬着头皮训,而是先把该类别标记为候选合并或剔除。目标检测任务里类别数量是固定的,硬训的结果往往是整体 mAP 被少数类拉低,且稀有类别的 AP50 接近 0。后面 5.5 节会给出具体的取舍策略。

3.4 训练集与验证集划分:按目录分组,别只看文件名好看

划分数据集是清洗里最容易被“随手一 split”的一步,但对农业数据来说,随机拆分是危险的。田间图像通常沿着地块一行一行拍摄,连续帧之间重叠度很高;如果随机分,同一地块的相邻帧可能一部分进训练、一部分进验证,模型等于“背过答案”,验证集指标虚高。到了新地块、新光照条件下,表现立刻崩。

所以划分前先看文件名。对20240501_field01_001.jpg这种命名,前面20240501_field01就是“场景组”;按组切分才符合真实使用场景。下面这个脚本按前缀分组后,把组整体放入训练集或验证集:

import random, shutil from pathlib import Path def split_by_group(image_dir, label_dir, out_dir, val_ratio=0.15, seed=42): random.seed(seed) groups = {} for img in Path(image_dir).rglob("*.jpg"): group = "_".join(img.stem.split("_")[:2]) groups.setdefault(group, []).append(img) group_names = list(groups.keys()) random.shuffle(group_names) val_groups = set(group_names[: max(1, int(len(group_names) * val_ratio))]) for group, imgs in groups.items(): subset = "val" if group in val_groups else "train" for img in imgs: label = Path(label_dir) / img.name.replace(".jpg", ".txt") if not label.exists(): print(f"missing label: {label}") continue dst_img = Path(out_dir) / subset / "images" / img.name dst_lbl = Path(out_dir) / subset / "labels" / label.name dst_img.parent.mkdir(parents=True, exist_ok=True) dst_lbl.parent.mkdir(parents=True, exist_ok=True) shutil.copy(img, dst_img) shutil.copy(label, dst_lbl) if __name__ == "__main__": split_by_group("images/train", "labels/train", "split_dataset", 0.15)

val_ratio按“组”的比例算,不是按图片数算;seed固定后结果可复现。切分时发现缺 label 就直接跳过,同时打印缺失名单。这一步做完,数据才真正具备送进训练脚本的条件。

4. 把 VOC 标注转成 YOLO 格式:转换脚本与四个边界坑

网上很大一部分农业数据集以 VOC 形式分发,Annotations下的 XML 文件给人“规范”的错觉。但 YOLO 系训练脚本大多数只认 txt,所以转换是绕不开的一步。转换本身不复杂,真正的坑在边界条件:坐标反向、类别对不齐、图片尺寸不一致、文件数目对不上。这一章先把脚本写出来,再逐个拆坑。

4.1 XML 坐标转归一化中心点:convert 脚本与参数说明

转换逻辑一句话:XML 里是绝对值,YOLO 要的是 0 到 1 的归一化值。中心点坐标和宽高都要除以图片宽、图片高:

import xml.etree.ElementTree as ET CLASS_IDS = { "rice_planthopper": 0, "rice_blast": 1, "leaf_hopper": 2, } def xml_to_yolo(xml_path, out_txt, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter("object"): name = obj.findtext("name", "").strip() if name not in CLASS_IDS: continue cls_id = CLASS_IDS[name] bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) w = xmax - xmin h = ymax - ymin if w <= 0 or h <= 0: continue cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h norm_w = (xmax - xmin) / img_w norm_h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {norm_w:.6f} {norm_h:.6f}") with open(out_txt, "w", encoding="utf-8") as f: f.write("\n".join(lines)) xml_to_yolo("Annotations/001.xml", "labels/001.txt", 640, 480)

这里的img_w和img_h必须是图片真实尺寸,而不是 XML 里<size>节点写的值。有的标注工具在 XML 里写的是缩略图尺寸,框是原图标出来的,混着用会让所有框整体偏移。稳一点的做法是转之前用 PIL 读一次原始图像,把真实宽高传进来。

4.2 边界坑一:坐标越界与反向框

转换脚本里我已经加上w <= 0 or h <= 0的跳过逻辑,这是第一个坑:标注人员拖框时可能从右下往左上拖,导致xmax < xmin。如果只是把框当负宽高强行算,中心点坐标会跑到图外,训练时模型学着输出负数宽高,loss 大概率变 nan。

另一个隐蔽问题是坐标越界:框的左边界超过了图片宽度,或右下角压到图片外面。转换时cx可能算出来大于 1。对越界框我的处理是“能修则修,不能修就丢”。用numpy.clip把归一化坐标截断到 [0, 1] 区间,但如果框本身大部分在图像外部,截断会得到一个面积很小、位置异常的框,影响训练,直接丢掉更干净。建议转换脚本里加一行打印,把反向框和越界框的数量统计出来,人工决定是修还是删。

4.3 边界坑二:单类文件自动补齐与类目对齐

XML 里类别名常常不干净:“褐飞虱”“褐飞虱(若虫)”“褐飞虱-1”会被标成三个不同名字。如果直接用原始名写进训练脚本,类别数会莫名膨胀,且两个名字实际指向同一个类。我在CLASS_IDS字典里做的其实是“别名归一”:把带括号、带横线的名称统一映射到规范名。每次新增数据集,我都会先grep '<name>'把所有 name 枚举一遍,再决定映射关系,而不是拿到字典就直接跑。

还有一个常被忽略的情况:某些 XML 里<name>的文本带有 Unicode 不可见字符,比如全角空格。用name.strip()能去掉常规空格,去掉全角空格要用name.replace("\u3000", "")。农业数据的采集者不一定统一输入法,这个问题出现频率比想象中高。转换完成后,随机打开几个 txt 看一眼第一行的类别编号是否和 classes.txt 对应,这一步比后面训练时的任何调试都值钱。

4.4 边界坑三:多标签与大尺寸图

一份农业害虫与植物病害目标检测数据集可能不是单纯的水平框。叶片上的病斑往往用多边形标注(<polygon>),无人机俯拍图中的目标也可能用旋转框(<robndbox>)。这类标注强行转换成长方形框会损失角度信息——病斑框面积虚增,害虫框框住大量背景,模型学到的特征被稀释。如果 XML 里出现这些节点,我一般直接把那份 XML 单独拎出来,用旋转框检测框架处理,而不是硬塞进 YOLO。做旋转框检测时,常见做法是参考 mmrotate 训练 DOTA 数据集的标注格式,把<robndbox>转成(cx, cy, w, h, angle)格式的 txt,再用对应的数据加载器读入。

大尺寸图像是同一个问题的另一种形态。植保无人机拍的是 4000×3000 甚至更大的图,病斑可能只有十几个像素。直接 resize 到 640×640,小目标等于被抹掉了。常见做法是先滑窗切图再转换:把一块地切成 512×512 的小块,对每块内的标注重新归一化。切图时如果目标跨边界,我一般放弃跨边界的框,而不是把它复制到两侧,否则验证时同一个目标会被重复统计,mAP 虚高。

4.5 边界坑四:标注文件与图片名编号错位

有的 VOC 数据里图片叫001.jpg,标注叫001.xml,看起来一一对应,但打包时可能混进过别的文件,导致 XML 比 JPG 多或少几张。转换前先做一次配对:

for f in images/*.jpg; do base=$(basename "$f" .jpg) ls annotations/${base}.xml >/dev/null 2>&1 || echo "missing xml: $base" done

基线配对没问题后,再用xml_to_yolo落盘。这一步看似简单,却能避免最尴尬的训练报错:dataloader 按图片目录遍历,某张图没有对应 txt,可能只是被跳过,更糟的是随机数顺序错乱时,txt 和图片错位,模型学到一堆交换后的标签。我自己会把配对结果打印成一个 manifest 清单,存进数据集根目录,便于后续复现和排错。

5. 常见问题与排查:zip 伪加密、中文路径、类别错位和微调崩溃

这一章把训练中真正会卡住人的问题列出来,按“现象 → 原因 → 解决”的顺序说。这些问题不解决,数据集再好也跑不出效果。

5.1 zip 伪加密:开箱弹密码,其实是文件头被改了一个 bit

现象:下载的农业害虫与植物病害目标检测数据集.zip 在 Windows 资源管理器里双击要求输入密码,文档里却只字未提密码。

原因:zip 文件头里有一个“加密标志位”,位于 local file header 的 general purpose bit flag 第 0 位。如果这一位被写成 1,解压工具就会认为文件加密;但这份数据可能只是从某个网盘、邮件中转或内部分享流程走了一遍,文件头被改动但不涉及实际加密算法,数据区域并没有真正加密。这就是资料圈常说的“伪加密”。

解决:先用一个支持查看 zip 细节的工具观察文件头。Linux 下可以用zipinfo -v,Windows 下可以用 010 Editor 或 HxD。看到加密位被设置后,再写一个简单脚本清理标志位:

import struct import sys def fix_fake_encryption(in_zip, out_zip): with open(in_zip, "rb") as fr: data = fr.read() # local file header 以 PK\x03\x04 开头,示例只处理 local header idx = 0 fixed = 0 while idx < len(data): if data[idx:idx+4] == b"PK\x03\x04": flags = struct.unpack_from("<H", data, idx + 6)[0] if flags & 0x0001: flags &= ~0x0001 data = data[:idx+6] + struct.pack("<H", flags) + data[idx+8:] fixed += 1 idx += 4 with open(out_zip, "wb") as fw: fw.write(data) print(f"fixed {fixed} local headers")

这个脚本只处理 local file header 的加密位,central directory 里的对应位置也要清掉,否则部分工具仍然会提示加密。完整的做法是对两处 header 分别扫PK\x03\x04和PK\x01\x02。如果文件是真加密,清标志位后解压出来也是一堆乱码,此时只能找原作者要密码,逻辑上不要绕开。

注意:伪加密修复只适用于“本身没加密、只是标志位被置位”的文件,不要拿它去绕过别人的密码限制。

5.2 Windows 下的中文路径:cv2.imread 返 None,PIL 却正常

现象:训练脚本在 Windows 上跑,图像目录名是中文(例如农业害虫_数据集/images/train),OpenCV 读图返回 None,但同样的代码在 Linux 上没有问题。

原因:OpenCV 的imread依赖系统编码,Windows 默认编码与 UTF-8 路径不兼容,中文路径直接读取失败。PyTorch 的 DataLoader 常配合 OpenCV 解图,所以报错经常发生在collate阶段或者__getitem__里。

解决:最简单的方法是把数据集根目录改成纯英文字母和数字,例如agri_dataset,一劳永逸。如果项目目录名改不了,可以在读取图片时绕过 OpenCV 的内置读取函数:

import numpy as np import cv2 def imread_unicode(path): data = np.fromfile(path, dtype=np.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)

np.fromfile读取原始字节,cv2.imdecode从内存解码,绕过了imread的编码问题。注意 PIL 在同一路径下通常是正常的,这也导致很多人误以为“图片没问题”,然后陷入无意义的训练参数排查。我的经验是:数据集在 Windows 上处理时,第一件事就重命名成纯英文路径。

路径过长是另一个相关坑。Windows 默认最大路径长度是 260 个字符,把 zip 解压到深层目录后,标注文件可能因为路径超长写入失败。在项目根目录下新建agri_dataset作为解压目标,能避免大多数路径问题。

5.3 类别编号错位:只改 classes 名,训练直接崩

现象:训练 loss 正常下降,验证集的整体 mAP 也还行,但单独看每个类别的 PR 曲线,某个类总是 0,另一个类却异常高。

原因:类别编号错位。最常见的是自己重新整理了 classes.txt 顺序,或者从别人那里拿到一份新数据集后直接替换 classes.txt,但是标签 txt 里的第一个数字没有改。比如原数据里0代表稻飞虱,新类别表里0是稻瘟病,所有框都标成了错误类别。

解决:转换脚本里不给硬编码映射,而是先加载classes.txt生成一份旧编号到新编号的映射字典,再重写标注:

new_classes = [line.strip() for line in open("classes_new.txt", encoding="utf-8")] old_to_new = {0: 2, 1: 0, 2: 1} # 人工核对后填写 for txt in label_dir.rglob("*.txt"): lines = txt.read_text(encoding="utf-8").splitlines() new_lines = [] for line in lines: parts = line.strip().split() if len(parts) < 5: continue parts[0] = str(old_to_new[int(parts[0])]) new_lines.append(" ".join(parts)) txt.write_text("\n".join(new_lines), encoding="utf-8")

执行后随机抽查 10 个 txt 文件,确认第一列数字都在 0 到nc-1之间。这一步不能省,特别是在多个数据集的分类体系合并时,类别错位是“目标检测模型微调崩了”的一类高发原因。

5.4 微调崩溃:先查学习率、图片尺寸和头文件匹配

现象:加载 YOLO 预训练权重后开始微调,第一个 epoch loss 正常,到第二个 epoch 突然变 nan;或者 loss 在一两百步内一路冲到inf。

原因:我的排查顺序是固定的。第一,预训练权重里的类别数和当前数据集的nc不一致,模型 head 层的类别预测分支被随机初始化,这部分需要相对小的学习率来稳定,如果直接用默认 0.01,很容易第一天就崩。第二,图片输入尺寸改了但没同步调整锚框或 stride,YOLOv8 这类无锚框模型还好,老版本 YOLOv5 对输入尺寸很敏感。第三,数据里有损坏标注,比如坐标超过图片边界。第四,学习率本身太大,没有给模型“预热”的机会。

解决:先用一个 3 个 epoch 的极小任务做烟雾测试:只取 200 张图片、关闭数据增强(mosaic=0.0、hsv=0.0)、学习率降到 0.001,确认模型能从训练集中学出东西。烟雾测试通过后再逐个打开增强项。如果 loss 仍在某个 epoch 之后频繁变 nan,回去跑一遍第 3 章的坏图扫描和坐标范围检查。

数据里有一些“看起来像图但其实已经被截断”的文件,也会让 loss 出现瞬时尖峰。这类图不一定是全黑帧,可能是某个.jpg尾部数据缺失,训练时随机裁切到损坏区域。这类问题用第 3 章的im.verify()扫一遍就能定位。

5.5 样本不足的取舍:宁可合并类别,也别硬练

现象:某类只有几十个框,训练 100 个 epoch 后该类的 AP50 仍然接近 0。

原因:目标检测的数据需求远高于分类,每个类别至少需要几百个有效实例才能学到稳定的纹理和形状特征。几十个框对这个任务来说基本是噪声,模型很容易把它学成背景。

解决:三个方向按顺序考虑。第一,合并精细类别,把“稻飞虱若虫”“稻飞虱成虫”合并成“稻飞虱”,把病斑的不同阶段合并成同一个“稻瘟病”,类别数量减少后,每个类的框数变多,训练更稳定。第二,如果业务必须区分精细类别,就用“先检测大类、再对 crop 区域做细分类”的两段式方案,检测模型学大类,分类模型学细节,两段的标注成本都更低。第三,如果条件允许,把没标注图像交给训练好的模型推理,人工过滤高置信结果做半监督回注,下一轮训练时补进数据集。这个手段在第 6 章还会再讲。

6. 训练完怎么才算“能用”:指标验证、小目标增强与数据集扩展技巧

训练完成不是终点,农业场景真正在乎的是“拿到新地块能不能用”。我会按下面三步做验证和增强,才能下“这个数据集和这套模型能投入”的结论。

先看指标,不能只盯一个 mAP。农业害虫和植物病害检测中,小目标占比高、样本不平衡常见,整体 mAP50 掩盖了太多问题。用一张表固定每阶段的检查项:

验证阶段指标关注点
基准训练mAP50数据集是否可学,能否收敛
分类别评估每类 AP50稀有类别是否被学成背景
小目标评估AP-small小于 32×32 的害虫/病斑是否漏检
跨地块验证新地块图像 mAP换光照、换相机后掉几个点

我自己的经验是,先跑一遍 mAP50,再单独打印每个类别的 AP。如果整体 mAP 是 0.6 但稻瘟病只有 0.2,这说明模型没学到病斑特征,而不是“数据集不够好”。

增强方面,YOLOv8 默认开启的 mosaic 对常规场景很有效,但对病斑这类小目标并不总是最优;把 mosaic 概率从 1.0 调到 0.5,增加随机裁剪和 CopyPaste,小目标 AP 会更稳。对低对比度的病斑图像,田间照片往往偏暗、偏灰,用 CLAHE 做一次对比度增强再训练,AP 的提升比换任何学习率调度器都明显。大尺寸图像则优先用滑窗切图训练,再在推理阶段把窗口预测结果合并回大图坐标。

数据扩展可以按“软增强 + 硬样本”的节奏做。软增强就是前面说的 mosaic、多尺度、旋转,它不增加新信息,但对小目标多的农业数据很有效。硬样本是从未标注图像中挖掘:跑一次当前最优模型的推理,挑出低置信度、模型犹豫的检测框,人工确认后进入训练集。完整跑一个验证集后,把新样本写入data.yaml的 train 路径,做第二轮微调。

我现在的习惯是任何一份数据集,先跑一个 3 个 epoch 的 smoke test,确认 loss 正常、类别编号没对错,再去追 mAP 指标;这个习惯帮我避开了大量无效训练。农业目标检测的数据集不会完美,但把坏图、错编号、样本不平衡在进训练前处理干净,剩下的问题基本都在模型侧而不是数据侧,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询