简介:这是一份面向目标检测初学者与算法验证人员的熊猫单类别数据集,采用Pascal VOC与YOLO双格式标注,可直接用于YOLO、Faster R-CNN等主流框架的训练与测试,省去格式转换的繁琐步骤。压缩包共332个文件,包含110张jpg原图、110个VOC格式xml标注文件以及110个yolo格式txt标注文件,另有少量说明性文本,整体约36.34MB,体积轻便,便于快速下载与本地部署。所有图片均由labelImg工具人工画框标注,类别统一为Panda,共114个标注框,标注准确合理,适合小样本实验、模型微调与教学演示。目前已有189人学习下载,可作为入门目标检测的练手素材,帮助读者快速跑通数据加载、训练与推理流程,验证算法在单类别场景下的表现。
1. 110 张熊猫图、114 个框:这份双格式数据集到底能拿来干什么
如果你正在找一个体量小、标注干净、拿来就能跑通训练流程的动物检测数据集,这份 110 张熊猫图片的包值得先看一眼。它同时给了 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注,类别只有一个:Panda,总框数 114。也就是说平均每张图 1 个出头的目标,画面里基本是单只或少量熊猫,背景干扰不算复杂。
这种规模的数据集,指望它训出一个能上生产的高精度模型不现实,但它的价值在别处:验证你的训练管线通不通、类别映射对不对、数据增强有没有把框带偏、评估指标能不能正常收敛。很多人第一次跑 YOLOv8 训练自己的数据集,翻车往往不是模型问题,而是标注路径、类别索引、图片和标签对不上这些琐事。这份包正好能当一块干净的试金石。
适合谁:刚接触目标检测、想跑通端到端流程的新手;需要快速搭一个熊猫检测 demo 做演示的开发者;以及想拿小数据集做数据增强、过拟合实验、标注格式转换练手的人。下面从格式结构讲到训练落地,再讲我踩过的坑。
2. 拆开压缩包:VOC 与 YOLO 双格式的目录结构和字段含义
2.1 两套标注的物理组织方式
拿到包先别急着写训练脚本,把目录结构看清楚能省掉后面一半的调试时间。这类双格式数据集常见的组织是图片放一个目录,VOC 的 xml 放一个目录,YOLO 的 txt 放一个目录,三者文件名主干一致,只是扩展名不同。图片命名形如firc_Panda_40.jpg,对应的标注就是firc_Panda_40.xml和firc_Panda_40.txt。
VOC 的 xml 是自描述结构,打开能看到folder、filename、size(宽高通道)、object节点,每个 object 里有name、pose、truncated、difficult和bndbox(xmin、ymin、xmax、ymax)。YOLO 的 txt 则是纯数值,每行一个目标,格式是class_id x_center y_center width height,后四个都是相对图片宽高的归一化值,范围 0 到 1。
这里有个容易忽略的点:VOC 的框是绝对像素坐标,YOLO 是归一化中心点坐标,两者换算时如果图片尺寸读错,框会整体偏移。xml 里的size节点就是用来做这个换算的,别用 PIL 重新读一遍图片尺寸去替代,除非你确认两者一致。
2.2 类别索引与框数核对
这个数据集只有一个类别 Panda,VOC 里name字段全是Panda,YOLO 里 class_id 全是 0。单类别的好处是不用纠结类别映射表,坏处是很多人会忘记写data.yaml里的names,导致训练时类别名显示成数字。
框数核对是上手第一步。110 张图对应 110 个 xml、110 个 txt,总框数 114,说明有 4 张图里不止一个目标。写个脚本统计一下每张图的框数分布,能提前发现有没有空标注文件或者异常多的框。
import os import xml.etree.ElementTree as ET xml_dir = "annotations" # VOC xml 所在目录 box_counts = {} total = 0 for fname in os.listdir(xml_dir): if not fname.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, fname)) root = tree.getroot() objs = root.findall("object") box_counts[fname] = len(objs) total += len(objs) print("图片数:", len(box_counts)) print("总框数:", total) # 打印框数大于 1 的文件,确认多目标图 for k, v in box_counts.items(): if v > 1: print(k, v)这段脚本做两件事:统计总框数是否等于 114,以及找出多目标图。如果统计出来对不上,说明有 xml 解析失败或者文件缺失,先解决再往下走。参数上xml_dir换成你解压后的实际路径即可,ET.parse对格式错误的 xml 会直接抛异常,正好能帮你定位坏文件。
2.3 为什么同时保留两套格式
VOC 格式生态老,labelImg 默认就导出它,很多传统检测框架和可视化工具吃这套;YOLO 格式轻量,训练时读取快,YOLOv5/v8 系列直接认。数据集同时给两套,等于把格式转换这一步替你做了。但要注意,两套标注必须指向同一批图片、同一套框,如果转换脚本有 bug,可能出现 xml 和 txt 框位置不一致的情况。上手时抽几张图把两边的框画出来叠一起看,是最省事的验证方式。
3. 从零跑通 YOLOv8 训练:data.yaml 配置与目录改造
3.1 把数据集整理成 YOLO 训练目录
YOLO 官方推荐的结构是 images 和 labels 两个大目录,各自下面再分 train 和 val。原始包通常不是这个结构,需要自己拆。110 张图按 8:2 分,训练 88 张、验证 22 张比较合理,小数据集别再切 test,否则验证集太小指标抖动大。
# 假设解压后图片在 images/,yolo txt 在 labels/ mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val # 用 python 做随机划分更可控,这里给 bash 思路 # 实际建议用脚本按文件名主干配对,避免图片和标签错位配对这件事必须按文件名主干来,不能图片和标签各自随机打乱,否则图和框就对不上了。常见做法是列出所有 jpg,取主干,检查同名 txt 是否存在,存在才纳入划分列表,然后对列表做 shuffle 再切分。
import os, random, shutil img_dir = "images" lbl_dir = "labels" out_img = "dataset/images" out_lbl = "dataset/labels" random.seed(42) pairs = [] for f in os.listdir(img_dir): if f.endswith(".jpg"): stem = os.path.splitext(f)[0] if os.path.exists(os.path.join(lbl_dir, stem + ".txt")): pairs.append(stem) random.shuffle(pairs) split = int(len(pairs) * 0.8) train, val = pairs[:split], pairs[split:] for subset, items in [("train", train), ("val", val)]: os.makedirs(f"{out_img}/{subset}", exist_ok=True) os.makedirs(f"{out_lbl}/{subset}", exist_ok=True) for stem in items: shutil.copy(f"{img_dir}/{stem}.jpg", f"{out_img}/{subset}/{stem}.jpg") shutil.copy(f"{lbl_dir}/{stem}.txt", f"{out_lbl}/{subset}/{stem}.txt") print("train:", len(train), "val:", len(val))random.seed(42)固定随机种子,保证每次划分一致,方便复现。先配对再打乱是关键,直接对图片列表 shuffle 而标签单独处理,是新手最常见的错位来源。跑完打印 train 和 val 数量,加起来应该等于 110。
3.2 data.yaml 的字段与路径写法
YOLOv8 训练靠一个 yaml 描述数据位置和类别。字段不多,但路径写错是最常见的报错来源。
path: /abs/path/to/dataset train: images/train val: images/val nc: 1 names: ["Panda"]path是数据集根目录,建议写绝对路径,相对路径在不同工作目录下启动训练时容易找不到。train和val相对path解析,指向图片目录,YOLO 会自动把images替换成labels去找同名 txt。nc是类别数,这里是 1。names是类别名列表,顺序必须和 txt 里的 class_id 对应,Panda 是 0 就放第一个。
提示:如果训练时报 “No labels found”,九成是 labels 目录结构和 images 不镜像,或者 txt 文件名和图片主干不一致。先手动确认
dataset/labels/train/下有对应 txt。
3.3 启动训练与关键参数
环境装好后,一条命令就能起训。小数据集不需要大模型,yolov8n.pt足够,训练轮数可以设大一点观察过拟合。
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.01 \ patience=20 \ project=runs/panda \ name=exp1imgsz=640是输入分辨率,小数据集不建议开太大,显存吃紧就降到 416。batch=8在 110 张图上够用,显存小可以降到 4。lr0=0.01是初始学习率,YOLOv8 默认值,小数据集如果 loss 震荡可以降到 0.005。patience=20表示 20 轮验证指标不提升就早停,避免在小数据上白跑。project和name决定权重和日志输出位置。
训练过程中重点看mAP50和mAP50-95两个指标。110 张图、单类别,正常收敛后 mAP50 能到较高水平,但如果验证集只有 22 张,指标波动会比较大,别因为某一轮掉了就慌。真正要警惕的是 loss 一直不降,那通常是标注或路径问题,不是模型问题。
4. 避坑与排查:小数据集训练最容易翻车的五个地方
4.1 图片和标签错位,模型学了个寂寞
现象:训练 loss 能降,但验证时框位置完全不对,或者 mAP 一直很低。 原因:划分数据时图片和标签分别打乱,或者复制时只复制了图片没复制对应标签,导致图和框对不上。 解决:按文件名主干配对后再统一 shuffle,复制时成对操作。训练前抽 3 到 5 张图,用脚本把 YOLO 框画回图片上肉眼确认,这一步花两分钟能省几小时排查。
4.2 归一化坐标算错,框跑到图片外
现象:可视化时框大面积超出图片边界,或者全部挤在左上角。 原因:VOC 转 YOLO 时用了错误的图片宽高,或者把绝对坐标当成了归一化坐标直接写进 txt。 解决:转换时严格用 xml 里size节点的 width 和 height 做分母,中心点坐标是(xmin+xmax)/2/width,宽高是(xmax-xmin)/width。转换完写个校验脚本,检查所有值是否落在 0 到 1 之间,超出就报错。
4.3 类别索引从 1 开始,训练直接报错
现象:训练启动时报 “Label class x exceeds nc” 或类似越界错误。 原因:有些标注工具或转换脚本把类别从 1 开始编号,而 YOLO 要求 class_id 从 0 开始。 解决:检查 txt 第一列的最大值,单类别数据集应该全是 0。如果发现 1,批量减 1 即可。写个脚本扫一遍所有 txt 的第一列,确认取值范围。
4.4 验证集太小,指标忽高忽低
现象:mAP 在相邻两轮之间大幅跳动,一会儿 0.9 一会儿 0.6。 原因:110 张图切 8:2 后验证集只有 22 张,单张图的预测好坏对整体指标影响很大。 解决:要么增大验证集比例到 7:3,要么用交叉验证的思路多切几次看平均表现。小数据集的指标本来就不稳,别拿单次结果下结论,重点看趋势是否收敛。
4.5 过拟合来得比想象中快
现象:训练集 loss 持续下降,验证集 loss 先降后升,mAP 到某个点后不再提升。 原因:110 张图对检测模型来说太少,模型很快记住训练样本。 解决:开数据增强,YOLOv8 默认带 mosaic、翻转、缩放,可以再加degrees、translate等参数。同时用早停patience控制,别硬跑到几百轮。如果只是验证流程,过拟合本身不影响你确认管线是否跑通。
5. 进阶玩法:用这 110 张图验证增强策略与格式互转
小数据集真正的价值不在训出多强的模型,而在当试验台。我一般会拿它做两件事:验证数据增强有没有把框带偏,以及练手 VOC 和 YOLO 的互转。
先说增强验证。YOLOv8 训练时会把增强后的图存到runs/.../train_batch*.jpg,直接打开看框有没有跟着目标走。如果发现翻转后框没翻、缩放后框偏移,说明增强管线有问题。这一步用 110 张图跑几十轮就能看出来,比在大数据集上浪费算力划算得多。
再说格式互转。虽然这个包两套格式都给了,但实际项目里经常遇到只有 VOC 或只有 YOLO 的情况,转换脚本得自己会写。下面是把 VOC 转 YOLO 的核心逻辑,反过来转就是逆运算。
import os import xml.etree.ElementTree as ET classes = ["Panda"] xml_dir = "annotations" out_dir = "labels_yolo" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(xml_dir): if not fname.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, fname)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.findall("object"): cls = obj.find("name").text if cls not in classes: continue cid = classes.index(cls) 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) xc = (xmin + xmax) / 2 / w yc = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cid} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") stem = os.path.splitext(fname)[0] with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))classes列表的顺序决定 class_id,必须和训练时的names一致。坐标保留 6 位小数足够,YOLO 读取时会再解析成浮点。转换完拿几张图把新 txt 画出来和原 xml 对比,确认框重合,这是最直接的验证。
| 检查项 | 合格标准 | 常见异常 |
|---|---|---|
| 图片与标签配对 | 主干名一一对应 | 有图无标签 |
| 坐标范围 | 全部在 0 到 1 之间 | 出现负数或大于 1 |
| 类别索引 | 单类别全为 0 | 出现 1 或其他值 |
| 框数统计 | 与 xml 一致 | 转换丢框 |
最后说个习惯。从那以后我每次拿到新数据集,不管多小,都强制先跑一遍「配对检查 + 坐标范围检查 + 抽图可视化」这三步,再动训练脚本。这三步加起来不到五分钟,但能挡掉后面绝大多数玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取