尧图精选

害虫目标检测数据集预处理与YOLOv8训练实战指南

🕒 发布时间:2026/10/2 3:34:20 📁 来源:尧图网络
简介害虫目标检测数据集覆盖15个数字类别害虫共1140张来自真实农田场景的JPEG图片每张均包含边界框与类别标签适合农业AI与目标检测研究者用于构建农田害虫自动识别与监测模型。压缩包内文件总数2000个包含1140个txt标注文件、858张jpg原图、1个yaml配置及1个docx说明文档整体大小约132.85MB。数据已按训练集912张、验证集114张、测试集114张划分类别多样且规模均衡YOLO格式标注可直接用于YOLO、Faster R-CNN等主流框架训练与评估。借助该数据集可快速搭建害虫检测实验支撑农田虫害实时预警、智能精准防治决策、生物学分布研究及教学培训等实际场景。目前已有194人学习下载适合农业AI开发者、研究人员与学生参考使用。1. 为什么说害虫目标检测数据集值得先拆开再训练拿到《害虫目标检测数据集.zip》绝大多数人第一反应是解压、点开几张图片、然后立刻启动训练。这个顺序反了。我的建议是先把这份 zip 当成一份需要验收的交付物花半小时摸清目录结构和标注质量再决定用什么模型、多大的输入尺寸去训。因为害虫目标检测的数据有鲜明特点目标尺寸小、姿态多样、背景是农田、叶片或诱虫板标注里的漏标和错框会直接压低 mAP而这个上限不是换一个新模型就能补回来的。这份 zip 通常包含田间采集的害虫照片和对应标注覆盖稻飞虱、玉米螟、棉铃虫等常见种类适合做植保巡检、虫情测报灯识别、农药喷洒决策这类落地任务。适合准备训练第一个目标检测模型、又不想从零开始用标注工具标几千张图的人。2. 解压后先看懂目录结构和标注格式VOC、YOLO 还是 COCO第一步就决定工作量2.1 压缩包里的目录骨架JPEGImages、Annotations 还是 images、labels解压之前先用unzip -l看一览表别直接整包解压。很多数据集不是原作者打包的可能从数据平台转了两手里面还套了一层目录。先看根目录长什么样能判断到底是学术风的 Pascal VOC 还是工程风的 YOLO 布局也能顺便确认 zip 里有没有混进无关文件。unzip -l 害虫目标检测数据集.zip | head -30head -30是因为数据量大时列目录会刷屏只看前 30 条就够判断结构了。输出里如果出现JPEGImages/和Annotations/两个并排目录基本是 VOC 风格如果是images/和labels/并排可能已经是 YOLO 风格省掉不少转换工作。还有一种单文件形态一个annotations.json躺在根目录配一个images/这是 COCO 风格。三种风格对应完全不同的后续处理路径。VOC 的标注是 XMLCOCO 是 JSONYOLO 是每张图一个同名 TXT。拿到手的第一件事就是确认是哪一种因为后面无论是训练 YOLOv8 还是做数据增强都绕不开格式统一这个步骤。我见过有人拿到 VOC 格式后直接硬塞给 YOLOv8结果训练能跑、mAP 惨不忍睹浪费一整天。这类数据集最常见的问题不是格式而是“看似是某种格式、实际缺了配套文件”。比如 JPEGImages 里 1000 张图Annotations 里只有 980 个 XML或者 labels 目录里有 TXT但没给 classes.txt。缺了类别清单YOLO 的标签编号就是黑匣子只能靠猜。2.2 三种标注格式的识别XML、TXT、JSON 的区别与配套怎么看打开一个样本就能识别格式不需要任何工具。VOC 的 XML 里找object标签里面有name和bndbox坐标是像素整数object namerice_planthopper/name bndbox xmin128/xmin ymin96/ymin xmax180/xmax ymax140/ymax /bndbox /objectYOLO 的 TXT 每行 5 列数字第一个是类别编号后四个是归一化坐标中心点 x、中心点 y、宽、高全部落在 0 到 1 之间0 0.3125 0.1875 0.1289 0.0859COCO 的 JSON 是两段式结构images数组存每张图的 id 和宽高annotations数组存每个框的类别和bbox坐标形式是[xmin, ymin, width, height]的像素值。三者的换算关系不复杂但格式差异决定了你要写多少转换代码。格式识别也可以顺带看用什么工具标注的。LabelImg 导出的默认是 VOC 风格 XMLlabelme 默认导 JSONCVAT 导出时可以选任意格式。这些目标检测常用标注工具在数据交付时通常会在 README 里写一句可惜多数压缩包里没有 README所以才需要从文件内容反推。2.3 给数据集做体检用十行代码统计类别、实例数与常见问题别急着训练先统计类别分布。这一步能暴露类别不平衡、标注遗漏、类别名拼写不一致等问题比训练到一半再发现省事得多。import os import xml.etree.ElementTree as ET from collections import Counter def voc_stats(anno_dir): 统计 VOC 格式每个类别的实例数 c Counter() for xml in os.listdir(anno_dir): if not xml.endswith(.xml): continue root ET.parse(os.path.join(anno_dir, xml)).getroot() for obj in root.iter(object): c[obj.find(name).text] 1 return dict(c) def yolo_stats(label_dir, names_txt): 统计 YOLO 格式每个类别的实例数需要类别文件映射编号 names [n.strip() for n in open(names_txt) if n.strip()] c Counter() for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue for line in open(os.path.join(label_dir, txt)): c[names[int(line.split()[0])]] 1 return dict(c)逻辑说明VOC 版本遍历所有 XML 的object节点按类别名计数YOLO 版本读 TXT 每一行的第一个数字映射到类别文件里的真实名称再计数。参数说明YOLO 版本的names_txt顺序必须和后面训练时的data.yaml完全一致否则统计出来的分布和训练时模型看到的分布是错位的。统计完如果发现某个类别只有几十个实例、另一个类别占七成这就是典型的类别不平衡场景。如果 VOC 版本统计到的类别名里有“rice_planthopper ”和“rice_planthopper”这种肉眼几乎分不清的差异就说明标注过程中有人手动改过标签尽早合并。2.4 图像尺寸的隐藏前提XML 里的 size 和图片本身是否一致VOC 的 XML 里有size字段记录了宽高但批量压缩时有人会先把图片缩小省空间、忘记同步 XML 里的 size。一旦两边对不上后续转 YOLO 归一化坐标时分母就是错的所有框整体偏移。这个错在可视化阶段不容易发现因为框的长度比例几乎看不出异常实际坐标却已经歪了。from PIL import Image import xml.etree.ElementTree as ET def check_size(image_dir, anno_dir): 对照图片真实尺寸与 XML 里的 size不一致就打印出来 for xml in os.listdir(anno_dir): if not xml.endswith(.xml): continue root ET.parse(os.path.join(anno_dir, xml)).getroot() fname root.find(filename).text w int(root.find(size/width).text) h int(root.find(size/height).text) im_w, im_h Image.open(os.path.join(image_dir, fname)).size if (w, h) ! (im_w, im_h): print(f{fname}: xml({w}, {h}) image({im_w}, {im_h}))这段脚本应该在你做任何格式转换之前跑。打印出来的文件如果只有个位数直接按图片真实尺寸修正 XML如果是大面积不一致就要重新导出标注了。和遥感图像目标检测动不动几千像素的图不同害虫数据集里图片分辨率通常不高尺寸不一致的问题更容易被忽略。3. 把数据统一成 YOLO 格式转换脚本、中文 zip 解压与三类边界坑3.1 从 VOC XML 到 YOLO 归一化坐标换算公式、精度与越界处理VOC 到 YOLO 的核心就一个公式像素坐标除以图片宽高得到归一化坐标再把xmin/xmax换成中心点加宽高。写成脚本并不难难的是处理各种边界情况。import os import xml.etree.ElementTree as ET from PIL import Image classes [rice_planthopper, corn_borer, cotton_bollworm] # 和后续 data.yaml 保持一致 def voc_to_yolo(anno_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml in os.listdir(anno_dir): if not xml.endswith(.xml): continue root ET.parse(os.path.join(anno_dir, xml)).getroot() filename root.find(filename).text im Image.open(os.path.join(img_dir, filename)) w, h im.size # 用真实图片尺寸而不是 XML 里的 size lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue # 过滤不在目标列表里的类 cls_id classes.index(name) b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) # 中心点 宽高的归一化换算 x_c ((xmin xmax) / 2) / w y_c ((ymin ymax) / 2) / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 越界保护防止写进非法坐标 x_c max(0, min(1, x_c)) y_c max(0, min(1, y_c)) bw max(0, min(1, bw)) bh max(0, min(1, bh)) lines.append(f{cls_id} {x_c:.5f} {y_c:.5f} {bw:.5f} {bh:.5f}) with open(os.path.join(out_dir, xml.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明分母用Image.open得到的真实宽高不信任 XML 里的size这是为了避免上一章提到的尺寸不一致导致全部坐标错位。classes顺序一旦定下来就不要改训练时data.yaml的 names 必须和它一模一样。参数说明:.5f保留 5 位小数对 640 到 1280 的图足够再高没有实际精度收益反而让文件变大。越界 clamp 那几行是“后悔药”能让训练不崩但坐标本身还是歪的。如果大量框都触发了 clamp说明源标注质量有问题要回去修 XML而不是靠 clamp 硬撑。你可以在转换脚本里加一个计数器记录 clamp 次数超过 5% 就该回头查。3.2 zip 解压的中文乱码与伪加密一份内部交付包的解压姿势害虫目标检测数据集经常是从网盘或标书交付包里传出来的压缩时用的工具五花八门。最常见的坑是 Windows 上压缩时用了 GBK 编码的文件名拿到 Linux 或 macOS 上一解压目录名变成乱码图片路径和标注文件名对不上整份数据直接没法用。# Linux 下按 GBK 解压-O 参数不是所有 unzip 都支持 unzip -O gbk 害虫目标检测数据集.zip -d pdata/ # macOS 或系统自带 unzip 不支持 -O 时改用 7z 7z x 害虫目标检测数据集.zip -opdata -mcpGBK如果不想依赖系统命令Python 的 zipfile 也能处理思路是手动纠正文件名编码import zipfile def unzip_gbk(zip_path, out_dir): with zipfile.ZipFile(zip_path) as zf: for info in zf.infolist(): try: # zipfile 默认按 cp437 解码非 UTF-8 文件名 info.filename info.filename.encode(cp437).decode(gbk) except UnicodeDecodeError: pass zf.extract(info, out_dir)逻辑说明Python zipfile 对非 UTF-8 的文件名会统一按 cp437 解码这一层是历史遗留行为。把它反向编码回原始字节再按 GBK 解码成正确中文即可。如果文件恰好既不是 GBK 也 decode 不了就保持原样至少不会让脚本崩溃。还有一种情况叫 zip 伪加密能正常列出目录但解压时提示要密码。技术原因是目录区把加密标志位置了 1实际内容并没有加密。遇到这种包先不要急着找付费解压工具拿zipfile.ZipFile.testzip()验证一下能否正常读出文件再核对一下数据包的来源。如果是别人交付的内部包直接找对方要一份正常压缩的更省事。3.3 数据划分按目录移动还是生成 list 文件分层抽样怎么选YOLOv8 按目录结构划分数据需要把图片和标签一起移动到images/train、images/val、labels/train、labels/val四个目录。YOLOv5 和一些开源仓库则用train.txt、val.txt每行一个图片路径的方式。两种方式没有绝对优劣按你后面的训练框架选。import os import random from glob import glob img_dir, label_dir images, labels imgs [x for x in glob(f{img_dir}/*.jpg) glob(f{img_dir}/*.png)] random.seed(42) random.shuffle(imgs) val_n int(len(imgs) * 0.15) val_imgs, train_imgs imgs[:val_n], imgs[val_n:] for split, split_imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(f{img_dir}/{split}, exist_okTrue) os.makedirs(f{label_dir}/{split}, exist_okTrue) for im in split_imgs: base os.path.splitext(os.path.basename(im))[0] os.rename(im, os.path.join(img_dir, split, os.path.basename(im))) txt os.path.join(label_dir, base .txt) if os.path.exists(txt): os.rename(txt, os.path.join(label_dir, split, base .txt)) else: print(fwarning: missing label for {base})逻辑说明图片和标签必须用同一份随机划分结果移动不能各自独立 shuffle否则会出现验证集图片没有标签的情况。random.seed(42)写死保证每次划分结果一致这是数据实验可复现的基础。参数说明val_n int(len(imgs) * 0.15)是验证集比例我这里用 15%数据量很大时可以降到 10%数据量只有几百张时提到 20% 更稳。这个版本的划分有一个隐患纯随机划分在高类别不平衡时可能把某个样本很少的类别全部划进验证集训练集里一个都见不到。更稳的做法是分层抽样先按图片包含的类别分组再从每个组里按比例抽验证集。如果某个类别本身只有 20 张就考虑对它在训练时做过采样或复制粘贴增强而不是指望随机划分雨露均沾。4. 训练前的数据集检查与常见问题排查5 个翻车现场4.1 Loss 降得飞快但验证集全背景类别编号和类别文件对不上现象训练前几个 epoch loss 掉得飞快降到 1 以下后停在 0.6 左右不动打开验证集预测结果所有框全标成背景。原因YOLO 模型自带默认的 COCO 80 类输出data.yaml里的 names 顺序和标签 TXT 里的 class_id 不是同一套。比如标签里写的是第 0 类训练时data.yaml的第 0 类是另一个虫子模型学到的映射从一开始就是错的。还有一种情况是标签里出现了class_id5而names只有 4 个类别越界编号被静默忽略。解决先用验证命令打印每个类别的 AP再单独看best.pt里记录的 names。AP 全为 0 时优先怀疑编号映射而不是模型结构。把data.yaml的 names 和标签文件第一列做一次差分检查确保两边编号一一对应。这个检查写进训练前的脚本里以后每次换数据集都跑一遍。4.2 转完格式发现框大面积飞出图片clamp 只能止血根因在源标注现象转成 YOLO TXT 后某些行的x_center大于 1 或width大于 1训练时报 invalid bbox 警告可视化时框直接画到图片外面。原因VOC 的bndbox坐标本来就超出图片范围。这种情况通常是标注时画框画大了边缘超出了图片边界XML 里记录了负数或超宽的像素坐标。前面转换脚本里的 clamp 只是让 YOLO 训练不报错框本身还是歪的模型会被这些错误样本带偏。解决不要只在转换时 clamp先定位哪些 XML 有问题再人工修正。import xml.etree.ElementTree as ET def find_bad_boxes(anno_dir): for xml in os.listdir(anno_dir): if not xml.endswith(.xml): continue root ET.parse(os.path.join(anno_dir, xml)).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) for obj in root.iter(object): b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) if xmin 0 or ymin 0 or xmax w or ymax h: print(f{xml}: bad box ({xmin}, {ymin}, {xmax}, {ymax}) vs ({w}, {h}))打印出来的文件如果只有两三个手动改掉如果数量很多说明这批数据是半自动标注工具生成的边界处理没做好要考虑整体重新标注或者做裁剪预处理。4.3 类别极端不平衡头类占七成尾类 AP 是零现象诱虫灯数据里某种害虫数量是其他类别的几十倍。训练完头类 AP 能到 0.9尾类 AP 直接是 0验证集里的尾类样本几乎全被预测成头类。原因数据本身采集偏差。诱虫灯晚上开灯附近某种虫子的数量天然就多这种偏差在数据采集阶段就固化了不是训练参数能修回来的。YOLOv8 的默认 loss 对各类别一视同仁样本少的类别贡献的梯度太小学不到有效特征。解决按顺序用三个手段。第一对尾类做复制粘贴增强把尾类小目标从原图裁出来随机贴到田间背景图上一张原图能生成好几张增强样本。第二采样时对尾类过采样训练时让尾类图片出现的频率高一些。第三如果尾类样本实在不足 50 张干脆把它从类别列表里删掉先保证剩下的类别训练是干净的。空类会让混淆矩阵多一行零反而干扰整体判断。4.4 小目标漏检几十像素的飞虱在 640 的输入里只剩几个点现象大目标比如棉铃虫能稳定框住稻飞虱这种在整张图里只有几十像素的小虫漏掉一半。把置信度降到 0.05 也不改善。原因训练输入尺寸按 640 设置时原图下采样 32 倍后小目标只剩 1 到 2 个特征点模型几乎没有信息可学。害虫检测的小目标问题比一般目标检测更尖锐因为虫体特征本来就弱背景又复杂。解决先把训练尺寸提到 1024 或 1280同时把 mosaic 增强保持开启让小目标有更多机会和其他上下文一起出现。推理时对原图做滑窗切块每块单独过模型再合并结果。切图是最后手段工程上已经有现成工具不需要自己写滑窗代码可以先从提高输入尺寸开始试。4.5 zip 解压报 CRC 校验失败先定位坏文件再决定重下还是修复现象解压到 60% 时报crc校验失败或文件头损坏但已经解出来的图片能正常打开。有的是某个特定文件反复报错有的每次报错的位置不一样。原因下载中断、网盘传输丢包、或者压缩工具本身异常。每次报错位置不一样大概率是文件本身不完整重新下载整个 zip 更省事固定同一个文件报错可能是那个文件的压缩块坏了。解决用 Python 定位到底是哪个文件坏了然后再决定下一步。import zipfile bad zipfile.ZipFile(害虫目标检测数据集.zip).testzip() print(损坏的文件:, bad)testzip()返回 None 代表全部完好返回文件名则定位到第一个坏文件。如果坏的是 annotations 或 labels 目录直接重新下载整个包别赌其他文件也完好。这个检查应该在解压后立刻做等到训练跑了一半再发现缺数据就亏大了。5. 用 YOLOv8 跑通第一轮基线最小命令、参数与结果验证5.1 把数据集组织成 YOLOv8 认得的目录结构data.yaml 是关键YOLOv8 不认 VOC 的JPEGImages/Annotations也不认train.txt列表。它要求的是一个干净的目录树加一个data.yaml。前面第 3 章的划分脚本已经把图片和标签按 train/val 分好现在只需要补一个配置文件。pest_dataset/ ├── data.yaml ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ ├── val/# pest.yaml # 路径建议写绝对路径避免相对路径在不同工作目录下翻车 path: /home/user/pest_dataset train: images/train val: images/val names: 0: rice_planthopper 1: corn_borer 2: cotton_bollworm注意names的编号顺序必须和标签 TXT 里的 class_id 严格一致。前面第 3 章转换脚本里的classes列表到这里变成了names映射中间任何一次调整都要同步。很多labels 转好的数据集到了这一步还是会翻车原因就是某次重新排序了类别忘了回头改 TXT。5.2 训练命令与 5 个必调参数别一上来就选最大模型第一轮基线训练用yolov8n.pt就够别上来直接上yolov8x。基线的目的是验证数据集质量不是刷最高精度。用最小模型跑通全流程确认数据没问题了再换大模型能省几个小时的试错时间。yolo detect train \ datapest.yaml \ modelyolov8n.pt \ epochs120 \ imgsz1024 \ batch16 \ device0 \ patience20 \ cacheTrue参数说明imgsz1024是害虫小目标场景的关键参数640 对小虫目标丢失细节patience20是早停轮数连续 20 个 epoch 验证集没提升就自动停避免干等cacheTrue把图片缓存进内存减少磁盘 IO 对训练速度的拖累但 16GB 内存以下建议关掉batch16在显存紧张时降到 8优先保证能跑完一轮。显存不够时不要急着换大卡先试三个调整imgsz降到 960batch降到 8cache关掉。这些改动能让一张 8GB 显卡跑通基线。训练完成后runs/detect/train/目录下会生成weights/best.pt和weights/last.pt后续验证和推理都用best.pt。5.3 验证结果怎么看PR 曲线、混淆矩阵和 results.png 的读法训练结束后先跑一遍验证得到每个类别的详细指标。yolo detect val \ datapest.yaml \ modelruns/detect/train/weights/best.pt结果输出在runs/detect/val/下重点看三个图。PR_curve.png的曲线下面积就是该类别的 AP曲线越靠近右上角越好某一类的曲线塌在左下角说明该类被系统性漏检或误检。confusion_matrix.png对角线越深越好纵向看能发现两个外观接近的害虫类别之间相互混淆。results.png里同时画了mAP50和mAP50-95如果mAP50-95比mAP50低 0.15 以上说明框定位不够准优先怀疑坐标转换时有没有系统性偏移。验证集上的分数说明不了全部问题。基线跑通以后拿一段不在训练集里的田间视频去实测看模型在真实场景下的表现。验证集 mAP 好看、真实场景翻车最常见的原因是训练集图片和真实场景的光照、角度、背景差异太大。这也是为什么我建议第一轮基线不要太纠结调参先把数据问题和场景差异暴露出来再说。6. 把几十像素的害虫从漏检里捞回来置信度、NMS 与切图的配合推理时降低置信度阈值能提升召回但代价是误检变多得靠 NMS 配合。对诱虫灯这类单一场景iou0.45的 NMS 阈值比默认的 0.7 更能合并重叠框因为同一只虫子经常被预测出多个高重叠候选框。yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcefield_videos/ \ conf0.05 \ iou0.45 \ imgsz1024conf0.05的目的不是追求那点漏检样本而是把模型对弱目标的预测释放出来交给 NMS 和后续业务逻辑去裁。如果业务目标是虫情计数漏检比误检更致命这组参数值得压到 0.03如果是喷洒定位精度优先conf回到 0.25。对真正难啃的小目标最后手段是切图推理。把原图按 512 像素滑窗切成块每块独立过模型再按坐标拼回等于用更大的有效分辨率看小目标mAP 通常能比整图推理高三到五个点。工程上直接用 SAHI 做不用自己造滑窗。伪标签迭代也是一个实用的扩充手段拿best.pt去预测一批无标签虫情图置信度卡在 0.75 以上的人工抽查一次再补进训练集但切记要抽查不然模型会把错检固化成“真理”。固件化的错标签一旦进入训练集后面清洗的代价远比重标这批数据贵别问我怎么知道的。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →