ARTICLE DETAIL

资讯详情

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

猪行为识别数据集实战:从COCO标注到YOLOv8训练与部署

猪行为识别数据集实战:从COCO标注到YOLOv8训练与部署 简介这份猪圈猪行为识别数据集面向智慧养殖、动物行为分析与计算机视觉方向的研究者及算法工程师用于构建猪只日常行为自动监测模型可识别喝水、进食、睡觉、站立等典型动作平均正确识别率达92.6%适合目标检测与行为分类任务的训练与验证。资源包共1275个文件其中1272张jpg图像构成主要样本另有3个json文件提供COCO格式标注涵盖类别与边界框信息压缩包整体约85.57MB目录结构便于直接接入主流检测框架。目前已有214人学习下载可作为养殖场景行为识别的实用数据基础。图像覆盖多段视频抽帧包含不同光照与栏舍环境下的猪只姿态标注规范统一能帮助读者省去繁琐的数据采集与标注环节快速投入模型训练、迁移学习与效果对比也可用于验证算法在真实猪圈场景中的鲁棒性。1. 猪圈里的猪行为识别数据集1272 张图、92.6% 识别率这套数据到底能不能直接上手猪行为识别这件事真正卡住人的从来不是模型结构而是数据。你打开 GitHub 想找一个能直接用的猪行为识别数据集翻到的要么是几十张图的 demo要么是只有检测框没有行为标签的半成品要么标注格式跟你的训练框架对不上。这个数据集的价值就在于它把三件事同时做完了1272 张真实猪圈场景图片、覆盖喝/吃/睡觉/站立等核心行为类别、标注格式是 coco json。平均正确识别率 92.6% 这个数字放在实际猪圈光照不均、猪只互相遮挡的条件下已经属于能落地的水平。它适合两类人一类是想做智慧养殖行为识别但苦于没有标注数据的算法工程师另一类是手里有猪场摄像头画面、想把行为统计跑起来但不想从零标数据的落地团队。下面我按「数据长什么样 → 怎么转成训练格式 → 怎么训 → 坑在哪」的顺序把这条路走一遍。2. 先看清数据coco json 里到底存了什么1272 张图够不够训2.1 coco json 的结构与行为类别的映射关系拿到一个 coco json 格式的数据集第一件事不是急着写 dataloader而是把 json 拆开看 categories 和 annotations 两个字段。coco 的结构是固定的images 数组存每张图的文件名、宽高、idannotations 数组存每个标注的 bbox、category_id、image_idcategories 数组存类别 id 和类别名。猪行为识别数据集的关键在于 categories 里到底有哪几类。按标题描述至少包含喝、吃、睡觉、站立四类实际拿到后你要确认是否还有「行走」「打架」「拱地」等扩展类。import json with open(pig_behavior/annotations/instances_train.json, r) as f: data json.load(f) # 看类别定义 for cat in data[categories]: print(cat[id], cat[name]) # 统计每类标注数量 from collections import Counter cat_counter Counter(ann[category_id] for ann in data[annotations]) for cid, cnt in cat_counter.items(): name next(c[name] for c in data[categories] if c[id] cid) print(f{name}: {cnt} 个标注框) # 看图片总数和尺寸分布 print(图片总数:, len(data[images])) sizes [(img[width], img[height]) for img in data[images]] print(尺寸样例:, sizes[:5])这段代码做三件事打印类别名、统计每类标注框数量、看图片尺寸分布。逻辑很直白但输出结果决定了你后面所有决策。如果某一类标注框数量不到总数的 5%比如「喝」只有 40 个框那训练时这一类几乎必然欠拟合需要考虑过采样或数据增强。参数上注意 category_id 不一定从 0 连续coco 允许跳号写映射表时别用列表下标去取类别名。2.2 1272 张图在行为识别任务里算什么量级1272 张图放在目标检测里不算多但放在猪行为识别这个垂直场景里要分情况看。如果四类行为分布均匀每类大约 300 张图对应的标注配合迁移学习和强增强训一个能用的模型是够的。但如果你的猪圈场景和数据集采集场景差异大——比如数据集是保育舍、你要用在中大猪舍——那 1272 张图的泛化能力会明显吃紧。我一般会先做一个场景相似度检查从数据集里抽 20 张图和自己摄像头截 20 张图放一起肉眼看光照、地面材质、猪只品种、摄像头角度四个维度。如果超过两个维度差异明显就要么补采数据要么在训练时加大颜色抖动和随机裁剪的强度。1272 张图的价值不在于数量本身而在于它省掉了你从零标注的时间——按一张图 30 秒标注算1272 张就是 10 个工时以上这还没算质检返工。2.3 行为识别和普通目标检测的区别在哪很多人拿到这个数据集第一反应是当普通检测任务训这是最大的认知偏差。猪行为识别里「睡觉」和「站立」的视觉差异可能只是身体长宽比和姿态角度的细微变化而「吃」和「喝」的差异可能只在于头部朝向料槽还是水嘴。这意味着单纯靠 bbox 回归模型很难区分这些类间差异极小的行为。常见做法是两条路一条是把行为识别当细粒度分类做在检测框基础上加一个分类头用 RoI Align 提取特征再分类另一条是直接上姿态估计先定位猪只关键点再根据关键点几何关系判断行为。前者实现简单、和 coco 格式兼容好后者精度上限高但需要关键点标注这个数据集不一定有。我一般先用第一条路跑 baseline看混淆矩阵里哪两类最容易混再决定要不要上姿态。3. 把 coco json 转成 YOLO 格式转换脚本与四个边界坑3.1 转换脚本从 coco bbox 到 YOLO txtYOLO 系列训练需要的是每张图一个 txt每行格式为class_id x_center y_center width height且坐标要归一化到 0-1。coco 的 bbox 格式是[x_min, y_min, width, height]绝对像素值转换时要做两步先算中心点再除以图片宽高归一化。import json import os def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path) as f: data json.load(f) # 建立 image_id - (filename, width, height) 映射 img_info {img[id]: img for img in data[images]} # 建立 category_id - 连续 class_id 映射 cat_ids sorted(c[id] for c in data[categories]) cat2cls {cid: i for i, cid in enumerate(cat_ids)} os.makedirs(out_dir, exist_okTrue) # 按 image_id 聚合标注 from collections import defaultdict ann_by_img defaultdict(list) for ann in data[annotations]: ann_by_img[ann[image_id]].append(ann) for img_id, anns in ann_by_img.items(): info img_info[img_id] w, h info[width], info[height] lines [] for ann in anns: x, y, bw, bh ann[bbox] # 过滤掉宽高为 0 的脏标注 if bw 0 or bh 0: continue xc (x bw / 2) / w yc (y bh / 2) / h nw bw / w nh bh / h # 裁剪到 [0,1] 防止越界 xc, yc min(max(xc, 0), 1), min(max(yc, 0), 1) nw, nh min(nw, 1), min(nh, 1) cls cat2cls[ann[category_id]] lines.append(f{cls} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) # 没有有效标注的图不生成空文件 if lines: name os.path.splitext(info[file_name])[0] .txt with open(os.path.join(out_dir, name), w) as f: f.write(\n.join(lines)) coco_to_yolo(pig_behavior/annotations/instances_train.json, pig_behavior/images/train, pig_behavior/labels/train)逻辑说明先建两个映射表一个是 image_id 到图片信息的映射一个是 category_id 到连续 class_id 的映射。coco 的 category_id 可能不连续YOLO 要求 class_id 从 0 开始连续所以必须重映射。然后按 image_id 聚合标注逐条转换坐标。参数上注意三点一是过滤宽高为 0 的脏标注这种标注在 coco 里合法但在 YOLO 里会导致 NaN二是坐标裁剪到 0-1有些标注框会超出图片边界三是没有有效标注的图不生成空 txt否则训练时会被当成负样本。3.2 四个边界坑越界框、空标注、类别跳号、文件名不匹配第一个坑是越界框。coco 允许 bbox 超出图片边界比如 x_min 为负、x_minwidth 大于图片宽。转换后 x_center 可能小于 0 或大于 1YOLO 训练时直接报错或产生异常梯度。解决就是在转换时做 clamp上面代码已经处理。第二个坑是空标注图。有些图片在 coco 里没有任何 annotation转换后不生成 txt 文件。但 YOLO 训练时如果图片目录里有图而 labels 目录里没有对应 txt不同版本行为不一致有的跳过有的报错。稳妥做法是给空标注图生成一个空 txt 文件或者干脆把这类图从训练集里移走。第三个坑是类别跳号。比如 categories 里 id 是 1、3、5、7你直接用 id 当 class_idYOLO 会认为有 8 个类中间几个类永远没有样本。必须用 sorted 后重新映射上面代码的 cat2cls 就是干这个的。第四个坑是文件名不匹配。coco 的 file_name 可能带路径前缀或扩展名不一致比如 json 里写的是images/001.jpg而实际文件是001.JPG。转换脚本里用os.path.splitext去扩展名时要注意大小写最好先统一转小写再比对。提示转换完成后抽 5 张图用可视化脚本把 YOLO txt 画回图上肉眼确认框和类别都对。这一步花 5 分钟能省掉后面几小时的排查。3.3 数据集划分训练集、验证集、测试集怎么分才不泄漏1272 张图怎么分常见做法是 8:1:1即训练 1017、验证 127、测试 128。但猪行为识别有个特殊点同一个猪圈、同一批猪、同一时间段采集的图片高度相似如果随机划分训练集和验证集里可能有几乎一样的图验证指标虚高。我一般按采集批次或摄像头编号做分组划分同一批次的图要么全在训练集要么全在验证集。如果数据集没有批次信息就按文件名前缀或时间戳排序后分段划分。这样验证指标虽然会低一些但更接近真实部署时的表现。测试集建议单独留一个猪圈的数据用来评估跨场景泛化能力。4. 用 YOLOv8 训练猪行为识别参数怎么设、指标怎么看4.1 环境准备与数据配置文件YOLOv8 的训练入口是yolo detect train数据配置用一个 yaml 文件描述。假设你已经把 coco json 转成了 YOLO 格式目录结构是 images/train、images/val、labels/train、labels/val。# pig_behavior.yaml path: /data/pig_behavior train: images/train val: images/val test: images/test names: 0: drink 1: eat 2: sleep 3: standnames 的顺序必须和转换脚本里 cat2cls 的映射一致否则类别全错。path 用绝对路径train/val 用相对路径这是 YOLOv8 的约定。如果类别不止四类按实际顺序往下加。4.2 训练命令与关键参数yolo detect train \ datapig_behavior.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ degrees10 \ translate0.1 \ scale0.5 \ fliplr0.5 \ namepig_behavior_v8s参数说明model 选 yolov8s 而不是 yolov8n是因为行为识别类间差异小n 模型容量不够容易欠拟合如果显存紧张可以降到 yolov8n 但 mAP 会掉几个点。epochs 设 150 配合 patience30意思是 30 轮验证指标不提升就早停避免过拟合。lr00.01 是初始学习率lrf0.01 是最终学习率系数余弦退火到初始的 1%。mosaic1.0 开启马赛克增强对遮挡场景帮助大mixup0.1 轻度混合再高会模糊行为边界。degrees10 做小角度旋转猪圈摄像头一般不会大角度倾斜所以不要设太大。4.3 看指标mAP50 够不够混淆矩阵才是关键训练完看 results.csv重点看 metrics/mAP50 和 metrics/mAP50-95。92.6% 这个识别率如果对应的是 mAP50那属于正常水平如果对应的是 accuracy那要看是 Top-1 还是加权平均。但指标只是参考真正要看的是混淆矩阵。from ultralytics import YOLO import matplotlib.pyplot as plt model YOLO(runs/detect/pig_behavior_v8s/weights/best.pt) metrics model.val(datapig_behavior.yaml) # 打印每类 AP for i, name in enumerate([drink, eat, sleep, stand]): print(f{name}: AP50{metrics.box.ap50[i]:.3f}) # 混淆矩阵会保存到 runs/detect/val/confusion_matrix.png如果「吃」和「喝」互相混淆严重说明模型没学到头部朝向和料槽/水嘴的关联。这时候两个方向一是补采这两个行为的边界样本比如猪头半抬半低的过渡帧二是加一个姿态分支用关键点辅助分类。如果「睡觉」和「站立」混淆通常是俯拍角度下身体长宽比接近可以尝试加大 scale 增强或换更高分辨率输入。注意验证集指标高不代表部署效果好。一定要用测试集而且测试集最好是不同猪圈的数据才能看出真实泛化能力。5. 避坑与排查猪行为识别训练里最容易翻车的五件事5.1 现象训练 loss 正常下降但验证 mAP 一直上不去原因通常是数据泄漏或验证集分布和训练集太像。如果按随机划分同一头猪的连续帧可能同时出现在训练和验证集模型记住了猪的花纹而不是行为。解决是按批次或摄像头分组划分确保验证集的猪和训练集的猪不重叠。另一个原因是类别不平衡某一类样本太少模型倾向于预测多数类。解决是对少数类过采样或在 loss 里加类别权重。5.2 现象模型在白天图片上表现好夜间红外图片上全错原因是数据集的 1272 张图如果全是白天可见光模型学到的特征高度依赖颜色和纹理夜间红外图是灰度且纹理不同直接翻车。解决是在训练时加颜色抖动、随机灰度化增强让模型对颜色不敏感。如果夜间数据能采到哪怕只有几十张混进训练集做微调效果也很明显。常见做法是先用白天数据训 baseline再用少量夜间数据 fine-tune 最后几层。5.3 现象同一张图里多头猪标注框互相重叠NMS 后漏检原因是猪只密集时 bbox 重叠度高标准 NMS 的 IoU 阈值设 0.5 会把相邻猪的框误删。解决是把 NMS IoU 阈值调到 0.6-0.7或者改用 Soft-NMS。另外可以在训练时开启 mosaic 增强让模型见过更多密集场景。如果漏检集中在边缘猪只检查图片是否有裁剪导致猪只不完整不完整的样本建议从训练集剔除。5.4 现象转换后的 YOLO 标签里出现坐标大于 1 或小于 0原因是 coco 原始标注有越界框转换时没做 clamp。上面转换脚本里已经加了min(max(xc, 0), 1)但如果你的脚本没加训练时 YOLOv8 会直接报错或静默丢弃这些框。排查方法是写个脚本扫一遍所有 txt找出坐标越界的行然后回溯到 coco json 里看原始 bbox。解决就是在转换阶段统一 clamp不要等到训练时报错才处理。5.5 现象训练完模型在测试集上 mAP 还行但实际部署时帧率只有几 FPS原因是模型选大了或输入分辨率太高。yolov8s 在 640 输入下中端 GPU 能到 100 FPS但如果部署在边缘设备如 Jetson Nano可能只有几 FPS。解决是导出 ONNX 或 TensorRT 做推理加速或者换 yolov8n 并降低输入到 416。另一个原因是预处理用了 Python 循环而不是批量处理部署时用 DataLoader 的 batch 推理能提升吞吐。猪行为识别不需要每帧都推理隔几帧跑一次完全够用行为变化是以秒为单位的。6. 让 92.6% 再往上走时序平滑与行为统计的落地技巧单帧识别率 92.6% 听起来不错但实际部署时你会发现猪的行为是连续的单帧抖动会导致统计结果跳变。比如猪在吃料时偶尔抬头单帧模型可能判成「站立」如果按帧统计吃料时长就被切碎了。我一般会在检测后加一层时序平滑用滑动窗口投票或卡尔曼滤波稳定行为标签。from collections import deque, Counter class BehaviorSmoother: def __init__(self, window15, min_votes8): self.window window self.min_votes min_votes self.buffer deque(maxlenwindow) def update(self, behavior): self.buffer.append(behavior) if len(self.buffer) self.window: return behavior # 滑动窗口投票取出现次数最多的行为 cnt Counter(self.buffer) top, votes cnt.most_common(1)[0] # 票数不够则保持上一帧结果避免频繁跳变 if votes self.min_votes: return top return self.buffer[-2] if len(self.buffer) 1 else behavior smoother BehaviorSmoother(window15, min_votes8) # 假设每帧检测结果依次传入 for frame_behavior in [eat, eat, stand, eat, eat, eat, eat, eat, eat, eat, eat, eat, eat, eat, eat]: stable smoother.update(frame_behavior) print(frame_behavior, -, stable)这段代码的逻辑是维护一个长度 15 的滑动窗口每帧把检测结果放进去当窗口满时统计出现次数最多的行为且票数要达到 8 票才切换否则保持上一帧结果。参数 window 和 min_votes 需要根据实际帧率调如果摄像头 25 FPS15 帧约 0.6 秒适合行为切换不频繁的场景如果行为切换快窗口要缩短到 8-10 帧。min_votes 设太高会导致响应迟钝设太低则平滑无效一般取窗口的 50%-60%。另一个落地技巧是行为统计的边界处理。猪的行为往往有过渡态比如从站到卧的中间过程模型可能判成「睡觉」或「站立」都不对。我一般会在统计时设一个最短持续时间阈值比如某个行为连续出现少于 3 秒就不计入统计这样能过滤掉过渡态的误判。统计输出按小时聚合生成每头猪的采食时长、饮水次数、躺卧时长这些才是养殖场真正关心的指标。最后说个血泪经验别追求单帧 99% 的识别率那在猪圈场景里不现实。把精力花在时序平滑和统计逻辑上让最终的行为时长统计误差控制在 5% 以内比单帧指标提升两个点有价值得多。我见过太多团队卡在调模型上结果部署时发现统计逻辑没做数据根本没法用。先把 pipeline 跑通再回头优化模型这个顺序别搞反。希望帮到你。本文还有配套的精品资源点击获取
返回列表