猪姿态检测数据集实战:从行为标注到YOLO训练与避坑指南
简介一份面向智能养殖与动物行为学研究的猪只姿态识别数据集聚焦猪只健康监测与福利评估场景。数据覆盖躺卧、睡眠、探索、进食、行走、骑跨六类常见姿态训练集共9744张标注图片验证集2518张可作为姿态分类与行为检测模型的标准化训练和评测基准。压缩包共2000个JSON文件含逐图标注结果与标签数量统计文件整体约809MB便于直接解析并接入深度学习流程。已有262人浏览学习。依托清晰的行为类别划分与统一标注格式读者可快速开展猪只行为识别模型训练与对比实验并应用于异常行为预警、精准饲喂优化等场景为智慧养殖和动物福利研究提供可靠数据基础。1. 猪姿态检测不是图像分类PigBehaviorRecognitionDataset要解决什么把猪的姿态和行为识别做成数据集很多人第一反应是“这不就是给猪拍照片标个框吗”。真正进场后才发现猪姿态检测面对的问题和常规目标检测完全不同猪是活的、会动、会转身、会叠在一起趴卧和倒地的区别直接决定养殖员要不要跑一趟猪舍。PigBehaviorRecognitionDataset这类数据集核心价值就是把“站、卧、走、跑、食、斗”这类行为语义转换成带坐标标注的样本让检测模型不光能回答“这里有一头猪”还能回答“这头猪在干什么”。适合的读者很明确做智慧养殖、畜牧AI、边缘端行为分析的同学以及想用自己的数据微调行为检测模型的一线算法工程师。这篇笔记从数据集结构讲到训练落地最后给到验证和避坑路径。2. 先摸透行为标签体系进PigBehaviorRecognitionDataset的第一件事拿到任何一个行为识别数据集先不要急着写加载脚本。第一步永远是读标签体系因为它直接决定后续模型输出层的设计。猪姿态检测的行为类别通常不是单一维度的它至少包含两个层级姿态层和行为层。2.1 行为类别怎么定姿态层和行为层要分开看姿态层描述的是空间形态比如站立、趴卧、躺卧、坐立行为层描述的是带语义的动作比如行走、奔跑、进食、饮水、争斗、爬跨。姿态可以靠单张图像判断行为很多时候必须依赖连续帧。PigBehaviorRecognitionDataset这类数据集通常把两者混在标签文件里但训练时要分开处理。我的习惯是先统计类别分布看哪些类样本太少再决定是合并类别还是做重采样。import json from collections import Counter with open(annotations.json, r, encodingutf-8) as f: anns json.load(f) label_counter Counter() for ann in anns[annotations]: label_counter[ann[category_id]] 1 print(类别分布, label_counter.most_common())这个脚本的核心是把标注文件里每个类别出现的总次数统计出来。如果发现“奔跑”只有几十条而“趴卧”有几千条训练出来的模型基本会对奔跑视而不见。后续处理无非两条路要么用类别权重给少数类更高的loss权重要么把奔跑和行走合并成“移动”。我在实际项目里更倾向于后者因为猪的奔跑和行走在二维图像上本来就容易混淆合并后模型稳定性更好。2.2 标注坐标的两种主流约定边界框还是关键点猪姿态检测数据集的标注格式目前常见的有两种。第一种是常规的边界框x_center, y_center, width, height适合做目标检测的迁移学习第二种是关键点格式会额外标出猪的头部、躯干、四肢位置适合做姿态估计。PigBehaviorRecognitionDataset如果带关键点你会发现它比纯框标注复杂得多因为猪的四肢在趴卧姿态下大量自我遮挡标错一个关节模型学到的就是错误的空间关系。这里有一个非常实际的边界坑边界框坐标系YOLO格式是归一化到0到1的中心点x、中心点y、宽度w、高度h而COCO格式是像素坐标左上角x、左上角y、宽度w、高度h。同样一份标注用错解析方式模型训练出来的mAP直接掉一半而且很难排查因为loss曲线看起来是正常的。我一般会在数据加载脚本里写一个强断言检查所有坐标是否在合法范围内把这个坑提前暴露出来。2.3 标签文件解析范例把框和类别映射到可训练样本不管原始标注是JSON还是XML落地的时候我一般会统一转成YOLO格式的txt文件因为YOLO系列的训练生态对这个格式支持最好。下面这段是把COCO风格JSON转成YOLO训练目录的参考实现。import os, json import numpy as np def coco_to_yolo(coco_path, img_dir, out_dir): with open(coco_path, r, encodingutf-8) as f: coco json.load(f) img_id_to_name {im[id]: im[file_name] for im in coco[images]} cat_id_to_label {cat[id]: idx for idx, cat in enumerate(coco[categories])} os.makedirs(out_dir, exist_okTrue) for ann in coco[annotations]: img_name img_id_to_name[ann[image_id]] img_path os.path.join(img_dir, img_name) h coco[images][0][height] # 注意实际要按image_id查 w coco[images][0][width] x, y, bw, bh ann[bbox] x_center (x bw / 2) / w y_center (y bh / 2) / h bw_norm bw / w bh_norm bh / h label cat_id_to_label[ann[category_id]] out_txt os.path.join(out_dir, img_name.replace(.jpg, .txt)) with open(out_txt, a, encodingutf-8) as f: f.write(f{label} {x_center:.6f} {y_center:.6f} {bw_norm:.6f} {bh_norm:.6f}\n)注意这里我故意留了一个bug风险提示coco[images][0]只取了第一张图的尺寸如果数据集里图像分辨率不统一所有标注都会错位。正确的做法是先用image_id去查对应图像的宽高再计算归一化坐标。这也是猪姿态数据集最容易踩的暗坑之一——猪舍监控画面常常是不同摄像头的分辨率混合在一起。出现这个问题时训练loss不降或者精度极低但标注文件看起来又没问题非常难排查。3. 把猪姿态数据集喂进YOLO数据清洗、划分与训练全流程数据集解析通了下一步就是训练。这里我不讲从零写网络而是直接走YOLOv8这条最稳妥的路线。猪姿态检测的样本量通常只有几千到几万张从头训练一个检测器不现实迁移学习用预训练权重是唯一靠谱的做法。3.1 数据划分不能随机切按猪舍或视频片段分组猪姿态检测最隐蔽的坑就是数据泄漏。同一头猪在连续视频帧里几乎一模一样如果随机划分train和val模型实际上是在“背答案”验证集精度虚高。正确的划分方式是按视频片段或者按猪舍分组保证同一头猪的样本不会同时出现在训练集和验证集里。这个原则在行人重识别领域是老规矩但在猪姿态检测里很多人会忽略。import os import random from collections import defaultdict video_to_images defaultdict(list) for fname in os.listdir(images): video_id fname.split(_frame_)[0] # 假设文件名格式是 videoID_frame_timestamp.jpg video_to_images[video_id].append(fname) video_ids list(video_to_images.keys()) random.shuffle(video_ids) train_videos video_ids[:int(len(video_ids) * 0.7)] val_videos video_ids[int(len(video_ids) * 0.7):int(len(video_ids) * 0.85)] test_videos video_ids[int(len(video_ids) * 0.85):] def write_split(video_list, split_name): with open(f{split_name}.txt, w) as f: for vid in video_list: for img in video_to_images[vid]: f.write(fimages/{img}\n) write_split(train_videos, train.txt) write_split(val_videos, val.txt) write_split(test_videos, test.txt)这段划分脚本的核心是把同视频帧绑在一起而不是逐张随机抽。如果你的数据集文件名没有明确的分组信息建议回看数据集的readme里有没有提供拍摄批次、猪舍编号这类元数据。没有的话只能按时间戳聚类——连续时间内出现在同一镜头的样本视为同组。3.2 训练命令和关键参数epochs、batch和imgsz怎么定数据划分完成YOLOv8的训练命令很短但参数选择有讲究。我一般会先把图像统一缩放到640或768batch size根据显存来定。猪姿态数据集的图像往往来自监控摄像头画质不高盲目用1280的大分辨率反而会让模型过拟合到噪点上。yolo detect train \ databehavior.yaml \ modelyolov8m.pt \ epochs100 \ batch16 \ imgsz640 \ patience15 \ device0 \ workers4 \ pretrainedTruepretrainedTrue是关键用COCO预训练权重做初始化。patience15表示验证集指标连续15轮不提升就早停防止猪姿态这种小数据集过拟合。如果你的显存只有8G把batch降到8imgsz降到512优先保证跑得完一个epoch再看loss曲线。3.3 训练前要检查的behavior.yaml配置behavior.yaml是YOLO训练的数据集描述文件内容直接决定训练能不能起来。最容易出错的地方是路径写错和类别索引不一致。标注文件里的类别ID是从0开始连续编号yaml里names的列表顺序必须和它完全对应。path: /data/pig_behavior train: train.txt val: val.txt test: test.txt nc: 8 names: 0: standing 1: lying 2: walking 3: running 4: eating 5: drinking 6: fighting 7: mountingpath建议写绝对路径相对路径在YOLO里经常因为运行目录不同而报错这个错误困扰了很多人很久明明文件都在就是找不到。训练开始后第一步不是看mAP而是看第一个epoch的loss有没有正常下降。如果loss直接是nan检查标注坐标是不是有负数或者大于图像尺寸的异常值。4. 猪姿态检测常见问题与避坑五个翻车现场复盘数据集质量决定模型上限这句话在猪姿态检测上体现得格外明显。以下五条都是实际项目中反复出现的坑按现象到原因到解决的顺序记录。4.1 趴卧姿态的框总是漏检现象是模型对站立和行走检测得很好但趴卧的猪经常漏检召回率掉到30%以下。原因是趴卧时猪的轮廓和地面背景融为一体边界框包含大量背景像素特征不够显著。另一个隐藏原因是部分趴卧样本被错误标注成了“躺卧”或“休息”标签内部不一致。解决办法分两步。第一步是做标签质量审计随机抽200张趴卧图人工复核确认标注类别没有漂移。第二步是做数据增强对趴卧类样本加随机光照扰动和对比度扰动让模型学会在不依赖颜色纹理差异的情况下识别轮廓。4.2 训练loss正常下降验证mAP却纹丝不动现象是训练集loss在20轮内从2.0降到0.5验证集mAP一直卡在0.3上下。这大概率是数据泄漏的反向问题——训练集和验证集太相似模型学的是“记住画面”而不是“学会识别”。也可能恰好相反验证集太难包含了大量训练集没有的极端角度。我的排查顺序是先看验证集图像和训练集图像是否来自同一批视频的相邻帧如果是重做数据划分如果划分没问题检查验证集中是否有遮挡严重的样本把遮挡面积超过60%的标注框列为hard case单独统计不要混在总体指标里。4.3 猪叠在一起时检测框打架现象是两头猪重叠时模型只输出一个框或者两个框剧烈抖动。原因是NMS非极大值抑制阈值设置太激进重叠区域大的两个同类目标被当成一个目标抑制掉了。解决方法是把NMS的IoU阈值从默认的0.7调低到0.4到0.5让同一区域的重叠框被更激进地抑制是不对的——恰恰相反调低IoU阈值意味着更严格的抑制条件反而会杀掉更多框。这里要讲清楚NMS的IoU阈值代表“两个框重叠多少才认为指向同一物体”阈值越低对重叠越敏感两个猪框只要稍有重叠就会被合并。所以实际做法是把阈值调高到0.6左右允许部分重叠的框同时保留再配合置信度过滤。yolo detect val \ modelruns/detect/train/weights/best.pt \ databehavior.yaml \ conf0.35 \ iou0.6验证时把iou调到0.6置信度阈值conf放到0.35。如果框的数量明显增多但误检也增多就按场景反复试0.5到0.7之间的值。这个参数在猪舍场景里属于玄学范围没有最优解只能结合监控画面的实际密度做调优。4.4 夜间红外画面下模型全面失效现象是模型在白天画面的mAP有0.85切到夜间的红外监控画面直接掉到0.2。原因是训练集里红外样本数量极少模型学到的全是彩色纹理特征。我在处理这类情况时会先统计数据集中红外和可见光图像的比例。如果红外样本确实稀缺第一选择不是硬调参而是引入红外图像的伪彩色变换做数据增强将可见光样本模拟成灰度或热成像风格。另外一个原因是摄像头切换时分辨率变了比如白天用400万像素夜间自动切成200万像素。模型对尺度突变非常敏感训练时用多尺度增强可以有效缓解在YOLO的训练参数里把scale0.5下调到scale0.3减少尺度的随机扰动范围保证模型看到的尺寸分布更稳定。4.5 标注类别分布严重不均衡少数类直接学不动现象是“爬跨”这类行为只占全部样本的1%到2%模型除了漏检还会把站立误报成爬跨。解决这类问题我推荐一个组合拳。第一步是类别重加权在loss计算里给少数类更高的权重第二步是过采样把少数类样本复制到3到5倍但要注意不要在同一步骤里和随机裁剪增强叠加那样会产生太多高度相似的重复样本。第三步是收集更多hard case——专门筛出容易混淆的站姿和爬跨姿态的过渡帧这比盲目扩充总数更有效。5. 验证模型到底检测得准不准mAP之外的检查方法训练结束看mAP只是第一步。猪姿态检测的落地价值在于行为统计的准确性而mAP衡量的是单帧框定位能力和“行为识别准不准”是两个维度。这一节给出三组更贴近实际使用的验证手段。5.1 按行为类别看混淆矩阵别只看整体指标整体mAP高不代表每个类别都好。按类别输出混淆矩阵才能真正看到哪个行为被模型混淆了。YOLO在验证后会在runs/detect/val目录里生成混淆矩阵图但我更喜欢自己写一行代码输出数值矩阵方便对比版本迭代。from pathlib import Path import numpy as np confusion np.load(runs/detect/val/confusion_matrix.npy) class_names [standing, lying, walking, running, eating, drinking, fighting, mounting] for i, name in enumerate(class_names): top_confused np.argsort(confusion[i])[-3:][::-1] confused_names [(class_names[j], round(confusion[i][j], 3)) for j in top_confused if j ! i] print(f{name}: 最容易混淆 - {confused_names})这个脚本把每一行里除了自己之外最高的三个混淆类别打出来。如果“running”和“walking”互相混淆严重说明类别定义本身边界不清需要回看视频重新讨论行为标注口径而不是继续调模型。5.2 视频序列上的时间一致性检查单帧检测对了不算对行为识别在视频里必须有时序一致性。一头猪在连续50帧里不应该出现“趴卧、站立、趴卧、站立、趴卧”这种高频跳变。检查方法很简单取一段连续的验证视频逐帧跑模型统计每头猪的行为标记切换次数。import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(val_videos/pig_pen_03.mp4) prev_labels {} switch_count 0 total_frames 0 while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, conf0.35, verboseFalse) frame_labels {} for box in results[0].boxes: cls_id int(box.cls[0]) track_id int(box.id[0]) if box.id is not None else -1 frame_labels[track_id] cls_id for track_id, cls_id in frame_labels.items(): if track_id in prev_labels and prev_labels[track_id] ! cls_id: switch_count 1 prev_labels[track_id] cls_id total_frames 1 cap.release() print(f行为切换次数: {switch_count}, 总帧数: {total_frames})注意这个脚本里box.id只有在开启了跟踪模式model.track()时才会有值普通model()检测不会输出track_id。实际操作是把模型换成model.track(frame, persistTrue)。如果行为切换频率超过了人类标注员的切换频率说明模型过拟合了纹理变化需要回到数据层面补充中间状态的训练帧。5.3 用伪标签迭代扩充hard case样本验证阶段往往能收集到大量模型置信度低、但人工一眼能分辨的样本。把这些样本挑出来人工修正后加入训练集这个闭环比换网络结构有用得多。伪标签的逻辑是让模型先跑一遍输出置信度在0.3到0.6之间的检测框框出那些模型犹豫不决的猪只。人工只用看一小批被筛选出来的片段不用从头标注全部数据。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourceunlabeled_frames/, conf0.3, save_txtTrue, save_cropTrue, projecthard_cases )关键在于conf0.3这个低置信度阈值——低于生产环境的0.35到0.5。这样筛选出来的不是模型能轻松搞定的样本而是模糊地带。我一般从这批hard case里再人工挑有效样本通常500到1000张就能带来几个点的mAP提升比多训练100个epoch划算得多。6. 进阶技巧把单帧检测延展成行为时长序列检测模型给的是单帧结果但养殖场景真正需要的是行为持续时长、频次这类统计指标。这一节讲从检测结果到行为序列的具体做法。6.1 用IOU匹配给猪只分配ID做帧间关联最简单的关联方案就是基于IoU的贪心匹配对相邻两帧的检测框计算IoU矩阵每次取IoU最大的配对超过阈值则认为同一头猪。这个方案的优点是没有额外依赖单张显卡就能跑缺点是在猪叠在一起时会跟丢。要进一步提升稳定性正式的方案是引入ByteTrack这类跟踪器它们的核心思路是把低置信度框也纳入匹配代价计算利用物体运动的连续性做补偿。import numpy as np from scipy.optimize import linear_sum_assignment def match_detections(prev_boxes, curr_boxes, iou_thresh0.3): iou_matrix compute_iou(prev_boxes, curr_boxes) row_ind, col_ind linear_sum_assignment(-iou_matrix) matches [] for r, c in zip(row_ind, col_ind): if iou_matrix[r, c] iou_thresh: matches.append((r, c)) return matcheslinear_sum_assignment求解的是全局最优匹配比贪心逐个取最大值要稳定。iou_thresh建议设在0.3到0.5猪的移动速度越快这个阈值就要越低因为相邻帧之间的位移变大框的重叠变少。6.2 行为片段的切分与去抖动有了稳定的ID序列行为统计就变成对每个ID的标签序列做后处理。常见做法是中值滤波去掉单帧抖动再合并连续的相同行为段。注意中值滤波的窗口不能太大通常取5到7帧窗口太大会把真实的短时行为比如一次快速的站立抹掉。到这里想分享一个实际教训最初我给猪做行为时长统计时直接按模型输出的每帧标签累计时长结果“站立”时长被高估了约20%原因是模型在站立和趴卧的过渡帧上跳来跳去每次跳变都算成了一次站立。后来加入状态机约束——只有连续5帧保持在同一个行为标签才认为行为真正切换——数值一下子贴合人工记录了。这个阈值也就是上面提到的中值滤波与状态机结合的思路。希望这篇笔记能帮你在猪姿态检测的数据处理和模型训练上少走弯路。本文还有配套的精品资源点击获取