ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv9的猪行为识别:数据集解析与训练避坑指南

基于YOLOv9的猪行为识别:数据集解析与训练避坑指南 简介猪圈场景下的猪只行为识别数据集面向计算机视觉与养殖智能化项目开发者解决猪只行为自动分类需求。数据集中包含喝、吃、睡觉、站立四类常见行为标注平均正确识别率92.6%可直接用于YOLOv9等目标检测模型的训练与验证。压缩包共2000个文件其中1272个txt标签文件对应每张图片的标注信息727张jpg原图覆盖多样猪圈环境1个yaml配置文件用于定义类别与训练参数整体约85.86MB结构规整便于快速启动实验。图片来自猪圈实际场景涵盖不同光线与角度标注边界框经过人工复核有助于提升模型在真实养殖环境中的泛化能力。已有251人学习使用适合正在开展动物行为识别、智慧养殖算法落地或目标检测迁移学习的开发者可直接获得带标注的训练集与完整配置减少数据采集和标注耗时。1. 猪行为识别数据集能做什么从值班室的监控墙到自动巡检智慧养殖这几年最尴尬的一件事是摄像头装了AI 却认不出猪圈里发生了什么。母猪站起来不吃料、仔猪趴窝不动、猪群围在饮水器旁这些行为每一个都是健康管理和发情判断的信号但传统视频监控只能存录像靠人熬夜看回放根本不现实。这个猪行为识别数据集解决的就是「让模型认识猪的日常」这一步1272 张现场图像标注了喝、吃、睡觉、站立四类行为平均正确识别率 92.6%并且直接给到 yolov9 格式的标注文件。你拿到手不需要做格式转换分好训练集就能喂给 yolov9 跑迁移学习适合做智慧养殖、动物行为分析、边缘端巡检这类项目的从业者用来起步。先说句实在话92.6% 这个数字在干净测试集上不难达到真正考验模型的是猪圈里的灰尘、逆光和相互遮挡后面我会讲清楚怎么复现这个指标以及哪些坑会把这个数字打下来。2. 拆解 1272 张图四类行为、yolov9 标注格式与标签质量检查2.1 小数据量意味着训练策略必须「迁移优先」1272 张图片在目标检测数据集里属于轻量级。对比一下COCO 的 train2017 有 11.8 万张常见的行人检测数据集也动辄几万张。但行为识别和通用目标检测有个本质差别猪圈里的场景非常单一背景基本就是栏位、水泥地面、食槽和饮水器目标类别只有猪变化的是猪的姿态和行为。这种「场景窄、类别少」的数据分布让一千张出头的量级变得可行。不过量少带来的约束是从零初始化训练一个 Darknet 骨架几乎没有可能参数量的拟合压力远超过数据能支撑的信息量。常见做法是使用 yolov9 的 COCO 预训练权重做迁移学习冻结骨干前几层只用猪行为数据微调后面几层。这样做的好处是模型已经学会了纹理、边缘、轮廓这些底层特征你的数据只需要教会它「什么样的猪姿态对应什么行为」。2.2 yolov9 格式的标注结构每一行都对应一个目标框这个数据集的标注格式是 yolov9 直接可读的——每个图片文件对应一个同名.txt文件放在labels目录下。文件里每一行代表一个目标实例格式如下class_id x_center y_center width height这五个值里class_id是从 0 开始的整数对应行为类别后面四个值全部是归一化坐标范围 0 到 1由像素坐标除以图片宽高得到。举例来说一张 1920x1080 的图片里一头猪的像素框中心在 (960, 540)框宽 400 像素、高 300 像素那么这一行就是2 0.5 0.5 0.2083 0.2778注意宽度和高度的归一化是分别除以图片宽和高的不是统一除以一个值。这个细节看起来不起眼但不少人在这上面翻过车——一旦宽高用了同一个除数框的位置全对尺寸却全错训练出来的模型预测框会明显偏大或偏小。2.3 拿到数据集第一件事把标签画出来检查不要急着开训练。拿到数据集后我一般会先写个脚本随机抽样几十张图把标注框和类别画回去肉眼过一遍。这一步能筛掉三类常见问题标注框与猪体错位、类别标错、归一化坐标因换行或空格解析失败。import cv2 import numpy as np from pathlib import Path def load_labels(label_path, img_width1920, img_height1080): boxes [] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) ! 5: print(f格式异常: {line}) continue cls_id, x_c, y_c, w, h map(float, parts) # 还原像素坐标 x1 int((x_c - w / 2) * img_width) y1 int((y_c - h / 2) * img_height) x2 int((x_c w / 2) * img_width) y2 int((y_c h / 2) * img_height) boxes.append((int(cls_id), x1, y1, x2, y2)) return boxes label_dir Path(./labels) image_dir Path(./images) class_names [drink, eat, sleep, stand] for label_file in list(label_dir.glob(*.txt))[:10]: img_path image_dir / (label_file.stem .jpg) if not img_path.exists(): continue img cv2.imread(str(img_path)) h, w img.shape[:2] for cls_id, x1, y1, x2, y2 in load_labels(str(label_file), w, h): color (0, 255, 0) if cls_id len(class_names) else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(f./check_{label_file.stem}.jpg, img)这段脚本的核心逻辑是读入标签文件的每一行把归一化坐标乘以图片的宽高还原成像素框然后直接画在图上。img_shape[:2]取的顺序是高度在前、宽度在后如果你习惯用shape[1]拿宽度、shape[0]拿高度别写反否则画出来的框位置会整体错位。运行完脚本后挑出标注明显有问题的图片把对应标签删掉或者手动修正再进入训练环节。这一步做扎实数据质量才有底。3. 把数据集喂给 yolov9目录组织、训练命令与六个关键参数3.1 数据目录的标准布局yolov9 的训练流程读取的是 YAML 配置文件里面指定了训练集、验证集图片路径和类别名。目录我会按官方惯例组织成下面这样换成你自己的项目名即可pigs-behavior/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── yolov9-c.pt注意一个容易踩的细节images目录和labels目录必须平级且子目录名要一致。yolov9 的LoadImagesAndLabels类会自动把图片路径中的/images/替换成/labels/来寻找对应标签文件如果你把图片放在data/imgs、标签放在data/labels代码找不到标签文件会报label image not found。这个映射机制是写死的路径替换不是靠配置文件指定所以目录取名要遵循默认约定。3.2 数据划分保证同栏猪不跨集数据划分不只是随机抽样那么简单。猪行为识别有个特性同一个栏位、同一批猪、同一时段拍的连续帧背景和个体都高度相似。如果这些图片被同时分到训练集和验证集验证集相当于开卷考试指标会虚高 3 到 5 个百分点。我一般会先看数据集的文件命名方式。如果是按时间序列编号的就按比例做分段划分前 70% 的序号进训练集中间 15% 进验证集后 15% 进测试集。如果是按日子或栏位分目录的就整目录划分。只有确认图片是分散抓拍、不存在连续帧依赖时才用纯随机划分。写个划分脚本import random from pathlib import Path import shutil random.seed(42) image_dir Path(./images) label_dir Path(./labels) all_images sorted(image_dir.glob(*.jpg)) all_labels [label_dir / (img.stem .txt) for img in all_images] # 确认每个图片都有标签文件 assert all(lab.exists() for lab in all_labels), 存在缺失标签的图片 index list(range(len(all_images))) random.shuffle(index) train_ratio, val_ratio 0.7, 0.15 train_idx index[:int(len(index) * train_ratio)] val_idx index[int(len(index) * train_ratio): int(len(index) * (train_ratio val_ratio))] test_idx index[int(len(index) * (train_ratio val_ratio)):] for split, idx in zip([train, val, test], [train_idx, val_idx, test_idx]): (image_dir / split).mkdir(parentsTrue, exist_okTrue) (label_dir / split).mkdir(parentsTrue, exist_okTrue) for i in idx: shutil.move(str(all_images[i]), str(image_dir / split / all_images[i].name)) shutil.move(str(all_labels[i]), str(label_dir / split / all_labels[i].name))random.seed(42)的目的不是玄学是为了让划分结果可复现。你调整训练参数、对比实验效果时如果每次划分都不一样误差来源就混进了数据分布差异指标对比就没有意义。建议把 seed 固定住后面所有实验都用同一份训练/验证划分。3.3 data.yaml 与训练命令划分完成后写数据配置文件。yolov9 的数据 YAML 长这样# data.yaml path: /path/to/pigs-behavior train: images/train val: images/val test: images/test names: 0: drink 1: eat 2: sleep 3: stand注意path写的是绝对路径。相对路径在单独跑训练脚本时没问题但换目录或换机器后容易报文件找不到。另外names的索引一定要从 0 开始连续编号不能跳号。这个索引对应标签文件里的class_id如果数据集做成了 1 到 4 的编号索引对不上训练时类别会错位——你以为的「drink」实际训练成了「eat」推理出来的结果自然没法看。接着启动训练。以 yolov9 官方仓库为基准最简训练命令是python train.py \ --data data.yaml \ --weights yolov9-c.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0解释一下六个关键参数的含义--weights必须是 yolov9 在 COCO 上的预训练权重。用yolov9-c.pt而不是yolov9-e.pt因为 c 版本参数量适中行为识别类别少、目标尺寸偏大不需要最大的骨干网络训练速度和解码速度都更快。--img是网络输入尺寸。640 是通用默认值但如果原始图片里猪的占比很大、目标框普遍偏大可以降到 512训练速度提高精度损失很小。反过来如果你的目标框普遍很小才需要往上调到 768 或 896。先看一眼数据集中标注框的尺寸分布再定。--batch-size在单卡上先设 16内存不够再降到 8。--epochs100 是起步值——我在迁移学习场景下见到的最优表现通常出现在 60 到 80 epoch 附近100 给足了收敛空间。最关键的是--patience参数python train.py \ --data data.yaml \ --weights yolov9-c.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --patience 20 \ --device 0patience是早停的容忍度连续 20 个 epoch 验证集指标没有提升就自动停止。小数据集最容易过拟合训练集 loss 一直在降、验证集 mAP 却原地不动甚至下降早停是防过拟合的第一道保险。3.4 数据增强参数小数据集的生存关键yolov9 的数据增强配置在hyp.yaml里。对猪行为识别这个场景重点关注三个参数fliplr: 0.5 # 水平翻转概率 mosaic: 1.0 # mosaic 增强启用 scale: 0.5 # 随机缩放范围fliplr对猪行为完全成立——猪站在栏位里从左往右走和从右往左走是同一类行为镜像不会改变行为语义但能让模型学到姿态的对称性。mosaic会把四张图拼在一起训练变相增大 batch 内目标数量对小数据集非常有效一般保持默认开启。scale控制缩放幅度0.5 够用。需要谨慎的是degrees随机旋转。猪是地面动物行为识别严重依赖头和躯干的水平关系旋转超过 10 度会产生现实中不存在的视角反而干扰模型学到的姿态特征。我一般关掉旋转或者只保留degrees: 5。光照增强在夜间样本多的时候可以适当调高hsv_h、hsv_s的值后面会展开说。4. 92.6% 是怎么算出来的指标口径、验证方法与复现路径4.1 正确识别率与 mAP 不是一回事标题里说的平均正确识别率 92.6%需要先搞清楚口径。行为识别在目标检测框架下通常有两个指标可以描述「正确识别率」第一个是分类准确率——模型检测出目标框后行为类别判断正确的比例。这个指标不关心框准不准只关心类别对不对。第二个是 mAP——综合考虑检测框位置和类别正确性的指标。yolov9 训练日志里看到的mAP50和mAP50-95和你口头上说的「正确识别率」不完全等价。mAP50要求预测框与真实框的 IoU 超过 0.5 且类别正确才记为命中mAP50-95更严格会在 0.5 到 0.95 多个 IoU 阈值下取平均。通常一个数据集给出的「平均正确识别率」如果描述朴素默认指的是第一个指标——分类准确率。92.6% 这个水平在四分类任务中算可用但不顶尖。行为识别类任务普遍卡在 90 到 95% 区间难点不在检测器而在行为边界本身——比如吃和喝在猪低头动作上区分度低。所以你在测试集上复现不要只盯最终的准确率数字。先分开看两个维度检测框的 mAP50 是否大于 90四类行为的分类混淆矩阵长什么样。如果 mAP50 明显低于准确率说明你的模型「类认对了但框画不准」这在行为计数场景里会产生严重的重复计数问题。4.2 复现的关键同一份数据划分、同一个预训练权重、固定随机种子复现 92.6% 不神秘但每一步都会引入偏差。最容易导致指标对不上的原因是数据划分不一致。如果你自己随机划分训练集里比作者多了几张难样本或者验证集里多了一张简单图指标波动 1 到 2 个百分点非常正常。1272 张的小数据集尤其敏感——每张图约占总数的 0.08%两张难图进验证集准确率就能掉 1.5 个百分点。我习惯的复现流程是固定random.seed用第 3.2 节同一套划分脚本生成相同的 train/val/test 划分下载同一个版本的 yolov9 COCO 预训练权重对比模型文件哈希值保持hyp.yaml全部默认只改必改参数img、batch-size、epochs训练结束后用test.py单独在测试集上做推理不做二次调优训练结束后跑验证命令python detect.py \ --weights runs/train/exp/weights/best.pt \ --source test/images \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txtconf-thres是置信度阈值默认 0.25。如果目标是行为分类准确率这个阈值可以提高到 0.5过滤掉低置信度的预测框分类准确率通常能再涨 1 到 2 个百分点。但代价是召回率下降——模型漏掉真猪框。在真实巡检场景里漏检比误检更危险因为漏掉一次饮水和吃食记录可能就漏掉了一次发病预警。4.3 推理阶段的性能观察在 CPU 和边缘设备上的实际表现yolov9-c 的模型复杂度在 GPU 上不是瓶颈真正的瓶颈是部署端。如果你打算跑在边缘盒子比如 Jeston Nano 或 RK3588上要提前算两笔账输入尺寸 640 时yolov9-c 的 GFLOPs 约为 100 量级。在 Jetson Nano 上用 TensorRT 加速后单帧推理大约 80 到 120 毫秒也就是每秒 8 到 12 帧。对行为识别来说猪的动作变化不快一秒钟 5 帧以上就够用但如果要同时分析 4 路摄像头单卡可能撑不住得换更高算力的设备或者降低输入尺寸。另一个做法是知识蒸馏用训练好的大模型当老师蒸馏到 yolov8n 或 yolov5s 这样的小模型上。蒸馏后行为分类准确率通常会比直接在小模型上训练高 2 到 3 个百分点因为小模型从小数据里学不到的东西被老师模型补上了。5. 猪行为识别训练避坑标注错位、类别不平衡与行为边界模糊5.1 标注框与猪体错位loss 居高不下的头号嫌疑现象训练几个 epoch 后验证集 mAP50 一直低于 70%画出来的预测框要么偏左要么偏上边界没有贴合猪身。原因标注框坐标在采集时就存在系统性偏移。常见来源是标注工具的画框习惯——标注员为了把整头猪框全把框往外扩了一圈但扩幅不一致有的图框覆盖了食槽有的图框切掉了半个猪头。yolov9 回归的是框的中心点、宽高和真实框的距离标注框不贴合模型学到的边界就是带着噪声的。解决不要手动去改所有框代价太高。用脚本先自动修正——对猪这种躯干明显的目标可以把标注框向内收缩 5% 像素再画回去检查一遍import cv2 from pathlib import Path label_dir Path(./labels) tolerance 0.05 # 收缩比例 for label_file in label_dir.glob(*.txt): lines [] for line in label_file.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue cls_id, x_c, y_c, w, h map(float, parts) # 对宽高做 5% 收缩中心点不变 w_new, h_new w * (1 - tolerance), h * (1 - tolerance) lines.append(f{int(cls_id)} {x_c:.6f} {y_c:.6f} {w_new:.6f} {h_new:.6f}) label_file.write_text(\n.join(lines) \n)修改后重新抽样画框核对。注意收缩比例不能一刀切先看几张图确定偏移方向如果只有横向偏移就只改宽度只有纵向偏移就只改高度。5.2 类别不平衡睡觉样本占一半站立样本只有几张现象训练日志显示sleep类的准确率 97%但stand类的准确率只有 40% 左右。原因猪的行为时间分布本来就极度不均——猪一天有 70% 以上的时间在睡觉或趴卧真正站立、走动的占比很低。1272 张图的标注如果能达到 92.6% 的平均正确率说明四类分布可能比较均衡——从一个行为持续时间长的场景里抽帧容易抽到大量同类片段数据集制作者应该有意识地做了行为片段均衡采样。说明作者在数据上做过均衡处理。说明这个数据集在制作时做了行为片段均匀采样。说明作者在采集时保留了行为的多样性。如果确认存在不平衡有两个修正手段先做采样调整。如果某类别的样本数远低于其他类别优先用图像增强来扩充而不是去网上找猪图——网上图片的拍摄角度、栏位环境和你的场景差异太大负迁移效应明显。再调 loss 权重。yolov9 的class_loss支持按类别设置权重但实际操作成本高、参数敏感。小数据集上最稳妥的办法反而是回传度量学习让你把站立帧放慢逐帧取注意要取不同时间段——早上刚开灯时的站立、喂料前的站立、人员走动时的站立虽然都是「站立」姿态差异非常大。5.3 吃与喝的行为边界同一个低头动作两种语义现象混淆矩阵里eat和drink互相误判严重误判率超过 10%。、eat和drink两类在头部动作上过于接近吃料时猪低头频繁、喝水时也是低头频繁用静止帧判断很容易混淆。原因始终是行为识别的边界问题。吃和喝在单帧图像上的区分线索其实有位置吃在食槽边、喝在饮水器旁、头部朝向吃料头朝料槽内侧、喝水头朝饮水器、口部动作喝水时有吞咽的喉部动。但单帧模型能利用的信息有限。解决这个问题的做法不是去换更大的模型而是从数据标注层面引入「上下文类别」在标注阶段就把「猪头在食槽内且口部有明显动作」记为eat把「猪头靠近饮水器且朝向特定位置」记为drink。如果一张图里猪正从食槽走向饮水器头朝向中间区域就归入stand不要硬标成eat或drink。模型的决策边界会越清晰。5.4 一个框里有多头猪猪叠猪导致漏检和错检现象检测结果的框要么只框住最前面那头猪把后面的猪漏了要么一个大框把两三头猪圈成一个目标类别正确但框的位置惨不忍睹。原因猪圈的栏位密度高于自然场景。猪重叠时可见轮廓被遮挡目标的中心区域被其他个体覆盖检测器难以区分实例。解决思路是换指标视角。如果应用场景是「统计栏位里有多少头猪处于某种行为」把重叠的两头猪识别成一个框结果会低估行为频次。常见做法是在后处理时引入 NMS 的参数调整——iou-thres从 0.45 降到 0.3允许保留更多重叠框同时配合目标置信度和类别置信度的双阈值过滤。但要注意这不是模型层面解决遮挡真正有效的方案是在数据中保留大量遮挡场景的标注——为那些部分可见的猪单独标注而不是只标注最外面的一头让模型学到「这头猪只露出头也属于站立行为」。5.5 夜间与弱光准确率从 90 多掉到 60 多的翻车场景现象白天测试集上准确率 93%换到夜间红外或低照度视频流上直接掉到 65%检测框抖动严重。原因猪舍夜间普遍使用红外补光图像是灰度化的模型在白天 RGB 图像上习得的颜色特征在红外域基本失效。更麻烦的是红外图像中猪体与地面温度差会产生光晕目标边缘变得模糊。解决最朴素也最有效的办法是数据侧做光度增强模拟红外域分布。在hyp.yaml里把色彩抖动参数调大是一个方向、hsv_h保持不变色调在红外下没有意义hsv_s调到 0.6 用于压低饱和度、hsv_v调到 0.5 用于加大亮度变化幅度让模型见过「低饱和、高对比」的输入形态。更彻底的方案是直接把--img输入做灰度转换后当作训练样本的辅助通道但这需要改网络输入工程复杂度高不建议初期就做。6. 数据集之外从单帧识别走向行为时序记录数据集能支撑的玩法不止静态检测。猪的行为识别在实际场区里往往是「连续过程」而不是「单帧事件」——喝水和吃食通常持续几十秒到几分钟单帧识别只能告诉你「此刻在喝」但如果要做行为时长统计、进食节律分析你需要对视频帧做时序聚合。一个不引入复杂时序模型的方案对连续视频帧跑检测模型把每帧的行为类别记录成一条时间线然后用滑动窗口做平滑。窗口里出现大于 5 帧的drink类别就认为发生了一次饮水事件记录起始时间、持续帧数。窗口太短会把单帧抖动误判成切换行为窗口太长会把一次间断的吃食合并成多次事件这个参数要根据现场行为时长标定。验证模型是否够用的方法是把数据集的test集按视频时间戳排序重新跑一次检测对比人工标注的「事件起止时间」和模型输出的「事件起止时间」。如果你发现模型把一次连续的吃食识别成「吃、站、吃、站」的片段交替说明单帧模型对行为边界的辨识还是不够这时候再考虑加时序模型也不迟——常见做法是用一个轻量的 LSTM 或 TSM 分支读取连续帧的检测结果。最后讲一个我自己踩过的教训拿到小数据集时最容易犯的冲动是「先加数据再训练」。但加数据是有成本的标注 1000 张图、复核、修正格式整个链路投入的时间足够你把现有 1272 张图做三到五轮数据增强实验而后者的收益往往更大。后来我养成的习惯是——固定数据不动先穷尽模型侧和训练侧手段确认当前数据的信息量被榨干了再决定要不要补样本。数据永远值得补但顺序不能反。希望这几条实操路径能帮你在猪行为识别这个方向上少走几步弯路。本文还有配套的精品资源点击获取
返回列表