多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

水稻褐飞虱目标检测数据集实战:从体检到YOLOv8训练全流程

水稻褐飞虱目标检测数据集实战:从体检到YOLOv8训练全流程 简介菲律宾水稻褐飞虱成虫目标检测数据集是面向农业智能监测与植保研究的专业标注数据标注对象为褐飞虱成虫。全部图像采集自菲律宾真实稻田涵盖不同生长阶段与复杂光照、遮挡条件经农业专家校验可直接接入YOLOv5/YOLOv8等主流检测框架用于训练水稻害虫自动识别模型支撑无人机巡检、田间物联网预警及精准施药决策。资源包共2000个文件包含1458个txt格式标注文件、540张现场jpg图片以及yaml配置和说明文档压缩包体积56.36MB目录结构清晰便于按训练集、验证集、测试集941/304/213张直接开展模型训练与评估。已有197人学习下载。基于该数据集研究者可快速完成褐飞虱检测模型的搭建与验证农业院校也能将其用于害虫识别教学配合标准YOLO标注还可对比不同网络结构的检测精度推动AI植保应用落地。1. 先别急着解压褐飞虱成虫目标检测数据集解决的是“数虫难”不是“看图难”拿到一份标题叫“菲律宾水稻褐飞虱成虫目标检测数据集.zip”的资源最怕的不是没有 GPU而是上来就解压、看两页图、把文件夹塞给 YOLO 开训。褐飞虱是菲律宾水稻产区最难缠的迁飞性害虫成虫体长只有两三毫米田间测报常用白瓷盘拍打稻丛再数虫一个人一天处理不了几个点数到后面眼花。这份数据集的真正价值是把“肉眼数虫”转成“目标检测模型输出置信度和坐标”替代人工盘点。它适合做智慧农业算法、虫情测报灯图像识别、植保无人机影像分析的人如果你只是拿它练 YOLO 跑 demo 也不是不行但指望它直接出可用的计数模型得先把数据集读懂。2. 解压之后先做数据体检目录结构、标注格式、小目标分布一次看清楚2.1 拿到 zip 先看目录结构别急着开训练数据集这个 zip 是一个打包体里面到底是按 YOLO 的images/labels排还是按 VOC 的JPEGImages/Annotations排决定了你接下来是改目录还是改脚本。我一般会先用一条命令做初步体检unzip -q 菲律宾水稻褐飞虱成虫目标检测数据集.zip -d ./rice_bph cd ./rice_bph find . -maxdepth 2 -type d | sort-q是安静模式防止解压时刷屏-d指定输出目录我习惯把数据集放在项目下的data/或datasets/不要随手丢到系统盘的临时目录。第二行find -maxdepth 2只看前两层目录再深的文件先不关心。这一步能快速判断是干净利落的images/train与labels/train平级结构还是JPEGImages、Annotations和ImageSets这种 VOC 三件套。如果是 YOLO 风格继续验证每个子集图片和标注是否成对find images/train -type f | wc -l find labels/train -type f | wc -l两个数字如果差很多说明有的图片没有对应标注或者标注文件后缀不是.txt。这时候就要先补全或剔除别指望训练脚本自己跳过。还有一种更隐蔽的情况目录里既有train又有train2017命名不统一yaml 指到train时只用了部分数据训练完才发现验证集里混进了训练图片。数清楚目录、把划分依据写下来比调十轮超参数都值。2.2 标注格式和类别定义先打开一个标注文件植保类数据集不一定按 YOLO 格式交付可能是标注工具导出的 Pascal VOC XML也可能是 COCO JSON。先随机抽几个标注文件看一眼再决定后续动作import glob, random label_files glob.glob(./rice_bph/**/labels/**/*.txt, recursiveTrue) if not label_files: label_files glob.glob(./rice_bph/**/Annotations/**/*.xml, recursiveTrue) for path in random.sample(label_files, min(3, len(label_files))): with open(path, r, encodingutf-8, errorsignore) as f: lines f.readlines() print(path, lines:, len(lines)) print(.join(lines[:3]))这段代码先按 YOLO 的labels目录找.txt找不到再退回Annotations找.xml。打印前几行能直接看到类别 id 和坐标范围。如果一行是0 0.5123 0.6841 0.0432 0.0261说明是归一化坐标后面训练不需要再除一次宽高如果看到0 312 204 145 89这种大于 1 的整数那一定是像素坐标必须转换。常见的褐飞虱数据集在交付时可能不止一个类别。有些版本会把长翅型、短翅型、若虫都标进去有些只标成虫。标题既然写了“成虫”优先按单类处理标注文件里对应的那个 class_id 归为常数 0names只写一个。如果打开后发现一个 txt 里出现多个 class_id我建议先统计所有类别数量再决定是全部拿来训还是只筛adult不要盲目相信 README。很多 README 只写一句“水稻害虫”具体细节要靠自己看文件。2.3 用统计脚本看小目标占比和类别平衡褐飞虱成虫在 4K 相机或诱虫板照片里就是几个像素的小点这类数据集体检的重点不是类别平衡而是小目标数量占比。我一般会跑一段脚本把所有标注框换算回原图像素面积from PIL import Image import glob, os from collections import Counter import numpy as np box_areas [] class_counts Counter() for label_path in glob.glob(./rice_bph/**/labels/**/*.txt, recursiveTrue): img_path label_path.replace(labels, images).replace(.txt, .jpg) if not os.path.exists(img_path): img_path img_path.replace(.jpg, .png) if not os.path.exists(img_path): continue try: W, H Image.open(img_path).size except Exception: continue with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) w float(parts[3]) h float(parts[4]) box_px w * h * W * H box_areas.append(box_px) class_counts[cls] 1 box_areas np.array(box_areas) print(类别统计:, dict(class_counts)) print(框总数:, len(box_areas)) print(小于 32x32 的占比:, (box_areas 1024).mean()) print(小于 96x96 的占比:, (box_areas 9216).mean())逻辑是YOLO 标注里的w和h是相对图片宽高的比例乘回原图W、H才能得到真实像素面积这样用小目标口径判断才准。32x32 1024像素是 COCO 小目标通用阈值96x96以下算植保图像里常见的虫体尺寸。跑完后如果“小于 32x32 的占比”超过 30%你就必须认真考虑imgsz960甚至滑窗切图否则模型九成会漏检。这个体检结果也会在第 3 章直接用上不是白跑。3. 用 YOLOv8 在褐飞虱数据集上跑通训练data.yaml、超参数与指标3.1 把数据集整理成 YOLO 需要的目录结构和 data.yaml看完了标注格式接下来就是把数据给到训练脚本。常见做法是统一成 YOLO 风格目录mkdir -p datasets/images/{train,val} datasets/labels/{train,val}如果数据集没有现成划分我会先用脚本按 8:2 做分层抽样把图片分成 train 和 val并保证同一来源的图片不跨集。这一步在虫害数据上尤其重要因为不同照片之间可能存在严重的背景相关性这个问题在第 5 章会专门展开。然后写一个data.yamlpath: ./datasets train: images/train val: images/val nc: 1 names: 0: adult_bphpath是项目根目录train和val是相对路径YOLOv8 会自动拼成./datasets/images/train。类别只有成虫时nc1names数组只需要一个名字。这里最容易犯的错是标注里的 class_id 是 1但 yaml 只写了0: adult_bph。如果你的标注 class_id 从 1 开始最好统一改成从 0 开始不要靠占位符硬撑否则模型会拟合一个永远不该出现的背景类输出混乱。3.2 训练命令与超参数选择训练命令可以这样起yolo detect train datadata.yaml modelyolov8n.pt \ epochs100 imgsz640 batch16 device0 workers8 \ patience50 close_mosaic10 projectruns/train namebph_yolov8nmodelyolov8n.pt表示用 Nano 预训练权重做起点。如果你希望小目标更稳可以换yolov8s.pt或yolov8m.pt代价是显存和训练时间明显上涨。imgsz是送入网络的输入边长对褐飞虱这种小目标我建议先试 640再试 960不要一上来就 1280很多桌面级 GPU 连 1280 的 batch 1 都吃不消。batch16是显存允许下的理想值显存不够就降到 8 或 4保证单卡能跑起来比什么都重要。close_mosaic10表示最后 10 轮关闭 Mosaic 增强让模型在接近真实分布的数据上收敛。patience50是早停参数50 轮内验证指标没提升就自动停。植保数据集常常又偏又小死训 300 轮大概率过拟合早停反而能保住一个可用的best.pt。如果你担心后期抖动可以改成patience80但不要关掉早停。3.3 训练日志与验收集看哪些指标而不是只看 loss训练完用验证脚本拿指标from ultralytics import YOLO model YOLO(runs/train/bph_yolov8n/weights/best.pt) metrics model.val(datadata.yaml, splitval, imgsz640) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map)对褐飞虱这类小目标我主要看mAP50也就是 IoU 阈值 0.5 时的平均精度。mAP50-95会随着 IoU 提高迅速下降同一份数据可能从 0.78 跌到 0.31但这不一定是模型坏了而是小目标框本身的定位精度很难达到更高 IoU。如果你有多个类别还要逐类看 AP。单类情况下真正要盯的是验证集图片的预测结果而不是训练 loss。训练日志里 loss 只能告诉你“模型有没有在学”不能告诉你“学的是虫还是背景”。我一般会在验证集上跑一批预测并把图保存下来看一眼框和置信度是否合理。这一步比打印一百行指标都有用也直接引出最后一章的验证方法。4. 把 VOC/COCO 标注转成 YOLO转换脚本与褐飞虱小目标增强的取舍4.1 VOC XML 转 YOLO 的 Python 脚本如果数据集给的是 Pascal VOCimport xml.etree.ElementTree as ET import os, glob classes [adult, nymph] # 按数据集实际类别顺序调整 xml_files glob.glob(./rice_bph/**/Annotations/**/*.xml, recursiveTrue) for xml_path in xml_files: tree ET.parse(xml_path) root tree.getroot() size root.find(size) W int(size.find(width).text) H int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(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) xc (xmin xmax) / 2 / W yc (ymin ymax) / 2 / H bw (xmax - xmin) / W bh (ymax - ymin) / H lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_path xml_path.replace(/Annotations/, /labels/).replace(.xml, .txt) os.makedirs(os.path.dirname(out_path), exist_okTrue) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))逻辑说明VOC 的框是xmin,ymin,xmax,ymax绝对像素YOLO 需要中心点和宽高的归一化比例所以每个字段都要除以图片宽高。classes顺序必须和后续data.yaml的names一致如果转换时把adult和nymph调换了位置模型只会错位但不会报错。这种问题排查起来最耗时间所以我习惯在转换后抽 3 个 txt 回画到原图上检查别扫一眼坐标就觉得自己转换成功了。4.2 COCO JSON 转 YOLO 要注意类别 id 对齐与坐标边界如果交付的是 COCO JSON重点在类别 id 和坐标边界import json, os from collections import defaultdict with open(annotations.json, r, encodingutf-8) as f: coco json.load(f) cat_id_map {cat[id]: idx for idx, cat in enumerate(coco[categories])} img_id_map {img[id]: img for img in coco[images]} anns_by_img defaultdict(list) for ann in coco[annotations]: anns_by_img[ann[image_id]].append(ann) for img_id, anns in anns_by_img.items(): img img_id_map[img_id] W, H img[width], img[height] lines [] for ann in anns: if ann.get(iscrowd): continue cls_id cat_id_map[ann[category_id]] x, y, w, h ann[bbox] # COCO bbox 是像素左上角 宽高 xc (x w / 2) / W yc (y h / 2) / H nw w / W nh h / H if not (0 xc 1 and 0 yc 1): continue lines.append(f{cls_id} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) out_path os.path.join(labels, img[file_name].replace(.jpg, .txt)) os.makedirs(labels, exist_okTrue) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))先说cat_id_mapCOCO 的category_id可能是 1、2、3YOLO 必须从 0 开始连续编号这个映射步骤不能省。bbox在 COCO 中是[x, y, width, height]的绝对像素且x,y是左上角转 YOLO 时先算中心点。最后的边界判断能拦掉那些标注越界到图片外的脏框避免训练时出现负坐标或超 1 的坐标。虫子往往贴在图片边缘越界情况在田间数据集里很常见这个过滤不能省。4.3 增强取舍Mosaic、Copy-paste 和翻转怎么配输入统一后训练阶段的数据增强直接影响小目标能不能被看到。Ultralytics 默认开启 Mosaic会把四张图拼在一起拼接后的图再缩放到输入尺寸褐飞虱原本只有 20 像素缩放后往往就消失了。所以对小目标占比高的数据集我一般会改mosaic0.5并把close_mosaic20让最后 20 轮完全关闭 Mosaic。copy_paste0.3可以把同一个标注图中的虫复制到别的背景上比 Mosaic 温和适合增加虫体密度。翻转方面fliplr0.5放心用flipud对水稻田背景来说影响不大褐飞虱没有固定朝向翻转基本不会破坏语义。hsv_h0.015, hsv_s0.5, hsv_v0.4模拟不同光照虫害图像常有强逆光和阴影保持默认就够。不要为了凑增强把所有开关都打开尤其是degrees180这种强旋转小目标框很容易转出图像外标签被丢弃后模型等于没学过那些样本。改完增强后怎么知道有没有效果最简单的办法是抽一张训练中间图看增强结果。另外第 6 章会讲一个更接近业务的验证方法。5. 训练褐飞虱检测最容易踩的 5 个坑现象、原因、解决办法5.1 “No labels found” 不是环境问题是目录和 class_id 问题现象训练一开始就出现WARNING: No labels found in ...或者打印出的有效样本数是 0训练直接退出。原因标注文件和图片没有按images与labels同级组织或者data.yaml的 train 路径写到了images/train但标注文件放在更深的子目录还有人把 Windows 路径的反斜杠带进了 yamlYOLO 解析不出来。解决先把目录收敛成datasets/images/train与datasets/labels/train两级再用find ... wc -l确认图片数和 txt 数一致。如果图片存在但标注缺失就用 4.1 的转换脚本重新导如果标注存在但没被识别打开一个 txt 确认后缀确实是.txt不是空文件也不是 UTF-8 带 BOM。这个排查流程五分钟能走完不要一上来就重装环境。5.2 训练不掉点但验证集一个框都画不出来先检查坐标是否归一化现象loss 下降正常mAP 显示 0 或接近 0可视化预测完全没框。原因把整数像素坐标当成 YOLO 归一化坐标直接训练这是最常见的翻车。另一个原因是标注文件里 class_id 超过ncYOLO 计算损失时行为异常但训练脚本不会立刻终止结果验证集乱成一团。解决用 2.2 的抽样脚本打印一行标注看数值是否都在 0~1 之间再检查 class_id 最大值必须小于nc。如果坐标是像素值回到 4.1 脚本除以图片宽高如果 class_id 从 1 开始建一个cat_id_map映射重新生成。索引错位的现象从 loss 上根本看不出来必须做一次可视化检查。5.3 小目标大量漏检不是模型差是输入尺寸不匹配现象mAP50 看起来有 0.6但实际数虫时漏了快一半把验证集图片放大很多没框的位置明显有虫。原因褐飞虱成虫在原始 3000×4000 的图里只占十几像素imgsz640会把图片缩到约六分之一虫子的特征几乎被抹平。解决先把imgsz提到 960 或 1280 重新验证显存不够就把 batch 降到 4 或 2。如果 1280 仍然不够把原图滑窗切成 640 的小块用切出来的子图训练。滑窗切图要保留 20%~30% 的重叠避免虫体被切两半切图后的标签要跟着平移坐标这一步必须写脚本统一处理不能手工改。切图跑的慢但确实能让小目标检测改头换面。5.4 数据增强让标签“消失”Mosaic 和强旋转对小框不友好现象训练曲线看着正常但统计出的实际有效标注框数在下降或验证集上预测框面积明显大于真实虫框。原因Mosaic 拼接和随机旋转会把原本就小的框裁出图像边界增强管线默认丢弃越界框。小目标占比越高丢得越厉害模型最后只能学到“中等大小的虫子”。解决把mosaic调到 0.5 以下degrees控制在 10° 以内scale不要设到 0.9 以下避免过度缩小后小目标彻底消失。同时把close_mosaic给到 20让最后阶段不在这种“丢框”环境下收敛。改完用增强预处理图把标签画上去看一遍比对着配置文件猜有用。5.5 验证集分数高、田间新照片泛化差数据划分泄漏不是模型问题现象内部验证集 mAP 0.8一到另一块试验田拍的照片就基本失效。原因数据集划分时按文件随机划分同一株稻丛、同一批黄板的连续照片被同时分进训练和验证模型实际记住了背景比如特定水箱、特定诱虫板纹理而不是褐飞虱本身的特征。解决把划分维度从“图片”改成“拍摄批次/地点/时间”保证同一个场景的照片不会既在训练又在验证。对菲律宾这类田间数据集常见做法是按采集日期或田块分组用GroupShuffleSplit做划分而不是默认的随机切分。这个坑不解决后面一切调参都是在给背景做拟合。6. 模型能不能真用可视化验证与计数一致性检验6.1 先跑一次 predict看“框”而不是看“指标”训练完成后先别急着部署跑一次yolo predict modelruns/train/bph_yolov8n/weights/best.pt sourcedatasets/images/val \ imgsz640 conf0.25 save_txt save_confsave_txt会把预测标签写到runs/predict/labelssave_conf会带上置信度。然后对照原图数一遍哪些框对了哪些框错了哪些没框上。小目标检测里 mAP 和实际计数经常脱节我习惯把置信度阈值调到 0.35 后用模型输出的框总数和人工标注数算平均绝对误差误差在 10%~20% 以内才敢说这个模型能辅助盘点。6.2 保留一份“数据集体检报告”作为项目资产我会在项目目录里放一个dataset_report.md记录图片数、框数、小目标占比、类别数和划分依据。下次换模型或换采集环境先看报告再决定要不要重训而不是直接迷信上一轮的参数。这套把“体检 → 训练 → 格式转换 → 避坑 → 验证”串起来的流程是我从翻车里总结出来的固定动作。说句实在话我第一次拿这类植保数据集训练就是没检查坐标是否归一化白跑了两天。后来把体检脚本固定下来才算真正会用数据集。希望帮到你。本文还有配套的精品资源点击获取
返回列表