猪姿态检测数据集详解:从YOLO训练到行为识别落地实践
发布时间:2026/9/26 10:33:39来源:尧图网络
简介在智慧养殖与计算机视觉交叉领域中目标检测和姿态估计是理解动物行为的基础技术路径。检测模型负责定位个体姿态估计则通过关键点或行为标签描述其动作语义两者结合构成了行为识别系统的核心原理。高质量的数据集是训练可靠模型的前提尤其对于非刚体目标如猪只其姿态多变、遮挡频繁更需规范的标注体系与类别定义。工程实践中将公开数据集转换为YOLO格式并调整训练策略可有效用于采食监测、异常步态分析等养殖场景。本文基于PigBehaviorRecognitionDataset拆解其标注结构、YOLOv8训练要点及数据预处理中的常见陷阱帮助视觉工程师和研究人员少走弯路。1. 猪姿态检测把猪的行为量化成可训练的数据资产做智慧养殖的都知道猪舍里摄像头不难装难的是让算法看懂猪在干什么。你问十个搞视觉的工程师九个会告诉你“猪姿态检测”听起来像目标检测加个分类真落下去才发现坑全埋在数据上。猪是高度非刚体站、卧、走、爬跨、采食姿态之间还有大量过渡帧同一个行为在不同栏位、不同光照下的差异能让你辛辛苦苦调的模型一夜回到解放前。这份 PigBehaviorRecognitionDataset 数据集给我的第一印象就是它没有把“猪检测”和“姿态识别”混为一谈而是按行为语义做了类别体系——这正是从“能用”走向“好用”的分水岭。适合谁准备做智慧养殖、猪只行为分析、或者拿公开数据做算法验证的视觉工程师和研究生。它不能替你解决摄像头安装位置问题但能让你少走一遍“标注规范自己拍脑袋定”的弯路。2. 数据集装了什么标注结构、类别体系与适用框架的边界2.1 先看目录结构和文件组织拿到解压后的文件夹第一件事不是急着跑代码而是把目录树打出来看。常见做法是在根目录下执行tree -L 3 --filelimit 50正常的数据集会按images和annotations两大块组织或者按行为类别分子目录。比如有的版本是Standing/、Lying/、Walking/、Mounting/这样的目录说明它偏图像分类也有的版本是JPEGImages/加Annotations/说明它偏检测或姿态估计。PigBehaviorRecognitionDataset 如果压缩包里有README.md或者labels.txt先读它里面一般写了类别映射关系和标注精度说明。我拿到手会先统计每个类别的样本数防止后续训练时没发现类别严重不平衡。这一步用 Python 一行逻辑就能完成from pathlib import Path from collections import Counter root Path(PigBehaviorRecognitionDataset) exts (.jpg, .jpeg, .png, .mp4, .avi) counts Counter(p.suffix.lower() for p in root.rglob(*) if p.suffix.lower() in exts) print(counts)这段代码的作用是统计所有图片和视频文件的数量帮你判断数据集是纯图像还是混合了视频帧。参数上rglob(*)会递归匹配所有子目录suffix.lower()统一后缀大小写避免.JPG和.jpg被算成两类。如果counts里视频占大头说明你需要额外做抽帧处理不能直接把文件夹丢给 YOLO 训练。2.2 标签体系姿态分类还是关键点这个数据集的名字叫 PigBehaviorRecognitionDataset重点在“行为识别”。市面上猪姿态数据集常见的标注体系有两类一类是按行为标签打框比如站立、坐、躺、行走、采食另一类是标关键点比如耳朵、眼睛、前蹄、后蹄、背部中线然后由关键点组合推断姿态。前者适合做行为分类任务后者适合做姿态估计任务两个体系训练出来的模型输出完全不一样。如果你拿到的是检测框加行为标签那训练目标就是“猪在哪 猪在做什么”对应 YOLOv8 的分类头如果你拿到的是关键点标注那训练目标就是“猪的骨架点在什么位置”对应 YOLOv8-pose 的输出头。常见误区是有人拿到关键点数据却不标可见性导致训练时模型被迫预测被遮挡的蹄子位置踩坑概率极大。我在实践中的选型逻辑很简单如果项目目标是监测采食时间、躺卧时长用行为标签就够了如果目标是做异常步态检测、跛行分析必须上关键点。这份数据的正确打开方式是先确认它的标注头再决定用什么框架。不要一上来就套 YOLO。2.3 输入前必须确认的边界条件数据集不是万能的。猪姿态检测最怕三件事一是相机视角固定训练数据全是俯视部署时换成侧视直接废掉二是猪的品种差异长白猪和杜洛克在体态、毛色上差异很大模型在 A 品种上收敛得好换 B 品种掉点能掉十几个点三是栏位环境漏粪板地面和水泥地面反射不同光照色温不同这都属于典型的域偏移。建议拿到数据集后先做一次统计把图片分辨率、标注框尺寸分布拉出来。比如import cv2 import json with open(annotations.json, r, encodingutf-8) as f: anns json.load(f) widths, heights [], [] for ann in anns: w ann[bbox][2] - ann[bbox][0] h ann[bbox][3] - ann[bbox][1] widths.append(w) heights.append(h) print(平均框宽:, sum(widths) / len(widths), 平均框高:, sum(heights) / len(heights))这里bbox如果是[x1, y1, x2, y2]宽高就按差值计算。平均框高如果远大于框宽说明猪在画面里多以侧卧、站立姿态出现模型对侧视角更敏感如果框高很小说明画面多是俯视模型可能学不到侧身姿态特征。这个统计决定了你在设计 anchor 或训练分辨率时该怎么偏重。3. 把原始标注转成 YOLO 格式转换脚本与四个边界坑3.1 从标签文件到 YOLO txt 的转换YOLO 系列对检测任务的标准标注格式是每个图片对应一个同名.txt文件每行内容为class_id x_center y_center width height坐标全部归一化。而 PigBehaviorRecognitionDataset 的原始标注可能是 COCO 的 JSON、VOC 的 XML也可能是简单的 CSV。无论原格式是什么转换的核心逻辑都是一样的解析原标注做归一化写 txt。下面这个脚本是常见的 COCO 转 YOLO 实现import json, os from pathlib import Path coco_path annotations/instances_train.json out_dir labels/train os.makedirs(out_dir, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: data json.load(f) img_id_to_info {img[id]: img for img in data[images]} categories {cat[id]: idx for idx, cat in enumerate(data[categories])} for ann in data[annotations]: img img_id_to_info[ann[image_id]] img_name Path(img[file_name]).stem txt_path os.path.join(out_dir, img_name .txt) w, h img[width], img[height] x1, y1, bw, bh ann[bbox] cx (x1 bw / 2) / w cy (y1 bh / 2) / h nw bw / w nh bh / h cls categories[ann[category_id]] with open(txt_path, a, encodingutf-8) as f: f.write(f{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n)这段代码的关键点在于COCO 的bbox是[x, y, width, height]单位是像素而且不是中心坐标YOLO 需要的是中心坐标和宽高的归一化比例。转换时一定要拿x1 bw / 2算出中心点再除以图像宽高。我见过有人直接拿 COCO 的x当cx用结果所有框全部偏到画面左上角训练出来 mAP 直接崩盘。3.2 多个目标写同一文件时注意覆盖问题猪舍场景里一个镜头往往有多头猪所以一个图片的标注里可能有多条记录。上面脚本用的是open(..., a)追加写模式这是对的。但如果你把这段逻辑封装成函数不小心用了w模式同一个图片的后续标注会把前面的覆盖掉。结果就是每张图只剩最后一只猪的框训练出来的模型永远只能检出一头猪。规避方法很简单每次写入前先检查文件是否存在不存在则新建存在则用追加模式from pathlib import Path def append_label(txt_path, line): txt_path Path(txt_path) with open(txt_path, a, encodingutf-8) as f: f.write(line \n)append_label这个函数把追加逻辑收敛到一处后续不管训练入口怎么改写入模式都不会错。3.3 归一化坐标超出 0~1 范围怎么办猪在画面边缘时标注框的一部分可能落在图片外导致归一化后cx、cy、width、height出现负数或大于 1 的值。YOLO 训练时遇到这种标注要么直接报警要么自动 clamp但不同框架的处理方式不一致。我的做法是转换时直接把越界的框裁剪到图片范围内再计算中心点。这样虽然会损失一点边缘信息但至少不会让 loss 出现 NaNx1 max(0, x1) y1 max(0, y1) x2 min(w, x1 bw) y2 min(h, y1 bh) if x2 x1 or y2 y1: continue bw, bh x2 - x1, y2 - y1注意这里的逻辑先把框裁到图像边界内再做归一化。continue是过滤掉完全越界的无效框比如x2 x1表示经过裁剪后宽为 0这类标注一定是原始数据出错宁可丢弃也不要带病训练。3.4 类别 ID 重映射PigBehaviorRecognitionDataset 里的类别编号可能从 1 开始而 YOLO 要求从 0 开始转换时直接减一即可。但更麻烦的是类别顺序不一定按字母排序比如Standing0, Lying1, Walking2是人为定义的。如果后续要跟别的数据混合训练必须先统一类别映射表。我一般会在代码开头维护一个字典category_map { standing: 0, lying: 1, walking: 2, mounting: 3, eating: 4, }然后转换时直接查表。这样做的好处是全局只改一处换数据集时改category_map就行不用去翻循环里的逻辑。4. 在 YOLOv8 上训练猪姿态检测配置、参数与评估4.1 数据配置文件和类别参数YOLOv8 用data.yaml描述数据集信息训练前先把这个文件写好。以检测行为标签为例path: ./PigBehaviorRecognitionDataset train: images/train val: images/val test: images/test nc: 5 names: [standing, lying, walking, mounting, eating]这里nc是类别数量names列表的索引顺序必须和前面转换脚本里的category_map一致。path建议用相对路径避免部署到别的机器时还要改绝对路径。train/val/test都是相对于path的目录。如果你把训练集和验证集放在同一个大目录下YOLO 会全量读入导致没有真正的验证集。4.2 训练命令与关键超参数训练可以直接用命令行跑起来yolo detect train datadata.yaml modelyolov8m.pt epochs200 imgsz960 batch16 device0 patience30这条命令里我重点说几个参数。imgsz960猪姿态检测里目标框尺度差异大如果猪在画面里只有 100 像素高用默认的640容易丢特征建议调大到960代价是显存占用变大。batch16是经验值在 24G 显存卡上够用如果显存只有 12G降到8或者加--cache让数据预加载。patience30是早停轮数训练集比较大的时候可以放宽到50防止验证集波动导致过早停止。训练过程中最该盯的是损失曲线。用 TensorBoard 或者直接看runs/detect/train/下的results.png如果验证损失在某个 epoch 后开始抬头但训练损失还在降说明过拟合了回退到那个 epoch 的权重即可。4.3 用带关键点的 YOLOv8-pose 训练姿态估计如果这份数据集的标注包含关键点那训练任务就不一样了。YOLOv8-pose 的配置文件里需要额外指定kpt_shape和flip_idxkpt_shape: [14, 3] flip_idx: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]kpt_shape的第一个值14是关键点数量第二个值3是每个关键点的数据维度通常是x, y, visibility。flip_idx是水平翻转时关键点的对称映射关系比如左前蹄映射到右前蹄。如果猪的关键点定义里没有左右对称关系flip_idx就写range(14)。这里是容易出问题的地方翻转后关键点不匹配数据增强会把模型练废。训练命令换成yolo pose train datadata_pose.yaml modelyolov8n-pose.pt epochs150 imgsz960 batch16 device0如果你想用姿态信息去做行为分类需要把关键点距离、角度提取成特征再喂给一个简单的时序分类器这就进入下一章的话题了。4.4 评估指标怎么读检测模型看mAP50和mAP50-95。猪姿态检测场景里mAP50更贴近实际部署因为养殖场景不需要像自动驾驶那样精准的像素级框关键点模型看OKS或者每个关键点的PCK。PCK阈值建议取 0.2意思是预测点落在真实点 20% 像素距离内算对。如果只看整体 AP 不看每类 AP很容易漏掉“站立识别很好、爬跨识别很差”的偏科问题。评估命令yolo detect val modelruns/detect/train/weights/best.pt datadata.yaml跑完以后看results.csv里面每个类别的 AP 单独列出。哪类 AP 低回头检查该类别的样本数和标注质量八成是样本太少或者标注框画错了。5. 避坑与排查猪姿态数据落地时的五个典型问题5.1 现象模型只会输出“站立”其他类别全部不触发原因分析训练集里站立样本占 80% 以上其他行为类别占比过少模型收敛到一个局部最优解把所有输入都判成站立因为这样整体 loss 最小。解决办法第一对少数类别做过采样把图片复制进训练目录时按倍率扩充第二给损失函数里的类别权重做加权cls参数可以在 YOLO 的超参数文件里调整但更直接的是让每个 epoch 见到少数类别的次数足够多。我在实践里会直接做离线过采样因为改动最可控。cp lying/* lying_oversample/ cp -r lying_oversample/* train_images/这种方式简单粗暴但注意不要把同一个文件复制太多次否则模型会死记硬背这几张图。5.2 现象训练时 loss 不降反升反复震荡原因分析标注框的噪声太大同一个站立姿态有的标注框贴着身体有的标注框框了半圈地面模型学不到一致的信息。这种噪声通常来自不同标注人员的主观判断不一致。解决办法用脚本统计同类别目标框的宽高比剔除离群值。比如站立姿态的框宽高比通常在 0.6~1.2 之间如果出现宽高比 3.0 的框大概率是标注失误。直接过滤掉这些样本效果比调学习率明显得多。import pandas as pd df[wh_ratio] df[width] / df[height] valid df[(df[wh_ratio] 0.5) (df[wh_ratio] 1.8)]这里是按类别分别统计的不同行为类别的宽高比分布不一样不能一刀切。躺卧姿态的框宽高比往往大于 2如果统一用 0.5~1.8 过滤躺卧类别会被清空。5.3 现象关键点模型训练完猪的耳朵点总跳到蹄子位置原因分析数据集中关键点遮挡时可见性标错了。猪侧卧时靠近地面的前蹄被身体遮挡标注人员可能把不可见点直接乱标一个位置导致模型学到错误对应关系。解决办法重新检查关键点标注的visibility字段将不可见点的坐标置 0 或者设为特殊值 -1并在训练配置中开启kpt_loss对不可见点不计算损失的机制。这个逻辑在 YOLOv8-pose 中默认是支持的前提是数据正确。python fix_visibility.py --input annotations.json --output fixed.json这个脚本做的事就是把visibility为空的点全部置为 0。5.4 现象白天训练数据测试效果不错晚上测试掉点严重原因分析猪舍夜间的红外补光导致图像变成黑白模式白天数据多为自然光 RGB。模型学到的是颜色特征而夜间只有亮度梯度信息导致域偏移。解决办法训练时做灰度化数据增强强制模型降低对颜色的依赖。在 YOLO 的augment参数里开启灰度增强或者在数据预处理时按概率对图像转灰度def gray_aug(image): if random.random() 0.5: gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) return image这里random.random() 0.5的意思是有 50% 概率随机把图转成灰度转换后再转回三通道保证模型输入尺寸不变。5.5 现象模型在训练集表现很好换到另一栋猪舍完全失效原因分析这是最经典的域偏移。不同猪舍的栏位颜色、漏粪板纹理、摄像头角度、猪的品种都不同模型把“猪舍背景”当成了“猪”的判别特征。解决办法从数据集阶段就做多场景混合。把 PigBehaviorRecognitionDataset 的数据按猪舍来源拆分训练集保留一部分另一部分做验证。如果你的部署场景是固定栏位最好重新采集当前场景的数据做少量 fine-tune。不要指望一个模型通吃所有猪舍那是玄学不是工程。6. 进阶技巧用滑动窗口加状态机过滤单帧误判先别急着把模型装进监控系统像猪姿态这种连续视频任务单帧模型输出的抖动会让人怀疑人生——上一帧站立下一帧躺卧再下一帧又站立这种结果根本没法用。通用做法是引入时间维度的后处理。我会把模型的输出按时间序列保存下来然后用一个大小为 30 帧的滑动窗口做多数投票。也就是说当前帧的最终行为类别由它前面 30 帧的中点决定。这个思路简单但能把偶然的误判吃掉一大部分。具体实现from collections import deque window deque(maxlen30) buffer [] for frame_result in video_inference: window.append(frame_result[class_id]) final_class max(set(window), keywindow.count) buffer.append(final_class)deque(maxlen30)会自动丢最老的帧保证窗口大小固定max(set(window), keywindow.count)是选出现次数最多的类别作为当前输出。如果你觉得状态切换太迟钝把窗口缩小到 10响应更快但噪声更多。对于关键点方案还可以做平滑。我对每个关键点的 x、y、visibility 先用卡尔曼滤波或简单指数滑动平均再把平滑后的关键点输入一个行为分类器。指数滑动平均的 alpha 取 0.4 到 0.6 之间太大起不到平滑作用太小会把真实的动作变化也抹掉。最后建议你做一个离线回放验证把测试视频的每一帧结果画到图上录屏回放用肉眼确认状态切换点是否合理。这个环节很枯燥但能发现标注规范里没定义清楚的行为边界比如“站立”和“走动”之间的过渡帧到底算哪一个。从那以后我每次训练完模型都会强制走一遍“先统计类别分布再检查标注噪声最后加上时序后处理”的流程省下来的返工时间远超那几分钟。希望这个数据集的拆解对你的落地项目有帮助。本文还有配套的精品资源点击获取
网站建设高端定制企业官网