ARTICLE DETAIL

资讯详情

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

基于YOLOv9的猪行为识别:从数据集标注到模型部署全流程

基于YOLOv9的猪行为识别:从数据集标注到模型部署全流程 简介面向猪只行为识别与养殖智能化应用场景这份数据集以猪圈监控画面为素材标注了喝、吃、睡觉、站立四类高频行为可用于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缩进用空格不要用TabPython的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(fchecked {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模式下直接写txtX-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版本不匹配时训练会静默退回CPU1272张图配200个epochCPU和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-size168G显存可降到81272张图batch过大会让每轮参数更新次数太少--epochs150到200四类行为特征差异大150轮前mAP仍在爬坡--imgsz640YOLOv9官方默认输入尺寸行为识别任务够用--patience30val连续30轮不提升就停止防过拟合--device0指定GPU编号多卡用0,1batch-size有个最常见的坑显存不够就调小batch但小batch要同步调低学习率。默认学习率是按batch16设计的切到batch8后建议把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%指的是IOU0.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, dtypeint) 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(fframes{len(seq)}, 平滑后修正的帧数{fixed})逻辑说明每帧取置信度最高的类别作为该帧的行为标签然后对当前帧及其前W-1帧取众数窗口内没有检测结果时沿用上一帧的平滑值防止视频开头出现空窗。W30按帧率折算约1.2秒能覆盖一次喝水的连续动作想要更稳可以放到60帧约2.5秒代价是行为切换的响应延迟增加。脚本输出的fixed表示被平滑修正的帧数如果这个值超过总帧数的15%说明单帧预测抖动太严重优先回头检查阈值和标注口径而不是继续加大窗口。部署时报警判定用过滤前的原始结果行为记录入库用平滑后的结果两头数据都保留排查问题时比单一结果字段有用得多。本文还有配套的精品资源点击获取
返回列表