基于YOLOv9的猪行为识别:从数据集标注到模型部署全流程
2026/9/14 22:43:25 网站建设 项目流程

简介:面向猪只行为识别与养殖智能化应用场景,这份数据集以猪圈监控画面为素材,标注了喝、吃、睡觉、站立四类高频行为,可用于YOLOv9等目标检测模型的训练、微调与效果验证。采集的1272张图片多来自真实视频帧抽取,覆盖不同光线条件、拍摄角度以及单猪、多猪等典型情形,配合对应标注,能够支撑从模型入门训练到行为频次统计、异常状态预警等更下游的分析任务。基于该数据集训练的目标检测模型平均正确识别率可达92.6%,整体实用性较强。

资源包共2000个文件,压缩后约85.86MB。其中1272个txt标注文件与图片一一对应,保存着目标框坐标及类别编号,采用通用YOLO标签格式,解析方便;727张jpg原图涵盖猪舍内常见场景;1个yaml文件预置了类别名称、类别数量与数据集路径等信息,便于快速上手训练。目前已有250人学习使用,无论是计算机视觉初学者想获得一份完整标注的实战数据,还是养殖项目开发者需要迁移学习或算法对比的基准数据集,都能从中获得直接帮助。

1. 猪行为识别数据集解决的是监控录像看不完的问题

规模化猪场一个栋舍常布6到10个摄像头,每天上百GB录像,饲养员巡栏只能抽查,猪喝没喝够、吃没吃够、有没有长时间躺卧,这些信号全淹没在回放里。这个猪行为识别数据集给出了一条能落地的路径:1272张猪圈实拍图片,标注喝、吃、睡觉、站立四类行为,平均正确识别率92.6%,并直接提供YOLOv9格式标注。所谓YOLOv9格式,就是每张图对应一个同名txt,每行记录"类别ID 归一化中心点x y 归一化宽高",这套格式从YOLOv5沿用至YOLOv9。1272张图对四分类行为检测不算充裕,但配合迁移学习和数据增强能训出可用模型,关键在标注质量和训练策略。下面按数据整理、格式校验、YOLOv9训练、指标解读、部署验证的顺序,把拿到图片和标注之后该做的事讲一遍,新手能照跑,老手能对照查缺。

2. 把1272张图整理成YOLOv9标注:目录结构、标签文件和校验脚本

2.1 YOLOv9标注格式:一图一TXT,坐标全部归一化

YOLOv9的标注格式继承自YOLOv5,官方仓库的datasets目录组织方式就是 images 放图片、labels 放同名txt,训练时通过data.yaml把两边关联起来。每个txt文件一行一个目标,五个字段依次是类别ID、归一化中心点x、归一化中心点y、归一化宽、归一化高,字段间用空格分隔。

对猪行为识别任务,类别编号要固定下来,训练和推理共用同一张映射表:

类别ID行为典型画面表现
0drink 喝嘴部贴近饮水器或水槽,低头,口部有吮吸动作
1eat 吃头部伸进料槽,有咀嚼或拱料动作
2sleep 睡觉侧躺或趴卧,身体贴地,常几只挤在一起
3stand 站立四肢支撑站立,但没有采食和饮水动作

编号顺序可以由标定的人自行决定,但一旦训练开始就不要改动,否则推理输出和业务含义对不上。这里有个细节:sleep在论文里有时写作rest,有的数据集把趴卧单独拆成一类。这个四分类数据集的sleep是包含趴卧状态的,标注时只要看到身体贴地、不活动就归sleep,不必纠结猪是否真的闭眼。行为识别模型的边界就定义在标注口径上,口径不一致,后面所有指标都没有可比性。

2.2 目录结构和data.yaml的正确写法

拿到1272张图后,先按官方惯例组织目录再开训,避免训练时报各种找不到文件的错。标准结构如下:

images/ train/ # 约1000张 val/ # 约272张 labels/ train/ val/

train和val按8:2或9:1切都可以,类别不均衡明显时建议9:1,把更多图片留给训练。images目录放原始帧,jpg或png都行,文件名必须唯一,因为label文件和它是靠同名前缀匹配的。

data.yaml里内容就这几行:

# pig.yaml 猪行为识别数据配置 path: /data/pig_behave # 数据集根目录,建议写绝对路径 train: images/train # 相对path的图片目录 val: images/val nc: 4 # 类别数量 names: ['drink', 'eat', 'sleep', 'stand']

这个文件最容易出问题的是path字段。YOLOv9的train.py读取yaml时,train和val的路径都是在path基础上拼接的,如果path写的是相对路径,而执行命令时的工作目录又不在数据集根目录下,就会报"No labels found"或者图片全部跳过。我一般把path写成绝对路径,或者干脆train和val写完整的绝对路径,绕开拼接歧义。另外yaml缩进用空格,不要用Tab,Python的yaml库对这类低级错误报错极不友好。

2.3 训练前跑一个校验脚本,省一半排错时间

很多人跳过校验直接开训,然后训练到一半发现某张图的框坐标越界,触发nan loss。标注环节最常见的错误有三种:txt里出现负坐标、宽高为零、归一化后的中心点或宽高超出0到1区间。还有一个隐蔽问题:标注时图片是1920x1080,训练目录里的图被改成640宽,但label坐标还是按原图画出来的,模型学到的框位置就整体偏移。

训练前跑一遍下面的校验脚本非常划算:

# check_labels.py 校验YOLO格式标注文件 from pathlib import Path label_dir = Path('/data/pig_behave/labels/train') img_dir = Path('/data/pig_behave/images/train') cls_num = 4 problems = [] for txt in label_dir.glob('*.txt'): img = img_dir / (txt.stem + '.jpg') if not img.exists(): problems.append(f'[no-img] {txt.name}') lines = txt.read_text().strip().splitlines() if not lines: problems.append(f'[empty] {txt.name}') continue for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: problems.append(f'[fields] {txt.name}:{i}') continue cid = int(parts[0]) cx, cy, w, h = map(float, parts[1:]) if cid >= cls_num: problems.append(f'[cls] {txt.name}:{i} id={cid}') if not (0 <= cx <= 1 and 0 <= cy <= 1): problems.append(f'[center] {txt.name}:{i}') if w <= 0 or h <= 0 or cx + w / 2 > 1 or cy + h / 2 > 1: problems.append(f'[beyond] {txt.name}:{i}') all_txt = list(label_dir.glob('*.txt')) print(f'checked {len(all_txt)} label files, {len(problems)} problems') for p in problems[:50]: print(p)

逐段说明这段脚本做的事情:先对每个txt检查对应jpg是否存在,图片被删或改名会产生孤儿label,这类图在训练时被静默跳过,等于损失一条样本;接着检查空文件,空txt表示该图没有目标,如果不是刻意保留负样本,通常意味着漏标;最后对每一行做字段校验,字段数必须为5,类别ID不能超过配置的4类,归一化中心点必须在0到1之间,框的右边界cx加宽的一半不能超过1。跑完输出前50条问题进行修复,修一个坏框的成本远低于训练时debug一次nan loss。

2.4 标注工具导出格式的三个注意点

标注工具常见做法是用LabelImg或X-AnyLabeling。LabelImg在YOLO模式下直接写txt,X-AnyLabeling支持加载辅助检测模型做预标注,人工只做修正,对猪圈这种目标数量多、互相遮挡的场景效率高得多。导出和标注时有三个注意点。

第一个注意点是LabelImg要显式切换到YOLO保存格式,切换后它才会同时生成txt,否则静默导出一堆PascalVOC的xml,很多人把xml当txt传,训练时报错才发现。第二个注意点是遮挡目标的标注:猪圈里栏杆、料槽遮挡很常见,一头猪只露出前半身时,不要画一个把遮挡物也包进去的大框,YOLO的框覆盖可见区域就好,宁可框小一点,也别让模型去学栏杆的纹理。第三个注意点是图片尺寸:标注期间不要对原图做任何缩放,导出的label坐标与图片像素一一对应,训练时imgsz参数由训练代码统一缩放,分辨率不一致是坐标偏差的隐藏源头。

提示:如果1272张图片来自多个猪舍或多种光线条件,切分train/val时要把不同猪舍的图片均匀打散,而不是按猪舍整体切分。否则val全是没见过的猪舍环境,mAP会明显偏低,跟模型真实水平对不上。

3. 用YOLOv9训练猪行为识别模型:环境、命令与关键参数

3.1 环境安装与预训练权重选择

YOLOv9的训练环境要求不高,Python 3.8及以上、PyTorch 1.8及以上即可,一块8GB显存的显卡就能训练,4GB显存则建议从yolov9-t这个轻量版本开始。先把仓库克隆到本地再装依赖:

git clone https://github.com/WongKinYiu/yolov9 cd yolov9 pip install -r requirements.txt

依赖装完后先用Python确认torch的CUDA是否可用,执行python -c "import torch; print(torch.cuda.is_available())",返回True才说明GPU参与训练。不要凭pip install时装了什么就以为能用,显卡驱动和torch版本不匹配时训练会静默退回CPU,1272张图配200个epoch,CPU和GPU的耗时差距在一个数量级以上。

预训练权重是迁移学习的核心。yolov9-c.pt是性价比最高的选择,参数量适中,对行为检测这种中小目标任务友好;yolov9-e.pt精度高但对显存要求更苛刻。直接用官方在COCO上训练好的权重做起点,收敛速度和最终精度都远优于随机初始化,这在1272张图的小数据集上几乎是决定性的,从头训练很容易陷入局部最小值,迁移学习能稳稳压住这个风险。

3.2 训练命令逐项拆解

官方仓库的入口脚本是train.py,训练命令长这样:

python train.py \ --data pig.yaml \ --weights yolov9-c.pt \ --batch-size 16 \ --epochs 200 \ --imgsz 640 \ --device 0 \ --patience 30

各参数的选择逻辑:

参数建议值选择依据
--datapig.yaml指向前面写好的数据配置文件
--batch-size16,8G显存可降到81272张图batch过大会让每轮参数更新次数太少
--epochs150到200四类行为特征差异大,150轮前mAP仍在爬坡
--imgsz640YOLOv9官方默认输入尺寸,行为识别任务够用
--patience30val连续30轮不提升就停止,防过拟合
--device0指定GPU编号,多卡用0,1

batch-size有个最常见的坑:显存不够就调小batch,但小batch要同步调低学习率。默认学习率是按batch=16设计的,切到batch=8后建议把hyp文件里的lr0对应减半,否则loss前期剧烈震荡,这是行为检测任务精度上不去的头号原因。epochs不要设太少,drink类样本往往到120轮以后才开始明显爬升,过早停掉会让小类别永远学不好。

3.3 数据增强参数里只有一处需要慎重

YOLOv9的hyp配置文件里,mosaic、fliplr、hsv_h这些增强开关默认都开着。对1272张的小数据集,mosaic必须保留,它把四张图拼成一张输入,等于在样本层面扩充组合多样性,对四类行为的空间关系学习很有帮助。fliplr(左右翻转)则需要谨慎:猪场的饮水器和料槽方向是固定的,左右翻转会让饮水器位置镜像,模型可能学到位置偏置而不是行为特征。如果训完发现drink类的val召回率明显低于train,优先把fliplr改成0.0再训一轮对比。

hsv_h和hsv_s这两个颜色抖动参数建议保持默认。不同猪舍的灯光色温差异大,从暖黄到冷白都有,颜色抖动开着能提升跨猪舍泛化,在多猪舍混合采集的数据集上收益明显。我的原则是:数据量越少越不要主动折腾增强参数,除了fliplr按需关闭,其余保持默认,先把baseline跑出来再谈优化。

3.4 训练日志里盯住三个数

训练过程中每个epoch结束会打印一组指标,核心是P(精确率)、R(召回率)、mAP50、mAP50-95。对猪行为识别这种类别易混淆的任务,最先盯的是R,也就是召回率。漏检意味着一头猪在某个行为状态被整帧丢掉,对后续的饲喂异常报警来说,漏检比误报更危险。

mAP50-95是严格版指标,它把IOU阈值从0.5逐步提高到0.95再求平均。如果mAP50不错但mAP50-95明显低,说明模型框的位置不准,这在猪行为数据里很常见,因为猪互相遮挡、身体蜷缩,边界框和真实区域稍偏,高IOU阈值下就失分。想提升mAP50-95,最直接的手段是提高标注框的贴合度,框不要随意放大留白。

训练结束后,runs/train/exp*/目录下的results.png把loss曲线和指标曲线画在一起。正常的走向是:loss在前50轮快速下降后趋平,val曲线最后20轮基本水平。如果val loss先降后升而train loss还在降,是明显的过拟合信号,此时回退到val loss最低那轮的best.pt使用,不要用最后一轮权重。

4. 92.6%正确率的真实构成:指标口径、混淆热点与类别均衡

4.1 先搞清楚92.6%是哪个口径的指标

不同的数据集发布者对"平均正确识别率"的定义可能完全不一样。如果92.6%指的是IOU=0.5下的mAP50,对1272张图、四类行为的数据集来说是一个相当好的数字,说明标注质量和训练配置都到位了。如果指的是逐帧准确率,那这个数字会被高频行为主导:数据里sleep帧占一半,模型全部输出sleep也能拿到接近50%的准确率,92.6%听起来高,实际可比性很差。行为识别方向的论文现在更常报告mAP50和F1,而不是直接给accuracy。

拿到别人给的数据集时,先别急着训,第一步是用脚本统计各类别的框数量分布,判断数据是否均衡:

# count_cls.py 统计每个类别的标注框数量 from pathlib import Path import numpy as np def count_boxes(labels_dir: Path, cls_num: int) -> np.ndarray: cnt = np.zeros(cls_num, dtype=int) for txt in labels_dir.glob('*.txt'): for line in txt.read_text().strip().splitlines(): cid = int(line.split()[0]) if cid < cls_num: cnt[cid] += 1 return cnt print('train:', count_boxes(Path('/data/pig_behave/labels/train'), 4)) print('val: ', count_boxes(Path('/data/pig_behave/labels/val'), 4))

假设输出是train: [215 480 1360 945],分别对应drink、eat、sleep、stand。sleep接近一半,说明数据采集大量覆盖了夜间和静默时段;如果drink只有几十个框,训练时梯度基本被sleep主导,val上drink的mAP会惨不忍睹。行为识别数据集在真实猪场采集时都有这个问题,夜间自动录像多、白天行为片段少,而drink恰恰是饲喂管理里最需要关注的信号。

4.2 行为混淆的四个高热区域

行为识别的混淆不是随机的,热点集中在几个固定组合,这受猪的物理姿态和圈舍布局支配:

易混对混的主要原因缓解方法
drink 与 eat饮水嘴和料槽常紧挨,嘴部坐标接近标注时把drink框收在嘴部区域,缩小位置重叠
stand 与 eat低头采食时躯干仍是站立姿势判定行为看嘴部动作,框的中心落在动作部位
sleep 与 stand躺下和起身瞬间的中间帧姿态模糊推理阶段用前后帧投票修正
多猪重叠睡觉聚堆时两个框IOU极高,NMS吞掉一个NMS的iou阈值保持0.6,不要为少框调高

前两行和标注口径直接相关。同一个画面里,一头猪站在料槽前低头,有人标eat,有人标stand,这种标注不一致会让模型在这两类上反复震荡。解决思路是在标注规范里写明判定优先级:只要嘴部接触或贴近料槽、水嘴,就归eat或drink;只有既不采食也不饮水的站立才归stand。判定优先级写死后重新过一遍标注,比任何调参都有效。

4.3 类别不均衡的三种调整手段,按顺序用

drink样本少时,处理顺序建议是:先重采样,再调损失权重,最后才动数据增强。YOLOv9仓库没有直接暴露类别加权参数,改loss源码对很多人成本太高,所以最常用的是对少数类图片做复制过采样:把drink的图片连同对应txt复制几份放进训练集,直到drink的框数量接近eat的量级。

复制时注意labels目录必须同步复制同名txt文件,否则多出来的图片没有标签,等于白加。过采样的代价是epoch变长、部分图被反复看到,但对小数据集收益非常直接。如果过采样两轮后drink类mAP还是上不去,再考虑在hyp配置里调整损失权重,或者给drink类单独构造一组以嘴部为中心的裁剪样本送入训练。数据增强放在最后,因为小数据集上增强参数放得太激进,容易让模型学到的特征漂移。

4.4 数据划分必须按视频片段切,不能按帧随机切

最后这一点直接影响92.6%这个数字能不能复现。如果1272张图是从几段连续视频里抽出来的,按帧随机切分train和val,会让同一段视频的相邻帧同时出现在两边。val里的画面和train高度相似,mAP虚高是必然的,部署到一段全新的监控视频上指标立刻缩水,这就是数据泄漏。

正确做法是先把视频按时间段切分成独立片段,然后以片段为单位整体划分到train或val,模拟"没见过的猪圈片段"。切分时还要让四类行为在train和val里的比例大致一致。分段时优先挑出包含drink行为的时段单独标注,这类时段占比小但价值高,把它作为先验切出好片段,也是行为识别数据收集时优先保证drink覆盖率的原因。

5. 把YOLOv9模型部署到监控流前:置信度分布、滑窗投票和验证脚本

5.1 用detect.py导出每帧结果,先看置信度的真实分布

模型训练完,很多人直接把默认conf阈值0.25拿去跑监控视频,结果误报满天飞。猪圈里画面静止时间长、光线波动、飞虫和灰尘反光,都会让模型在stand类上打出0.3左右的置信度,默认阈值把这些低置信度框全放进来,行为序列会非常毛躁。

先跑一段真实的监控片段看看分布:

python detect.py \ --source pen1.mp4 \ --weights runs/train/exp/weights/best.pt \ --conf 0.35 \ --save-txt --save-conf

--save-txt让每帧检测结果写成txt,--save-conf把每个框的置信度一并写入。行为识别场景里conf阈值从0.25提到0.35,通常能滤掉大部分飞虫和水雾噪声,同时不影响喝水和采食这类特征明显的框召回。如果val数据里drink类的置信度普遍在0.4上下,阈值就不能无脑提太高,否则把真信号也滤了。阈值要跟着每类的置信度分布走,而不是全局一个数。

5.2 用三秒滑窗投票把行为标签做平滑

行为状态在时序上是连续的,单帧预测却会频繁跳变:一头猪明明在睡觉,中间某一帧因为翻身被判成stand,又跳回sleep。这种毛刺在逐帧结果里很正常,直接入库就是噪声。常见做法是加一个滑窗多数投票,把短期抖动抹平:

# smooth.py 对逐帧检测结果做滑窗投票 from collections import Counter import glob files = sorted(glob.glob('runs/detect/exp/labels/*.txt')) def frame_label(path): """取一帧置信度最高的类别作为该帧行为标签""" best = None for line in open(path).read().strip().splitlines(): parts = line.split() # 类别 中心x 中心y 宽 高 置信度 score = float(parts[5]) if best is None or score > best[1]: best = (int(parts[0]), score) return best[0] if best else -1 seq = [frame_label(p) for p in files] # 每帧一个行为类别 W = 30 # 窗口30帧,25fps约1.2秒 smooth = [seq[0] if seq[0] >= 0 else 0] for i in range(1, len(seq)): win = [c for c in seq[max(0, i - W + 1): i + 1] if c >= 0] if win: cls = Counter(win).most_common(1)[0][0] else: cls = smooth[-1] smooth.append(cls) fixed = sum(1 for a, b in zip(seq, smooth) if a != b) print(f'frames={len(seq)}, 平滑后修正的帧数={fixed}')

逻辑说明:每帧取置信度最高的类别作为该帧的行为标签,然后对当前帧及其前W-1帧取众数,窗口内没有检测结果时沿用上一帧的平滑值,防止视频开头出现空窗。W=30按帧率折算约1.2秒,能覆盖一次喝水的连续动作;想要更稳可以放到60帧(约2.5秒),代价是行为切换的响应延迟增加。脚本输出的fixed表示被平滑修正的帧数,如果这个值超过总帧数的15%,说明单帧预测抖动太严重,优先回头检查阈值和标注口径,而不是继续加大窗口。部署时报警判定用过滤前的原始结果,行为记录入库用平滑后的结果,两头数据都保留,排查问题时比单一结果字段有用得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询