简介:这份空中飞鸟检测数据集面向从事目标检测的开发者、学生与科研人员,适用于机场飞鸟入侵预警、飞鸟驱赶、鸟类识别与计数等实际项目,也可直接用于课程作业、竞赛与算法验证。数据集共3362张图像,标注精准、背景丰富、鸟种多样,目标大小与分布均衡,算法拟合效果较好。压缩包内文件总数达10088个,以jpg原图、xml(VOC)、txt(YOLO)和json三类标签文件为主,三种格式可满足不同检测框架的直接读取需求,整体约548.94MB,目录组织清晰,便于按格式快速检索与训练。目前已有1209人学习下载,数据均来自作者实际项目与实验demo,质量有保障。读者可据此省去繁琐的标注与格式转换环节,快速搭建训练与评估流程,并借助多格式标签灵活适配YOLO、Faster R-CNN等主流算法,为飞鸟检测相关课题提供可靠的数据基础。
1. 空中飞鸟检测数据集三种标签格式:为什么 3362 张图能省掉两周标注
拿到一个空中飞鸟检测数据集,第一眼别急着看图片好不好看,先看标签格式全不全。3362 张图配上 VOC 的 xml、YOLO 的 txt、还有 json 三种标签,这件事本身就值钱——它意味着你从数据清洗到模型训练之间,少了一道最耗人的格式转换工序。做过检测项目的人都懂,标注格式不统一是血泪经验里排名前三的翻车点:VOC 的坐标是左上角加右下角的绝对值,YOLO 要的是归一化后的中心点加宽高,json 又常常是 COCO 那套 id 映射结构,三者之间转来转去,稍不留神框就偏了。
这个数据集解决的就是这件事。它面向的是需要做空中飞鸟检测的从业者——可能是机场周边鸟情监测,可能是风电场的鸟类碰撞预警,也可能是生态调查里的自动计数。这些人不一定缺算力,缺的是拿来就能进训练循环的干净标签。3362 张不算大,但三种格式齐备,等于把格式转换的坑提前填了。你拿到手可以直接喂 YOLO,也可以转成 COCO 跑 Detectron2,还能用 xml 做可视化核对。接下来我把这套数据从目录结构、格式校验、训练配置到踩坑排查,按我实际跑过的顺序讲一遍。
2. 三种标签格式的目录结构与字段对照:先看懂再动手
2.1 VOC xml、YOLO txt、json 各自长什么样
VOC 格式的 xml 是每张图一个文件,根节点是 annotation,里面 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 之间的小数。json 常见的是 COCO 风格,一个文件装下所有图,images 数组存文件名和尺寸,annotations 数组存 bbox 的[x, y, width, height]绝对像素值加 image_id 和 category_id,categories 数组存类别名。
这三种格式描述的是同一批框,但坐标系和存储粒度完全不同。VOC 和 YOLO 是一图一文件,json 是全集一文件。VOC 用绝对坐标,YOLO 用归一化中心点,json 用绝对左上角加宽高。理解这一点,后面所有转换和校验都是围绕这几个差异展开的。
2.2 目录组织与文件命名对齐
一个规整的空中飞鸟检测数据集目录通常长这样,我按常见做法列出来,你拿到手先核对:
bird_dataset/ ├── images/ │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── annotations_xml/ │ ├── 000001.xml │ └── ... ├── labels_yolo/ │ ├── 000001.txt │ └── ... ├── annotations_coco.json └── classes.txt关键是对齐:images 下的000001.jpg,必须能在 annotations_xml 找到000001.xml,在 labels_yolo 找到000001.txt。json 里的 file_name 也要能对上。我一般先跑一段脚本统计三者的文件名集合是否一致,差集不为空就说明有图缺标签或者标签缺图,这种样本进训练就是噪声。
import os img_dir = "bird_dataset/images" xml_dir = "bird_dataset/annotations_xml" txt_dir = "bird_dataset/labels_yolo" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir)} txts = {os.path.splitext(f)[0] for f in os.listdir(txt_dir)} print("图缺xml:", imgs - xmls) print("xml缺图:", xmls - imgs) print("图缺txt:", imgs - txts) print("txt缺图:", txts - imgs)这段脚本做的是集合差运算。os.path.splitext去掉扩展名只留主名,三个集合两两对比就能定位缺失。参数上没什么可调的,路径改成你自己的即可。跑完如果差集为空,说明命名对齐没问题,可以进下一步格式校验。如果有差集,先别急着删,人工看一眼是不是命名规则不统一,比如有的用下划线有的用短横线。
2.3 类别映射:classes.txt 与 json 的 categories 必须一致
YOLO 的 class_id 是从 0 开始的整数,json 的 category_id 有时从 1 开始,VOC 里存的是类别名字符串。这三者必须有一张统一的映射表。classes.txt 一般一行一个类别名,行号减一就是 YOLO 的 class_id。我见过太多翻车案例:训练时类别对不上,模型把鸟学成了背景,mAP 一直是零,查半天才发现 json 的 category_id 从 1 开始而 YOLO 从 0 开始,差了一位。
with open("bird_dataset/classes.txt") as f: classes = [line.strip() for line in f if line.strip()] print("类别数:", len(classes)) for i, c in enumerate(classes): print(f"YOLO class_id={i} -> {c}")这段把 classes.txt 读成列表并打印索引映射。参数上注意空行要过滤掉,否则索引会错位。如果 json 的 categories 里 id 不是从 0 连续递增,你需要单独建一张 json_id 到 yolo_id 的字典,转换时查表替换,不能想当然认为它们一样。
3. 用脚本校验三种标签是否自洽:坐标转换与可视化核对
3.1 VOC 与 YOLO 坐标互转公式
VOC 的 bndbox 是 xmin、ymin、xmax、ymax,转 YOLO 需要先算中心点和宽高,再除以图片宽高归一化:
def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h return x_center, y_center, width, height反过来 YOLO 转 VOC:
def yolo_to_voc(x_center, y_center, width, height, img_w, img_h): xmin = (x_center - width / 2.0) * img_w ymin = (y_center - height / 2.0) * img_h xmax = (x_center + width / 2.0) * img_w ymax = (y_center + height / 2.0) * img_h return xmin, ymin, xmax, ymax这两个函数是整个校验的核心。参数里 img_w 和 img_h 必须从图片实际尺寸读,不能从 xml 的 size 节点直接信,因为有些标注工具写的 size 和真实图片不一致,这是常见坑。我一般用 PIL 或 OpenCV 重新读一遍图片尺寸,和 xml 里的 size 对比,不一致就以图片为准。
3.2 批量校验脚本:找出越界框和零面积框
归一化坐标有个硬约束:x_center、y_center、width、height 都必须在 0 到 1 之间,且 x_center 加减 width 的一半不能越界。越界框进训练会让损失函数算出异常梯度,轻则 loss 震荡,重则 BN 崩溃。下面这段批量扫一遍:
import os def check_yolo_label(txt_path): bad = [] with open(txt_path) as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad.append((line_no, "字段数不对")) continue cls, xc, yc, w, h = parts xc, yc, w, h = map(float, (xc, yc, w, h)) if not (0 <= xc <= 1 and 0 <= yc <= 1): bad.append((line_no, "中心点越界")) if not (0 < w <= 1 and 0 < h <= 1): bad.append((line_no, "宽高非法")) if xc - w/2 < -1e-6 or xc + w/2 > 1+1e-6: bad.append((line_no, "横向越界")) if yc - h/2 < -1e-6 or yc + h/2 > 1+1e-6: bad.append((line_no, "纵向越界")) return bad for f in os.listdir("bird_dataset/labels_yolo"): if f.endswith(".txt"): issues = check_yolo_label(os.path.join("bird_dataset/labels_yolo", f)) if issues: print(f, issues)这段逐行解析 YOLO txt,检查字段数、中心点范围、宽高范围、以及框是否越出图片边界。容差用 1e-6 是为了放过浮点误差。跑完如果输出一堆文件,说明标签质量有问题,需要回到标注环节修。如果输出为空,恭喜,这批标签可以直接用。
3.3 可视化核对:把框画回图上
脚本校验只能查数值合法性,查不出语义错误,比如框标到了云上而不是鸟。最可靠的办法是抽样画框:
import cv2 import os def draw_yolo(img_path, txt_path, out_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f: cls, xc, yc, bw, bh = map(float, line.split()) x1 = int((xc - bw/2) * w) y1 = int((yc - bh/2) * h) x2 = int((xc + bw/2) * w) y2 = int((yc + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(out_path, img) for name in ["000001", "000002", "000003"]: draw_yolo(f"bird_dataset/images/{name}.jpg", f"bird_dataset/labels_yolo/{name}.txt", f"vis_{name}.jpg")这段读图、读 YOLO 标签、把归一化坐标还原成像素坐标画矩形。参数上矩形颜色和线宽随意,关键是坐标还原公式要和 3.1 节一致。抽十几张看一眼,如果框都套在鸟身上,说明标签语义没问题。如果框整体偏移,大概率是宽高顺序搞反了,YOLO 是宽在前高在后,别写反。
4. 把 3362 张图喂进 YOLO 训练:数据配置与必调参数
4.1 生成 train/val 划分与 data.yaml
YOLO 训练需要一个 data.yaml 描述数据路径和类别。3362 张按 8:2 划分,训练集约 2690 张,验证集约 672 张。我一般用脚本随机划分并生成文件列表:
import os, random random.seed(42) names = [os.path.splitext(f)[0] for f in os.listdir("bird_dataset/images")] random.shuffle(names) split = int(len(names) * 0.8) train_names = names[:split] val_names = names[split:] with open("train.txt", "w") as f: for n in train_names: f.write(f"bird_dataset/images/{n}.jpg\n") with open("val.txt", "w") as f: for n in val_names: f.write(f"bird_dataset/images/{n}.jpg\n")固定随机种子 42 是为了可复现,换种子会得到不同划分,对比实验时别乱换。data.yaml 内容:
path: /abs/path/to/bird_dataset train: train.txt val: val.txt nc: 1 names: ['bird']nc 是类别数,names 要和 classes.txt 一致。path 用绝对路径,相对路径在不同工作目录下容易翻车。
4.2 训练命令与关键超参
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0参数说明:imgsz 640 是常见起点,飞鸟目标偏小可以试 960 或 1280,但显存要够。batch 16 在 8G 显存上跑 640 尺寸一般没问题,爆显存就降到 8。lr0 初始学习率 0.01 是 YOLO 默认,小数据集可以降到 0.005 更稳。patience 20 表示 20 轮没提升就早停,省时间。device 0 指定第一块 GPU,CPU 训练把 device 设成 cpu 但会很慢。
4.3 小目标飞鸟的锚框与增强策略
空中飞鸟在 640 尺寸下往往只有几十像素,属于小目标。YOLO 的默认锚框是 COCO 上聚类的,对小鸟不一定最优。常见做法是开 mosaic 和 mixup 增强,让模型多见小目标组合:
mosaic: 1.0 mixup: 0.1 scale: 0.5 flipud: 0.5 fliplr: 0.5mosaic 1.0 表示每张图由四张拼成,能显著增加小目标出现频率。mixup 0.1 轻度混合,太高会让小目标更糊。scale 0.5 允许缩放抖动,flipud 和 fliplr 是上下左右翻转,飞鸟场景上下翻转合理,因为鸟在空中各个朝向都有。这些参数写在训练配置里或命令行传都行。
5. 避坑与排查:标签格式转换里最容易翻车的五件事
5.1 现象:训练 loss 正常但 mAP 始终为零
原因:类别映射错位。json 的 category_id 从 1 开始,YOLO 的 class_id 从 0 开始,转换时没减一,导致所有标签指向一个不存在的类别,模型学不到有效目标。
解决:转换前先打印 json 的 categories 和 classes.txt 对照,建一张显式映射字典,转换时查表。训练前用 3.3 节的可视化脚本确认框和类别都对。
5.2 现象:框整体偏移或大小不对
原因:宽高顺序写反,或者归一化时除错了维度。YOLO 是x_center y_center width height,有人写成x_center y_center height width,框就会横竖颠倒。另一种是把 x 除以了高、y 除以了宽。
解决:回到 3.1 节的转换公式逐行核对,用一张已知框的图手动算一遍,和脚本输出对比。可视化画框是最快的验证手段。
5.3 现象:xml 里的 size 和真实图片尺寸不一致
原因:标注工具读图时用了缩略图,或者图片被后期裁剪过但 xml 没更新。这会导致 VOC 转 YOLO 时归一化分母错误,框全部偏移。
解决:转换时不要信 xml 的 size,用 OpenCV 或 PIL 重新读图片真实宽高。批量脚本里加一句断言,size 不一致就打印文件名人工处理。
5.4 现象:json 解析报错或字段缺失
原因:json 文件不完整,或者用了非标准 COCO 结构,比如 bbox 存成了[x1, y1, x2, y2]而不是[x, y, w, h]。COCO 标准是左上角加宽高,但有人图省事存成两角点。
解决:解析前先看 json 顶层键,确认 images、annotations、categories 三个都在。bbox 长度是 4 但语义不确定时,抽一条和对应图片的可视化结果比对,确认是哪种约定再写转换。
5.5 现象:训练中途 BN 崩溃或 loss 变 NaN
原因:标签里有零面积框或越界框,归一化后 width 或 height 为 0,计算损失时除零。也可能是某张图所有目标都被裁掉,变成空标签文件。
解决:跑 3.2 节的校验脚本,把所有非法框找出来。零面积框直接删,越界框裁剪到边界内或删。空标签文件要么补标要么从训练集移除,别留着。
6. 用 json 标签做 COCO 评估与跨格式一致性抽查
三种格式齐备的最大好处,是你可以用 json 走 COCO 评估流程,再用 VOC 或 YOLO 做训练,最后交叉验证。具体做法:把 YOLO 训练出的预测结果转成 COCO 格式的 results.json,用 pycocotools 算 mAP,和 YOLO 自带的验证指标对比。如果两者差距很大,说明某一环的坐标转换有问题。
from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval coco_gt = COCO("bird_dataset/annotations_coco.json") coco_dt = coco_gt.loadRes("predictions.json") evaluator = COCOeval(coco_gt, coco_dt, "bbox") evaluator.evaluate() evaluator.accumulate() evaluator.summarize()这段加载真值 json 和预测 json,跑标准 COCO 评估。predictions.json 里每条要有 image_id、category_id、bbox、score,bbox 是[x, y, w, h]绝对像素。参数上注意 category_id 要和真值 json 一致,别用 YOLO 的 0 基 id 直接填。
跨格式一致性抽查我一般这么做:随机抽 50 张图,分别用 xml 和 txt 还原框,算两者的 IoU,正常应该全是 1.0,因为描述的是同一批框。如果出现 IoU 小于 0.99 的,说明转换有精度损失或某一边标签本身不一致。这个抽查能兜住大部分隐蔽的格式 bug。
最后一个习惯:每次拿到新数据集,先跑一遍文件名对齐、坐标校验、可视化抽样这三板斧,再进训练。3362 张图跑完这三步也就几分钟,但能省掉后面几小时的 debug。希望帮到你。
本文还有配套的精品资源,点击获取