YOLOv9猪行为识别数据集:从标注到训练部署的完整实践
简介面向计算机视觉研究者和智慧养殖开发者的猪行为识别数据集聚焦猪圈场景下的自动监测需求主要用于识别喝、吃、睡觉、站立等典型行为整体平均正确识别率为92.6%适合目标检测模型训练、验证与部署场景。资源共2000个文件包含727张jpg原始图像、1272个txt格式标注及1个yaml数据集配置文件压缩包大小约85.86MBtxt文件对应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, fclass {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.yamlimages和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_okTrue) os.makedirs(os.path.join(label_root, sub), exist_okTrue) 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-size816越大梯度越准但显存涨得最快epochs80150小数据集100轮足够多跑容易过拟合imgsz640起步1280对小目标友好显存占用接近翻倍patience2030验证指标连续不涨就早停省时间device0或cpu多卡训练还需补device列表参数要注意的是1272张图里的“小目标”不是城市航拍那种微观目标喝水嘴部区域在640分辨率下大约二十几个像素imgsz640可以覆盖大多数情况。如果2.3节的统计显示小框比例超过三成先用imgsz768跑一轮对比下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_splits1, test_size0.2, random_state42) for train_idx, val_idx in splitter.split(filenames, groupsgroups): 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在imgsz640下的激活显存比理论计算大日志报的是“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(maxlen15) # 每次跟踪更新时把该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轮的快速验证确认标签没大问题再进完整训练流程。这轮快速验证省下的时间比调任何参数都多。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →