简介这份资源是面向计算机视觉开发者与农业智能化研究者的牛只姿态检测标注数据集可用于训练和评估动物关键点检测算法适合具备深度学习基础、希望切入畜牧行为分析场景的中高级学习者。压缩包共795个文件约603.28MB其中467个jpg图像为原始牛只场景照片328个json文件为Labelme标注结果每头牛精确标记了头部、眼睛、耳朵、颈部、躯干及四肢关节等16个关键点可直接用于OpenPose、DeepPose等关键点检测模型的训练与验证。目前已有2186人学习下载说明该数据在同类任务中具备一定参考价值。借助这份数据读者能够跳过繁琐的图像采集与人工标注环节直接进入模型选型、训练调优与泛化验证阶段也可用于疾病预警、舒适度评估等农业智能化课题的实验复现与二次开发。1. 牛只姿态检测数据集一份标注好的 zip 到底能省掉多少活拿到「牛只姿态检测数据集已标注.zip」这个标题很多人第一反应是解压看图片然后直接丢进 YOLO 训练脚本里跑。我见过不止一个团队这么干结果 mAP 卡在 0.4 上不去回头查了两天才发现标注框把整头牛和栏杆一起框进去了。牛只姿态检测这件事本质上是把目标检测从「牛在哪」推进到「牛在干什么」——站立、躺卧、行走、爬跨、进食这些姿态直接对应发情识别、跛行预警、卧床时长统计等牧场刚需。一份已标注的数据集能省掉的是标注工时省不掉的是格式对齐、关键点定义、场景分布核查这些脏活。这篇写给两类人一类是刚接手畜牧视觉项目、想快速跑通基线的算法同学另一类是想评估这份数据值不值得投入的工程负责人。下面按「数据长什么样 → 怎么转成可训练格式 → 怎么训出能用的姿态模型 → 坑在哪」的顺序讲透。2. 先摸清数据底细姿态标注的三种形态与选型判断2.1 目标框、关键点、姿态分类别混为一谈「姿态检测」这个词在牛只场景里至少有三种落地形态选错了后面全白干。第一种是纯目标检测框标注只有x_center, y_center, w, h能回答「画面里有几头牛、各在哪」但回答不了「这头牛是站着还是躺着」。第二种是关键点标注每头牛标出肩、髋、前膝、后膝、蹄等若干点位配合框一起用能算出关节角度进而判断跛行和卧姿。第三种是姿态分类标签直接给每头牛一个standing / lying / walking / feeding的类别通常和检测框绑定成「框 类别」的形式。一份 zip 里到底属于哪种解压后看标注文件就能判断.txt里每行 5 个数是 YOLO 检测框每行 5 2N 个数N 为关键点数是 YOLO-Pose 格式如果是.json且带keypoints字段多半是 COCO 关键点格式如果每行只有class_id x y w h且类别名是姿态词那就是「检测框 姿态类别」的混合标注。我一般会先跑一段统计脚本把类别分布、框数量、关键点数量摸清楚再决定训练路线。import os from collections import Counter label_dir labels/train cls_counter Counter() box_per_img [] kp_lens set() for fn in os.listdir(label_dir): if not fn.endswith(.txt): continue with open(os.path.join(label_dir, fn)) as f: lines [l.strip() for l in f if l.strip()] box_per_img.append(len(lines)) for line in lines: parts line.split() cls_counter[parts[0]] 1 # 5 个数是纯检测框多出来的是关键点坐标 kp_lens.add((len(parts) - 5) // 2) print(类别分布:, cls_counter) print(每图框数 min/max/avg:, min(box_per_img), max(box_per_img), sum(box_per_img)/len(box_per_img)) print(关键点数量集合:, kp_lens) # {0} 表示纯检测框这段脚本的价值在于三个判断类别分布严重倾斜比如lying占 80%说明后面要加权或重采样每图框数最大值异常大比如 40说明有密集栏舍场景小目标检测要调 anchor关键点数量集合如果是{0}那这份数据只能做检测 姿态分类做不了关节角度回归。参数上没什么可调的label_dir换成你的实际路径即可注意 YOLO 的标签目录通常和images平行。2.2 场景分布比标注质量更容易被忽略标注质量可以靠抽检 50 张看出来场景分布却要统计才暴露。牛舍视觉的难点集中在四类场景俯视通道牛只重叠严重、侧视卧床躺卧牛被栏杆遮挡、夜间红外对比度低、纹理丢失、室外放牧背景杂乱、光照剧变。如果 zip 里 90% 是白天俯视通道你训出来的模型搬到侧视卧床场景必然翻车。我的做法是给每张图打三个元标签视角俯视/侧视/斜视、光照白天/夜间/逆光、密度单牛/群牛。不用人工全标用文件夹名或文件名前缀推断即可很多数据集本身就按day_side/、night_top/这样分目录。统计完画一张交叉表看哪个格子样本少于 200 张那个格子就是你的短板场景训练时要单独切验证集。场景维度取值低于 200 张时的风险视角俯视 / 侧视 / 斜视侧视样本少躺卧姿态召回崩光照白天 / 夜间 / 逆光夜间少红外图直接漏检密度单牛 / 群牛群牛少重叠场景 NMS 后大量丢失2.3 选型结论这份数据适合走哪条训练路线摸完底就能定路线了。如果标注是「框 姿态类别」走 YOLOv8/v11 的检测头把姿态词当类别最简单也最稳适合做卧床时长统计这类业务。如果是关键点格式且关键点定义完整≥8 点走 YOLO-Pose 或 RTMPose能算关节角度做跛行评分但要求关键点标注一致性高抽检时重点看同一姿态下膝点位置是否漂移。如果只有纯检测框没有姿态标签别硬做姿态分类先用框做计数和跟踪姿态用规则框的宽高比 时序稳定性粗判等积累够标注再升级。常见做法是先跑检测基线拿到 0.85 以上的 mAP再决定要不要投入关键点标注这个顺序反了会浪费大量标注预算。3. 把 zip 变成可训练集格式转换与目录规范3.1 解压后的目录体检与清洗zip 解压出来常见三种脏结构图片和标签混在同一层、标签文件名和图片名对不上多了前缀或扩展名不一致、混进了__MACOSX或缩略图缓存。先做一次体检把孤儿标签没有对应图片和孤儿图片没有标签列出来孤儿图片要么补标要么剔除别留着当负样本——牛只场景的负样本应该是「空栏舍」而不是「漏标的牛」。# 体检列出没有对应图片的标签文件 cd dataset for f in labels/*/*.txt; do base$(basename $f .txt) img$(find images -name ${base}.jpg -o -name ${base}.png | head -1) [ -z $img ] echo 孤儿标签: $f done # 清理系统缓存目录 find . -name __MACOSX -type d -exec rm -rf {} find . -name ._* -delete第一段循环是核心find里同时匹配.jpg和.png是因为很多数据集两种格式混用。跑完如果孤儿标签超过 5%说明打包时图片和标签不是同源导出的得回头找原始清单核对别硬训。第二段清理缓存目录是血泪经验__MACOSX里的._xxx.jpg会被某些数据加载器当成真图片读进去报一堆解码错误。3.2 转成 YOLO 检测格式的脚本与四个边界坑如果原始标注是 COCO json 或 VOC xml需要转成 YOLO 的class x_center y_center w h归一化到 0~1。转换脚本本身不长坑全在边界处理上。import json, os from PIL import Image def coco_to_yolo(json_path, img_dir, out_dir, class_map): with open(json_path) as f: data json.load(f) img_info {im[id]: im for im in data[images]} os.makedirs(out_dir, exist_okTrue) for ann in data[annotations]: im img_info[ann[image_id]] W, H im[width], im[height] x, y, w, h ann[bbox] # COCO 是左上角 宽高 # 坑1越界裁剪框超出图像边界要截断 x1, y1 max(0, x), max(0, y) x2, y2 min(W, x w), min(H, y h) if x2 - x1 4 or y2 - y1 4: # 坑2过滤极小框 continue cx, cy (x1 x2) / 2 / W, (y1 y2) / 2 / H nw, nh (x2 - x1) / W, (y2 - y1) / H cls class_map[ann[category_id]] line f{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n out os.path.join(out_dir, os.path.splitext(im[file_name])[0] .txt) with open(out, a) as fo: # 坑3追加模式一图多框 fo.write(line) coco_to_yolo(annotations.json, images, labels/train, class_map{1: 0, 2: 1, 3: 2, 4: 3, 5: 4})四个边界坑分别是越界裁剪牛只贴边时框会超出图像不裁剪归一化后坐标大于 1训练时被静默丢弃、极小框过滤标注噪声里常有 2×3 像素的误标留着会拉低小目标精度、追加模式一图多框必须a不能w否则只剩最后一个框、类别映射COCO 的category_id往往从 1 开始且不连续必须显式映射到 0 起的连续整数直接减 1 会错位。class_map按你数据里实际的类别 id 填转换后务必抽 10 张用可视化脚本画框核对别信脚本不报错就等于对。3.3 划分训练验证集时按场景分层别随机切随机train_test_split在牛舍数据上是个陷阱同一头牛、同一机位、相邻帧的图片会被随机分到训练和验证两侧验证集 mAP 虚高 10 个点以上上线就现原形。正确做法是按「机位 日期」分组切分同一机位同一天的图只进一侧。如果数据里没有机位和日期信息退而求其次按视频片段切连续帧号的前 80% 进训练、后 20% 进验证。import os, random from collections import defaultdict # 假设文件名形如 cam01_20240315_000123.jpg groups defaultdict(list) for fn in os.listdir(images/all): parts fn.split(_) key f{parts[0]}_{parts[1]} # 机位 日期 groups[key].append(fn) keys list(groups.keys()) random.seed(42) random.shuffle(keys) split int(len(keys) * 0.8) train_keys, val_keys set(keys[:split]), set(keys[split:]) for phase, kset in [(train, train_keys), (val, val_keys)]: with open(f{phase}.txt, w) as f: for k in kset: for fn in groups[k]: f.write(fimages/all/{fn}\n)分组键cam01_20240315是关键它保证同一机位同一天的所有帧整体进同一侧。random.seed(42)固定切分结果方便复现和对比实验。生成的train.txt / val.txt是 YOLO 支持的列表式数据配置比目录式更灵活也方便你后续按场景再切子集做消融。4. 训练牛只姿态模型参数怎么设、指标怎么看4.1 基线配置从检测头起步的最小可用参数先跑检测基线别一上来就上关键点。以 YOLOv8n 为例输入 640、batch 16、100 epoch 是牛舍场景比较稳的起点。牛只目标普遍偏大俯视通道下单头牛占画面 1/4 以上所以imgsz不用拉到 1280640 足够且显存友好但如果你的数据以群牛远景为主单头牛只有 60×40 像素那就得升到 960 并开multi_scale。# data.yaml path: ./dataset train: train.txt val: val.txt names: 0: standing 1: lying 2: walking 3: feeding 4: mountingyolo detect train modelyolov8n.pt datadata.yaml \ epochs100 imgsz640 batch16 \ lr00.01 lrf0.01 warmup_epochs3 \ hsv_h0.015 hsv_s0.7 hsv_v0.4 \ degrees0 fliplr0.5 mosaic1.0 close_mosaic10 \ patience20 projectruns namebovine_pose_v1参数逐个说lr00.01是 SGD 默认起点如果你换 AdamW 要降到 0.001close_mosaic10表示最后 10 个 epoch 关掉马赛克增强这一步对姿态分类精度影响明显因为 mosaic 会把不同姿态的牛拼在一起边界处语义混乱degrees0是因为牛舍机位固定旋转增强反而引入不真实视角侧视场景可以给到 5fliplr0.5左右翻转安全牛只姿态左右对称但如果你有关键点且左右关键点语义不同比如左前蹄 vs 右前蹄翻转时要同步交换关键点索引否则关键点全乱。patience20是早停牛舍数据噪声大验证集 mAP 抖动常见给足 20 轮再停。4.2 姿态分类的类别不平衡处理姿态类别天然不平衡躺卧样本远多于爬跨爬跨又恰恰是发情识别的关键类别。直接训会让模型对爬跨几乎无召回。三种处理按投入排序最简单是cls_pw类别权重在损失里加权YOLO 系列可通过自定义损失或采样实现中等是过采样少数类把爬跨样本复制 3~5 份进训练列表最重是合成增强用 copy-paste 把爬跨牛只贴到空栏舍背景上。我一般先试过采样成本最低效果不够再上加权。from collections import Counter import random with open(train.txt) as f: lines [l.strip() for l in f] # 统计每张图包含的类别读对应标签文件 def img_classes(img_path): lbl img_path.replace(images, labels).rsplit(., 1)[0] .txt with open(lbl) as f: return {l.split()[0] for l in f if l.strip()} cls_imgs {c: [] for c in 01234} for p in lines: for c in img_classes(p): cls_imgs[c].append(p) target max(len(v) for v in cls_imgs.values()) balanced [] for c, imgs in cls_imgs.items(): if not imgs: continue repeat max(1, target // len(imgs)) balanced.extend(imgs * min(repeat, 5)) # 上限 5 倍防过拟合 random.shuffle(balanced) with open(train_balanced.txt, w) as f: f.write(\n.join(balanced))min(repeat, 5)这个上限很重要少数类复制超过 5 倍会严重过拟合模型开始背图而不是学特征。跑完对比train.txt和train_balanced.txt的类别分布确认爬跨类占比从个位数提到 15% 左右即可不必追平。注意过采样只作用于训练列表验证集保持原始分布否则指标不可比。4.3 关键点路线YOLO-Pose 的标注一致性校验如果数据带关键点训 YOLO-Pose 之前必须做一致性校验否则 loss 降不下去还找不到原因。校验方法很简单把同一姿态比如全部standing的样本抽 30 张把关键点画在原图上拼成网格图人眼扫一遍。常见问题是膝点标注在关节上方还是下方不统一、被遮挡的腿是标在可见位置还是估计位置不统一。这两种不一致会让模型学到矛盾的回归目标表现为关键点 OKS 卡在 0.6 左右上不去。import cv2, numpy as np def draw_kps(img_path, lbl_path, kp_names, out_path): img cv2.imread(img_path) H, W img.shape[:2] with open(lbl_path) as f: for line in f: p list(map(float, line.split())) cls, cx, cy, w, h p[:5] kps np.array(p[5:]).reshape(-1, 3) # x, y, visibility for i, (x, y, v) in enumerate(kps): if v 0: continue px, py int(x * W), int(y * H) cv2.circle(img, (px, py), 3, (0, 255, 0), -1) cv2.putText(img, kp_names[i], (px4, py-4), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1) cv2.imwrite(out_path, img)visibility字段0 不可见 / 1 可见 / 2 遮挡是 YOLO-Pose 的关键校验时重点看v2的点位是否标在了合理估计位置。如果数据集里这个字段全是 1说明标注方没区分遮挡训练时遮挡样本会拖累精度建议在 loss 里对遮挡点降权或干脆过滤。kp_names按你数据的关键点定义顺序填画出来的网格图人工过一遍比看任何指标都直接。5. 避坑与排查牛只姿态数据落地最常见的五个翻车点5.1 验证集 mAP 很高上线漏检严重现象本地验证 mAP50 到 0.92部署到新机位后站立牛只漏检一半。原因几乎总是数据泄漏——同一机位相邻帧被随机分到了训练和验证两侧模型记住了背景而不是牛。解决按 3.3 的分组切分重做数据集重训后 mAP 通常会掉 8~12 个点这个掉下来的才是真实水平。判断有没有泄漏的快速方法看验证集里有没有和训练集文件名只差帧号的图片有就是泄漏。5.2 夜间红外图整批漏检现象白天指标正常夜间样本召回接近 0。原因有两个一是夜间样本在训练集里占比太低常见低于 5%二是红外图是单通道或伪彩直接喂给按 RGB 预训练的 backbone 分布不匹配。解决先把夜间样本过采样到 15% 以上再在数据加载时对红外图做通道复制灰度转三通道并单独做一次直方图均衡别指望模型自己适应。如果夜间样本实在少考虑把红外和可见光分开训两个模型按时间切换比强行混训稳。5.3 躺卧和站立混淆框对了类别错现象检测框位置很准但lying被判成standing混淆矩阵里这两类互相串。原因是俯视视角下躺卧和站立的框形状差异小模型靠框内纹理判断而牛背纹理在两种姿态下相似。解决给类别判断补时序信息——单帧不可靠就用跟踪连续 5 帧框中心位移小于阈值判为躺卧或者在训练时加入框的宽高比作为辅助特征。纯视觉单帧方案在这个点上天花板明显别死磕。5.4 关键点 loss 震荡不收敛现象YOLO-Pose 训练时关键点 loss 在 0.3~0.8 之间反复横跳检测 loss 却正常下降。原因通常是关键点标注坐标系不一致——部分样本关键点是相对整图归一化部分是相对检测框归一化两种混在一起模型学不动。解决写脚本抽查 20 个标签文件把关键点坐标反归一化回像素看是否落在对应检测框内落在框外的就是坐标系错了统一转成整图归一化再训。5.5 数据增强把姿态语义改坏了现象训练 loss 正常验证精度上不去可视化预测发现模型对翻转后的牛判断错乱。原因是开了fliplr但关键点没同步交换左右索引或者开了大角度旋转把站立牛转成了不存在的姿态。解决有关键点时翻转必须成对交换左右关键点索引degrees控制在 ±5 以内mosaic在最后 10 轮关掉。增强的目的是模拟真实变化牛舍里牛不会倒立别为了涨点开一堆不真实的增强。6. 从能跑到好用姿态时序后处理与验收方法单帧姿态检测只是半成品真正决定这套数据值不值得投入的是后处理能不能把帧级结果变成业务可用的姿态事件。我一般会在检测输出后面挂一个轻量时序模块对每头牛用跟踪 ID 串起来维护一个长度 15 的滑动窗口窗口内统计各姿态占比占比超过 60% 才确认为该姿态避免单帧抖动导致卧床时长统计忽高忽低。这个模块不需要训练纯规则二十行代码就能写但对业务指标的提升比换更大 backbone 明显得多。from collections import deque, Counter class PoseSmoother: def __init__(self, win15, thresh0.6): self.win win self.thresh thresh self.buf {} # track_id - deque def update(self, track_id, pose_cls): if track_id not in self.buf: self.buf[track_id] deque(maxlenself.win) self.buf[track_id].append(pose_cls) cnt Counter(self.buf[track_id]) top, n cnt.most_common(1)[0] # 窗口未满或占比不够时返回 None表示姿态未确认 if len(self.buf[track_id]) self.win or n / self.win self.thresh: return None return topwin15对应 25fps 下约 0.6 秒足够滤掉单帧误判又不至于让姿态切换太迟钝thresh0.6是经验值爬跨这类短时行为可以降到 0.5 提高灵敏度卧床统计可以升到 0.7 求稳。返回None的设计很关键它让下游知道「当前姿态不确定」而不是硬给一个可能错的标签。验收方法上别只看 mAP。我习惯做三层验收第一层是帧级看各类姿态的 precision/recall重点盯少数类第二层是事件级人工标 2 小时视频里的姿态切换时刻和模型输出比对算事件级 F1这个指标才贴近业务第三层是业务级比如卧床时长统计误差控制在 ±8% 以内、爬跨事件漏报率低于 15%。三层都过才算这份数据真正用起来了。参数上事件级验收的容差窗口一般给 ±3 秒太严会把正常检测延迟算成错误。最后说个我踩过的坑早期我拿到标注数据就直接训训完指标漂亮就交付结果业务方反馈「牛躺着的时长统计出来比实际少一半」。回头查是躺卧样本里有一批是侧视被栏杆遮挡的标注框只框了露出的半身模型学到的是「半身 躺卧」完整躺卧反而判成站立。从那以后我养成了习惯——任何姿态数据集先按场景分层抽 50 张画框看一遍再动手这一步花半小时能省掉后面两周的返工。希望帮到你。本文还有配套的精品资源点击获取