资讯动态

鸟窝图像标注数据集构建:LabelMe标注到YOLO训练全流程

发布时间:2026/9/15 4:43:18 来源:尧图企业网站定制
简介一份面向目标检测与实例分割研究的鸟窝图像标注数据集包含319张野外鸟窝实拍图像并逐图绘制边界框和类别标签适合计算机视觉学习者、算法工程师及生态研究人员用于模型训练、效果评估和方案验证。资源包共957个文件由jpg原图、txt标注YOLO格式和xml标注VOC格式三类组成每张原图均带有对应标注压缩包约933MBtxt遵循YOLO坐标归一化规则xml遵循VOC结构可直接接入常见检测框架。已有1237人学习/下载可作为YOLO、Faster R-CNN、Mask R-CNN等模型训练与对比的基准数据覆盖不同光照、角度和背景下的鸟窝实例支持野外巢穴识别、栖息地监测、智能监控等场景落地。数据集目录结构清晰便于按图片与标签快速划分训练集和验证集为后续算法迭代与指标分析提供了规范化输入。1. 鸟窝图像标注数据集第2部分在做的事第一批四百张鸟窝图像标注完模型在晴天逆光场景下的 mAP50 能到 0.87但一到阴天、树叶遮挡严重时下落三个点不止。这是鸟窝图像标注数据集第2部分最常见的推进场景不是从零重新标一批而是在已有数据基础上做增量补充、格式统一和难例修正。本文要讲的就是把第2部分数据集做扎实的完整路径——源图选哪些、LabelMe 怎么标、转成 YOLO 格式时坐标系别混、校验脚本查出哪几类坏标注、以及如何用已有模型给新图做预标注来缩短人工时间。适合正在做鸟类目标检测、生态监测或无人机巡检的工程师也适合刚拿到一批凌乱标注文件准备整理成正规数据集的人。第2部分的核心不是数量是让数据分布补上第一版漏掉的真实场景。2. 构建鸟窝图像标注数据集源图筛选、类别定义与 LabelMe 标注规范2.1 第2部分的源图怎么选哪些图不该收进来第2部分补数据最容易犯的错是把时间花在“标更多同一类图”上。真正该做的是先统计第一批数据集里哪些场景占比过低。常见做法是写个脚本把已有标注文件按拍摄时间、天气、角度做粗略分组看分布缺口在哪里。对鸟窝图像标注数据集来说典型缺口有三个强逆光或晨昏低照度下拍摄的鸟窝轮廓和背景融为一体巢体被树枝遮挡超过 30%但鸟仍在巢内或返回巢边的关键帧两个以上鸟窝出现在同一画面、且距离较近的密集目标场景。无人机低空航拍和固定相机监拍是两种主流采图方式前者视角较正、背景干净后者多为仰拍或侧拍、遮挡更多。第2部分建议两者按 6:4 混合。选图时有一个硬性门槛原始图像分辨率不低于 1920×1080且鸟窝在画面里占据的最小边不小于 40 像素。低于这个尺寸的巢哪怕是人工标注转成检测框后也会让模型学到噪声而不是特征不如直接切成瓦片再参与标注。2.1.1 用遥感图像标注的思路处理大图遥感图像标注领域常用影像裁剪策略对超大原图做预处理把 4000×3000 的无人机正射影像按 640×640 或 512×512 切成瓦片再逐片标注。鸟窝数据集如果原图过大同样建议先切瓦片再进 LabelMe而不是在整张大图上直接画框。切瓦片有两个好处一是标注时能看到巢的边缘细节边界画得更准二是训练时不用频繁做大图缩放小目标不至于被压到十几个像素。切图建议带 10% 重叠率避免目标正好被切在瓦片边缘导致标注框被截断。2.2 LabelMe 图像标注的类别体系与 polygon 坐标含义标注工具选 LabelMe 是目前最常见且稳妥的做法它输出 JSON 格式支持多边形、矩形、圆形等标注形状。对于鸟窝这类边缘不规则的物体建议统一用多边形标注而不是矩形框。原因是鸟窝边界往往呈不规则椭圆矩形框会把大量树枝、树叶背景包进来后续训练时误检率会明显升高。LabelMe 保存的 JSON 结构里关键字段是shapes数组其中每个元素包含label、points、shape_type三部分。需要注意points是像素坐标不是归一化坐标坐标原点在图像左上角。类别体系是数据集的灵魂第2部分建议按巢体状态来划分而不是按鸟的种类划分bird_nest_active 有鸟、卵或雏鸟在巢中 bird_nest_empty 巢体完整但当前无鸟 bird_nest_partial 巢体残缺、半成品或倒塌状态按状态而非物种分类的理由很直接鸟的种类可能多达几十种标注成本高且样本不均衡而状态分类只有三类在检测任务中已经能支撑“该巢是否处于使用中”这个核心问题。部分生态类项目会再加一个bird_species属性字段但那是辅助信息不建议作为主要检测类别。2.3 边界情况的判定规则遮挡、重叠与远距离小目标数据标注最怕的是不同标注员对同一张图给出不同结果所以第2部分必须要定一组硬性规则。我一般用下面这套可以直接抄进标注规范文档情形是否标注标注方式巢完全可见轮廓清晰是多边形尽量贴合巢体边缘遮挡面积小于 30%仍可辨认巢边界是按可见边缘合理推测完整轮廓遮挡面积大于 30%无法判断巢体完整度否不标注记为 hard_example两个巢体在图像中有重叠是分别标注两个多边形重叠区域各标各的鸟站在巢体外侧边缘是按巢体实际边界标注不把鸟包含进框目标边小于 25 像素否记录图片后续用切片另行处理这套规则的目的是保证“同一个目标任何标注员画的结果差异不超过 5% 的面积”。如果第2部分要追求更好的边界精度可以在 LabelMe 里开启工具栏中的画笔调整功能对自动生成的多边形顶点做二次微调。注意 LabelMe 画多边形时首尾自动闭合画完后顶点越少越好8 到 14 个点即可覆盖一般鸟窝轮廓顶点过多会让后续格式转换时外接矩形计算出现微小偏差。3. 从 LabelMe 原生 JSON 到 YOLO 格式坐标转换与数据集划分3.1 读取 LabelMe JSON 并解析标注字段LabelMe 保存的文件名通常是图片名.json与图片在同一目录下也可能单独放在labels/子目录。解析时用 Python 原生json模块即可不需要额外安装依赖。一个典型的 JSON 片段如下{ version: 5.3.1, imagePath: nest_0042.jpg, imageWidth: 1920, imageHeight: 1080, shapes: [ { label: bird_nest_active, points: [[1420, 322], [1518, 336], [1590, 452], [1488, 550], [1395, 468]], shape_type: polygon } ] }解析逻辑很简单从shapes中逐项取出label和points把类别名映射成整数 ID再将多边形坐标转换为水平外接矩形。这里有几个容易忽略的细节imagePath可能包含../这类相对路径拼接时要做os.path.basename()清洗points里的坐标全部是像素整数部分标注工具会写入浮点数解析时统一转成float再参与计算避免整除导致精度丢失。3.2 将 polygon 转成 YOLO 矩形框的完整脚本YOLO 系列训练时需要 TXT 格式标注每行内容为类别ID centerX centerY width height四个坐标值都是相对图像宽高的归一化比例取值范围 0 到 1。从多边形转换到外接矩形的代码可以直接复用。import json import os from pathlib import Path def convert_labelme_to_yolo(json_path, output_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] yolo_lines [] for shape in data[shapes]: label shape[label] if label not in class_map: print(f警告: 未知类别 {label} 在 {json_path}) continue class_id class_map[label] points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 归一化到 [0, 1]并限制最小边长为 0.01防止退化框 x_center ((x_min x_max) / 2) / img_w y_center ((y_min y_max) / 2) / img_h box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h box_w max(box_w, 0.01) box_h max(box_h, 0.01) yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if yolo_lines: txt_name Path(json_path).stem .txt with open(os.path.join(output_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(yolo_lines)) return len(yolo_lines) class_map { bird_nest_active: 0, bird_nest_empty: 1, bird_nest_partial: 2, }这段脚本里两个关键点一是class_map字典要与训练配置里的类别顺序完全一致第2部分如果只维护前三类后续新增类别必须在字典末尾追加不能插入到中间否则已生成的 TXT 坐标全部错位二是坐标归一化时统一除以imageWidth和imageHeight两个维度分别用对应的边长。有些早期转换脚本会把宽高都除以imageWidth在非正方形图像上会导致框被横向拉长这种错误特别隐蔽。你如果在排查模型精度上不去的问题先检查这里。批量转换时用glob.glob(json目录/*.json)遍历即可每处理一个文件就打印一次转换结果结束时统计总标注框数。建议在转换脚本里加入一个断言检查imageWidth是否大于 0 且与图片实际尺寸一致防止 JSON 与图片版本不匹配导致归一化坐标漂移。3.3 训练集、验证集划分与目录组织数据集划分最怕图片和 TXT 文件不同步图片被移动到了 train 目录对应 TXT 还在 labels 目录里。推荐做法是先随机打乱图片列表再按比例移动图片和同名 TXT一步到位避免后续手动同步。用 shell 脚本就可以完成mkdir -p datasets/bird_nest/{images/{train,val,test},labels/{train,val,test}} images(datasets/raw_images/*.jpg) total${#images[]} val_start$((total * 8 / 10)) test_start$((total * 9 / 10)) for i in ${!images[]}; do img${images[$i]} name$(basename $img .jpg) if [ $i -lt $val_start ]; then splittrain elif [ $i -lt $test_start ]; then splitval else splittest fi cp $img datasets/bird_nest/images/$split/ cp datasets/raw_labels/$name.txt datasets/bird_nest/labels/$split/ 2/dev/null done这段脚本按 8:1:1 划分数据。测试集一般只在全部训练结束后用一次用来报告最终精度如果你平时要反复调参可以把测试集的比例降一点多分给验证集。这里用的是cp复制而不是mv保留原始文件可以在后续校验失败时重新走流程。划分完成后务必执行一次统计确认每个子集的图片数和 TXT 数完全对应。一个快捷检查命令是find datasets/bird_nest/images/train -name *.jpg | wc -l find datasets/bird_nest/labels/train -name *.txt | wc -l两个数字必须一致不一致就去掉2/dev/null重看有什么文件没找到对应标签。3.4 类别均衡的统计与欠采样策略数据集划分完还不算结束。我习惯再跑一个类别分布统计检查第2部分新增的bird_nest_partial类样本是否太少。三个类别的比例如果出现 50:30:5 这种状况训练时模型会对第三类极端不敏感。均衡策略按以下优先级选择数量最少的类不足最多类的 20% 时做在线增强YOLO 训练时对partial类多做一个 1.5 倍的裁剪缩放增强数量差距在 2 倍以内不做处理正常训练即可单一图像里同时包含多个不同状态的巢这类样本要保留它能让模型学会区分相邻目标。统计代码可以用一条极简 Python 循环完成对每个 TXT 文件读取行首的类别 ID 并计数。这一步属于数据集的日常体检可以作为第2部分交付前的必做动作。4. 鸟窝图像标注数据集的自动校验坐标越界、坏文件与格式混用排查4.1 三类最容易混进第2部分的坏标注标注数据里的脏数据不会让模型完全瘫痪但会压低 mAP 曲线的上限。鸟窝图像标注数据集里最常见的坏标注是这三类第一类是坐标越界。多边形手工修正时顶点被拖到图像边缘外几个像素YOLO 里表现为x_center w/2大于 1或者y_center h/2大于 1。训练时 OpenCV 读取标注框时不会直接报错但损失函数会把这部分梯度算错。第二类是标签名与 JSON 不一致。常见场景是同一个标注员在部分图上写了bird_nest在另一批图上写了bird_nest_active。因为有自定义的class_map映射脚本遇到未知标签会丢弃该目标导致目标漏检。这类问题不需要靠眼睛看直接做标签种类聚合统计就能发现。第三类是坐标格式混用。部分标注者手动编辑过 TXT把x_center y_center w h写成了x_min y_min x_max y_max但前两个值看起来又像归一化坐标从数值上很难识别。最可靠的判断方法是按两种格式分别解析然后用 SDK 画框到原图上抽查看框是否贴合目标。这里给出一个一次性校验脚本它整合了越界检查、文件对应检查和基本统计报告。import os import glob from PIL import Image def validate_yolo_dataset(images_dir, labels_dir): issues [] stats {} for label_path in glob.glob(os.path.join(labels_dir, *.txt)): base os.path.basename(label_path).replace(.txt, ) img_candidates [ os.path.join(images_dir, base ext) for ext in [.jpg, .jpeg, .png, .bmp] ] img_path next((p for p in img_candidates if os.path.exists(p)), None) if img_path is None: issues.append(f标签无对应图片: {base}) continue try: with Image.open(img_path) as im: im.verify() img Image.open(img_path) img_w, img_h img.size except Exception: issues.append(f图片损坏: {base}) continue with open(label_path, r, encodingutf-8) as f: lines f.readlines() if not lines: issues.append(f空标签文件: {label_path}) continue for idx, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: issues.append(f{base} 第{idx1}行字段数异常: {line.strip()}) continue cls int(parts[0]) try: x_c, y_c, w, h map(float, parts[1:]) except ValueError: issues.append(f{base} 第{idx1}行浮点数解析失败) continue stats[cls] stats.get(cls, 0) 1 # 允许轻微越界 0.02因为外接矩形贴合边缘时存在四舍五入误差 if not (0 - 0.02 x_c 1 0.02 and 0 - 0.02 y_c 1 0.02): issues.append(f{base} 目标中心越界: {line.strip()}) if w 0 or h 0 or w 1.5 or h 1.5: issues.append(f{base} 宽高异常: {line.strip()}) return issues, stats这段脚本的设计逻辑分三层第一层是文件级检查确保每张图片都有对应 TXT、每个 TXT 都有对应图片同时验证图片文件用 PIL 能正常解码防止死链或截断文件混入输出第二层是行级检查解析失败直接记录行号方便定位到具体标注文件第三层是坐标范围检查越界容错值取 0.02 而不是 0因为多边形转外接矩形时边缘贴合容易出现四舍五入导致中心点略微越界。统计返回的stats字典按类别 ID 聚合目标数直接打印就能看到类别均衡情况。4.2 校验报告与修复动作对照错误类型判定条件修复方式中心点越界x_c 或 y_c 超出 [0,1]读取原 JSON取多边形顶点裁剪后重新归一化宽高异常w 或 h 小于等于 0检查多边形顶点顺序删除重复点和共线点图片损坏PIL 解码失败删除对应 TXT从源数据集重新导出图片标签无对应图片找不到任何同名图片检查文件名编码问题补充或删除孤儿标签字段数异常行内以空格分隔后不等于 5检查是否误用逗号分隔或混入制表符未知类别 ID类别 ID 大于 class_map 最大值对 class_map 做反向映射确认新 ID 来源修复动作里最常用的是“裁剪后重新归一化”。有些越界是因为标注人员画框时在图片边缘上多拖了一个点这类目标直接 clamp 到 [0,1] 范围即可。但如果越界是因为整个目标的一半在画面外那就不能 clamp要么删除这个目标要么保留原图并在扩边后重新标注。判断方法很简单看越界的是中心点还是边缘中心点在画面内的目标可以救中心点都在外的直接删。4.3 黑白名单与版本化存储校验通过的数据要单独建立一份dataset_manifest.csv记录图片文件名、尺寸、目标数、标注链路的 md5 值。第2部分的交付不应该是一堆 JPG 和 TXT 的散装文件而应该是带版本的目录快照。用 Git LFS 或普通的文件清单都能达到目的关键是要让后续训练实验能追踪到“这份模型是用哪一批数据训出来的”。校验脚本跑完后把报告的 txt 存为audit_report_202401.txt并提交到仓库这个文件就是你将来排查精度回退时的第一份证据。5. 用第2部分鸟窝图像标注数据集训 YOLOv8配置参数与性能验证5.1 构建 YOLOv8 的 data.yamlYOLOv8 训练自己的数据集时第一步是准备bird_nest.yaml文件。配置里的路径建议用绝对路径或相对于当前工作目录的路径不要用~展开符号部分训练环境不会自动解析。path: /home/user/datasets/bird_nest train: images/train val: images/val test: images/test # 类别 ID 必须与 3.2 的 class_map 对应 names: 0: bird_nest_active 1: bird_nest_empty 2: bird_nest_partial这段配置里最容易出错的是path字段。如果你把数据集放在了训练机的不同路径后续迁移到服务器时第一件事就是改这个字段。另一个常见坑是 YOLOv8 默认从path下拼接train和val路径如果你还用./datasets/images/train这种相对写法它会尝试查找path/./datasets/images/train导致目录不存在。第2部分如果使用了符号链接来避免复制数据也要确保链接在每台训练机上都能解析。5.2 训练命令与关键超参使用 YOLOv8 训练模型的基础命令如下yolo detect train \ databird_nest.yaml \ modelyolov8s.pt \ epochs200 \ imgsz1280 \ batch16 \ device0 \ patience30 \ projectruns/nest_part2 \ nameyolov8s_finetune_1280 \ seed42几个关键参数按鸟窝数据集的特性调整过imgsz1280是第2部分最重要的改动。鸟窝平均尺寸在 40 到 120 像素之间用默认 640 尺寸训练小目标在特征图高层基本退化成一个点。把推理尺寸提高到 1280 后小目标在 P3 特征层还能保留足够的空间信息。代价是显存占用大约增加三倍如果单卡显存不足 16GB可以把batch降到 8或者改用切片瓦片策略而不是调高分辨率。参数建议值说明epochs200数据量小训练轮数必须够但配 patience 防过拟合batch8~16显存优先batch 太小 BN 层不稳定imgsz1280小目标检测的关键参数不要省patience30连续 30 轮 val mAP 不提升就早停cacheTrue数据集小直接缓存到内存加快迭代mosaic1.0保持默认有效增加遮挡样本多样性scale0.5缩放增强降低防止巢体被缩得太小mosaic增强默认是对四张图拼图一定程度上会增加目标重叠度对遮挡样本多的鸟窝数据集有利。但训练后期如果发现小目标丢失严重可以把mosaic在最后 10 个 epoch 关闭做法是在命令里加上close_mosaic10。这个参数是 YOLOv8 特有的作用是在最后 N 个 epoch 恢复为普通增强让模型在更接近真实分布的输入上收敛。5.3 验证阶段看什么mAP50、漏检分布与混淆矩阵训练结束后的验证指标优先看验证集上的mAP50这不是因为指标高级而是对鸟窝这类单类目标来说它对少量像素偏移的容错更高。目标框边缘有个三五个像素的偏差在 mAP50 上依然能判为正样本。再看mAP50-95如果这个值特别低而 mAP50 很高说明定位框偏大或偏小的问题比较普遍需要回头检查标注是否统一。yolo detect val \ modelruns/nest_part2/yolov8s_finetune_1280/weights/best.pt \ databird_nest.yaml \ imgsz1280 \ save_jsonTrue验证命令里的save_jsonTrue会输出每个类别的逐项指标到 JSON 文件。打开这个文件重点看per_class里三类目标的ap数值。如果bird_nest_partial的 AP 显著低于另外两类说明第2部分补的残缺巢样本还不够或者在增强中这类目标被裁掉的概率太大。另一个直接办法是把验证集预测结果可视化YOLOv8 在val_batch_pred.jpg里会画出预测框。肉眼看一遍重点检查两个地方密集相邻的巢是否正确分开树叶遮挡后的巢是否直接漏检。漏检通常表现为图片里有清晰的巢但没有任何框这类坏样本要捞出来送回标注流程形成第2部分数据的闭环迭代。5.4 当水平框不够用时的旋转框路线衔接 MMrotate部分高空俯拍的鸟窝图像里巢体随树枝倾斜角度很大水平框之间重叠率超过 0.7NMS 会把相邻目标合并掉。这种情况水平框已到性能上限常见做法是走旋转框检测路线把 LabelMe 的 polygon 顶点坐标直接用作旋转框的四个角点制作成 DOTA 格式再交给 MMrotate 训练。注意 DOTA 格式每行是x1 y1 x2 y2 x3 y3 x4 y4 class类别名后面可以接difficult标记字段。MMrotate 的训练配置与 YOLO 完全不同但数据处理部分可以复用第2部分的 JSON 转换逻辑只是把外接矩形的计算改成保留原始四边形的角点。鸟窝检测对旋转框的需求不是刚需但如果你的部署场景里相邻巢体多、漏检集中出现在 NMS 环节这个方向值得投入。6. 第2部分数据集的增量标注技巧预标注加人工修正的协作流程第2部分数据集往往建立在已有模型基础之上。直接用第1部分训练好的模型对新采集的未标注图片做推理生成预标注文件再由标注员在 LabelMe 里做修正能显著压缩标注工时。小规模实验里一张图从全手动 45 秒降到约 20 秒。这个技巧对“图像目标识别标注”的日常工作非常实用。预标注脚本的逻辑不复杂用 YOLO 模型跑一遍新图把置信度高于 0.35 的检测结果写成 LabelMe 的 JSON。关键点是只保留高置信度框低于阈值的宁可漏掉再人工补也不要给标注员制造大量需要删除的错误框。import json import glob from ultralytics import YOLO model YOLO(runs/nest_part2/yolov8s_finetune_1280/weights/best.pt) class_names {0: bird_nest_active, 1: bird_nest_empty, 2: bird_nest_partial} for img_path in glob.glob(new_images/*.jpg): results model.predict(img_path, conf0.35, imgsz1280, verboseFalse) shapes [] for r in results: boxes r.boxes.xyxy.cpu().numpy() clss r.boxes.cls.cpu().numpy().astype(int) for box, cls_id in zip(boxes, clss): x1, y1, x2, y2 [float(v) for v in box] shapes.append({ label: class_names[cls_id], points: [[x1, y1], [x2, y1], [x2, y2], [x1, y2]], shape_type: polygon, group_id: None }) json_path img_path.replace(.jpg, .json) with open(json_path, w, encodingutf-8) as f: json.dump({ version: 5.3.1, imagePath: img_path.split(/)[-1], imageWidth: r.orig_shape[1], imageHeight: r.orig_shape[0], shapes: shapes }, f, indent2, ensure_asciiFalse)这段代码生成的是四边形坐标组成的 polygon标注员在 LabelMe 里打开后只需要拖动顶点贴合巢体边缘不用从零画起。如果模型置信度足够高一个巢框里四五个顶点挪一两下就能完成。第2部分有一个细节值得注意预标注生成的 JSON 里imagePath建议只存文件名与 LabelMe 默认行为保持一致否则跨目录打开时文件路径找不到导致图片加载失败。人工修正完成后重新跑一遍第2章的 JSON 转 YOLO 脚本并做一次第4章的校验即完成“新图进入数据集”的全流程。整个第2部分的推进节奏可以压缩为三步先对未标注图跑预标注再人工逐张修正和补漏最后重训模型并在新增难例上比对 AP 提升。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价