ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

鸟窝图像标注数据集第二阶段:从标注整理到YOLO训练与难例回灌

鸟窝图像标注数据集第二阶段:从标注整理到YOLO训练与难例回灌 简介一份面向计算机视觉学习与研究者的鸟窝图像标注数据集专为目标检测模型训练与生态监测场景设计。压缩包内共957个文件包含319张jpg原图、319个txt标注文件与319个xml标注文件txt对应YOLO格式的坐标与类别信息xml对应PASCAL VOC格式的详细标记整体约933MB。已有1237人学习下载。这套数据可直接用于YOLO、Faster R-CNN、Mask R-CNN等模型的训练与评估帮助研究者快速开展鸟窝自动识别实验也可结合无人机或摄像头用于鸟类栖息地监测、繁殖行为观察和非法捕猎预警等实际项目。对正在学习目标检测原理、需要高质量标注样本做算法对比或部署验证的开发者而言是格式规范、可直接投入实验的优质数据集。1. 鸟窝图像标注数据集的第二阶段从“画完框”到“能用来训练”如果你手里攒着一批鸟窝照片多半来自输电杆塔巡检、果园驱鸟相机或者观鸟站的定时抓拍。第一轮采集时大家忙着把图片整理好、把明显的目标画出来然后就把文件夹丢给了训练脚本。真正卡住的往往是第二阶段同一个鸟窝有人框整棵树有人只框巢体里面带幼鸟的巢和空巢被揉成同一个类别验证集里来自同一基站的相似背景反复出现模型在训练集上收得很好换一塔就失效。鸟窝图像标注数据集-第2部分要解决的不是再补一万张新标注而是把已有标注整理成可信任、可复现、能持续迭代的训练资产数据怎么组织、类别怎么定、标注怎么复核、模型反查出来的问题怎么回灌。这篇文章面向正在做鸟类目标检测、图像目标识别标注和各类小样本前景检测的工程团队。结论先放在开头对鸟窝这种静态、外观差异大、背景强相关的目标数据集的第二阶段质量往往比继续增加标注量更影响最终 mAP。2. 鸟窝图像标注数据集的数据组织与类别体系设计2.1 先把训练任务定下来再谈如何组织文件夹鸟窝和车辆、行人这类目标有一个明显差异它几乎不移动变化集中在尺度、巢材纹理和背景环境。常见做法是把任务定成“单类检测”或“占用状态检测”。确定了检测粒度数据形态才能定下来。我一般会把数据目录按“编辑态”和“读取态”分开birdnest_dataset/ ├── images/ │ ├── train/ # 训练集原始图像 JPG │ └── val/ # 验证集原始图像 JPG ├── labels_json/ │ ├── train/ # labelme JSON人工编辑的唯一入口 │ └── val/ ├── labels_yolo/ │ ├── train/ # 由 JSON 脚本生成的 YOLO txt只读 │ └── val/ ├── class_names.txt # 类别顺序与 YOLO 编号严格一致 └── birdnest.yaml # 训练入口配置labels_json是人的编辑态用 LabelMe 打开、修改、再保存labels_yolo是模型的读取态由脚本从 JSON 批量转换生成。两条目录必须分开。直接手工改 txt 的后果是显而易见的一旦 class_names.txt 里加了一个类别原先所有 txt 的编号全部错位模型在训练中途就会出现类别混淆。这一步看起来只是文件整理实际上它是数据集第二部分里最基础的一致性约束。2.2 类别与属性的界定鸟窝和通用鸟类数据集信息量的差异通用鸟类目标检测的数据集通常按物种、姿态、成幼来划分鸟窝数据集的划分逻辑不一样巢体外观高度重合真正影响使用的是“巢的使用状态”和“所处位置”。类别不宜太细建议控制在 2 到 3 个状态类其余信息放进属性键。类名判定标准边界情况occupied_nest巢体内有鸟、卵或幼鸟或巢沿有密集啄痕和新鲜粪便鸟站在巢沿且大半身体在画面外仍判为 occupied不单标鸟empty_nest巢体完整无鸟无卵无近期活动痕迹巢被树叶遮住一半但主体可辨判为 empty遮住大半则不标defunct_nest巢体明显残破、松散、塌陷或经雨季冲刷变形无法确定是否废弃时属性中标 uncertaintrue不并入 defunct标注属性我建议至少维护三个键location杆塔/树冠/建筑物/峭壁、material树枝/草茎/泥塑/人工巢基、has_bird0/1。分类网络和检测网络通常不直接消费这些键值但后续做难例分析、按场景划分验证集、做域适应时这些键是唯一的切面依据。这一类偏好和桥墩病害数据集、水下管道裂缝数据集的标注习惯是一致的状态决定训练目标位置决定验证划分。农业监测场景里的鸟害防治也沿用同一套逻辑巢体状态直接决定驱离策略所以“状态类 空间属性”的写法比单一目标框信息量更大。2.3 数据划分与训练配置验证集要按场景分桶数据划分是第 2 部分最容易被低估的环节。鸟窝检测失败通常不是模型不认识巢而是背景太相似同一个杆塔上拍了 8 张图其中 6 张进训练集、2 张进验证集模型其实是在记忆塔形。验证集应该按拍摄点、塔号、果园编号做分桶而不是随机切。对应 YOLO 的入口配置如下path: /data/birdnest_dataset train: images/train val: images/val names: 0: occupied_nest 1: empty_nest 2: defunct_nestpath指向数据集根目录train和val写相对路径让配置在不同机器之间可迁移names的索引顺序必须与 class_names.txt 一行对一键不能只看名字对不对。验证集规模建议占总量的 15% 到 20%但要保证按地点分桶后每个桶里至少有两个地点否则验证集出现波动时你分不清是标注噪声还是地点太少导致。如果原图来自无人机航拍或大画幅相机这里就要借鉴遥感图像标注常用的切片思路把大图按 1280×1280 且带 25% 重叠率切成小图再进入标注流程。切片可以在 JSON 阶段做也可以在训练时用片段加载器做但必须在划分验证集之前完成切完再去分桶。3. 用 LabelMe 完成鸟窝图像标注的规范流程与半自动预标注3.1 labelme 图像标注的最小可用配置LabelMe 是这个流程里最常见的标注工具多边形标注和 JSON 导出都比较完善。对鸟窝这类目标第一步是统一标注工具的启动参数pip install labelme labelme --labels birdnest_labels.txt --nodata --autosave \ images/ labels_json/--labels指定类别清单文件让下拉框只允许选择预设类别避免每个人手打出七八种拼写--nodata不让 JSON 内部嵌入 base64 图像文件体积能小一个数量级标注目录用 Git 管理时才不会涨到失控--autosave切换图片时自动保存减少丢失。命令末尾的两个路径分别是图片目录和 JSON 输出目录。多人协作时我一般要求各自使用独立输出目录合并时用脚本统一文件名避免两个人在同一张图上写出同名冲突的 JSON。3.2 图像目标识别标注中的框型决策水平框、旋转框还是多边形标注工具定下来之后下一个问题是画什么形状。鸟窝边缘参差不齐多边形视觉上最精确但标注成本和模型兼容成本最高。我建议按下面这张表做决策场景推荐框型原因杆塔、树冠上的常规巡检照片水平框YOLO 原生支持状态类差异是主要区分维度鸟窝沿塔材斜向排列、巢体密集旋转框减少框内背景占比参考 MMRotate 训练 DOTA 数据集的旋转框工作流需要输出分割掩码或巢材分析多边形与实例分割统一但标注成本约为水平框的 2 到 3 倍对大多数项目默认从水平框开始即可。旋转框能改善斜向巢体的定位精度但数据格式、增强策略、模型选型都要跟着改训练和部署链路变长。只有当场景里巢体主轴夹角分布在 20 度以上时再切换旋转框方案。判断方法很简单从第一批 JSON 里随机挑 50 个框量一下长边倾斜角如果半数以上超过 15 度就值得迁移。3.3 让模型先画一遍半自动预标注脚本的写法标准流程是人工逐张画框但进入第 2 部分时手上通常已经有一版模型哪怕粗糙。常见做法是先用模型对新增图推理输出候选框人工只做删错、补漏、改类别。这样的半自动流程能把单张标注时间压掉一半以上。from pathlib import Path from ultralytics import YOLO model YOLO(weights/birdnest_s.pt) image_dir Path(data/unlabeled) out_dir Path(data/prelabel_json) out_dir.mkdir(exist_okTrue) for img in sorted(image_dir.glob(*.jpg)): results model.predict(str(img), conf0.35, iou0.5, imgsz1280, verboseFalse) boxes [] for r in results: for box in r.boxes: conf float(box.conf[0]) if conf 0.35: # 低置信框留给模型自省不导出 continue x1, y1, x2, y2 [float(v) for v in box.xyxy[0]] if (x2 - x1) * (y2 - y1) 32 * 32: # 小碎片是典型噪声 continue boxes.append({x1: x1, y1: y1, x2: x2, y2: y2, conf: conf, label: occupied_nest}) # 此处把 boxes 转成 labelme JSON 落盘再交给人工复核这段代码的思路是“宁可多给候选不让它漏”。置信度阈值取 0.35 而不是 0.5是为了预标注阶段偏向召回imgsz1280 是考虑到鸟窝在 4K 原图里经常只占几十个像素缩到 640 推理基本会漏。面积小于 32×32 的候选通常是树杈、铁丝头这类错检结构直接过滤掉能省掉大量无意义的复核动作。如果不用检测模型而是让多模态模型辅助标注图像标注提示词要按同样的逻辑设计输入图像后要求它输出巢体的水平包围框、遮挡状态、巢材类型并明确告知“不确定时宁可不标”而不是让它同时完成描述和定位。提示词里把“像素范围、遮挡规则、类别定义”三条写清楚输出质量会有明显提升。3.4 质检把人工抽查变成全量统计很多人对标注质量的控制是“抽查几张大图看看画得准不准”这对第 2 部分来说不够。我倾向于把检查做成可重复的清单统计每轮标注结束跑一次脚本对异常项逐个人工复核。最基础的两个统计是每张图的框数分布和每个类别的框数量import json, glob from collections import Counter c Counter() files glob.glob(labels_json/train/*.json) for f in files: d json.load(open(f)) c[images] 1 c[boxes] len(d[shapes]) for s in d[shapes]: c[label_ s[label]] 1 print(c)运行结果里如果出现“某个文件 20 个框同批次其他文件普遍 1 到 2 个框”大概率是把整群鸟误当作巢体或者整张图被无意义密集标注。如果某个类别占比异常回到类别定义看判定标准是否含糊。每次数据发布前保存这份统计快照等模型训完再回头对照标注质量波动和 mAP 波动就能对应上。4. 用 YOLO 训练自己的鸟窝数据集用基线模型反查标注质量4.1 先拉一版基线损失曲线与验证指标怎么看标注整理完、类别确认、验证集按地点分桶之后接下来的动作和直觉相反不是继续调标注而是先训一版基线模型。对鸟窝这种静态小目标YOLOv8 的 s 模型跑 300 轮就够损失曲线和验证集表现就是整份数据集质量的反光板。命令并不复杂关键是参数怎么定yolo detect train databirdnest.yaml \ modelyolov8s.pt \ imgsz640 epochs300 batch16 patience50 \ cos_lrTrue optimizerAdamW lr00.001参数取值作用与调法modelyolov8s.pt数据集规模在千级时 s 足够不直接上 ximgsz640目标像素占比小于 2% 时改成 1280patience50验证指标 50 轮不涨即停省时间cos_lrTrue余弦衰减配合 AdamW小数据集收敛更稳训练结束后先看两件事第一损失曲线是否在最后阶段反复震荡如果是优先怀疑某张训练图外接框错位第二混淆矩阵里 occupied_nest 和 empty_nest 是否互相渗透如果是类别判定标准需要回炉。对桥墩病害这类小样本项目我通常会把验证集 mAP 和标注质量评价绑定完整性看漏标比例一致性看同类框的宽高比方差可复现性看同一张图重复标注的交并比。三个维度各有分数比单看 mAP 更能定位问题。4.2 低置信正样本与高置信负样本筛选脚本验证集里比 mAP 更有用的信息是模型“不太自信”的预测框。低置信正样本指模型给出了明显的目标响应但自己都觉得不像——这一类往往对应标注框偏移、类别错误或目标模糊高置信负样本指模型高分预测但真实标签没有命中——这一类基本是漏标。YOLO 验证时加 save_jsonTrue 会生成 predictions.json配合真实标签可以做交叉分析import json from collections import defaultdict pred json.load(open(runs/detect/val/predictions.json)) # predictions 里每个元素含 image_id, category_id, bbox, score # 筛出置信度在 0.2 到 0.45 之间的检测框属于模型自己都拿不准的候选 cand [p for p in pred[predictions] if 0.2 p[score] 0.45] low_conf_by_img defaultdict(list) for p in cand: low_conf_by_img[p[image_id]].append(p) # 输出 top 20 张需要重点复核的图检出数多且置信度都偏低 for img_id, items in sorted(low_conf_by_img.items(), keylambda kv: (-len(kv[1]), -max(i[score] for i in kv[1])))[:20]: print(img_id, len(items), round(max(i[score] for i in items), 2))筛选逻辑是先看模型犹豫的样本而不是看它确定的部分。把这些 image_id 导成图片清单人工逐张比对真实框和预测框通常三轮下来就能修掉大部分系统性问题。高置信负样本的排查方向相反从预测框出发找真实标签里没有匹配的区域这比肉眼扫全图找漏标快得多。修复后重新训练如果 mAP 提升幅度超过 3 个点说明之前的数据噪声确实拖了后腿也验证了这一轮反查是值得的。4.3 小目标切图策略先看目标的像素占比再定预处理鸟窝在巡检图里常属于小目标且分布不均匀。我一般先统计标注框面积占全图面积的百分比再决定训练尺度避免拿着 640 的输入硬训一堆 20×20 像素的目标。目标像素占比现象推荐动作小于 2%目标在缩略图里几乎看不见imgsz 提到 1280或按 1280 切片2% 到 8%缩略图能辨认但边缘纹理弱imgsz640正常增强大于 8%巢体充满画面关注遮挡与多尺度必要时降低切片重叠率小目标场景下切片带来的增益通常大于换大模型的增益。这也符合遥感图像标注里“先切图、再训练”的通用前提把一张 4000×3000 的巡检图缩到 640巢体只剩十几个像素任何骨干网络都很难提取纹理切成 1280 后目标至少占 40 像素特征才算有效。切图带来的副作用是同一巢体在重叠区出现两次训练时可能造成重复梯度所以切片重叠率控制在 25% 以内并让切图逻辑与验证集划分同步执行。5. 难例回灌与数据集版本化鸟窝数据集越训越强的操作规则5.1 难例回灌的五条规则模型训完不等于数据集闭环结束真正让鸟窝图像标注数据集-第2部分产生长期价值的是把模型暴露的问题持续回灌到标注集。回灌要有规则否则训练集会越搞越脏。我一般按下面五条执行规则操作内容目的1每轮训练后把验证集里出现漏检的图像送回待标队列让模型反复接触弱项2难例暂时放入独立目录攒够 200 张再统一复核批量复核比单张零散更高效3模型输出与标签出入较大的框必须二次人工判定防止模型噪声污染标注4新增样本务必记录来源字段 hard_mining/retake/raw_append保留数据血缘5回灌前清掉空标注、越界框、重复文件保持数据集健康度难例回灌最容易犯的错误是把模型的新错误直接当标注加回去。回灌的本质是让人教模型新样本而不是让模型教自己。所以规则 3 卡得很死凡是模型输出与现有标签差异超过阈值的目标都走一次人工核对核完才允许并入训练集。5.2 用哈希把数据集冻结成可复现资产模型迭代到后期最大的风险不是算法不够新而是同一份数据在不同人的机器上被悄悄改过指标不再可比。解决方法是把数据集版本冻结成哈希清单训练前后各校验一次cd /data/birdnest_dataset find images labels_json labels_yolo -type f | sort \ | xargs sha256sum dataset_manifest.sha256 chmod a-w images labels_json labels_yolo sha256sum -c dataset_manifest.sha256 --quiet \ || { echo 数据集与清单不一致禁止训练; exit 1; }find 先输出三个目录下所有文件的稳定排序xargs 逐批计算 SHA256汇总成 manifestchmod 去掉写权限是防止有人手滑改了图片或标签。第二段命令放在训练脚本最前面启动时自动校验。以后不管谁在哪台机器上复现实验结果只要 manifest 一致字节级别就完全相同。把这行校验放在训练脚本开头日志里出现 exit 1 时第一反应是去查谁动了数据而不是去调学习率。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进