简介:森林害虫目标检测数据集是一套面向林业害虫智能监测与农业生态保护的YOLO格式目标检测数据,覆盖松毛虫、松墨天牛、卷叶蛾三类常见且危害严重的害虫,适用于森林健康监测系统、无人机巡检、精准施药等AI模型的训练与验证。数据来源于实际场景采集,包含1715张JPEG图片,按训练集1199张、验证集257张、测试集259张划分,并附有准确的边界框和类别标签,可直接用于YOLO、Faster R-CNN等主流目标检测框架。资源包共2000个文件,以1715个txt标注文件、283张jpg图像为主,另含1个yaml配置文件与1个docx说明文档,压缩包大小约189.26MB。已有301人学习下载,适合林业院校学生、科研人员及算法工程师用于虫害识别研究、模型调优或教学实践,可显著减少数据采集与标注成本,为森林病虫害早期预警提供可靠的数据支撑。
1. 森林害虫目标检测数据集:先搞清楚你拿到的是什么
说实话,做目标检测这些年,我接手过的数据集没有一百也有八十份,其中“农业林业害虫检测”这类细分数据反而最考验工程功底。这套森林害虫目标检测数据集一共1715张JPEG图片,分成了训练1199张、验证257张、测试259张,覆盖松毛虫、松墨天牛、卷叶蛾三个类别,全部采用YOLO格式标注。看到“rf.”这种文件名后缀,有经验的人一眼就懂——这是从Roboflow导出的项目,图片对应同名txt标注文件。对要用YOLOv8或者YOLOv5自己训练模型的人来说,这份数据集几乎就是即拿即用的半成品;但如果你没先把它理清楚再喂给训练脚本,后面踩的坑绝对比省下的时间多。
2. YOLO标注格式拆解:从txt文件反推训练前的全部信息
2.1 标注文件里到底存了什么
很多人拿到数据集第一反应是直接解压开训练脚本,我一般会先随机打开一个txt文件,看看里面的数值到底规不规范。YOLO格式的每一行代表一个目标框,结构是:
class_id x_center y_center width height四个坐标值全部是归一化到0~1之间的小数,x_center和y_center是边界框中心点相对图片宽高的比例,width和height是边界框宽高相对图片宽高的比例。比如某个txt里有一行2 0.478125 0.361458 0.129687 0.143750,意思就是类别编号为2(对应卷叶蛾),框的中心在图片横向47.8%、纵向36.1%的位置,宽约为图片宽度的12.9%,高约为图片高度的14.4%。
打开这份数据集里的标注文件时,要确认的就是几点:第一,class_id必须在0~2范围内,超出就是标注工具写错了;第二,所有数值在0~1之间;第三,如果出现w或h为0的行,说明标注框退化成了一条线,训练时容易报错。
2.2 三个类别映射与文件命名规则
类别顺序直接影响训练结果。这套数据集里三个类别没有单独给出classes.txt文件,但通过Roboflow导出的习惯,默认顺序是松毛虫(0)、松墨天牛(1)、卷叶蛾(2)。我自己处理这类数据时,会先写一段确认类别顺序的代码,避免训练完才发现标签对不上:
import os label_dir = "labels/train" class_count = {} for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname), "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: print(f"异常标注: {fname} -> {line}") continue cls = int(parts[0]) class_count[cls] = class_count.get(cls, 0) + 1 print("0: 松毛虫, 1: 松墨天牛, 2: 卷叶蛾") for cls in sorted(class_count.keys()): print(f"类别 {cls} 框数量: {class_count[cls]}")这段代码的作用有两层:一是把每一类害虫的标注框总数统计出来,二是把不满足五个字段的行打印出来。我处理数据集时最怕的就是某个txt文件里混入了逗号分隔或只写了四个数字,这类脏数据不提前排查,训练到一半loss突然变NaN回都回不来。
图片和标注文件的命名是完全同名的,9_0_jpg.rf.02488166257d9f7cc9a279d5e73a6393.jpg对应的是9_0_jpg.rf.02488166257d9f7cc9a279d5e73a6393.txt,这也是目标检测数据集的基本约定。文件名中间的rf.后缀就是Roboflow的身份标识,后面的长哈希是导出时生成的唯一编号,不需要去解析它,只要保证配对时按basename匹配就行。但这里有一个隐患:如果数据集被反复导出、再经过网盘二次打包,偶尔会遇到图片在、txt丢失或者反过来的情况,这一步必须有代码兜住。
2.3 数据集目录结构重建
解压之后文件直接平铺在一个文件夹里,train/valid/test的划分信息其实已经藏在标识信息里了——Roboflow导出时通常会在文件名里有迹可循,但不保证每个版本都这样。我一般直接按照数量比例来分配,1715张按1199/257/259划分。下面这段脚本把平铺的文件整理成标准结构:
import os import random import shutil src_images = "forest_pest/images" src_labels = "forest_pest/labels" dest = "forest_pest_yolo" splits = {"train": 0.7, "valid": 0.15, "test": 0.15} all_images = [f for f in os.listdir(src_images) if f.endswith(".jpg")] random.seed(42) random.shuffle(all_images) split_points = {} count = 0 for split, ratio in splits.items(): num = int(len(all_images) * ratio) split_points[split] = all_images[count:count + num] count += num for split, images in split_points.items(): imgs_dir = os.path.join(dest, "images", split) lbls_dir = os.path.join(dest, "labels", split) os.makedirs(imgs_dir, exist_ok=True) os.makedirs(lbls_dir, exist_ok=True) for img_name in images: base = os.path.splitext(img_name)[0] src_img = os.path.join(src_images, img_name) src_lbl = os.path.join(src_labels, base + ".txt") shutil.copy(src_img, os.path.join(imgs_dir, img_name)) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(lbls_dir, base + ".txt")) else: print(f"警告: 缺少标注文件 {src_lbl}")这段脚本核心是random.seed(42),固定随机种子后每次执行划分结果都一致,这是可复现性的关键。比例上我用了7:1.5:1.5,比原始数据略有一点移动,如果你完全信任原始划分,可以直接用自带的分配。但要注意,如果用shutil.copy保留原始文件,磁盘要预留双倍空间;用shutil.move则省空间但不可回退,我一般先copy一轮,确认训练能跑通再删原始文件。
3. 训练前可视化校验:框是否画对,比模型结构更重要
3.1 在原始图像上画出标注框
数据整理完毕后,我几乎从不会直接进训练环节,除非已经完成可视化核查。标注格式再规范,也不代表框对应的位置和物种是对的。把txt里的数值还原回像素坐标,画在图上逐张看,是成本最低但收益最高的环节。
import cv2 import os img_dir = "forest_pest_yolo/images/train" lbl_dir = "forest_pest_yolo/labels/train" out_dir = "visual_check" os.makedirs(out_dir, exist_ok=True) class_names = ["松毛虫", "松墨天牛", "卷叶蛾"] colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] img_files = [f for f in os.listdir(img_dir) if f.endswith(".jpg")][:30] for img_file in img_files: img_path = os.path.join(img_dir, img_file) label_path = os.path.join(lbl_dir, os.path.splitext(img_file)[0] + ".txt") img = cv2.imread(img_path) h, w = img.shape[:2] if not os.path.exists(label_path): continue with open(label_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) x_center = float(parts[1]) * w y_center = float(parts[2]) * h box_w = float(parts[3]) * w box_h = float(parts[4]) * h x_min = int(x_center - box_w / 2) y_min = int(y_center - box_h / 2) x_max = int(x_center + box_w / 2) y_max = int(y_center + box_h / 2) cv2.rectangle(img, (x_min, y_min), (x_max, y_max), colors[cls_id], 2) cv2.putText(img, class_names[cls_id], (x_min, y_min - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[cls_id], 2) cv2.imwrite(os.path.join(out_dir, img_file), img)这个脚本的核心是把归一化坐标乘回图片宽高,画框之后输出到visual_check文件夹。你重点看三类情况:一是框是否包含完整虫体,二是框是否是“贴边”的(太贴近图像边缘导致目标不完整),三是同类目标大小差异是否悬殊。森林害虫场景里,卷叶蛾通常比松墨天牛小很多,如果小目标框覆盖不足,后面训练的模型对小虫子的召回率会很差。
3.2 用YOLO训练自己数据集时的高危配置项
可视化通过后,接下来就是训练参数。YOLOv8官方训练命令看起来很简单,但图片尺寸、batch size、epochs、预训练权重这几个参数之间是有连带关系的。森林害虫的虫体在监控画面里往往偏小,如果你直接按默认的imgsz=640去缩,小目标可能直接缩成几个像素,检测头根本学不到特征。
yolo detect train \ model=yolov8n.pt \ data=forest_pest.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4 \ patience=15 \ project=forest_pest_runs \ name=exp_base这里的data=forest_pest.yaml内容要严格按格式写,我放出来供参考:
path: /absolute/path/to/forest_pest_yolo train: images/train val: images/valid test: images/test nc: 3 names: ['松毛虫', '松墨天牛', '卷叶蛾']patience=15表示验证集mAP连续15轮没提升就早停,对小数据集非常有用,能省下大量无效训练时间。如果你显卡显存只有8G左右,batch=16配合yolov8n基本能稳住,换成yolov8s就要掉到8甚至更小。imgsz=640是速度和精度的折中点;如果你看了可视化结果发现小目标多,可以试试imgsz=896或者imgsz=1024,但训练时间会显著拉长。
3.3 数据集统计脚本与类别失衡判断
训练前还有一步:统计每张图片里的目标数量分布。这套数据集的三个类别样本数量肯定不是均匀的,松墨天牛是松材线虫的主要传播媒介,实际采集时会刻意多拍,卷叶蛾因为体型小、伪装强,数量天然少。我写了一个快速统计脚本:
import os import numpy as np label_root = "forest_pest_yolo/labels" stats = {c: [0, 0] for c in ["train", "valid", "test"]} # [图片数, 框数] for split in stats.keys(): lbl_dir = os.path.join(label_root, split) if not os.path.isdir(lbl_dir): continue for fname in os.listdir(lbl_dir): if not fname.endswith(".txt"): continue stats[split][0] += 1 with open(os.path.join(lbl_dir, fname), "r") as f: for line in f: stats[split][1] += 1 for split, (img_count, box_count) in stats.items(): print(f"{split}: 图片 {img_count} 张, 框 {box_count} 个, 平均每张 {box_count/max(img_count,1):.2f} 个")这个输出有两个用途。如果某张图有超过10个框,检查是否把虫卵或幼虫也框进去了,标注风格不统一会导致模型学到的“目标”长得不一样。如果某类框数占总框数比例低于15%,就该考虑做简单的类别加权,或者在训练时提高该类别的loss权重,否则最后的mAP会被大头类别拉高,看起来好看但小类别实际不可用。
4. 训练及效果验证:让模型告诉你数据集的真实水平
4.1 训练启动后的实时监控点
命令敲下去之后,不是等着看结果就行。YOLO训练过程会实时输出box_loss、cls_loss、dfl_loss和mAP50-95,前三者持续下降、后面持续上升是健康状态。森林害虫数据集图片分辨率本身不算高,实际场景中虫体占比又小,我盯得最多的就是mAP50和mAP50-95的差距。如果mAP50冲到0.9左右但mAP50-95只有0.4,说明框定位精度差,边界框没有精确贴合虫体;这种情况回过头去检查标注框,大概率是标注时框画大了,把树叶背景也包进去了。
训练时我喜欢把plots=True保持默认打开,这样每个epoch结束后,Ultralytics会自动在runs/detect/expX/下生成训练曲线和验证样例图。验证样例图能直观看到模型在验证集上的预测框对比真实框,哪些漏检、哪些误检,比只看数字直观得多。
4.2 从验证指标反推数据质量
我习惯在训练结束后做一次独立的指标汇总:
yolo detect val \ model=forest_pest_runs/exp_base/weights/best.pt \ data=forest_pest.yaml \ split=test \ imgsz=640跑完之后看三个数字:mAP50、mAP50-95、每类的mAP50。这套数据集里如果有某一类的mAP50明显低于其他两类,不要急着调模型结构,先确认是不是该类的标注框偏小。卷叶蛾的体型就是比松墨天牛小一圈,同一个640分辨率下小目标本来就吃亏。你可以在验证命令里试imgsz=1024,如果小类别的mAP明显上升,说明这个类别需要更高分辨率输入,而不是模型能力不够。
4.3 数据增强在森林害虫场景里的取舍
Ultralytics默认启用的增强管线里有随机的hsv变化、平移、缩放、翻转、马赛克等。森林害虫背景高度复杂,树枝、树干纹理和虫体颜色天然接近,马赛克增强虽然能提升泛化性,但也会让小目标的像素变得更小,导致“背景干扰”变成“目标混叠”。我一般会在数据集配置文件里这样控制:
augment: True mosaic: 0.5 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 translate: 0.1 scale: 0.5 fliplr: 0.5这里故意关掉degrees旋转增强,因为松墨天牛在树干上的姿态基本是纵向的,旋转90度模拟出来的样本不符合真实分布,反而会误导模型。mosaic: 0.5是压缩后的比例,样本量本来就只有1715张,mosaic开太低等于浪费有限的数据增强机会,开太高又会让小目标被其他图块挤压,0.5是我在这类场景下常用的折中值。
5. 常见问题排查:标注错位、样本失衡与模型误检
5.1 训练完loss正常但验证mAP徘徊在0.5以下
现象:训练过程平稳,loss曲线也下降,验证集mAP50始终在0.4~0.5之间上不去。
原因:最常遇到的是类别不平衡。森林害虫场景里,松毛虫可能占了总数的一半以上,而卷叶蛾只占5%,模型把所有精力都学在了松毛虫和松墨天牛上,卷叶蛾基本等于被忽略。另一个可能原因是图片分辨率太小,害虫在原始图中就只占几十个像素,640缩放下更小,小目标检测基本上靠猜。
解决:先跑一次统计脚本确认类别框数量,占比低于15%的类别考虑用复制粘贴式增强,把它贴到不同背景上补充样本。再把imgsz从640提到896,小类别的mAP通常能涨3~5个点。如果两个操作都不明显,再考虑换yolov8m或者yolov8l,但样本量再大也撑不起太深的网络,所以优先动数据不动模型。
5.2 验证集上虫体明显但模型就是检测不到
现象:把验证集图片放大看,虫体清晰可见、框也标准,但模型输出一个框都没有。
原因:大概率是训练集和验证集来自不同采集时段或者不同光照环境。森林里光线变化大,上午逆光和正午顶光照出来的虫子颜色完全不同,模型在训练集上见过的“松毛虫”都是黄绿色,验证集里换成灰棕色毛虫,特征对不上就检测不到。
解决:把训练集和验证集的图片混在一起重新划分,保证每个集合里都有不同光照、不同角度、不同背景的样本。划分前最好按图片采集批次做分层抽样,不要单纯按文件名排序切分,否则同类图片会扎堆在一个集合里。
5.3 训练过程中loss出现NaN
现象:前几个epoch一切正常,跑到十几轮时loss突然变成NaN,后面全部报废。
原因:数据集里有异常标注,比如某个txt文件坐标值大于1,或者w/h等于0。YOLO内部在计算IoU时遇到非法框,梯度就炸了。到这份数据集里对应的情况,是某个图片只有半个目标标注,框的中心点在图片外,坐标负值或者超过1。
解决:写一段校验脚本把所有txt逐行检查,坐标值不在0~1直接删除该行,文件里删完就剩0行就删掉整个标注文件。这一步在所有数据集上都适用,现在已经是我的固定套路了:
import os def clean_labels(label_dir): cleaned = 0 for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue path = os.path.join(label_dir, fname) valid_lines = [] with open(path, "r") as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) < 5: continue cls_id, xc, yc, w, h = parts if cls_id not in ["0", "1", "2"]: continue xc, yc, w, h = float(xc), float(yc), float(w), float(h) if not (0 <= xc <= 1 and 0 <= yc <= 1 and 0 <= w <= 1 and 0 <= h <= 1): continue if w == 0 or h == 0: continue valid_lines.append(line) if len(valid_lines) != len(lines): with open(path, "w") as f: f.writelines(valid_lines) cleaned += 1 print(f"修复了 {cleaned} 个标注文件") clean_labels("forest_pest_yolo/labels/train") clean_labels("forest_pest_yolo/labels/valid") clean_labels("forest_pest_yolo/labels/test")这段代码最后两层if是保命符:第一层确保五元组结构完整,第二层确保归一化坐标合法。坐标合法性检查看起来基础,但Roboflow导出偶尔会混入像素坐标或者没有归一化的标注行,这种行混进训练集就是一颗定时炸弹。
5.4 搬迁到另一台电脑后路径失效
现象:把整个项目文件夹拷贝到新机器上,训练命令报错路径不存在,或者数据集被重新划分后验证指标对不上。
原因:YOLO的data yaml里如果写的是绝对路径,换机器、换账号路径就全变了。另外,如果解压时没有保留原始目录结构,图片和标注文件的相对关系一乱,训练时加载数据就是一场灾难。
解决:yaml里能用相对路径就尽量写相对路径:
path: forest_pest_yolo train: images/train val: images/valid这样只要yaml文件放在数据集根目录的同级位置,无论项目搬到哪里,路径都能自动对上。另外,拷贝项目时建议带一个文件清单md5校验,防止网盘传输过程中个别jpg被损坏。这种“下载资源完好性校验”看似玄学,但真的能挡住不少返工。
5.5 解压zip后文件名乱码或配套缺失
现象:Windows自带的压缩文件夹中文文件名解压后乱码,或者解压到一半提示文件被占用,导致jpg和txt配对不齐。
原因:zip包里的文件名编码与本地系统不一致,再加上杀毒软件实时防护锁定文件句柄,就会丢文件。这份数据集的文件名是英文加哈希,理论上不会出现中文乱码,但网盘二次打包后谁也不能保证结构不变。
解决:优先用7-Zip或Bandizip解压,命令行一步到位更可控:
7z x forest_pest.zip -oforrest_pest -y解压后先用前面的配对脚本检查jpg和txt数量是否一致,再开始统计类别。资源下载类场景里有个习惯值得养成:解压后第一件事跑一遍校验和统计脚本,而不是直接双击开训练脚本。
6. 进阶验证技巧:用混淆矩阵和bad case反哺数据集
训练完成后,大多数人到mAP好看就收工了。但真正决定系统能不能部署到林场监控里的,不是平均值,是那些容易混淆的类别。松毛虫和卷叶蛾在某些姿态下外观接近,模型很容易在两者之间跳来跳去。
跑一次带混淆矩阵的验证:
yolo detect val \ model=forest_pest_runs/exp_base/weights/best.pt \ data=forest_pest.yaml \ split=test \ imgsz=640 \ plots=True跑完去runs/detect/val/下翻confusion_matrix.png和val_batch0_pred.jpg。如果松毛虫和卷叶蛾之间的误检条目偏高,就把那些预测错的图片挑出来集中看,找出它们共同的视觉特征。常见的一种情况是:松毛虫幼虫身上有毛刺纹理,卷叶蛾的翅膀花纹在低分辨率下也有类似纹理,模型就只能靠颜色去分。这时候正确的做法不是加大模型,而是回到标注层面,把卷叶蛾的框往虫体中心收得更紧,同时保证松毛虫的框不包含太多树叶杂背景,强制模型去学虫体本身而不是背景连带特征。
从那以后我每次拿到新数据集都强制走一遍“目录重建→可视化校验→统计清洗→训练→混淆矩阵回查”的闭环,宁可前面慢十分钟,也不让训练跑错方向返工两小时。这份森林害虫数据集底子不差,把上面的流程完整走一遍,你训练出来的模型在真实林场监控画面里的可用性会明显高一个台阶。希望帮到你。
本文还有配套的精品资源,点击获取