yolov5生猪行为检测全流程:从数据集构建到训练部署实战
简介面向养殖场智能化管理场景YOLOv5生猪行为状态检测训练权重与PyQt界面工程能够帮助算法工程师、农业信息化开发者快速搭建猪只进食、站立、躺卧、攻击等行为识别系统。包内包含1000多张基于养殖场视频监控帧的已标注图像及对应txt标签数据已按train/val/test划分完毕并配好data.yaml可直接用于YOLOv5、YOLOv7、YOLOv8、YOLOv9等主流模型训练。整套资源共包含2000个文件以1210个标签txt、706张JPG图像、34个Python脚本、28个YAML配置为主另有少量XML标注、PDF使用说明及Markdown笔记整体压缩包约274MB。目前已有447人学习下载除训练数据外还提供PyQt界面代码可生成较为简洁的检测交互系统方便直接验证模型效果、集成到养殖场监控流程中同时辅以训练脚本和文档适合希望跳过数据准备、直接开展生猪行为识别训练与部署的开发者。1. yolov5生猪行为检测从数据集到训练权重这条链路把四个状态一次讲透养殖场里最让技术负责人头疼的往往不是猪价而是栏舍里那几百头猪到底是在吃料、站立、趴卧还是互相攻击。这四个行为状态直接关系到饲料转化率和伤亡率可人工巡检一个万头猪场一圈下来至少四十分钟等巡查到另一栋栏舍时前面的情况又变了。yolov5生猪行为状态检测方案解决的就是这个问题——把摄像机画面实时切成四类行为标签让异常攻击行为在发生时就能被捕捉。这套资源包含1000多张真实养殖场场景截图数据已经按train/val/test划分完毕并配好data.yaml严格来说拿到手改一行路径就能开训。适合谁用正在做智慧养殖项目但苦于缺乏现场数据的技术人员以及想快速验证yolov5在自己场景下能否落地的算法工程师。注意一个前提——这里的行为类别只有eat、stand、lie、attack四类是典型的单目视频行为分类不涉及多猪只身份追踪和跨摄像头关联。2. 数据集构成与标注体系1000张图训出可用模型的关键不在数量在分布2.1 数据来源与目录结构视频抽帧标注的长尾效应这批数据的文件名带着vlcsnap前缀明显是从监控视频里抽帧得到的时间戳从2022-01-08到2022-01-09跨了两天覆盖了白天和傍晚不同光照时段。这样做的好处是背景多样性足够——栏舍栏杆阴影、料槽反光、地面水渍都在训练时被模型见过推理时遇到类似光线变化不会发懵。但代价是同一场景相邻帧的画面高度相似如果不去重直接训练模型容易对特定背景过拟合。数据集目录已经按yolov5项目约定排好dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ ├── test/ │ ├── images/ │ └── labels/ └── data.yamllabels目录下每个txt文件名与对应图片名完全一致内容格式是class_id x_center y_center width height归一化坐标。这里要提醒一句拿到资源后先随机挑十个txt文件核对坐标值是否在0~1区间我曾经碰到过某数据集坐标没归一化直接导致训练loss炸掉的情况。data.yaml里nc设为4names顺序是eat、stand、lie、attack——这个顺序后面训练时千万别动改了names顺序但没改标签文件的话类别就会全部错位。2.2 四类行为的视觉区分度与标注难点这四类行为在视觉上并不全是好区分的。eat和stand在侧视角下容易混淆——猪低头采食时躯干角度和站立差不多仅凭目标框内的小面积局部特征很难判断。lie类相对好认躯干大面积贴地、高度显著变小。attack类是最难标也最关键的两头猪接触缠斗时目标框会高度重叠如果标注时只框单头猪模型学到的是“单猪接触区域”的错误特征。标注标准建议按这样的优先级执行行为类别判定标准标注要点易混淆场景eat头部低于肩胛线且持续接触料槽区域框包含头部和颈部不框全身低头走动的猪站立时头朝下stand四肢直立且躯干离地头部高于肩胛线框全身四肢可部分不框正在踱步的猪吃料瞬间抬头lie躯干贴地或侧卧四肢不可见或弯曲框包含完整躯干轮廓趴卧但头部抬起的猪侧卧露出腹部attack双猪以上接触且伴随撕咬或冲撞动作框两猪接触区域宁大勿小嬉戏追逐叠在一起睡觉有个细节值得注意该数据集的attack类如果只占总量10%以下训练时要额外注意类别权重设置否则模型精度会被样本量大的lie类带偏。从文件名数量看这套数据的总规模在1000张对yolov5s来说刚好能跑到mAP 0.75左右想提到0.85以上必须做在线数据增强叠加。2.3 数据划分的合理性检验拿到数据集先不要急着训练花十分钟看一眼划分是否合理。合理划分的判断标准是同时间段、同栏舍的画面不能同时出现在train和val里否则val loss虚低上线就垮。这批数据来自两天不同时刻的视频抽帧train/val/test理论上已经按时间窗口切好了但稳妥起见建议写个脚本验证一下import os from collections import defaultdict train_imgs os.listdir(dataset/train/images) val_imgs os.listdir(dataset/val/images) def get_video_segment(filename): # 从vlcsnap-2022-01-09-19h41m40s412这样的文件名提取时间戳前缀 parts filename.split(_) return parts[0] parts[1] if len(parts) 2 else filename train_segments defaultdict(int) for img in train_imgs: train_segments[get_video_segment(img)] 1 # 简单检查val中是否有和train同时间段的图 val_overlap [] for img in val_imgs: seg get_video_segment(img) if seg in train_segments: val_overlap.append(img) print(fval中与train同源图像数量: {len(val_overlap)}) print(ftrain时间片段数: {len(train_segments)})如果val_overlap数量超过20建议重新划分。时间片段提取逻辑是按文件名分割下划线取前两段这段代码你自己按实际命名规则调整。另外一个重要检验是类别分布——写个循环统计labels目录下每个类出现次数如果某个类别占比低于5%考虑用yolov5自带的超参数里的class_weights做补偿。3. 训练全流程配置从yolov5s到yolov8的迁移参数指南3.1 环境准备与数据配置校验yolov5的训练门槛这几年已经降得很低了Python 3.8以上加PyTorch 1.10以上就能跑。先把项目克隆下来再把数据软链过去git clone https://github.com/ultralytics/yolov5 # 官方仓库目前仍在维护 cd yolov5 pip install -r requirements.txt # 数据软链避免复制大文件占用双倍磁盘 ln -s /path/to/your/dataset ./datasets/pig_behavior注意data.yaml里train和val路径要改成相对于yolov5项目根目录的形式常见写法是../datasets/pig_behavior/train/images不要写绝对路径——换机器跑的时候绝对路径直接翻车。训ultralytics全家桶时一个让新手栽跟头的细节是标签格式兼容性问题。3.2 yolov5s训练命令与关键超参数yolov5s是这个场景下的首选模型参数量适中GTX 1080Ti级别显卡能跑到30ms/帧的推理速度满足养殖场实时监控的响应需求。yolov5n虽然更快但小目标检测能力弱——猪只行为检测的目标框平均尺寸在100×80像素以上不算小目标所以n的优势不明显精度损失却不小。训练命令如下python train.py \ --data ../datasets/pig_behavior/data.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640 \ --patience 20 \ --device 0batch-size设16而不是默认的8因为这是单类别内的四分类问题梯度估计方差较小可以适当加大batch加速收敛。img参数用640不要盲目上1280——养殖场监控画面虽然有1080P甚至4K但yolov5会先做resize分辨率拉上去对速度影响是平方级的而行为分类的收益很小。patience设20意味着20个epoch内val mAP没有提升就停止这套数据大概50~60个epoch就能收敛早停能防止后期过拟合。epochs设100听着不多实际训练中前40个epoch都在做特征提取器的适配。如果你用的是coco预训练权重迁移学习效果会非常明显mAP从零起步到稳定基本都在第10个epoch左右。训练时每5个epoch会输出一次验证集PR曲线和混淆矩阵重点关注attack类的召回率——这个类别漏检的代价比误检大得多。3.3 从yolov5到yolov7/yolov8结构差异与参数迁移这套数据集的data.yaml和txt标签格式是通用的yolov7和yolov8的训练入口略有不同。yolov7沿用yolov5的train.py参数体系直接把权重路径换成yolov7.pt即可。yolov8的ultralytics包命令风格变化比较大# yolov8训练方式 yolo detect train \ data../datasets/pig_behavior/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ patience20yolov8s在这个任务上的优势是anchor-free机制对姿态变化更鲁棒——猪只attack纠缠时的目标框宽高比剧烈变化yolov5的预设anchor需要重新聚类才能适配而yolov8通过解耦预测头天然适应这种变形。代价是yolov8s的权重比yolov5s大30%左右部署到边缘设备时注意内存预算。如果切换yolov9注意它的GeneralizedELAN结构对数据量更敏感1000张图训yolov9容易欠拟合建议至少要3000张以上才考虑。这个资源的核心价值就在数据集本身换算法时它完全不需要改造因为txt格式标签是一切yolo系算法的通行证。3.4 训练日志的判读基准什么样的曲线算正常训练不是丢进终端等结果你得能看懂日志里的指标含义。正常训练时box_loss从0.08左右逐步下降到0.03~0.04cls_loss从0.04降到0.01出头。如果cls_loss在30个epoch后还高于0.02十有八九是标注噪声太大——attack这个类别标注得不准很容易出现这种表现。val/box_loss如果比train/box_loss低很多说明训练集和验证集分布差异大回到2.3节的方法重新划分。每张卡的训练时间在GTX 1660 Super级别大约是每epoch 40~60秒100个epoch跑完大概1.5小时yolov8s在这个数据规模上单卡V100约40分钟。4. 模型推理验证与PyQt界面部署把权重塞进可视化工具4.1 训练权重的导出与检测效果评估训练完成后best.pt存放在runs/train/exp目录下用detect.py先跑几张测试图看定性效果python detect.py \ --weights runs/train/exp/weights/best.pt \ --source ../datasets/pig_behavior/test/images/ \ --conf 0.35 \ --iou 0.45 \ --save-txt \ --save-confconf阈值设0.35而不是默认的0.25原因是养殖场监控画面里栏舍遮挡会造成部分误检稍微抬高阈值过滤掉低置信度的漂浮框。iou用0.45而不是0.5attack场景下两个高度重叠的目标框需要更激进的NMS去重。这一步先跑通全流程再谈界面。4.2 PyQt界面集成代码结构与调用逻辑这个资源配套的PyQt界面解决的是现场使用问题——给养殖场管理员用的系统不可能让操作人员对着命令行敲python。界面部分核心逻辑就是一个while循环定时读取摄像头帧丢给模型推理把结果画到QLabel上。关键代码模式import cv2 from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import QTimer from models.experimental import attempt_load from utils.augmentations import letterbox from utils.general import non_max_suppression class PigMonitor(QMainWindow): def __init__(self): super().__init__() self.model attempt_load(weights/best.pt, devicecpu) self.model.eval() self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.timer.start(100) # 100ms 10FPS养殖场监控够用 def update_frame(self): ret, frame self.cap.read() if not ret: return # 推理逻辑 img letterbox(frame, 640, stride32, autoTrue)[0] img img.transpose((2, 0, 1))[::-1] img torch.from_numpy(img.copy()).float() / 255.0 img img.unsqueeze(0) with torch.no_grad(): pred self.model(img)[0] pred non_max_suppression(pred, conf_thres0.35, iou_thres0.45) for det in pred[0]: x1, y1, x2, y2, conf, cls det.tolist() color {0: (0, 255, 0), 1: (255, 255, 0), 2: (0, 128, 255), 3: (255, 0, 0)}[int(cls)] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), color, 2) cv2.putText(frame, names[int(cls)], (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # 转成QImage显示 rgb_img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_img.shape qimg QImage(rgb_img.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg))这段代码在CPU上跑yolov5s大约能到3~5FPS如果现场配了NVIDIA显卡把attempt_load的device参数改为cuda并把模型搬到GPU上帧率能跳到20FPS以上。QTimer间隔100ms是折中值——太短CPU占用率飙升但画面没明显提升挑到50ms以下时检测结果可能比显示滞后两帧。4.3 界面功能扩展预警逻辑与数据记录界面只做实时画框其实是半成品真正对养殖场有价值的是异常行为告警。attack行为持续超过3秒就应该触发提醒lie和eat的比例也能反映猪群健康状况。这段告警逻辑实现起来很直接在update_frame里维护一个行为计数器self.behavior_counter {attack: 0, eat: 0, stand: 0, lie: 0} # 每帧推理后更新计数 for det in pred[0]: cls int(det[5]) self.behavior_counter[names[cls]] 1 # 攻击行为连续出现10帧以上约1秒就触发告警线 if self.behavior_counter[attack] 10: self.show_warning(检测到持续攻击行为)5. 踩坑记录5条你会遇到的血泪经验5.1 训练loss值为nan的真相现象训练到第7个epoch时loss突然变成nan终端输出显示train loss: nan。原因数据集里有两张标志腐烂的JPEG图片——vlcsnap文件名后缀为rf.5888b784a5b4f702503b2ad1620f25fd.jpg的图片可能因视频抽帧截图时未完全写入磁盘导致文件截断yolov5读取这类图片时解码出错向损失函数传入了非数值。解决训练前先跑一遍数据清洗脚本把所有打不开的图片剔除python -c from PIL import Image import os for root, dirs, files in os.walk(dataset): for f in files: if f.endswith((.jpg, .jpeg, .png)): path os.path.join(root, f) try: Image.open(path).load() except Exception as e: print(fcorrupted: {path}) os.remove(path) # 或移动到quarantine目录 5.2 val mAP很高但现场实测漏检一大堆现象验证集mAP显示0.82但拿手机在现场拍摄的短视频测试时attack行为基本框不出来。原因数据集全来自固定机位的监控摄像头视角是固定的俯视侧向。现场手机拍摄是移动端低角度视角目标尺度和姿态分布与训练集差异巨大属典型的external domain shift。解决部署前要确认现场摄像头的安装高度和角度与数据采集时一致如果现场机位确实不同必须增量采集现场数据做fine-tuning。我一般要求现场至少补充200张机位画面做微调否则权重只在原场景上有效。5.3 PyQt界面卡死的幕后推手现象界面运行30分钟后无响应CPU占用100%。原因检查发现是视频帧累积问题——detect推理速度如果低于摄像头采集速度queue里的帧会不断堆积。代码里用的是直接读取cap.read()同步模式推理阻塞导致内部缓冲区堆满。解决把定时器回调里的推理部分放到独立线程主线程只负责刷新UI推理线程把结果写入共享变量# 简单做法丢帧策略 if not ret or time.time() - self.last_frame_time 0.1: return # 超时直接丢帧不堆积 self.last_frame_time time.time()5.4 类别名顺序错位引发全线误导现象界面画框时把站立状态的猪标成eat、卧躺的标成attackdb文件里数据看起来乱七八糟。原因模型输出的是类别索引界面代码里没有按data.yaml的names顺序渲染标签文本。自己在代码里写的列表[lie, stand, eat, attack]跟训练配置不一致索引对不上。解决统一从data.yaml读取namesimport yaml with open(../datasets/pig_behavior/data.yaml) as f: names yaml.safe_load(f)[names] # 确保这是唯一标签来源5.5 数据集增强参数瞎调的后果现象加了mosaic和mixup增强后mAP不升反降val loss波动剧烈。原因mosaic增强会拼接四张图小目标比例降低到原来的四分之一。这套数据的目标框本来就不算大mosaic之后更难检测。mixup混合两张图对行为分类任务是灾难——两个不同状态的目标重叠导致标签互相污染。解决关闭mixup、保留mosaic但把概率降到0.3以下python train.py --hyp hyp.finetune.yaml # 在这个文件里调低mosaic和关闭mixup6. 把模型压到极致ONNX导出、INT8量化与边缘设备部署模型训练完成只是工程化第一步真正上线要过推理性价比这关。yolov5s在GPU上能跑很快但养殖场的现实是摄像头装在栏舍里、机房可能在几百米外布网线成本不低很多项目希望直接在摄像头旁边放一块Jetson Nano或树莓派搞定推理和告警减少视频流回传带宽。ONNX导出是必经之路。PyTorch模型直接跑推理有两个痛点一是python环境依赖太重现场部署机不可能连着训练机一起搬二是推理路径上有不少非结构化控制流在TensorRT或OpenVINO上加速效果不好。导出命令python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --simplify \ --opset 12导出后建议用onnxruntime验证一遍输出和PyTorch原版是否一致import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name output sess.run(None, {input_name: np.random.randn(1, 3, 640, 640).astype(np.float32)}) print(output[0].shape) # 期望输出形状(1, 25200, 8)如果这一步输出形状对不上先检查模型版本和onnxruntime版本兼容性——yolov5用opset12跑旧版onnxruntime会有坐标解码不兼容问题。踩过这个坑之后我学乖了导出后先用记事本把输出层的feature map打印一遍再移交部署。下一步是INT8量化。yolov5s全精度模型占用约14MB显存Jetson Nano勉强能带但想跑实时还是紧张。量化到INT8后模型大小砍掉四分之三推理速度翻倍代价是mAP降2~3个百分点。对行为检测来说mAP掉到0.79左右仍然可用因为四类行为的判定裕度够大。量化需要校准数据集直接从数据集的val里抽200张就够python export.py \ --weights runs/train/exp/weights/best.pt \ --include engine \ --device 0 \ --halfTensorRT的int8量化需要额外提供calib图片目录yolov5源码里默认用data目录下coco128的图片做校准这里改成自己的val目录。实测量化后attack类召回率下降了5%这是INT8量化的典型损伤位——攻击行为的目标框边界模糊量化丢弃的边界细节正好是关键判别信息。补救方案是在校准集里手动多放attack帧或者训练时把attack这个类别的损失权重调高10%。部署也不是终点。我在一个项目里用量化后的engine模型跑了两周发现夜间灯光切换的瞬间会出现大量误报——原因是灯光从钠灯换成LED时色温突变INT8量化模型的输入分布被打破。从那以后我每次部署都强制走一遍白天黑夜光照切换的压测用现场采集的过渡帧跑推理并记录置信度波动置信度掉到0.3以下的场景把夜视模式的白平衡参数一起调掉。行为检测模型从来不是训完就完事的活它是一个需要持续喂现场数据、持续微调权重的迭代闭环。这套资源给你的是一个靠谱的起点路还得自己走——希望这些踩过的坑能帮你少绕几个弯。本文还有配套的精品资源点击获取