简介:这是一份面向目标检测与深度学习研究者、YOLO 系列算法实践者的鸟类识别数据集,提供 YOLO 与 VOC 两种标注格式,覆盖 10 个常见鸟类类别,包含 16287 张图片。数据已划分训练集、验证集和测试集,并附带指定类别信息的 yaml 配置与 txt 标签,可直接用于 YOLOv5 至 YOLOv10、Faster RCNN、SSD 等模型训练。压缩包共 2000 个文件,以 1999 个 txt 标注文件为主,另有 1 个 yaml 类别配置文件,整体约 167.9MB,结构简洁清晰。目前已有 482 人浏览学习,适合需要现成标注数据、快速开展鸟类识别或目标检测实验的开发者,下载后可立即了解数据分布、类别信息和标注格式,省去自行采集与整理的耗时步骤。
1. 鸟类识别数据集:16287 张图、10 类目标,为什么我劝你别急着丢进 YOLO
做目标检测的应该都经历过这种时刻:数据集下载完,解压一看,一堆文件名长到离谱的 txt 和 xml,图片散落在各个文件夹里,标注全有但不知道坐标对不对。这次的鸟类识别数据集就是这种典型——16287 张真实图片,10 类鸟,YOLO 和 VOC 两种标注格式共存,训练集、验证集、测试集已经帮你切好。它面向的是鸟类识别、生态监测、相机陷阱这类具体场景,也适合拿来练手 YOLOv5 到 YOLOv10 的完整训练流程。但我要先说一个反直觉的结论:数据到手第一件事不是训练,而是清洗和验证。Roboflow 导出的数据集看着规整,文件命名、类别索引、目录结构里藏着不少能让你白跑一晚上的细节。这篇文章就围绕这个数据集,把标注格式、目录重组、训练参数和五个高频坑一次讲透。
2. 双格式标注:YOLO txt 与 VOC xml 的字段解释和对应关系
拿到手你会看到两类标注文件:一类是.txt,文件名以rf.结尾,这是 Roboflow 导出的 YOLO 格式;另一类是.xml,对应 Pascal VOC 格式。同一个目标在两种文件里都有记录,只是表达方式不同。理解这一点,后面的所有操作才有根基。
2.1 从文件名认识标注来源:rf. 后缀代表了什么
先看一个真实文件名:
51cb3bb0-frame_________341_jpeg.rf.14ba1ff718955e886c47accd7021ea6a.txt这个文件名拆开看有三段。51cb3bb0-frame_________341_jpeg是原始图片的标识,大概率来自某段视频的抽帧;中间的rf.是 Roboflow 的标志;最后那串14ba1ff718955e886c47accd7021ea6a是这份标注在 Roboflow 平台上的唯一 ID,用来保证导出后每个文件不重名。
逻辑说明:Roboflow 在导出数据集时,会把原始文件名和平台内部 ID 拼在一起生成新文件名。这么做的好处是同一张图片的不同格式(jpg、txt、xml)可以通过前缀关联起来,坏处是文件名又长又丑,目录列表一眼扫过去全是重复前缀。参数说明:如果后期需要把标注文件改回简短名字,绝不能直接删中间段,否则 txt 和图片会脱钩。
我一般会先做一次全量文件名扫描,确认同前缀的图片、txt、xml 三者数量一致,再开始动目录结构。
2.2 YOLO txt 的五个数字:坐标边长归一化前后的边界
随便打开一个 txt,例如51cb3bb0-frame_________341_jpeg.rf.xxx.txt,内容通常是五行左右,每行五个数字:
3 0.615625 0.431944 0.237500 0.322222五个数字的含义非常固定:第一个是类别索引,从 0 开始;后面四个依次是目标中心点的 x、y、宽度 w、高度 h。这四个值全部做了归一化——除以图片原始宽高,所以取值范围都在 0 到 1 之间。
逻辑说明:归一化坐标让标注不再依赖具体分辨率,换到 640 还是 1280 的输入都不需要改标签。类别索引则必须与数据集自带的 yaml 文件一一对应,这个数据集里0是 Chestnut Munia,1是 Zebra Dove,顺序不能自己改。参数注意:如果某一行出现了大于 1 的 cx 或 cy,说明标注出了问题,训练时轻则 ignore,重则 loss 震荡,后面避坑章会细说。
2.3 VOC xml:同一目标在两种格式下的语义完全一致
XML 标注和 YOLO txt 描述的是同一些框,只是把目标信息拆成了结构化字段。典型的 VOC 结构长这样:
<annotation> <folder>train</folder> <filename>51cb3bb0-frame_________341_jpeg.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>Collared Kingfisher</name> <bndbox> <xmin>672</xmin> <ymin>320</ymin> <xmax>1024</xmax> <ymax>608</ymax> </bndbox> </object> </annotation><size>里的宽高是原始像素值,<bndbox>是绝对像素坐标。而同一目标在 YOLO txt 里会被换算成归一化后的相对值。逻辑说明:YOLO 用中心点加宽高的表达,VOC 用左上右下两个角点表达,二者数学上等价。转换公式是:
cx = (xmin + xmax) / 2 / width cy = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height反过来也成立,只不过要乘以 width 和 height。做转换时最容易出错的不是公式,而是忘记乘回原始分辨率,或者把 xmin、xmax 写反。
2.4 用脚本验证 txt 与 xml 中同一目标的坐标一致性
双格式最大的价值在于可以互相校验。我习惯截图第一件事就写个十行脚本,挑几张图对比两份标注的框是否重合:
import xml.etree.ElementTree as ET from pathlib import Path # 假设同名 txt 和 xml 在相同目录下 xml_file = Path("51cb3bb0-frame_________341_jpeg.xml") txt_file = Path("51cb3bb0-frame_________341_jpeg.txt") # 从 xml 读取第一个目标框 root = ET.parse(xml_file).getroot() size = root.find("size") W = float(size.find("width").text) H = float(size.find("height").text) obj = root.find("object") box = obj.find("bndbox") x1, y1 = float(box.find("xmin").text), float(box.find("ymin").text) x2, y2 = float(box.find("xmax").text), float(box.find("ymax").text) # txt 第一行的归一化坐标 with open(txt_file) as f: cls, cx, cy, w, h = map(float, f.readline().split()) # 还原成像素值,tol 设 2 像素容忍度 assert abs(cx * W - (x1 + x2) / 2) < 2, "中心点X不匹配" assert abs(cy * H - (y1 + y2) / 2) < 2, "中心点Y不匹配" print("txt 与 xml 坐标一致")逻辑说明:先读 XML 里的原始宽高,再把 txt 的归一化坐标乘回去,分别比较中心点和宽高。参数说明:容差设 2 个像素就够了,因为 Roboflow 导出时偶尔会做一次整数取整。如果这个脚本报错,优先检查是不是拿错了同名前缀里属于不同图片的 txt。
3. YAML 配置与分析:重组目录结构、对齐类别索引、统计样本分布
原始压缩包解压后的目录通常长这样:train/、valid/、test/三个大目录,各自下面又散着图片和标注。直接拿这种结构去训练,YOLO 是能跑,但后续排错会非常痛苦。标准做法是先统一成 images/labels 的二级目录,再写 yaml。
3.1 按训练、验证、测试划分的目录重组
Roboflow 导出的目录一般是:
bird_dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── valid/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/如果你的压缩包解出来是扁平结构,或者图片和标注混在一起,用下面这段脚本一次理清:
import shutil from pathlib import Path root = Path("bird_dataset") images_ext = {".jpg", ".jpeg", ".png"} for split in ["train", "valid", "test"]: img_dir = root / split / "images" lbl_dir = root / split / "labels" img_dir.mkdir(parents=True, exist_ok=True) lbl_dir.mkdir(parents=True, exist_ok=True) for f in (root / split).iterdir(): if f.suffix.lower() in images_ext: shutil.move(str(f), str(img_dir / f.name)) elif f.suffix.lower() == ".txt": shutil.move(str(f), str(lbl_dir / f.name)) elif f.suffix.lower() == ".xml": shutil.move(str(f), str(root / split / "xml"))逻辑说明:遍历每个 split 目录下的文件,按后缀分流到 images 和 labels。注意 xml 我单独挪到split/xml,避免和 txt 混在一起让 YOLO 误读。参数说明:jpg 和 jpeg 都要覆盖,这个数据集里两种后缀都有;exist_ok=True是为了重复执行时不报错。
3.2 准备好的数据配置在 yaml 里要注意
目录整理好之后,新建一个bird.yaml:
# 鸟类检测数据集配置 path: /home/user/bird_dataset # 改成你自己的绝对路径 train: train/images val: valid/images test: test/images nc: 10 names: 0: Chestnut Munia 1: Zebra Dove 2: Garden Sunbird 3: Collared Kingfisher 4: Crested Myna 5: Philippine Pied-Fantail 6: Red Turtle Dove 7: Lowland White-eye 8: Eurasian Tree Sparrow 9: Asian Glossy Starlingnc必须等于names的长度,这里就是 10。path我建议写绝对路径,很多人在相对路径上栽过跟头——YOLO 是以启动命令的工作目录为基准解析 path 的,换终端跑就全错。逻辑说明:train、val、test 的值是相对 path 的,所以不能写成/home/user/bird_dataset/train/images,会拼出双重路径。参数注意:如果你要把这份数据同时用于 YOLOv5 和 YOLOv8,yaml 格式是通用的,唯一区别是 YOLOv5 老版本不认test字段,删掉即可。
3.3 类别频率分布统计,防止样本量失衡
十个类别听上去很均衡,实际分布往往不是那么回事。训练前先跑一段统计,看看每类有多少目标框:
from collections import Counter from pathlib import Path label_dirs = [Path("bird_dataset/train/labels"), Path("bird_dataset/valid/labels")] cls_counter = Counter() for d in label_dirs: for txt in d.glob("*.txt"): with open(txt) as f: for line in f: label = int(line.split()[0]) cls_counter[label] += 1 for cls_idx in range(10): print(cls_idx, cls_counter.get(cls_idx, 0))逻辑说明:逐行读 txt,把行首的类别索引累加。参数说明:0 到 9 的索引顺序与 yaml 完全对应。如果某个类只有几百个框,另一个类上万,那就需要做后续的过采样或调 loss 权重。这个数据集来自真实拍摄,Zebra Dove 这种常见鸟类样本量偏大是大概率事件,发现类别不平衡不丢人,直接拿去训才丢人。
3.4 空标签与超框标签的边界检查
训练前还要扫一遍边界情况:空 txt 文件、框超出图片边界、坐标出现负数。处理后的数据一般没有这种问题,但保不齐你的预处理脚本动了手脚。检查代码:
from pathlib import Path import cv2 for split in ["train", "valid", "test"]: img_dir = Path(f"bird_dataset/{split}/images") lbl_dir = Path(f"bird_dataset/{split}/labels") for img_path in img_dir.glob("*.jpg"): txt_path = lbl_dir / (img_path.stem.split('.')[0] + ".txt") if not txt_path.exists(): print("缺标注:", img_path) continue h, w = cv2.imread(str(img_path)).shape[:2] with open(txt_path) as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) if not (0 <= cx <= 1 and 0 <= cy <= 1): print("越界:", img_path, line) if not (0 < bw <= 1 and 0 < bh <= 1): print("尺寸异常:", img_path, line)逻辑说明:对每一张图,校验对应 txt 是否存在、归一化坐标是否在合法范围内。参数说明:注意img_path.stem.split('.')[0]这一步很关键——文件名里可能有多个点,比如51cb3bb0-frame_________341_jpeg,要按点切一次取第一段才能配上 txt 名。这段脚本跑完没有任何输出,才敢把数据喂给模型。
4. YOLOv8 训练自己的数据集:参数配置、启动训练与结果验证
目录和 yaml 就绪后,进入真正训练环节。我以 YOLOv8 为例,讲清楚每个关键参数的含义和改法。训练任务是 10 类鸟类识别,单卡即可完成,不需要太多资源。
4.1 数据探查:从图片分辨率到锚框的适配
先花两分钟看一组统计——图片尺寸、目标框尺寸分布,直接决定 imgsz 和 anchor 设置:
import cv2 from pathlib import Path import numpy as np img_dir = Path("bird_dataset/train/images") sizes = [] box_wh = [] for img_path in list(img_dir.glob("*.jpg"))[:500]: img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] sizes.append((w, h)) txt_path = img_path.with_suffix(".txt") if not txt_path.exists(): continue with open(txt_path) as f: for line in f: _, _, _, bw, bh = map(float, line.split()) box_wh.append((bw * w, bh * h)) sizes = np.array(sizes) box_wh = np.array(box_wh) print("图片尺寸中位数:", np.median(sizes, axis=0)) print("目标框宽高中位数:", np.median(box_wh, axis=0)) print("最宽目标框:", box_wh[:, 0].max(), "最高目标框:", box_wh[:, 1].max())逻辑说明:抽查部分图片,算目标框的实际像素尺寸。如果大量目标框只有二三十像素宽,说明小目标居多,YOLO 默认的 640 输入很可能不够用。参数说明:样本数取 500 足够,目的是看趋势不是做精确统计。摄像头俯拍或远距离摄影的数据集,普遍存在框小、背景占比大的问题,看完这个统计再定 imgsz。
4.2 YOLOv8 训练参数详解
核心训练命令一行就能跑起来:
yolo detect train \ model=yolov8s.pt \ data=bird.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ cos_lr=True \ fliplr=0.5 \ scale=0.5 \ hsv_h=0.015参数说明逐个展开。model=yolov8s.pt是预训练权重,s 版本在速度和精度中间比较平衡,10 类数据量不算大,没必要直接上 x。imgsz=640是训练输入尺寸,后面讲小目标的时候会改到 960。batch取决于显存大小,16 的 batch 在 8GB 显存上配 640 输入勉强能跑,显存不够就把 batch 降到 8。patience=20是早停,连续 20 轮 mAP 不涨就停,防止无效的长时间训练。cos_lr=True让学习率按余弦曲线衰减,比固定步长衰减更容易收敛到更优解。scale=0.5是随机缩放增强的范围,对鸟类这种目标尺寸差异大的场景,温和的缩放能提升泛化能力。fliplr=0.5是水平翻转概率,不算激进。hsv_h=0.015是色调抖动,适合作息环境光照变化产生的色彩差异。
逻辑说明:这些参数不是标准的 doc 模板,而是这个数据集场景下最常用的一组经验值。如果你在训练时发现 loss 震荡、mAP 波动,优先降 lr、调小 hsv 强度,而不是继续堆 epoch。
4.3 训练过程监控与常见状态判断
训练启动后,终端会实时输出每一轮的 loss 值,包括 box_loss、cls_loss、dfl_loss。这里说三个关键的判断信号:
第一,如果前 5 轮 box_loss 从 1.5 快速掉到 0.8 左右,说明模型正常学习。如果前 10 轮还稳定在 1.5 以上,基本可以断定标注有大面积错误,或者 yaml 的类别索引和 txt 对不上。第二,如果 cls_loss 持续下降但验证集 mAP50-95 一直卡在 0.1 以下,先别调参,回 3.3 节重新看类别分布。第三,训练结束看results.png,尾部出现明显的训练 loss 下行、验证 loss 上行的剪刀差,就是过拟合。这个数据集有上万张图,100 轮内一般不会严重过拟合,但如果你把 patience 关了、batch 又调得很大,还是会遇到。
4.4 用验证集评估模型真实水平
训练完成后跑一次验证,拿到每个类别的 AP 数值:
yolo detect val model=runs/detect/train/weights/best.pt data=bird.yaml输出结果里会有一张class列表,10 行对应 10 个类别,每行的mAP50和mAP50-95是判断重点。逻辑说明:mAP50 更宽容,框的位置差一点也能算命中;mAP50-95 苛刻,要求框的重合度足够高。如果你的 mAP50 能到 0.7 以上,但 mAP50-95 只有 0.3,说明框的位置精度不够,通常是把 imgsz 调大的直接收益场景。
我自己的标准是:这个 10 类鸟类数据集,mAP50 低于 0.5 先查数据问题,0.5 到 0.7 可以接受,0.7 以上说明标注质量和训练参数都比较健康。
5. 鸟类识别数据集的五个经典踩坑:从标签错配到小目标漏检
这章是全文最核心的部分。不是理论推导,全是实际跑数据会撞上的事。每一条都是现象到原因到解决。
5.1 坑一:训练时报 No labels found in bird_dataset/train/images
现象:命令启动没几秒就报错,提示在 train/images 里找不到标签。你确认 labels 目录里躺着几百个 txt,但 YOLO 就是不认。原因:YOLO 找 label 时,会拿图片文件名的 stem 去匹配。而 Roboflow 导出的文件名是51cb3bb0-frame_________341_jpeg.rf.14ba1ff7.jpg,对应 txt 是51cb3bb0-frame_________341_jpeg.rf.14ba1ff7.txt,看似配对,但如果你移动文件时用了img_path.stem做关键字,就会得到被截断的一部分,匹配不上。解决:用 stem 后先按点分割取第一段,再在 labels 目录里搜。排错命令:
find bird_dataset/train -name "*.txt" | head -5 find bird_dataset/train/images -name "*341*" | head -5肉眼对比两列文件的命名模式是否一致。从那以后我每次整理数据集,第一件事就是跑一遍同名匹配脚本,确认图片和标签一一对应才继续。
5.2 坑二:验证集 mAP 虚高,换新图片立刻打回原形
现象:训练时 valid 集 mAP50 能到 0.85,模型拿到真实拍摄的视频上推理,效果明显打折扣。原因:数据泄露。这个数据集的图片有一部分来自视频抽帧,同一段视频里相邻帧高度相似。Roboflow 划分 train/valid 时按文件随机分,同一个视频的帧可能同时出现在训练集和验证集里,验证集的指标自然虚高。解决:先做相似图片去重,再手动按视频片段划分。按帧序列前缀分组,将帧号相近的图片放在同一集合中。去重脚本:
from PIL import Image import imagehash def dhash_path(p, hash_size=8): return imagehash.phash(Image.open(p).convert("L"), hash_size) seen = {} for img_path in sorted(Path("bird_dataset").rglob("*.jpg")): h = dhash_path(img_path) if h in seen: print("疑似重复:", seen[h], img_path) else: seen[h] = img_path逻辑说明:感知哈希把图片降采样成一个指纹,相似的图片指纹相同。参数说明:hash_size=8是常用值,太小误报多,太大漏报多。发现问题后,以视频片段为单位重新划分,而不是简单删掉重复图。
5.3 坑三:loss 降得很顺,mAP50 却一直卡在 0.3
现象:训练曲线一切正常,loss 稳中有降,但验证集 AP 就是上不去。原因:类别不均衡被 loss 曲线掩盖了。Zebra Dove 和 Tree Sparrow 这类常见鸟样本量占了一大半,模型学成背景和常见类别的分类器,把 Chestnut Munia 这类稀疏样本完全忽略。YOLOv8 默认的 cls_loss 对不同类别一视同仁,少数类梯度被多数类淹没。解决:优先做类别重采样,其次再试 loss 权重。简单做法是把少数类的图片复制几份,让每个类别的目标框数量接近同一量级。数据增强层面,对少数类做更强的旋转和马赛克处理,也能帮助模型学会更多特征,而不是只靠背景分类。
5.4 坑四:远处的鸟全都漏检
现象:模型对大目标的框很准,画面里只有一小团的鸟完全无响应。原因:目标尺寸撑不起特征提取。场景里摄影师常用长焦,鸟类只占画面的几个百分点。YOLOv8 在 640 输入下,默认 anchor 的最小尺度可能覆盖不到这种小目标。解决分两步。第一步把训练 imgsz 提到 960,直接扩大目标像素占比。第二步开autoanchor,让模型根据数据分布自动调整锚框。命令:
yolo detect train \ model=yolov8s.pt \ data=bird.yaml \ imgsz=960 \ batch=8 \ autoanchor=True如果换用 960 后显存吃紧,把 batch 压制 8 或 6。逻辑说明:加大 imgsz 是治本,代价是训练时间和推理时间成倍增长。如果训练完推理仍然漏检高发,考虑切图推理,将大图切成几个 640 的子图分别推理,再合并结果,效果立竿见影但流程多一层。
5.5 坑五:训练到一半显存爆掉
现象:epoch 跑到 3 左右,CUDA out of memory,训练中断或频繁卡死。原因:imgsz=960 加 batch=16 的显存占用远超预算。很多时候是训练脚本默认的 batch 值没有跟着 imgsz 一起改。解决:先测单卡能承受的 batch,再乘一个安全系数 0.8。批量调参命令:
yolo detect train \ model=yolov8s.pt \ data=bird.yaml \ imgsz=960 \ batch=6 \ device=0 \ amp=Trueamp=True是混合精度训练,显存占用能减少约三成,速度还有提升。逻辑说明:amp 通过半精度浮点代替部分单精度计算,参数梯度不受影响,是超显存场景的首选手段。如果 batch 已经低到 4 还爆显存,检查是不是后台有其他占用显存的进程,用nvidia-smi看一遍总没错。
6. 进阶用法:把标注画回原图,用模型反向验证数据质量
训练完成了,最后一件事是人工验证。你要知道模型学到的框放回原图上长什么样,标注有没有偏移,边界有没有漏掉,只有看了才知道。别只盯 mAP 数字。
6.1 用 OpenCV 把 txt 画回图片,做视觉检查
import cv2 from pathlib import Path color_map = { 0: (0, 255, 0), 1: (0, 0, 255), 2: (255, 0, 0), 3: (0, 255, 255), 4: (255, 0, 255), 5: (255, 255, 0), 6: (128, 0, 128), 7: (255, 128, 0), 8: (0, 128, 255), 9: (128, 255, 128), } def draw_labeled_image(img_path, txt_path, class_names): img = cv2.imread(str(img_path)) h, w = img.shape[:2] with open(txt_path) as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) cv2.rectangle(img, (x1, y1), (x2, y2), color_map[int(cls)], 2) cv2.putText(img, class_names[int(cls)], (x1, max(20, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color_map[int(cls)], 2) return img class_names = ["Chestnut Munia", "Zebra Dove", "Garden Sunbird", "Collared Kingfisher", "Crested Myna", "Philippine Pied-Fantail", "Red Turtle Dove", "Lowland White-eye", "Eurasian Tree Sparrow", "Asian Glossy Starling"] out_img = draw_labeled_image( Path("sample.jpg"), Path("sample.txt"), class_names)逻辑说明:归一化坐标乘回宽高后转成整数,OpenCV 才能画框。max(0, x1)这类裁剪是为了防止标注稍微越界时画到画布外。参数说明:color_map给不同类别分配不同颜色,10 类刚好一组,肉眼区分起来很快。画出几十张图翻一遍,框有没有整体偏移、有没有漏掉目标和错误目标,一目了然,这比任何统计指标都直接。
6.2 用模型反向验证:把预测框和真实标注叠加对比
训练完的模型本身就是一把尺子。把验证集的预测结果和真实标注叠在同一个画面里,看三个现象:预测框有没有出现在完全没有标注的区域,真实标注四周有没有大量空白,大目标是否被切成了多个小框。这些能帮你判断标注质量瓶颈到底在哪。
具体做法是跑一次带保存功能的验证:
yolo detect val \ model=runs/detect/train/weights/best.pt \ data=bird.yaml \ save_json=True yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=unnamed_folder/ \ save_txt=True逻辑说明:save_txt=True会把验证集的预测框写成 YOLO 格式,与标注文件结构一致,可以直接用 6.1 的脚本改个路径画对比图。参数注意:跑预测时的imgsz必须和训练一致,否则框的位置会整体偏差。
说个我自己的习惯。去年做类似的一个野外鸟类项目,训练集 mAP50 过了 0.8,我一度以为模型已达标。结果把验证图片画出来一看,发现模型把树叶间的阴影全部当成了目标——标注数据里这种例子太多了,框选正确的是少数。后来我强制自己建立一个固定流程:每个数据集训练完,必须抽样 30 张图,人工看图上的框,再统计误检率。这个流程虽然原始,每次都帮我发现指标掩盖的问题。从那以后我每拿到一个新数据集,第一遍仍然会强制走一遍全量画框检查,再谈训练调参。这个鸟类数据集经过 Roboflow 标注,整体质量在公开数据集里算不错的,但拿到手的第一件事依然是相信自己的眼睛,而不是相信压缩包里的说明。希望帮到你。
本文还有配套的精品资源,点击获取