☰
YOLOv9猪行为识别数据集:从标注到训练部署的完整实践
2026/10/1 11:01:11 网站建设 项目流程

简介:面向计算机视觉研究者和智慧养殖开发者的猪行为识别数据集,聚焦猪圈场景下的自动监测需求,主要用于识别喝、吃、睡觉、站立等典型行为,整体平均正确识别率为92.6%,适合目标检测模型训练、验证与部署场景。资源共2000个文件,包含727张jpg原始图像、1272个txt格式标注及1个yaml数据集配置文件,压缩包大小约85.86MB;txt文件对应YOLOv9标准标签,yaml文件定义了类别与数据集结构,可直接接入常见训练流程。数据集图片取自不同视频帧,覆盖多角度猪圈画面,能在一定程度上提升模型对不同光照、姿态和环境的泛化能力。目前已吸引251人学习,适合用于智慧养殖异常行为监测、猪只行为自动分类等项目的模型训练,也可作为目标检测入门与进阶的实操样本。

1. 猪行为识别数据集:92.6%的平均正确率,先看数据还是先跑模型

1272张图,四类行为,平均正确识别率92.6%,支持yolov9格式标注。这个组合放在论文里不算惊艳,放在猪场监控场景里却意味着可以少安排一个盯着屏幕的人。猪行为识别这个方向一直不缺算法,缺的是带标注的小而干净的训练集——而这个数据集把喝、吃、睡觉、站立四类高频行为做成了目标检测标注,解压后按yolo格式喂给训练脚本就能跑。适合的人群有三类:做农业AI落地的工程师、拿垂直小数据集练手yolov9的入门者、以及想验证小样本能不能训出可部署模型的选型人。下面从yolov9标注格式、训练参数、指标口径到避坑经验,一条流程拆完。

2. 读懂yolov9标注再动手:txt标签格式、类别映射与一张图片的检查脚本

很多人拿到数据集的第一件事是直接训练,结果指标虚高或者推理结果对不上。翻一次车就明白,先花十分钟检查标注比训练两小时更值得。yolov9格式的标签结构、类别映射和检查脚本拆开,后续所有训练和复现都以这份核对过的数据为基础。

2.1 行为识别在这里其实是目标检测:先对齐任务边界

看到「行为识别」四个字,容易先入为主用到TSN、slowfast这类视频时序模型。目录里躺着的yolov9格式标注明确告诉你,这个数据集的任务被定义成了目标检测:每一帧里出现的行为由一个矩形框给出,而不是整段视频的分类标签。四类行为各自对应一类框,类别名通常是drink、eat、sleep、stand。

这个定义方式直接影响两个决策。第一,不需要额外搭时序网络,拿目标检测器就能出结果;第二,框的语义完全靠标注者的规则决定——喝水框的是整头猪还是嘴部,吃食框的锚点在哪里,同一套图片给不同人标,mAP可以差出好几个点。我的习惯是先抽样看20到30张原图配框,确认这四类的标注边界是否一致,再决定训练策略。这一步看下来,后面调参时才不会把标注噪声当成模型玄学。

2.2 yolov9格式长什么样:txt里的class和五个归一化数字

yolov9沿用了yolo系列通用的txt标注格式,不需要额外转换。每一张jpg图片对应一个同名txt文件,文件里每一行是一个目标框,五个数字加一个类别号:

class_id x_center y_center width height

坐标全部按图片宽高归一化到0到1之间。比如一行0 0.45 0.60 0.12 0.08,表示第0类行为,框的中心在图片横向45%、纵向60%的位置,框宽是图片宽度的12%,框高是图片高度的8%。class_id不是随便写的,它和data.yaml里names列表的下标一一对应。

先拿命令行直接看第一张图的标注:

head -n 5 labels/000001.txt

如果输出为空,说明这张图没有目标,要么是负样本,要么标注遗漏。紧接着用下面这段Python把框画回原图,肉眼确认框位置是否贴合猪体:

import cv2 img_path = "images/000001.jpg" label_path = "labels/000001.txt" img = cv2.imread(img_path) h, w, _ = img.shape with open(label_path) as f: for line in f: parts = line.split() if len(parts) < 5: continue cls_id, cx, cy, bw, bh = parts cx, cy, bw, bh = map(float, (cx, cy, bw, bh)) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"class {cls_id}", (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("check_000001.jpg", img)

脚本做的事情不复杂:把归一化坐标还原成像素坐标,然后画框并写上类别id。两个关键点:一是换算公式x1 = (cx - bw / 2) * w,归一化坐标乘回图片宽高;二是画文字时y1 - 5可能越界,所以包了一层max(0, y1 - 5)。如果这个数据集里某一类行为框住了半头猪,画出来一眼就能看出问题。

2.3 用Python检查标注质量:类别分布、目标尺寸与错位标注

1272张图算小数据集,类别的绝对数量直接决定训练效果。用下面脚本统计每个类别的框数和框尺寸分布:

import os from collections import Counter label_dir = "labels" cls_counter = Counter() size_buckets = {"small": 0, "medium": 0, "large": 0} for name in os.listdir(label_dir): if not name.endswith(".txt"): continue with open(os.path.join(label_dir, name)) as f: for line in f: parts = line.split() if len(parts) < 5: continue cls_id = int(parts[0]) bw = float(parts[3]) bh = float(parts[4]) cls_counter[cls_id] += 1 area = bw * bh if area < 0.01: size_buckets["small"] += 1 elif area < 0.09: size_buckets["medium"] += 1 else: size_buckets["large"] += 1 print("per class:", cls_counter) print("size distribution:", size_buckets)

这段把每个txt里的每一行读出来,按类别id计数,同时按归一化面积分成小、中、大三档。判断依据:0.01大约是640像素输入下64×64的目标,0.09大约是256×256。猪圈监控里喝水这类行为经常是小框,站立和睡觉则可能是大框。如果小框占比过高,训练时默认的anchor可能照顾不到,可以考虑把imgsz从640提高到768甚至1280再试。

还要顺带做一次文件名匹配检查:每个jpg必须有一个同名txt,反之亦然。漏一个文件,训练时不是FileNotFoundError就是空标签直接把loss拉偏。这些都是标注检查里最常见的坑,早发现早省时间。如果检查下来发现缺框或者错位,别急着训练,先用cvat或labelimg这类数据标注工具把漏标补上再继续。

3. 把数据集跑成模型:YOLOv9训练的最小命令、目录组织与5个关键参数

数据核对完之后进入训练环节。这里不铺开讲网络结构,给一套能出结果的最小流程:目录怎么摆、data.yaml怎么写、训练命令怎么敲、几个参数改完有什么后果。

3.1 标准目录结构:images与labels必须严格同名前缀

yolo系列训练脚本对目录的要求不复杂,但前缀错一个字符就白跑。常见做法是下面这个结构:

pig_dataset/ ├── images/ │ ├── train/ │ │ └── 000001.jpg │ └── val/ │ └── 000100.jpg ├── labels/ │ ├── train/ │ │ └── 000001.txt │ └── val/ │ └── 000100.txt └── data.yaml

images和labels两个根目录下,train和val子目录名必须一致,图片和标签文件的文件名必须完全相同(只有后缀不同)。下面的Python脚本按8:2划分train/val,并对文件名做交叉校验:

import os import random import shutil random.seed(42) img_root = "images" label_root = "labels" images = [f for f in os.listdir(img_root) if f.endswith(".jpg")] random.shuffle(images) split = int(len(images) * 0.8) train_imgs = images[:split] val_imgs = images[split:] for sub in ("train", "val"): os.makedirs(os.path.join(img_root, sub), exist_ok=True) os.makedirs(os.path.join(label_root, sub), exist_ok=True) for name, group in (("train", train_imgs), ("val", val_imgs)): for img_name in group: label_name = img_name.replace(".jpg", ".txt") if not os.path.exists(os.path.join(label_root, label_name)): print("missing label:", img_name) continue shutil.move(os.path.join(img_root, img_name), os.path.join(img_root, group, img_name)) shutil.move(os.path.join(label_root, label_name), os.path.join(label_root, group, label_name))

划分逻辑是纯随机的,先看结果能不能跑通。注意它做了一个前置检查:图片文件存在但对应txt不存在时,直接把这张图跳过并打印警告,避免脏数据进入训练集。随机种子固定在42,方便复现。

3.2 训练命令:batch、epoch、imgsz一轮拆清楚

目录整理好后,写data.yaml。names的顺序必须和txt里的class_id一致,这是最容易翻车的点:

path: /absolute/path/to/pig_dataset train: images/train val: images/val names: 0: drink 1: eat 2: sleep 3: stand

跑yolov9训练,常见路径是原版仓库里的train.py,而不是ultralytics那条yolo命令行。原版需要同时给--data、--cfg和--weights三个参数:

python train.py \ --data /absolute/path/to/pig_dataset/data.yaml \ --cfg models/yolov9-c.yaml \ --weights yolov9-c.pt \ --batch-size 16 \ --epochs 100 \ --imgsz 640 \ --device 0

--cfg指定模型结构,--weights指定预训练权重,二者必须搭配同一个版本,否则结构对不上直接报错。--batch-size 16在8GB显存上比较安全;显存只有6GB就降到8,或者把--imgsz改成416。--epochs 100对千张级别数据集通常够用,训练脚本会按验证集指标自动保存best.pt和last.pt,不需要手动盯loss曲线。

关键参数的作用如下表:

参数取值建议改大/改小的影响
batch-size8~16越大梯度越准但显存涨得最快
epochs80~150小数据集100轮足够,多跑容易过拟合
imgsz640起步1280对小目标友好,显存占用接近翻倍
patience20~30验证指标连续不涨就早停,省时间
device0或cpu多卡训练还需补device列表参数

要注意的是,1272张图里的“小目标”不是城市航拍那种微观目标,喝水嘴部区域在640分辨率下大约二十几个像素,imgsz=640可以覆盖大多数情况。如果2.3节的统计显示小框比例超过三成,先用imgsz=768跑一轮对比下mAP再决定要不要上1280。

3.3 推理验证:从best.pt到val目录跑一遍detect

训练日志里如果best.pt的mAP50能到85以上,说明数据本身是干净的。下一步用训练好的权重跑验证集推理,看实际的框效果而不是只看指标:

python detect.py \ --weights runs/train/exp/weights/best.pt \ --source /path/to/pig_dataset/images/val \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt

--conf-thres 0.25表示置信度低于0.25的框直接丢弃,这个值决定漏检和误检的平衡点;--iou-thres 0.45用于NMS,两个猪框高度重叠时只保留得分高的。--save-txt把预测结果写成yolo格式的txt,方便后续对比标注文件。跑完翻几张推理图,重点看两个地方:喝水这种小框有没有被漏掉,以及睡觉这种大框有没有把一个猪栏里两头相邻的猪合并成一个框。这一步通过之后再进指标口径的讨论。

4. 复现92.6%先对齐口径:验证集划分、类别不均衡与置信度阈值

标题里的92.6%平均正确识别率是很多人的第一关注点,但它是个需要追问的黑匣子。同一份数据,不同划分方式、不同指标定义,能得出完全不同的数字。

4.1 92.6%是哪个“正确识别率”:指标口径决定一切

目标检测里常见的指标有三个:图片级准确率、框级precision/recall和mAP50。图片级准确率是这样算的:一张图预测出一个主要行为,和真实标注比对,猜对就算对;mAP50则要求框的位置和类别都对齐IoU大于0.5才算命中。二者对同一个模型可以差出十个百分点以上。

如果数据集作者说的92.6%是图片级结果,这个数字并不代表每个动作框都抓得准,可能只代表大多数图片上的主要行为被认对了。小数据集的另一个特点是val集合很小,validation的准确率方差很大——1272张图按8:2划分,val只有250张左右,少8张分类正确的图,准确率就掉3个百分点。所以复现的第一步是问自己:我要复现的是那个92.6,还是做一个自己验证过、能交代清楚指标的模型。

4.2 验证集怎么切:按时间分组防连续帧泄漏

这是整个复现流程里最容易犯的错误。如果数据集是从监控视频里抽帧得到的,同一段视频相邻帧之间背景和猪只位置几乎没变。纯随机划分会把这些近似重复的帧同时分进train和val,模型等于直接见过答案,val指标会虚高。

应对办法是给图片按采集时间或视频片段分组,按组划分而不是按单张划分。常见的数据集文件名会带时间戳,或者按日期分目录。用GroupShuffleSplit的思路做分组切分:

from sklearn.model_selection import GroupShuffleSplit filenames = ["20250101_081000.jpg", "20250101_081001.jpg", "20250101_081500.jpg"] groups = [name.split("_")[0] for name in filenames] splitter = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) for train_idx, val_idx in splitter.split(filenames, groups=groups): print("train:", train_idx) print("val:", val_idx)

核心区别在groups这一列:它把属于同一个日期或同一个视频片段的图片绑成一组,划分时整组进train或者整组进val,从源头断了连续帧泄漏。需要留意GroupShuffleSplit只接受整数索引,文件名列表要保持和索引一一对应才能工作。这比纯随机划分麻烦一点,但val指标的含金量完全不同。

提示:val集只有两百多张时,优先保“按时间分组”,不要为了凑数量把同一段视频的帧拆进两个集合。

4.3 类别不均衡的对策:过采样与按类设置信度

四类行为的出现频率天然不均衡。猪场监控里站立和睡觉出现时间长、占比高,喝水和吃食发生时间短、占比低。如果不处理,模型会偏向占比高的类,最常见的表现是stand类precision高、drink类recall低。

处理办法有两层。训练层面可以按类别数量做重采样,把样本不足的类别的图片多复制几遍;也可以调整类别权重,让稀疏类别在loss里占更高比例。推理层面更直接:给稀疏类别单独设更低的conf阈值。下面是一份针对四类行为的推荐阈值参考:

行为框大小建议conf常见误检
drink小0.15吃食时的嘴部动作
eat中0.25低头闻地面
sleep大0.30趴着休息被框成站立
stand大0.30行走中短暂停顿

注意这张表的逻辑:小框、样本少的类别阈值要放低,宁可多几个误检框也不要漏掉真正的喝水动作;大框类别阈值可以高一些,把明显误检挡在外面。实际部署时阈值还要跟着摄像头的安装高度和角度看,后面会讲怎么结合自家场景微调。

5. 猪行为识别实战避坑:5条现象、原因与解决办法

yolov9训练猪行为识别不算难,难的是模型从实验室指标到猪场现场之间那段路。下面五个坑按现象、原因、解决三段拆开,都是我反复遇到过的问题。

5.1 val高、现场低:场景泛化不是数据集的锅

现象:训练集和验证集mAP50都在90上下,换到另一个猪圈摄像头拍出来的视频,漏检一大片,尤其站立和喝水。

原因:1272张图很可能来自同一个猪圈、同一路摄像头,背景、光照、猪的品种高度一致。模型背住了背景,而不是学会了行为本身。

解决:扩数据比调参优先级更高。去不同猪圈、不同机位、不同光照时段补拍一批同一标注格式的图片,至少补到2000到3000张再谈上线。如果没法补,用更重的mosaic和HSV增强让模型别依赖背景色块。

5.2 喝水和吃傻傻分不清:框的标注口径不一致

现象:模型在视频里频繁把喝水识别成吃,或者反过来,指标却显示两类都很高。

原因:两种行为的框都集中在猪栏一侧的水槽和料槽附近,框住了整头猪时,嘴部到槽位的距离只有几个像素,框的内容几乎一样。标注员在打标签时各自按自己的理解定义边界。

解决:统一标注口径。推荐做法是把框缩到头部加前蹄区域,以口部是否接触水或接触食物作为类别判据。已经标好的数据不用全部返工,挑50张容易混淆的样本,给标注员写一份带截图的标准,重新校准后再补一个新版本。这个坑不解决,调任何网络结构都没用。

5.3 class id错位:yaml顺序就是txt里的0、1

现象:训练正常收敛,推理结果类别全对不上——站着不动标成了drink。

原因:txt里class_id的数字顺序和data.yaml里names列表的顺序不一致。数据集简介里写0代表喝水,yaml里却把0写成了eat。

解决:训练前跑一遍2.3节的类别统计,把txt里每个id出现的次数打印出来,再对照data.yaml的names人工确认。两个列表对不上,改yaml或改txt都行,但一定只改一边。训练中的loss曲线看不出这个问题,必须靠这个清单卡一道关口。

5.4 OOM翻车:小显存怎么喂大batch

现象:以为8GB显存能跑batch-size 16,启动两轮之后就报CUDA out of memory,训练中断。

原因:yolov9-c在imgsz=640下的激活显存比理论计算大,日志报的是“out of memory”,实际是batch-size加分辨率叠加的结果。

解决:把batch-size降到4先确认能跑通,然后逐次翻倍到8或12;还不行就把imgsz降到416,推理时再回640。不要为了指标硬撑大batch,小数据集上batch 8和16的差距远没有一轮训练中随机种子带来的波动大。

5.5 浅色地面漏检:白猪在浅色地面几乎隐身

现象:白色杜洛克或大白猪趴在浅灰色水泥地上,sleep类在val里检出率只有一半,框子时有时无。

原因:浅色猪和浅色地面的对比度太低,模型学到的主要是颜色特征而不是纹理或轮廓特征。

解决:强化mosaic和mixup增强、把HSV的饱和度扰动调大;更彻底的做法是换红外摄像头采集夜晚数据,红外图里猪体温和地面温度天然分开,对比度比可见光高很多。如果现场只有可见光摄像头,把灰度化和直方图均衡加进预处理管线,能让浅色猪的轮廓稍微顶出来。

6. 从1272张到你的猪圈:扩充、时序投票与YOLOv8迁移

6.1 扩充数据:cvat和labelimg补标yolo格式

想在别的猪圈用起来,第一步永远是补数据。半自动标注的流程很成熟:用best.pt先跑一批新猪圈的图,把预测结果生成yolo格式txt,再导入cvat或labelimg人工修正。cvat的优势是多人在线协同、有现成的yolo格式导出,labelimg则更轻量,适合单机几百张图的量级。修正完的txt直接放回labels目录,和旧数据合并重训,不用改任何脚本。

6.2 把帧级结果变行为时序:滑窗投票去抖

目标检测只能回答这一帧里什么行为发生,不回答“这头猪今天喝了多少次”。部署时可以对同一头猪的跟踪框做滑窗投票,把单帧误检平滑掉:

from collections import deque, Counter window = deque(maxlen=15) # 每次跟踪更新时把该id的预测类别push进来 window.append(pred_cls) if len(window) == window.maxlen: vote = Counter(window).most_common(1)[0][0] if vote == "drink": drink_count += 1

这段逻辑用一个长度为15帧的滑窗,窗口内出现次数最多的类别作为这一小段的行为结果,能挡掉单帧的误检抖动,代价是行为切换的响应滞后大约半秒,对猪圈监控完全可接受。接入跟踪器后,每个猪只id独立维护一个窗口即可。

6.3 迁移到YOLOv8流程:几乎零成本

如果团队内部其他项目都跑在yolov8或更新版本上,这个数据集的迁移成本很低。要复刻网上常见的yolov8训练自己的数据集教程,yolo格式的txt不用改,data.yaml不用改,只把训练命令换成对应框架的写法就能跑起来。实际体验下来,这类几百到一千张的垂直小数据集,不同yolo版本间的差距经常小于标注噪声带来的差距。所以不必纠结必须用yolov9,重点是数据标注口径、验证集切分逻辑和阈值调整这套流程,这些换到任何检测框架里都复用。

我的习惯是每收一套新的行为识别数据,先在本地用小模型跑一个50轮的快速验证,确认标签没大问题,再进完整训练流程。这轮快速验证省下的时间比调任何参数都多。希望帮到你。

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

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

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

立即咨询