简介:面向智能养殖与动物行为识别研究,PigBehaviorRecognitionDataset提供经过标注的猪只姿态数据集,涵盖躺卧、睡眠、探索、进食、行走、骑跨六类常见行为,可用于训练目标检测或姿态分类模型,服务健康监测、疾病预警与发情识别等场景。资源包共2000个文件,全部为JSON格式标注文件,每个文件以标准结构保存姿态类别与空间位置信息,并附有按训练集、验证集划分的类别统计;压缩后大小为809.17MB,适合直接接入主流深度学习框架。目前已有262人学习下载。借助该数据集,研究者可省去繁重的数据采集与标注环节,快速开展猪只行为识别模型的训练与验证;也可结合自身数据迁移学习,用于构建智能养殖系统、优化饲喂策略或实现疾病早期预警,为动物福利评估与精准养殖算法研发提供可靠支撑。
1. 猪姿态检测数据集在解决什么问题:从“看到猪”到“看懂猪”
做智慧养殖项目的人,早晚会撞上这个瓶颈:猪只检测跑通了,但甲方真正问的不是“图里有几头猪”,而是“这头猪现在在干什么”。猪姿态检测数据集 PigBehaviorRecognitionDataset 就是冲着这个需求来的——它把监控视频里的猪,按站立、躺卧、进食、饮水、行走等行为状态打上标签,让识别链路从“检测到猪”推进到“理解猪的行为”。这类数据集的难点不在“框得准不准”,而在“姿态类别怎么定义、时间轴上的行为怎么连贯”,很多团队拿它训练出的模型单帧准确率不低,一上视频就抖动、跳变,根子往往在标注规范和时序策略上。这篇文章会把数据集结构、格式转换、训练参数和踩坑点一次讲透,适合正在做智慧养殖视觉落地的工程师和研究生照着复现。
2. 读懂PigBehaviorRecognitionDataset的标注体系:姿态与行为的边界怎么画
2.1 姿态类别定义:数据集的标签体系是模型性能的上限
拿到任何行为识别数据集,第一件事不是看图片,而是看标签清单。PigBehaviorRecognitionDataset 这类数据集通常会把“姿态(posture)”和“行为(behavior)”分成两个层次来标。姿态是静态的,比如站立、侧卧、腹卧、坐立;行为是带时间语义的,比如进食、饮水、行走、拱地、打斗。有的版本把这两类混在一个标签集合里,有的拆成两个任务文件,这直接决定你后续是训一个多分类检测器,还是训“检测+时序分类”的两段式模型。
以常见的五类标签为例:standing、lying_lateral(侧卧)、lying_sternal(腹卧)、eating、walking。这里最容易被低估的是侧卧和腹卧的区分——俯视相机里,猪侧卧时身体轮廓更宽、四肢朝向一侧,腹卧时四肢收在身体下方、背部轮廓更窄。如果标注规范里没写清楚“以肩宽和四肢可见性为准”这类判别标准,不同标注员会给出不一致的框和标签,模型训练出来就在这两个类之间反复横跳。
下表是我通常建议团队在开工前核对一遍的标签定义表,具体类别名以你手上的数据集清单为准:
| 标签 | 定义 | 可观察信号 | 养殖场景价值 |
|---|---|---|---|
| standing | 四肢直立承重 | 背部水平、腹部离地 | 采食、活动基础的姿态 |
| lying_lateral | 侧躺,四肢伸向一侧 | 体宽明显、单侧肢体可见 | 深度休息、可能发烧或产后恢复 |
| lying_sternal | 腹卧,四肢收于腹下 | 体窄、背部拱起 | 轻度休息、警惕状态 |
| eating | 头部持续在食槽区域 | 头部下探、嘴部接触料线 | 采食量估算的关键状态 |
| walking | 位移+四肢交替摆动 | 检测框中心连续移动 | 活动量、跛行检测的依据 |
这个表的意义在于:它把“类别名”翻译成了“模型能从图像里学到的视觉信号”。如果数据集文档里没有给出这类明确定义,我一般会先自己写一份,再拿十来张图做一致性抽检。标注的一致性比标注数量更影响模型上限,这一点在后面避坑章节还会展开。
2.2 采集视角、标注格式与目录结构:拿到数据集后先看清这三件事
第二个要确认的是采集视角和传感器类型。PigBehaviorRecognitionDataset 一类的数据多数来自猪舍顶部俯视的 RGB 或深度相机,少数来自走廊侧视。俯视的好处是猪只彼此遮挡少、个体轮廓完整,坏处是“站立”和“行走”在单帧里几乎长得一样,必须靠时序信息才能区分。侧视的好处是能看到嘴部动作和食槽接触,坏处是群体场景里遮挡严重,检测框经常断。你先想清楚自己的部署机位和数据集采集视角是否一致,不一致的话迁移效果会打折扣。
标注格式方面,这类数据集常见三种形态:Pascal VOC 的 XML、COCO 的 JSON、YOLO 的 TXT,姿态类项目还可能附带关键点标注(耳朵、肩胛、尾根)。如果你拿到的原始包是 VOC,而训练工具是 YOLO 系,就必须先做格式转换。典型的目录结构长这样,你拿到手后先对一遍:
PigBehaviorRecognitionDataset/ ├── annotations/ │ ├── train/ │ │ ├── 001.xml │ │ └── 002.xml │ └── val/ ├── images/ │ ├── train/ │ └── val/ ├── labelmap.txt └── README.md注意两点。第一,确认 train/val 的划分是“按图片随机切”还是“按猪只个体切”。前者实现简单,但同一头猪的连续帧会同时出现在训练集和验证集里,导致验证指标虚高,这个坑我放在第 5 章细讲。第二,确认 labelmap.txt 里类别顺序和 XML 里的 name 字段是否一致,很多转换脚本翻车都是因为这个顺序对不上。拿到数据集后,我建议先用一行命令把类别分布打出来,看看是不是长尾分布:
grep -h "<name>" annotations/train/*.xml | sort | uniq -c | sort -rn这一步能让你在看到训练曲线之前,就预判哪些类会掉点。类别数量差距超过 10 倍,就要准备类别权重或过采样,而不是指望模型自己学均匀。
3. 把数据集转成YOLO格式并跑通第一次训练:转换脚本、配置与命令
3.1 VOC/COCO转YOLO的转换脚本:类别映射、路径修正与空标签
大多数猪姿态检测项目最终会落到 YOLO 系模型上,因为养殖现场的推理设备往往是 Jetson 或工控机,YOLO 的部署生态最成熟。所以拿到 VOC 格式的 PigBehaviorRecognitionDataset,第一步是写一个可靠的转换脚本。这里给一个我常用的 VOC 转 YOLO 脚本,按需修改即可:
import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_txt_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_map: continue # 跳过未映射的类别 box = obj.find('bndbox') x1 = float(box.find('xmin').text) y1 = float(box.find('ymin').text) x2 = float(box.find('xmax').text) y2 = float(box.find('ymax').text) # 归一化,计算YOLO格式的中心点和宽高 cx = ((x1 + x2) / 2) / img_w cy = ((y1 + y2) / 2) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h # 防止边界框超出图像范围导致的训练警告 cx = max(0.0, min(1.0, cx)) cy = max(0.0, min(1.0, cy)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) lines.append(f"{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_txt_path, 'w') as f: f.write("\n".join(lines)) class_map = { 'standing': 0, 'lying_lateral': 1, 'lying_sternal': 2, 'eating': 3, 'walking': 4, } xml_dir = Path('annotations/train') out_dir = Path('labels/train') out_dir.mkdir(parents=True, exist_ok=True) for xml_file in xml_dir.glob('*.xml'): voc_to_yolo(xml_file, out_dir / (xml_file.stem + '.txt'), class_map) print(f"转换完成,共处理 {len(list(xml_dir.glob('*.xml')))} 个文件")脚本逻辑不复杂,但有两个细节值得说明。第一个是“跳过未映射类别”,数据集里偶尔会出现你没打算训练的杂类标签,比如 personnel(饲养员)、unknown,直接跳过比强行归入某一类更干净。第二个是边界框 clamp 到 [0,1],XML 里偶尔有标注员手滑把 xmax 画超出图像边界,YOLO 训练时会出现“all box coordinates are out of bounds”的警告,提前夹住能省掉不少排查时间。
转换完成后,务必做一次反向校验:随机抽几张图,把 txt 里的坐标画回去看是否贴合原标注。常见的做法是写十行 OpenCV 代码画框对比,也可以直接用 LabelImg 打开原图和 txt。这一步看着笨,但能拦住脚本里 class_map 顺序错误这类低级问题,值得花五分钟。
3.2 训练配置与启动命令:用YOLOv8第一次跑通猪姿态检测
转换好标签后,创建一个数据配置文件。以 YOLOv8 为例,YAML 内容如下,路径用绝对路径或相对路径都可,但 train/val 路径必须指向你刚建好的 images 目录:
# pig_pose.yaml path: ./datasets/PigBehaviorRecognitionDataset train: images/train val: images/val nc: 5 names: ['standing', 'lying_lateral', 'lying_sternal', 'eating', 'walking']注意 names 的顺序必须和转换脚本里的 class_map 编号严格一致。这一步如果错位,模型会“张冠李戴”——框的位置是对的,类别全是错的,而且训练 loss 看起来还挺正常,属于最坑的翻车方式之一。
启动训练的命令,我一般这样写:
yolo detect train \ data=./pig_pose.yaml \ model=yolov8s.pt \ epochs=60 \ imgsz=640 \ batch=16 \ device=0 \ patience=15 \ project=./runs/pig_pose参数含义和选择理由:model 用 yolov8s.pt 而不是 v8n,是因为猪体目标在俯视画面里占比大,但姿态差异(腹卧和侧卧)需要足够的特征容量,s 是性价比平衡点;imgsz=640 是默认值,如果你的采集画面是 1080p 且猪只偏小,可以提到 960,显存不够就保持 640;patience=15 表示 15 个 epoch 验证指标没提升就早停,能省时间;batch=16 以 16G 显存为参考,显存小就降到 8。第一次训练不要一上来就调一堆增强参数,先把这条链路跑通,再看验证集每类的 mAP。
如果你拿到的数据集带关键点标注(比如耳朵、背部中心、尾根),可以改用 YOLO pose 训练,命令几乎一样,只是把detect换成pose,数据 YAML 里需要额外指定 kpt_shape。关键点模型的好处是能直接输出“头朝向”和“四肢分布”,对区分进食和站立很有帮助。没有关键点也能做,行为分类照样能跑,只是少了一个强信号。
4. 从检测到行为识别的三个必调参数:帧率、滑动窗口与类别置信度
4.1 单帧检测为什么不够:行为识别需要时间轴上的连续性
单帧检测模型训练完成后,你很快会发现一个现象:视频里同一头猪,前一帧还是 standing,后一帧变成 walking,再后一帧又变回 standing。实际上这头猪就没动过。原因在于“站立”和“行走”在单帧图像上的差异极小——前后腿有没有交错、检测框有没有位移,这都需要时间上下文才能判断。这不是模型笨,而是任务本身是时序性的,你却在用静态图的方式解。
所以做猪姿态检测数据集落地,正确的技术路线是“检测 + 时序分类”。具体地说,先让检测模型输出每帧的类别,再用一段时间窗口内的类别序列做平滑决策。这个思路和动作识别里常用的 SlowFast、TSM 是同一套逻辑,只是养殖场景大多数不需要那么重的模型——因为猪的行为变化节奏比人的动作慢得多,轻量时序处理就能取得很好的效果。参数调得对不对,往往比模型选得大不大更影响最终效果。
4.2 帧率、滑动窗口长度和重叠率怎么定
时序后处理有三个参数必须自己定,数据集的 README 里通常不会给你答案:采样帧率、滑动窗口长度、窗口重叠率。这三个参数互相牵制,调整顺序一般是先定帧率,再定窗口,最后定重叠率。
帧率方面,我建议先确认原始视频或图像序列的采集帧率。如果原始数据是 25~30 FPS 的视频流,你不需要每帧都送模型推理——猪的姿态变化周期通常在秒级,5~10 FPS 的采样率足够捕获进食、行走等行为切换。推理帧率降下来,工控机的负载直线下降,这在实际部署里比模型精度更值钱。
滑动窗口长度 W 和步长 S 的组合,我放在下面的表里,按“行为最短持续时间”来选:
| 场景 | 帧率 F (FPS) | 窗口长度 W (帧) | 步长 S (帧) | 实际覆盖时长 |
|---|---|---|---|---|
| 快速行为(咬架、突然起卧) | 10 | 15 | 5 | 1.5 秒窗口,0.5 秒更新 |
| 常规行为(采食、行走) | 5 | 25 | 5 | 5 秒窗口,1 秒更新 |
| 慢行为(躺卧休息监测) | 1 | 30 | 5 | 30 秒窗口,5 秒更新 |
窗口长度选 W = F × 行为最短持续时长,步长一般取 W/5 到 W/3。覆盖时长的含义是:模型每次决策看到的时间跨度。跨度太短,抖动压不住;跨度太长,行为切换的响应延迟变大,比如猪已经离开食槽两秒了,系统还判定它在进食,这会直接带偏采食时长统计。
4.3 行为类别的置信度阈值与样本不均衡处理
检测模型每个框会输出一个总置信度,但在行为识别里,我建议把“框置信度”和“类别置信度”分开调。框置信度管的是“这里有没有猪”,类别置信度管的是“这头猪在干什么”。养殖场景里常见的问题是把框置信度设得太低,导致大量半遮挡的猪进入时序模块,类别预测乱跳。我一般把框置信度设在 0.45~0.55,类别置信度设在 0.35~0.5,后者低于前者,因为类别预测天然比目标存在性更容易混淆。
设置代码在 YOLO 推理参数里直接配置:
yolo detect predict \ model=./runs/pig_pose/weights/best.pt \ source=./test_video.mp4 \ conf=0.45 \ iou=0.5 \ max_det=50这里的 conf=0.45 是总置信度阈值,想分开控制类别阈值,需要在后处理脚本里读results[0].boxes.data的第五列(类别概率),单独写过滤逻辑,而不是依赖这个命令行参数。
类别不均衡是行为数据集的常态:猪每天睡十几个小时,lying 类样本可能占总量的 60% 以上,walking 可能只占 5%。训练时不开任何处理,walking 的 mAP 会很难看。常见做法有三种:一是给 YOLOv8 的 loss 加 class weights,按“总样本数 / 类别样本数”归一化;二是对少数类做过采样,把连续帧复制进训练集;三是在后处理阶段对少数类放宽类别置信度阈值。其中第三种成本最低、见效最快,我一般先试这个,还不行再回到训练端处理。注意一点,过采样视频帧容易让模型对特定猪只个体过拟合,跨栏舍泛化会变差,样本复制次数控制在 2 倍以内比较稳。
5. 猪姿态检测数据集的避坑指南:标注翻车、漏标与迁移学习的坑
5.1 侧卧与腹卧标注翻车:现象、原因与解决
这是我在实际项目里遇到最多的标注问题。现象是训练曲线收敛得很漂亮,验证集 mAP 也不错,但一部署到新栏舍,模型把大量侧卧判成腹卧,或者反过来。原因在于俯视视角下,一个瘦一点的侧卧猪和一个背拱的腹卧猪,轮廓形态非常接近,标注员如果没有统一的判别规则,边界案例全靠手感,标签一致性不足。解决分两层:一是先清洗标注,抽取每个类别的困难样本做二次标注,把明显标错的帧挑出来改掉;二是模型层面增加输入分辨率,让模型看到更多纹理细节(比如四肢是否外露),imgsz 从 640 提到 768 或 896 会有改善。注意这是缓解,不是根治,根治还得靠标注规范的明确化。
5.2 群体场景漏标导致的数据不平衡:怎么发现与补救
现象:训练好的模型在单猪栏表现正常,一到群体栏舍,检测框数量明显少于真实猪只数,尤其是躺卧在角落的猪经常被漏掉。原因很直接——原始标注时,群体画面里角落的、遮挡的、离镜头远的猪只被标注员忽略,漏标区域在模型看来成了“背景的合法样子”。这种数据偏差在验证集上不明显,因为验证集同样来自原始标注,漏标是“一致”的。解决方法是采样检查:每类随机抽 20 张图,数一下“可见但未标注”的猪头数。如果漏标率超过 5%,就值得重标一轮。重标时别只补框,把类别也确认一遍,顺带修正上一轮的姿态误标。
5.3 光照与地面反光让检测框漂移:数据增强与曝光统一
现象:白天靠窗一侧的栏舍,地面水泥反光强烈,猪只边缘和背景融合,检测框时大时小,置信度忽高忽低。原因很朴素——训练数据里如果有大量暗光或强反光的图,模型会把“反光区域”学成猪的一部分;而训练集里没有的极端光照模式,推理时就成了干扰。解决优先在数据增强侧处理:训练时开启 HSV 增强,把 hue 扰动控制在 ±0.015,sat 和 val 扰动放宽到 ±0.5,让模型见过更宽的光照范围。如果迁移场景的栏舍光照和数据集差异太大(比如数据集是室内灯光,你要部署到半露天),我建议用简单直方图匹配把两边的亮度分布对齐,比强行堆更多数据更高效。这个问题属于典型的“数据环境鸿沟”,它不会让训练报错,但会让你的模型在跨场部署时突然变笨。
5.4 迁移学习时类别名对不上:YAML里的小问题大坑
现象:用别人训练好的权重做微调,loss 正常下降,但一张站立猪的图被稳定预测成“牛”。原因多半是你下载的预训练权重类别数量和你本地 YAML 不一致,而 YOLO 微调时最后一层检测头会重新初始化,如果加载权重时没抓到对应层,齐平层的语义完全错位。解决方法是看启动日志里有没有 “unexpected key” 或 “missing key” 警告,出现这类警告就先别训练。正确做法:第一次用nc和names完全一致的数据集验证预训练权重能正常加载,再换到你的猪姿态数据集做微调。和这个坑类似的是 labelmap 顺序问题——有些人习惯把 names 按出现频率排列,导致和你转换脚本的 class_map 错位,训练不报错,验证时 manual 看输出就容易懵。
6. 从静态姿态到行为时段:一个轻量的滑动窗口投票后处理
前几章解决了“单帧分得准不准”,最后这一步解决“视频里稳不稳”。我常用的做法是滑动窗口投票加孤立片段合并,不引入 LSTM 也能把抖动压到很低。
from collections import Counter def vote_smooth(preds, window=9, min_seg=5): n = len(preds) smoothed = [] for i in range(n): left = max(0, i - window // 2) right = min(n, i + window // 2 + 1) win = preds[left:right] smoothed.append(Counter(win).most_common(1)[0][0]) # 合并短于 min_seg 的孤立片段 out = smoothed[:] i = 0 while i < len(out): j = i while j < len(out) and out[j] == out[i]: j += 1 if j - i < min_seg and 0 < i and j < len(out): # 前后类别相同则合并,否则并入较长侧 out[i:j] = [out[j] if (j - i) < (i - (i - 1)) else out[i-1]] * (j - i) i = j return out这段代码做的事很简单:第一个循环对每个帧取其前后共 window 帧做众数投票,用来压掉单帧抖动;第二个循环把长度小于 min_seg 的连续片段替换为相邻类别,用来压掉短促误判。参数上,window 按第 4 章的帧率表选,min_seg 取“行为最短有效时长 × 帧率”的一半。这段逻辑在部署时可以直接挂在检测模型输出后面,CPU 上跑都毫无压力。
我的习惯是先在标注好的验证视频上跑一遍投票前后对比,记录“行为切换时间点”和“单次行为持续时长”两个指标。如果投票后把一次真实的行为切换也磨平了,那就是 window 太大,需要回退。这个平衡没有玄学,就是拿数据说话。做这类数据集项目最大的收获是:数据集决定上限,参数决定下限,时序后处理决定用户能不能接受。希望这篇笔记能帮你少走点弯路,祝你的猪姿态检测项目一次落地成功。
本文还有配套的精品资源,点击获取