简介:本资源为蝴蝶目标检测数据集,面向从事计算机视觉、深度学习目标检测的开发者与研究者,可用于训练和验证蝴蝶识别模型。数据集包含1425张jpg图片,每张均配有对应的VOC格式xml标注与YOLO格式txt标注,标注类别统一为butterfly,使用labelImg工具完成,标注过程遵循准确框选边界、尽量覆盖全部目标、并进行一致性检查的原则。压缩包为rar格式,无需解压密码,共2000个文件,其中xml文件1425个、txt文件575个,整体约60.54MB,解压后含图片、xml标注和txt标注三个文件夹,可直接用对应软件查看标注情况。目前已有235人学习下载,适合需要快速获取规范标注数据、开展蝴蝶检测实验或作为教学案例的读者使用。
1. 蝴蝶数据集 VOC 与 YOLO 双格式:1425 张标注到底怎么用
拿到一个蝴蝶数据集,标着 VOC 和 YOLO 两种格式、1425 张图,第一反应不该是直接丢进模型开跑,而是先搞清楚这两套标注到底差在哪、为什么同一批图要存两份。VOC 格式用 XML 描述每个目标的边界框,坐标是绝对像素值,一个文件对应一张图;YOLO 格式用 txt 描述,每行一个目标,坐标是归一化后的中心点加宽高,一个文件同样对应一张图。1425 张这个量级,说大不大,说小也不小,刚好卡在「够训一个能看的检测器,但不够挥霍」的位置。做蝴蝶识别的人,多半是想做生态监测、科普应用或者细粒度分类的前置检测,这批数据能帮你把「蝴蝶在哪」这个问题先解决掉,至于「是哪一种蝴蝶」,那是后面分类模型的事。这一篇就按我实际处理这类数据集的顺序,把格式转换、目录组织、训练配置和踩过的坑一次讲清楚。
2. 先搞懂 VOC 和 YOLO 的坐标账:为什么不能直接混用
2.1 两种格式的坐标体系差异
VOC 的 XML 里,每个<object>下有<bndbox>,里面是xmin、ymin、xmax、ymax,这四个值是图片上的绝对像素坐标,左上角为原点。YOLO 的 txt 里,每行是class_id x_center y_center width height,后四个都是相对于图片宽高的归一化值,范围在 0 到 1 之间。很多人第一次转换时翻车,就是因为忘了归一化或者用错了宽高基准。举个例子,一张 640×480 的图,VOC 里有个框是xmin=100, ymin=80, xmax=300, ymax=280,转成 YOLO 就是:中心点 x = (100+300)/2/640 = 0.3125,中心点 y = (80+280)/2/480 = 0.375,宽 = (300-100)/640 = 0.3125,高 = (280-80)/480 = 0.4167。算错一步,框就飘了。
2.2 类别映射与文件命名的一致性
VOC 的 XML 里类别是字符串,比如<name>butterfly</name>;YOLO 的 txt 里类别是整数索引,从 0 开始。转换时必须维护一张类别到索引的映射表,而且这张表在训练和推理时要保持一致。另一个容易忽略的点是文件命名:VOC 的 XML 和图片同名不同后缀,YOLO 的 txt 也要和图片同名不同后缀。如果图片是img_001.jpg,那 XML 应该是img_001.xml,txt 应该是img_001.txt。我见过有人把 txt 命名成img_001.jpg.txt,训练时找不到标签,白跑一晚上。
2.3 一个可直接抄的转换脚本
下面这个脚本处理 VOC 到 YOLO 的转换,假设你的目录结构是annotations/放 XML,images/放图片,输出到labels/。脚本会读取每张图的实际宽高,避免用错基准。
import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射,按你的实际类别修改 CLASS_MAP = {"butterfly": 0} def voc_to_yolo(xml_path, img_path, out_txt_path): tree = ET.parse(xml_path) root = tree.getroot() # 用图片实际尺寸,不要用 XML 里可能缺失的 size with Image.open(img_path) as im: w, h = im.size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASS_MAP: continue cls_id = CLASS_MAP[name] bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 归一化并限制在 0~1,防止越界框 xc = max(0.0, min(1.0, (xmin + xmax) / 2.0 / w)) yc = max(0.0, min(1.0, (ymin + ymax) / 2.0 / h)) bw = max(0.0, min(1.0, (xmax - xmin) / w)) bh = max(0.0, min(1.0, (ymax - ymin) / h)) lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") with open(out_txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": img_dir = "images" xml_dir = "annotations" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(xml_dir): if not fname.endswith(".xml"): continue stem = os.path.splitext(fname)[0] xml_path = os.path.join(xml_dir, fname) img_path = os.path.join(img_dir, stem + ".jpg") if not os.path.exists(img_path): print(f"missing image for {stem}, skip") continue out_txt = os.path.join(out_dir, stem + ".txt") voc_to_yolo(xml_path, img_path, out_txt) print("done")逻辑说明:脚本先解析 XML,拿到每个 object 的类别和绝对坐标,再用 PIL 打开对应图片取真实宽高,做归一化。参数方面,CLASS_MAP必须和你的数据集类别完全一致,多一个空格都会导致漏标;max(0.0, min(1.0, ...))是保险,防止个别标注越界导致训练时 loss 爆炸。跑完后建议抽查几个 txt,用可视化脚本画框确认。
3. 1425 张蝴蝶图怎么切分与组织:目录结构决定训练能不能跑起来
3.1 训练集、验证集、测试集的划分比例
1425 张不算多,划分要谨慎。常见做法是训练集 80%、验证集 10%、测试集 10%,也就是大约 1140 / 142 / 143。但如果你的蝴蝶种类多、每类样本少,建议用分层抽样,保证每个类别在三个集合里都有代表。我一般会先统计每个类别的图片数,如果某个类别少于 20 张,就把它在训练集里的比例调高,验证集里至少留 2 到 3 张,否则验证指标会剧烈波动,你根本不知道模型是真差还是验证集太小。
3.2 目录结构长什么样
YOLO 训练时,数据集的目录结构直接影响你能不能少改配置。推荐这样组织:
butterfly_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml图片和标签分开存放,但文件名一一对应。data.yaml里写清楚路径和类别名:
path: ./butterfly_dataset train: images/train val: images/val test: images/test nc: 1 names: ["butterfly"]如果你的蝴蝶数据集有多个类别,nc改成实际数量,names按索引顺序列出。注意names的顺序必须和转换脚本里的CLASS_MAP一致,否则训练出来的模型会把类别搞反。
3.3 划分脚本与随机种子
手动拷贝文件容易出错,写个脚本按比例划分,固定随机种子保证可复现:
import os import random import shutil random.seed(42) img_dir = "images_all" lbl_dir = "labels_all" out_root = "butterfly_dataset" split_ratio = {"train": 0.8, "val": 0.1, "test": 0.1} files = [f for f in os.listdir(img_dir) if f.endswith(".jpg")] random.shuffle(files) n = len(files) n_train = int(n * split_ratio["train"]) n_val = int(n * split_ratio["val"]) splits = { "train": files[:n_train], "val": files[n_train:n_train + n_val], "test": files[n_train + n_val:] } for split, flist in splits.items(): img_out = os.path.join(out_root, "images", split) lbl_out = os.path.join(out_root, "labels", split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for f in flist: stem = os.path.splitext(f)[0] shutil.copy(os.path.join(img_dir, f), os.path.join(img_out, f)) lbl_src = os.path.join(lbl_dir, stem + ".txt") if os.path.exists(lbl_src): shutil.copy(lbl_src, os.path.join(lbl_out, stem + ".txt")) print("split done")参数说明:random.seed(42)保证每次划分结果一样,方便对比实验;split_ratio按你的实际需求调整,如果数据量更小,可以改成 0.9/0.05/0.05。跑完后检查三个目录的文件数是否和预期一致,特别是标签文件有没有漏拷。
4. 用 YOLO 训练蝴蝶检测器:从环境到第一轮结果的完整命令
4.1 环境安装与版本选择
YOLO 系列现在主流用 Ultralytics 的包,安装命令很简单:
pip install ultralytics如果你要用 GPU 训练,先确认 CUDA 和 PyTorch 版本匹配。我一般会先跑python -c "import torch; print(torch.cuda.is_available())",返回 True 再继续。蝴蝶数据集 1425 张,用 YOLOv8n 或 YOLOv8s 就够,n 版最快,s 版精度稍好。别一上来就上 x 版,数据量撑不住,过拟合风险高。
4.2 训练命令与关键参数
假设你的data.yaml放在butterfly_dataset/下,训练命令:
yolo detect train \ data=butterfly_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/butterfly \ name=exp1参数逐个说:epochs=100是上限,patience=20表示验证指标 20 轮不提升就早停,避免浪费时间;imgsz=640是输入尺寸,蝴蝶目标通常不大,640 够用,如果小目标多可以试 960,但显存要够;batch=16根据显存调,8G 显存跑 640 的 n 版没问题;lr0=0.01是初始学习率,默认值,数据量小可以降到 0.005。训练过程中看runs/butterfly/exp1/下的results.csv,重点看mAP50和mAP50-95是否在涨。
4.3 训练完怎么验证和推理
训练结束后,用验证集跑一次:
yolo detect val \ model=runs/butterfly/exp1/weights/best.pt \ data=butterfly_dataset/data.yaml \ imgsz=640推理单张图片:
yolo detect predict \ model=runs/butterfly/exp1/weights/best.pt \ source=butterfly_dataset/images/test \ conf=0.25 \ save=Trueconf=0.25是置信度阈值,蝴蝶背景复杂时可以调到 0.3 减少误检。结果图会保存在runs/detect/predict/下,打开看看框得准不准,有没有漏掉小蝴蝶。
5. 避坑与排查:蝴蝶数据集训练中最容易翻车的 5 个点
5.1 现象:训练 loss 不降,mAP 一直是 0
原因:标签路径不对,YOLO 找不到 txt 文件,或者 txt 内容为空。解决:检查labels/train/下是否有对应图片名的 txt,打开一个看里面有没有内容。如果 txt 是空的,说明转换脚本里类别映射没匹配上,回去检查CLASS_MAP的键和 XML 里的<name>是否完全一致,包括大小写和空格。
5.2 现象:训练报错「No labels found」
原因:data.yaml里的路径写错了,或者图片和标签的目录结构不符合 YOLO 的预期。解决:YOLO 默认会在图片路径的同级目录找labels文件夹,或者按data.yaml里的path拼接。我一般直接用绝对路径测试,确认没问题再改成相对路径。另外注意train和val后面跟的是目录,不是文件。
5.3 现象:验证集 mAP 很高,但测试集一塌糊涂
原因:划分数据时没有分层,验证集和测试集里某些类别样本太少,或者训练集和测试集有重复图片。解决:用脚本检查三个集合的文件名是否有交集,有交集就重新划分。另外统计每个类别的样本数,如果某个类别在测试集里只有一两张,指标没有参考价值。
5.4 现象:推理时框的位置明显偏移
原因:VOC 转 YOLO 时归一化用错了宽高,比如用了 XML 里的size而不是图片实际尺寸,或者宽高写反了。解决:重新跑转换脚本,确保用 PIL 读取图片真实尺寸。抽查几个框,用 OpenCV 画出来对比原图,偏移超过几个像素就要查。
5.5 现象:训练到一半显存爆了
原因:batch设太大,或者imgsz设太高。解决:先把batch降到 8 或 4,如果还爆就降imgsz到 416。另外检查有没有其他进程占着显存,nvidia-smi看一眼。蝴蝶数据集 1425 张,用 n 版模型 640 尺寸,8G 显存跑 batch=16 是安全的,如果你同时开了其他任务,降到 8。
6. 把 1425 张用到极致:小数据集的增强策略与验证技巧
1425 张对于检测任务来说属于小数据集,想让它发挥出超出量级的性能,增强策略和验证方法得讲究。我一般会在 YOLO 的默认增强基础上,针对蝴蝶的特点做调整。蝴蝶的形态、颜色、翅膀纹理是判别关键,所以颜色抖动可以适当加大,但几何变换要克制,因为蝴蝶的对称性一旦被破坏,模型可能学到错误的特征。在data.yaml同级建一个hyps.yaml,覆盖默认增强参数:
hsv_h: 0.02 hsv_s: 0.7 hsv_v: 0.5 degrees: 10.0 translate: 0.1 scale: 0.3 shear: 2.0 perspective: 0.0 flipud: 0.0 fliplr: 0.5 mosaic: 1.0 mixup: 0.1hsv_s调到 0.7 是让饱和度变化更丰富,模拟不同光照下的蝴蝶;degrees只给 10 度,避免过度旋转导致翅膀方向错乱;flipud设为 0,因为蝴蝶上下翻转不符合自然姿态;mosaic保持 1.0,小数据集靠拼接增加场景多样性;mixup给 0.1,轻微混合防止过拟合。训练时加上hyp=hyps.yaml即可。
验证阶段,除了看 mAP,我习惯用混淆矩阵和 PR 曲线判断模型到底学到了什么。YOLO 训练完会自动生成confusion_matrix.png和PR_curve.png,打开看:如果蝴蝶类别和背景的混淆很高,说明误检多,回去调conf阈值或者增加背景负样本;如果召回率上不去,说明漏检多,检查标注有没有漏框,或者把imgsz调大。另一个技巧是拿几张训练集里的图做推理,看模型是不是在「背答案」,如果训练集上完美、验证集上拉胯,那就是过拟合,减少epochs或者加大dropout(YOLO 里通过dropout参数控制,默认 0.0,可以设 0.1)。
最后说个我自己的习惯:每次处理新数据集,先拿 100 张跑一个 10 轮的快速实验,确认流程通了、指标在动,再上全量。1425 张全量跑一次 100 轮,n 版在单卡上大概几十分钟,但如果你前面格式转错了,这几十分钟就是白等。先小后大,先通后优,这个顺序能帮你省下不少后悔药。希望帮到你。
本文还有配套的精品资源,点击获取