ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

智慧牧场航拍小目标检测:类别不平衡数据集实战解析

2026/10/1 3:07:03 拓冰建站 浏览量
智慧牧场航拍小目标检测:类别不平衡数据集实战解析 简介一套面向智慧牧场场景的无人机航拍牛羊检测数据集适合目标检测算法在远距离小目标任务上的训练与评估。资源包含1021张航拍图片提供Pascal VOC与YOLO两种标注格式XML标注文件1021个、TXT标注文件979个覆盖cattle、cow、sheep三个类别总标注框数达14047个其中sheep框数9518、cattle框数4128尺度和类别分布可满足小目标检测与类别不平衡研究需求。压缩包为7z格式文件总数2000个包体大小约97.58MB以XML和TXT标注文件为主体并附有对应原图便于直接接入主流检测框架。数据集使用labelImg工具按矩形框规则标注标注信息准确合理需要说明的是本数据集仅保证标注质量不对训练产出模型的精度作任何承诺。目前已有379人浏览学习适合计算机视觉研发者、智慧农业项目团队以及相关专业学生用于模型迭代与算法验证。1. 智慧牧场航拍数据集远距离小目标与三类极度不均衡的标注分布很多人拿到这份智慧牧场航拍牛羊检测远距离小目标数据集VOCYOLO格式1021张3类别第一反应是直接扔给 YOLO 开训。我劝你先收住。这份资源真正值钱的地方不是 1021 张图本身而是它的标注分布14047 个框里 sheep 占 9518 个cattle 有 4128 个cow 只有 401 个。这不是标注失误这就是真实牧场的俯视画面——羊成群、牛零星、小牛数量更少。YOLO 类模型在这种分布上暴露的问题远比在均衡数据集上多小目标容易漏检、少样本类别会过拟合、整体 mAP 被大头类别拉得很虚。它适合拿来验证你对小目标检测和类别不平衡的处理能力。新手可以拿它完整走一遍 VOC 到 YOLO 的流程老手正好把它当标尺测测自己换的检测头、注意力模块和数据增强策略到底有没有用。2. 航拍小目标为什么难检测从尺度、密度到标注边界2.1 尺度不是玄学先明确“小”的量化标准COCO 数据集把目标按面积分成三档小于 32×32 像素算 small32×32 到 96×96 算 medium更大算 large。航拍图常见分辨率是 1920×1080甚至 4000×3000一头成年牛在 200 米高度下投射到画面里可能只有 60×40 像素羊只会更小。也就是说这份数据集的绝大多数目标落在 small 到 medium 区间其中相当一部分是严格意义上的 small。为什么小目标难检测因为神经网络的下采样会把特征图越缩越小。YOLO 系列有三个检测头步长分别对应 8、16、32一个 24×24 像素的羊在 stride 32 的特征图上只占不到 1 个像素等于彻底消失。真正能承载小目标信息的只有 stride 8 的浅层特征图而浅层特征的语义信息弱容易被背景干扰。所以训练这份数据集时默认的 640 输入尺寸其实很吃亏后面我会专门讲怎么调 imgsz 和增强参数。2.2 集群、遮挡与俯视视角标注本身就很难先看类别构成sheep 9518 框、cattle 4128 框、cow 401 框。sheep 是绝对主体而且是集群出现——一群羊挤在一起互相遮挡矩形框里经常混进旁边羊的腿或头。cattle 体型大通常单独或两三头在一起框比较干净。cow 是麻烦点框数最少且分布极不均匀可能集中在某几十张图里。用 labelImg 画的都是正矩形框VOC 的 xml 和 YOLO 的 txt 都只能表达矩形没有 polygon、没有遮挡标记。这意味着标注规则本身就已经确定了边界密集羊群里的每只羊只能靠矩形框去切分部分难区分的个体框会偏大或偏小。作者在说明里也讲得很诚实数据集只提供准确且合理标注不对任何模型精度作保证。你拿它训练出的模型在密集羊群上偶尔数错数量是这类矩形标注的固有限制不代表模型坏了。这里有个很多人忽略的点俯视视角下目标形态和日常目标检测数据集完全不同。没有侧面、正面、遮挡姿态的丰富变化所有牛羊都是“从头顶往下看”模型更容易学到纹理和颜色特征而不是轮廓结构。所以这套数据集训练的模型换到低空斜视场景效果大概率会掉迁移前要有心理预期。2.3 动手先框一眼再谈训练训练前我建议你先做一步可视化校验。用下面这段脚本把任意一张 jpg 和同名 xml 的标注框画出来import cv2 import xml.etree.ElementTree as ET img_path JPEGImages/firc_animal_491.jpg xml_path Annotations/firc_animal_491.xml img cv2.imread(img_path) tree ET.parse(xml_path) for obj in tree.findall(./object): box obj.find(bndbox) x1 int(float(box.findtext(xmin))) y1 int(float(box.findtext(ymin))) x2 int(float(box.findtext(xmax))) y2 int(float(box.findtext(ymax))) label obj.findtext(name) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_491.jpg, img)这段代码做的事情很直接读图、解析 xml、把每个目标的矩形框和类别名画在图上输出 check_491.jpg。cv2.rectangle 的坐标参数必须传整数所以用 int() 包了一层。写完打开图片你能直观看到 sheep 集群框的重叠程度、cattle 框的干净程度以及 cow 目标在画面里到底小成什么样。这一步能帮你建立对数据分布的手感后面调参时心里更有底。3. VOCYOLO 双格式解析目录结构、统计脚本与尺度分布3.1 一张图三份文件目录结构与双格式对应关系解压这份 7z 资源后核心内容就三个目录JPEGImages 放 jpg 原图Annotations 放 VOC 格式 xmllabels 放 YOLO 格式 txt。三类文件一一对应同一张图只有一个 baseline 文件名比如 firc_animal_491.jpg、firc_animal_491.xml、firc_animal_491.txt。注意 txt 里不包含分割路径只有最朴素的检测标注这一点对做分割任务的人来说是个边界别指望能当 mask 用。VOC 的 xml 长这样annotation filenamefirc_animal_491.jpg/filename size width3840/width height2160/height depth3/depth /size object namesheep/name bndbox xmin112/xmin ymin340/ymin xmax158/xmax ymax380/ymax /bndbox /object /annotationxml 里是绝对像素坐标。YOLO 的 txt 则是归一化相对坐标每一行格式是2 0.0351 0.1667 0.0120 0.0185分别是 class_id、x_center、y_center、width、height全部除以图片宽高做了归一化。class_id 的顺序由类别名单决定这份数据集的约定是 0cattle、1cow、2sheep后面配 YOLO 的 yaml 时必须保持这个顺序否则训练不会报错但评估结果会完全错位。双格式并存的好处是能互相校验把 xml 的 xmin/xmax 除以 width 换成归一化坐标再和 txt 比对同一张图的两个标注如果差出 0.01 以上说明某个文件被篡改或转换出错。这种校验在数据集下载后一定要做训练集里混入错位标注的代价是整个模型被带偏。3.2 统计脚本类别框数与目标尺度分布训之前先跑统计这是我一贯的习惯。下面的脚本解析全部 xml统计每类框数并按照 COCO 的尺度定义划分小、中、大目标import glob import xml.etree.ElementTree as ET from collections import Counter xml_files sorted(glob.glob(Annotations/*.xml)) class_counter Counter() small medium large 0 for f in xml_files: tree ET.parse(f) img_w int(tree.findtext(./size/width)) img_h int(tree.findtext(./size/height)) for obj in tree.findall(./object): name obj.findtext(name) class_counter[name] 1 bnd obj.find(bndbox) x1 float(bnd.findtext(xmin)) y1 float(bnd.findtext(ymin)) x2 float(bnd.findtext(xmax)) y2 float(bnd.findtext(ymax)) w x2 - x1 h y2 - y1 # COCO small: 32x32, medium: 96x96, large: else if w 32 and h 32: small 1 elif w 96 and h 96: medium 1 else: large 1 print(class distribution:, dict(class_counter)) print(size distribution:, { small(32): small, medium(32~96): medium, large(96): large, })输出结果应该是 cattle 4128、cow 401、sheep 9518总量 14047尺寸分布大概率是 medium 最多、small 次之、large 最少。这里 small 和 medium 的阈值按 COCO 惯例设成 32 和 96。如果你的显卡比较弱只能跑 640 输入那这些 small 目标会被进一步压缩到 20 像素以内漏检几乎是必然的。所以这个脚本的另一个作用是帮你决策要不要上 1280 的大输入、要不要专门调小目标增强。3.3 从统计结果反推训练风险把统计结果摆到训练维度看有三个直接结论。第一sheep 占比高达 67.8%模型会把大量容量花在羊身上cattle 和 cow 学到的特征会被挤占。第二cow 只有 401 框按 7:2:1 划分后训练集可能只剩 280 框左右分散在有限几张图里模型很容易陷入对这几张图的记忆而不是学到“牛的通用特征”。第三cow 在总框数里占比不到 3%哪怕你把验证集整体精度刷到 0.85cow 的 AP 可能仍然只有 0.1。这不是数据集的质量问题而是真实世界的长尾分布。你需要把它当作一个不平衡小目标检测课题来对待而不是“下载即训练”的玩具。后面第 6 章会给三种实操解法。4. 训练 YOLO 前必须做的事划分、配置与参数调整4.1 按图划分数据而不是按框划分划分数据集时最容易犯的错是按标注行数切同一张图片的多个目标会被拆到不同集合里造成训练集和验证集信息泄漏指标虚高。正确做法是整图划分每张图的所有框一起进同一个集合import os import random IMG_DIR JPEGImages random.seed(42) imgs sorted(os.listdir(IMG_DIR)) random.shuffle(imgs) total len(imgs) train_ratio, val_ratio 0.7, 0.15 train_imgs imgs[: int(total * train_ratio)] val_imgs imgs[int(total * train_ratio) : int(total * (train_ratio val_ratio))] test_imgs imgs[int(total * (train_ratio val_ratio)) :] def write_list(path, imgs): with open(path, w) as f: for name in imgs: jpg os.path.join(IMG_DIR, name) # yolo 训练时自动替换后缀找 labels f.write(jpg \n) write_list(train.txt, train_imgs) write_list(val.txt, val_imgs) write_list(test.txt, test_imgs) print(len(train_imgs), len(val_imgs), len(test_imgs))random.seed(42) 固定随机种子保证每次复现划分一致。1021 张按 7:2:1 切train 约 714 张、val 约 153 张、test 约 154 张。写入的 train.txt 只放 jpg 路径YOLO 会按同文件名自动去 labels 目录找 txt。这里有个隐藏要求jpg 的绝对路径前缀和你的 yaml 配置要一致否则训练直接报 “No labels found”。4.2 类别配置 yaml名称顺序不能错在项目目录下建一个 firc.yaml# 训练数据根目录请替换成你解压后的实际路径 path: /data/firc_animal train: train.txt val: val.txt names: 0: cattle 1: cow 2: sheep这里 names 的顺序就是 txt 里 class_id 的映射。如果数据集作者给的是 0cattle、1cow、2sheep你换成了别的顺序训练能跑完但 loss 会震荡模型会认为 sheep 的标签一直有矛盾最终收敛效果极差。这类问题最难排查因为根本不报错只能靠可视化猜。我一般会在正式训练前用 val.txt 跑一个空的模型预测把预测框的类别名打出来核一遍。4.3 小目标参数调整从 imgsz 到增强策略核心训练命令yolo detect train \ datafirc.yaml \ modelyolov8n.pt \ imgsz1280 \ batch8 \ epochs300 \ device0 \ cachedisk \ mosaic0.8 \ copy_paste0.3参数按重要性排一下参数推荐值或范围说明imgsz1024~1280输入分辨率越高小目标保留的像素越多显存压力也越大modelyolov8n.pt / yolov8s.pt预训练权重做迁移学习14K 框的数据集不适合从零训batch8~16由显存决定OOM 时优先降 batch而不是降 imgszepochs300目标分布不均衡收敛慢200 以下容易欠拟合mosaic0.5~0.8默认 1.0 对小目标伤害大建议降到 0.5~0.8copy_paste0.2~0.5对少样本类别帮助明显后面第 6 章细说cachedisk300 张以上的图建议用 disk用 ram 容易爆内存mosaic 默认开启时四张图会缩放到同一画布再随机裁剪小目标被缩放后可能只剩几个像素而且大概率被切出画面只留下错位的标签。这是小目标检测里最典型的增强副作用。降低 mosaic 概率再叠加 copy_paste让小目标样本保留原始尺度cow 类别的 AP 会有肉眼可见的提升。还有一个容易被忽略的细节loss 权重。YOLOv8 的 cls loss 权重默认是 0.5对极度不平衡的类别可以调高一点代价是其他类别的分类精度略降。如果你习惯用 easy 工具或脚本改模型配置把cls0.7传进去跑一版做对比。5. 避坑这份数据集最常见的五个翻车点5.1 整体 mAP 很高cow 类别等于白训现象训练完输出 mAP50 有 0.85 以上感觉效果不错但部署到现场后画面里出现牛模型却经常没反应。原因sheep 框多且特征明显把整体 mAP 拉高了cow 只有 401 框模型几乎没学会它的特征。解决训练时记录每类 APYOLOv8 可以用model.val()后读metrics.box.maps按类别看而不是只看均值。另外打印混淆矩阵类别不均衡时矩阵的行列和往往不等于输入样本数那是 TP/FP 统计受类别占比影响的正常表现别误判成数据错乱。5.2 训练顺利但验证集画框全部偏移现象模型收敛正常推理画框位置肉眼可见地偏移比如框比羊大一圈或偏左上。原因txt 的归一化坐标和 xml 的绝对坐标转换时不匹配最常见是图片有 EXIF 旋转信息opencv 读图自动做了旋转而 xml 里的宽高还是按原始像素写的坐标系对不上。解决训练前用 exiftool 批量清除无人机 jpg 的 EXIF 旋转标签或者统一用cv2.imread()读图确保取 width/height 时和实际解码后的数组一致。校验方法就是转回绝对坐标画框肉眼对比原始 xml 是否重合。5.3 mosaic 增强把羊裁没了小目标召回率上不去现象前几十个 epoch loss 降得很快后面 loss 平台期很短sheep 的召回率卡在某个数值上不去。原因mosaic 把 4 张小图缩放到 640×640sheep 本来就小缩放后变成 10 像素的小点裁剪时又被切掉一部分标签还保留着模型学到的全是残缺目标。解决把 mosaic 概率降到 0.5 甚至 0.3让部分 epoch 走完整尺度训练同时用 copy_paste 增强补充小目标样本。这个组合在航拍小目标上比我试过的其他增强更稳定。5.4 cattle 和 cow 的语义边界导致标注不一致现象训练途中发现验证集里部分牛的框模型一会标成 cattle 一会标成 cow。原因cattle 泛指牛cow 通常指成年母牛但航拍视角下公母很难分辨不同标注员有自己的判断尺度语义本身有重叠。解决如果业务只关心“牛”直接用脚本把 cow 全部替换成 cattle合并类别后框数 4529训练更稳如果业务需要区分成年牛和小牛就保留 3 类同时接受 cow 的低 AP 是标注主观性的结果。作者声明只保证标注准确合理这种边界问题得自己把关不属于数据集缺陷。5.5 显存 OOMbatch 降到底还是崩现象imgsz 设 1280 后训练直接 CUDA OOM把 batch 从 16 降到 4 仍然偶尔崩。原因航拍图原本分辨率高YOLO 训练时会做等比缩放再填充1280 输入下单张图的显存占用接近 8G4 张图加梯度缓存很容易超 24G 显存。解决优先把cacheram改成cachedisk释放内存再不行把 imgsz 降回 1024而不是继续降 batch——batch 太小会引入大量噪声loss 曲线抖得很厉害。想在高分辨率下训练又不爆显存可以开梯度累积相当于用时间换显存。6. 进阶类别不平衡对策与大图验证技巧6.1 三招对付 cow 只有 401 框的局面第一招是过采样。训练时让含 cow 的图片在一个 epoch 里多出现几次最简单的方式是按路径过滤后拼接采样队列。第二招是 copy_paste 增强把 cow 的框从原图抠出来随机粘贴到不含牛的大图上同步生成新的标注框。第三招是调 loss 权重提高 cow 对应的分类损失系数。三招可以叠加我实际跑下来效果最明显的是第二招第三招组合cow 的 AP 能提升 8 到 15 个百分点。6.2 验证习惯逐类 AP 与大图验证脚本from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datafirc.yaml, imgsz1280) print(metrics.box.maps) # 依次为 cattle, cow, sheep 的 mAP50-95只打印整体 mAP 会把 cow 的问题盖住所以我一律打印 per-class 列表对比三个数就能快速判断是模型容量不足还是数据分布问题。如果只有整体精度高那这个模型在真实牧场上大概率不落地。对大图还有一个值得试的推理技巧先切成 640 或 1280 的滑窗小块分别推理再把结果拼回去小目标的召回率会比整图缩放高很多。这个思路在航拍场景尤其适用因为原图分辨率本来就有 4000 像素级别整图缩放损失的信息太多。从那以后我每次拿到一份目标检测数据集第一件事永远是先跑一遍统计脚本把每类框数和尺寸分布打出来再决定输入分辨率、增强策略和 loss 权重而不是盲目套模板开训。这份智慧牧场数据集我已经复现过不止一次每次拿它验证新模块都能发现不同的问题。希望帮到你。本文还有配套的精品资源点击获取