☰
猪行为识别数据集+YOLOv8:从数据准备到训练避坑全指南
2026/9/28 1:16:42 网站建设 项目流程

简介:面向计算机视觉与智慧农业领域从业者的猪只行为识别数据集,聚焦猪圈监控场景,覆盖喝、吃、睡觉、站立四类常见行为,平均识别正确率达92.6%。压缩包共含2000个文件,1272个txt为YOLOv8格式的标注文件,与727张jpg原始图像一一对应,另附1个yaml配置文件,整体体积约85.86MB,解压即可接入主流目标检测训练流程。图像截取自多个猪圈监控片段,涵盖不同姿态、角度与光照条件,标注边界框贴合目标,适合直接用于模型训练、验证与精度调优,也可作为智慧养殖、动物行为分析等课题的基准数据。资源按图像与标注文件成对组织,目录结构简洁,便于批量读取与数据划分。已有224人学习下载,适合从事行为识别算法研发、养殖智能化改造及YOLO系列模型实践的研究者与开发者使用。

1. 猪圈里的猪行为识别数据集:92.6%还不够,先要会正确使用它

养猪场做智能化管理,第一步往往不是调算法,而是先问“有没有能用的数据”。这个猪圈里的猪行为识别数据集,把喝、吃、睡觉、站立四类行为做成了目标检测标注,1272张图片不算大,但够把yolov8从零教会,对外公布的平均正确识别率是92.6%。我的判断是:它适合用来验证“猪舍行为识别能不能落地”这件事,也适合做毕业设计或边缘算法原型。但92.6%这个数字是特定标注规范、特定验证划分下的成绩,想在自己的猪舍复现并稳定使用,就得先把数据集格式、训练参数和指标口径吃透。下面按这个顺序讲。

2. 把行为识别当目标检测做:为什么不是分类,以及数据格式地基

2.1 帧内行为不等于视频行为:先搞懂yolov8在这套数据上做的是“框+类别”

很多刚接触猪行为识别的人,第一反应是拿一个图像分类模型,给整张监控画面打个“喝”或“吃”的标签。这在圈舍里只有一头猪、机位固定的“理想实验”里勉强能看,真实猪圈里往往十几头猪挤在一起,猪栏上有水渍、地面有漏粪板,一头低头喝水,另一头同时站起来蹭痒。分类模型只能输出一个主导标签,占比小的行为被直接吃掉,结果看起来准确率不低,但养殖人员根本没法信。

这份数据集走入正轨的地方在于:它按yolov8格式给每头猪画了边界框,并且把框和“drink/eat/lie/stand”四类绑定。这实际是目标检测任务,而不是图像分类。目标检测同时输出“在哪儿”和“干什么”,后续如果要统计“某头猪今天总共喝了多久、吃了多久”,才能把不同帧里同一头猪关联起来,按行为段累加。没有框的纯分类,连具体头数都数不清楚,更别提个体行为曲线。

提示:四类行为在标注上是互斥的,同一头猪在同一帧只能有一个标签。但猪群遮挡严重时,一个框里可能混进两只猪的肢体,这种噪声会直接影响模型训练,建议在进入训练前人工扫一遍密集场景。

这里还要说一个选型问题:既然是多目标多行为,能不能用Faster R-CNN之类的两阶段检测器?能,但实际场景很多会选yolov8。原因有三:一是猪舍监控推理对帧率有要求,yolov8s在中等显卡上能跑几十毫秒一帧,方便接视频流;二是这个数据集只有1272张,迁移学习时yolov8的工程设定最顺手,数据增强、多尺度训练开箱即用;三是目标类别只有4类,不需要复杂的稀疏关系建模,yolov8这种anchor-free设计足够。如果是一个超大猪场每天千万帧级别数据,再考虑换更强backbone。

2.2 目录结构与data.yaml:把1272张图按yolov8的规矩摆好

yolov8训练不强制要求某个绝对路径,但它默认读取“images”和“labels”同级目录,并且train和val分开放。我拿到一个新数据集的第一件事,就是把它整理成标准结构。

mkdir -p dataset/images/{train,val} mkdir -p dataset/labels/{train,val}

假设原始图片都在raw_imgs/,对应标注txt在raw_labels/,下面会用python按9:1划分,并做分层采样,确保四类行为在训练集和验证集里都有代表性。

import random from pathlib import Path from collections import defaultdict random.seed(42) img_dir = Path("raw_imgs") label_dir = Path("raw_labels") train_img_dir = Path("dataset/images/train") train_lab_dir = Path("dataset/labels/train") val_img_dir = Path("dataset/images/val") val_lab_dir = Path("dataset/labels/val") train_img_dir.mkdir(parents=True, exist_ok=True) train_lab_dir.mkdir(parents=True, exist_ok=True) val_img_dir.mkdir(parents=True, exist_ok=True) val_lab_dir.mkdir(parents=True, exist_ok=True) items = [] for img_path in sorted(img_dir.glob("*.jpg")): label_path = label_dir / (img_path.stem + ".txt") if not label_path.exists(): continue classes = set() for line in label_path.read_text().strip().splitlines(): if line: classes.add(int(line.split()[0])) items.append((img_path, label_path, tuple(sorted(classes)))) class_combos = defaultdict(list) for img_path, label_path, classes in items: class_combos[classes].append((img_path, label_path)) train_items, val_items = [], [] for combo, lst in class_combos.items(): random.shuffle(lst) val_cut = max(1, int(len(lst) * 0.1)) val_items.extend(lst[:val_cut]) train_items.extend(lst[val_cut:]) for img_p, lab_p in train_items: img_p.rename(train_img_dir / img_p.name) lab_p.rename(train_lab_dir / lab_p.name) for img_p, lab_p in val_items: img_p.rename(val_img_dir / img_p.name) lab_p.rename(val_lab_dir / lab_p.name) print("train:", len(train_items), "val:", len(val_items))

这段脚本的关键是分层采样:class_combos按“图中出现的类别组合”分组,每组抽10%。如果只是全局随机抽样,某一类行为只出现在50张图里,抽到验证集不一定覆盖得到;分层后至少保证验证集里每一类都有样本。比例9:1对1272张图而言大约是1145张训练、127张验证。如果你的显卡很紧张,8:2也可以,只是一部分样本从训练里拿走了,模型少学一点点,验证估计会更乐观一点点,权衡看个人。

目录就位后,写data.yaml,内容是给yolov8指明路径和类别名。

path: dataset train: images/train val: images/val nc: 4 names: 0: drink 1: eat 2: lie 3: stand

这里names的顺序必须和标注txt里的class id完全一致。如果之前标签文件里写的是1、2、3、4而非0、1、2、3,不改yaml、直接训练,模型会把“吃”当成“喝”来学,训练日志上mAP照样很高,到最后推理结果全是错位。我见过最典型的错位是model输出class 3,names列表只有3项,推理直接索引越界报错。写yaml前,先扫一眼任意一个标签文件的开头一行,确认类别编号从0开始还是从1开始。

2.3 校验标注文件:yolov8训练前先扫一遍“坏标签”

yolov8对标注格式的要求很死:每行是class x_center y_center width height,坐标归一化到0~1。很多转格式来的标签,常见问题包括:坐标还是像素值未归一化、类别id从1开始、box超出图像边界、宽度或高度写成负数。训练中途才报错,白烧GPU时间。我习惯在训练前先跑一个快速校验脚本。

from pathlib import Path for split in ["train", "val"]: for label_path in sorted(Path(f"dataset/labels/{split}").glob("*.txt")): bad_lines = [] text = label_path.read_text().strip() if not text: bad_lines.append("空标注文件") for line_no, line in enumerate(text.splitlines(), 1): parts = line.split() if len(parts) != 5: bad_lines.append(f"{line_no}: 字段数不是5") continue try: cls = int(parts[0]) cx, cy, w, h = map(float, parts[1:]) except ValueError: bad_lines.append(f"{line_no}: 坐标不是数字") continue if cls not in (0, 1, 2, 3): bad_lines.append(f"{line_no}: 类别id {cls} 越界") if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_lines.append(f"{line_no}: 坐标或尺寸未归一化") if w * 1280 < 5 or h * 720 < 5: bad_lines.append(f"{line_no}: 框过小(<5px)") if bad_lines: print(label_path, bad_lines)

脚本里用的是1280x720作为近似原图尺寸,具体图片尺寸可用PIL读一下再替换。框过小这条规则值得展开:猪圈监控里远处的猪可能只有十几个像素,标注虽然正确,但训练时这类小目标贡献的梯度极不稳定,有时还会反向干扰。我不会直接删除它们,而是等第一轮训练后看混淆矩阵里小目标分类的错误率,确实影响严重再考虑过滤。校验脚本的价值在于把问题一次性暴露出来,而不是等训练到第100个epoch才告诉你“某个标签第3行parse error”。

除了数字格式,还要检查类别名映射。数据集比较简单,四类行为名称明确,但如果你后面自己补充了“can”或“其他”,训练时别忘记在names里同步。我常遇到的事实是:这种行为数据集最大的坏标签来源不是框画歪,而是“喝”和“吃”在嘴部细节上确实很像,标注员不一致就会让模型学到一个“混合体”,体现为空框和重复框。遇到这样的情况,只能回到标注阶段做二次核查,模型再聪明也补不了标签的账。

3. 训练yolov8行为识别模型:跑通命令与哪些参数值得动

3.1 用预训练权重起步:迁移学习对小数据集几乎是必须的

1272张图对深度学习来说是标准小样本。随机初始化从头训练,yolov8很容易在三十几个epoch内把训练集“背”下来,验证集的precision和recall却上不去,最后只能靠早停捡回一个勉强能用的模型。常见做法是加载在COCO上预训练好的yolov8s.pt,把最后的检测头换成4类再训练。Ultralytics框架允许直接指定model=yolov8s.pt,它会自动适配新的nc=4。

pip install ultralytics

环境上,CPU也能训练,但1272张图乘以150个epoch在CPU上可能要跑一夜到两天,若有NVIDIA显卡会更省心。安装后执行训练命令:

yolo detect train \ model=yolov8s.pt \ data=dataset/data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ patience=20 \ lr0=0.01 \ device=0

用yolov8s而不是更小的n模型,是考虑到“喝”和“吃”的区别往往在头部和嘴部细节,模型容量太小会丢失这些线索;更大尺寸的m/l在这种规模数据集上容易过拟合,训练速度也慢。s是性价比首选。imgsz=640是默认值,但猪圈原图如果是1080p监控,缩到640后小猪可能只有10像素,这时建议根据数据里最小有效标注框来定,后面专门讲。

patience=20是早停机制:验证集指标连续20个epoch不提升就终止。对小数据集非常实用,否则完整跑完150个epoch很可能在最后30轮里反复震荡。device=0表示用第一块GPU;笔记本只有CPU的话改成device=cpu,但要有耐心。这种场景下我宁可用官方提供的yolov8n.pt在CPU上先跑通流程,再换s上GPU。

3.2 三个最值得手动调的参数:epochs、imgsz、batch

yolov8的训练参数很多,但对这个规模的数据集,大多数保持默认就好,真正影响结果的是下面三个,我按实际项目经验整理成表。

参数默认值行为识别建议理由
epochs100120~200数据量少,需要更多轮次达到稳定的验证指标曲线
imgsz640640~960监控中小目标多,提升分辨率能显著改善小框召回
batch168~32根据显存调,batch太小训练震荡明显

batch直接控制每次梯度更新用的图片数。行为数据集里“喝”和“吃”相似度极高,batch如果只有4,一个batch里可能全是“站立”,下一batch又全是“睡觉”,梯度方向来回甩,训练曲线锯齿严重。我一般先看显存:12GB显存跑yolov8s、imgsz=640时batch设16没压力;如果改imgsz=960,batch降到8。不要为了凑batch把imgsz砍得很低,那样小目标检测损失更大。

imgsz的选择依据,我会先读一下所有标注里宽度和高度的像素第5百分位,取一个约数。如果最小框不到15像素,建议imgsz=960。注意yolov8会把输入缩放到该尺寸,不是原图直接花更多像素,而是相当于把远处的小猪放大,模型有机会看到嘴部结构。代价是显存占用和训练时间成倍增加,但对猪群密集样本往往是值得的。

还有一个容易忽略的lr0。加载预训练权重后,模型已经具备通用特征,学习率太大容易把COCO里的特征全部洗掉,小数据集反而学歪。0.01是我常用的起点;如果看到训练集loss掉得很快、验证集mAP纹丝不动,改到0.005再来一轮。调参时一次只动一个变量,不然出了问题也不知道是哪一步造成的。

3.3 数据增强参数:别让增强扭曲了猪的姿态

yolov8默认开了mosaic、hsv、flip等增强,对小数据集帮助很大,但具体幅度要按场景限制,否则等于主动给模型加噪声。我在行为识别上最在意四个方面:

  • mosaic=1.0:默认开启,把4张图拼成1张。这对密集猪群有奇效,能让模型学会在“画面里到处是猪”的环境下分辨个体。但如果标注框本来就歪,mosaic会把多个错误叠加,建议先跑一轮校验再启用。
  • hsv_h/hsv_s/hsv_v:饱和度微调可以增加对圈舍灯光波动的鲁棒性,但hsv_v太大会把夜间红外图的黑暗部分提亮成白天,反而让模型困惑。我一般把hsv_v从默认的0.4降到0.2。
  • degrees:默认0不加旋转。猪的躯干大多水平,加一点旋转(小于5度)可以模拟监控安装角度差,但不要拉到30度,那会把“睡觉”的肚皮朝上变成“站立”的视觉特征。
  • flipud:垂直翻转最好关掉。猪舍监控一般不会倒装,垂直翻转后“站”和“睡”的姿态语义就被颠倒了,训练收益微乎其微。

这些参数可以写进超参数文件或直接在训练命令里传:

yolo detect train \ model=yolov8s.pt \ data=dataset/data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ mosaic=1.0 \ degrees=3.0 \ hsv_v=0.2 \ flipud=0.0

数据增强的本质是让模型见过足够多“不大一样”的猪,但不能把猪变成不像猪的东西。行为识别里,“行为”由姿态和位置定义,过度增强会破坏姿态语义,这一点和普通物体检测有区别。比如旋转90度,猪躺着变成竖着,模型就无法区分“趴着”和“站着”了。

3.4 看训练结果:别只盯最后一行,要拆开看曲线和混淆矩阵

训练结束后,runs/detect/train/下会生成results、混淆矩阵、验证集预测图等文件。新手最容易犯的错是只看最后一个epoch的mAP,然后自信地进入部署阶段。正确的顺序是先看results.png里的四条曲线:train/loss、val/loss、metrics/mAP50、metrics/mAP50-95。

如果val/loss在训练中后段转头向上,说明过拟合已经出现,这时mAP50可能还在缓慢上涨,但那是在吃老本。要回到参数上,要么降lr0、要么把patience设到5来早停。小数据集上,过拟合和欠拟合之间的窗口很窄,多盯着曲线的斜率能少走弯路。

再打开confusion_matrix.png。行为识别里最该看的就是它。四类行为在框层面各归各位,模型到底把谁认成了谁,一看便知。比如“吃”有15%被分到“喝”,那说明嘴部和料槽的局部特征被模型当作饮水器的特征了。这个信息比mAP更直接:mAP是把所有框的匹配好坏揉成一个数,混淆矩阵能告诉你哪对类别是冤家。

val_batch_pred.jpg是验证集预测的可视化图。放大后重点看两类问题:一是角落里的小猪漏检,二是有没有框把两头挨着的猪圈成一个框。这两类在指标上不一定难看,但在行为统计的后续流程里会成倍放大误差。因为漏检的那头猪可能一整天都“不存在”,影响行为时长统计的分子和分母。

注意:混淆矩阵里如果background类占比很高,通常代表标注框框得比实际猪身紧,模型把大量猪尾巴、露出的腿判成背景。这种情况调阈值没用,需要回看训练前标注是否有系统性偏紧。

4. 92.6%意味着什么:用验证指标反推数据集的边界

4.1 mAP与正确识别率:口径不对,数字再高也没用

标题里“平均正确识别率92.6%”,在工程验收时要先问清楚它精确指哪个指标。可能是整图分类准确率、逐框行为准确率、或per-class准确率的平均。对这套带框的数据集来说,更权威的指标是mAP50和mAP50-95,但这两个数字往往和“92.6%”对不上,原因在于计算方式。

mAP50要求预测框和真值框的IoU超过0.5才算匹配成功,然后对四类分别算平均精度再取均值。这个数字对“框是否贴近”很敏感。而“正确识别率”如果是按框统计,它只问“匹配上的框里,类别猜对多少个”,完全不看IoU的严格程度。两者评的东西不一样:mAP惩罚“框偏”和“类别错”,正确识别率只看“类别错”。所以一篇报告说92.6%,另一篇说mAP50=89.5%,可能都不矛盾。

我建议在项目文档里明确写清楚,避免验收时扯皮。更实用的做法是同时报三个数:mAP50、mAP50-95、逐框行为准确率。逐框行为准确率可以自己从混淆矩阵算出来:把对角线上的四个数字相加,除以所有有效匹配框的个数。这个数字才最接近养殖场用户的直觉——他打开后台看到“这头猪在喝水”这条记录,到底有多少概率是对的。

4.2 喝、吃为什么最容易互相认错,以及怎么统计类间混淆

先跑一个统计脚本,看看四类框的样本数量差异。

from pathlib import Path from collections import Counter counter = Counter() name_map = {0: "drink", 1: "eat", 2: "lie", 3: "stand"} for split in ["train", "val"]: for label_path in Path(f"dataset/labels/{split}").glob("*.txt"): for line in label_path.read_text().strip().splitlines(): if line: cls = int(line.split()[0]) counter[name_map[cls]] += 1 total = sum(counter.values()) for name in ["drink", "eat", "lie", "stand"]: print(f"{name}: {counter[name]} ({counter[name]/total:.1%})")

这段脚本输出每一类行为一共有多少个框,以及占比。我拿类似猪舍数据集跑过,结果常常是“站立”占35%以上,“喝水”可能只有12%。这种不均衡意味着模型天然偏向多数的“站立”,因为多数样本的梯度都在告诉它“输出站准没错”。所以只看整体准确率是不够的,要看每类的recall。

“喝”和“吃”的混淆还有一个客观原因:猪喝水时头伸向饮水器,身体姿态和低头采食几乎一样,区别仅在嘴部接触的位置。如果标注规范标注的是“包含整头猪”的框,那框内特征大量是相同的猪身,行为差异只在很小的一块区域,模型很难学。你可以做一个更合理的约定:对“喝”,额外给一个头部或嘴部关键点,或把检测框缩到更贴住头颈位置。不过本数据集既然已经是整猪框,我们只能承认边界,靠大量数据让模型记住“水嘴位置的相对高度”这些弱线索。

4.3 用conf阈值卡出你要的现场识别率

推理置信度阈值conf是一个可以现场调节的旋钮。yolov8默认conf=0.25,偏向于高召回、低精度;监控画面里一点影子晃动都可能被当“站立”。对不同的行为监控目的,最优阈值不同。

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=dataset/data.yaml \ conf=0.25 \ iou=0.45 \ save_json=True

执行后得到predictions.json,里面每条记录包含image_id、category_id、bbox、confidence。下一步写脚本扫描conf,从0.2到0.7每0.05一档,计算每一类的precision和recall,画成曲线后选择合适的工作点。如果应用是统计喝水和采食时长,漏报会让后台把“喝水”算成“没发生”,危害更大,conf可以设低到0.2;如果应用是发给饲养员的异常报警,误报太多会被直接关掉,conf要提到0.5以上,宁可漏报也不能让系统变成“狼来了”。

这里给出一个扫描核心片段:

def compute_pr(json_result, gt, conf): tp = fp = fn = 0 # 先按conf过滤,再匹配IoU,统计四类的tp/fp/fn return per_class_precision, per_class_recall

真正的实现要把预测框和真值框做IoU匹配,代码不复杂但有点长,关键是不要为了省事把所有框不加区别去算。我习惯按类分开统计,因为“喝”和“吃”的最优conf大概率不一样。这也算这个数据集的一个特点:四类行为姿态差异大,统一的conf很难同时照顾远距离小猪和近距离大猪。生产中可以在模型后处理里给类别设置不同的conf阈值,ultralytics的predict支持class过滤,但不支持per-class阈值,需要自己包一层。

4.4 验证集划分泄漏:一个让92.6%虚高的隐性原因

前面分层采样只保证了类别覆盖,还有一个数据泄漏点需要留意:如果原始1272张图片是从视频里连续抽帧得到的,前后帧内容几乎一样。随机划分train和val时,某一头猪的同一行为连续帧可能几张落在训练集、几张落在验证集,模型等于“提前见过答案”,val指标就会虚高。

识别方法很简单:看图片文件名是否带时间戳或序号。如果文件名形如frame_00001.jpg到frame_01272.jpg,最稳妥的做法是按时间连续段切分,先把前90%时间段的帧划给训练,后10%划给验证,而不是随机打乱。如果文件名没有可解析顺序,也可以用图片内容相似度粗排,但这属于额外工作了。

我在自用脚本里是这样处理的:解析文件名中的时间戳,按分钟聚合成片段,再把片段整体划到train或val。这样做出来的验证集更贴近实际部署时的“没见过的时间段”,模型在这个验证集上的mAP会比随机划分低几个点,但那才是可信的数字。所以当你在别处看到92.6%时,先问一句“是不是随机划分的”,这句话能帮你判断它有多少水分。

5. 避坑:猪行为识别训练的5个高频翻车点

5.1 现象:训练loss不降甚至反复跳,mAP始终在0.6徘徊

原因:这类问题十有八九不在模型结构,而在标注数据。最常见的有三种:一类是标注坐标还是像素值,没归一化,模型输入看到的目标尺寸变成几百倍,梯度直接爆掉;一类是存在空txt文件,被当作纯背景训练,背景样本占比无限大;还有一类是负数坐标或box宽度为0,yolov8读取时静默忽略某些行,导致训练样本数和图片数对不上。

解决:先跑第2.3节里的校验脚本,把所有坏文件暴露出来。然后逐张看校验输出,不要自动删除,因为可能是单位不统一的问题,比如某一张图来自另一个标注工具,格式不同。统一格式后重新划分,再训练。我发现九成“loss乱跳”是标签问题,不是调参问题。所以遇到这种情况,先停模型,去查数据。

5.2 现象:验证集mAP很高,一放到现场监控视频里狂误报

原因:数据集的训练集和验证集大概率来自同一段监控视频,背景光照几乎一致。模型学到的不只是猪,还有圈舍地面的纹理、墙上的水渍和饮水器的位置。换到另一间猪舍,背景变了,模型就会把类似纹理的东西当猪。这是小样本目标检测最典型的过拟合到场景的问题。

解决:不能只在这个数据集上原地打转。去目标部署的猪舍抽几分钟视频,每2~3秒抽一帧,人工补标100~300张,和原数据集混合训练。补标不需要画得很精细,重点是覆盖新背景、新机位和不同的光照。另外把数据增强里的hsv_h和hsv_s开大一些,减小模型对颜色纹理的依赖。最立竿见影的还是加入目标场景的真实帧,这比任何正则化都管用。

5.3 现象:猪睡觉识别成站立,尤其在红外夜视图上

原因:红外夜视图是单通道灰度风格,yolov8训练时默认用RGB三通道,夜间图在三个通道上的数值几乎一样,模型的色彩线索失效。睡觉的猪蜷成一团,侧影轮廓和站立的猪非常接近。更麻烦的是,如果数据集里白天图和夜间图混在一起,模型会试图找一个能同时解释两者的特征,结果两边都不讨好。

解决:先把数据集按光照拆成白天和夜间两份,分别统计混淆矩阵。如果夜间样本足够(比如超过300张),单独训一个夜间模型;不够就做亮度增强,用hsv_v=0.3让模型对亮度的敏感度下降。再复查夜间图标签,部分“趴着不动但眼睛微睁”的猪被标成了站立,这是标注主观性造成的噪声,必须人工统一:“lie”要求前腿伸直、肚皮着地,“stand”要求前腿支撑,两个状态在侧视图中区别明显。标注规范统一后,模型预测才稳定。

5.4 现象:一张图十几头猪,模型漏掉角落里的几头,总数永远对不上

原因:猪群密集时,角落的小猪目标只有二三十像素,anchor和NMS很容易把它们合并到邻近的大目标上。yolov8的检测头设计对小目标不友好,加上imgsz=640压缩后,小目标退化成了几个像素,模型根本没有足够信息分辨它是猪还是粪便块。

解决:三管齐下。一是imgsz提到960,让小目标占更多像素;二是iou从默认0.45提到0.5,降低NMS对紧邻小框的抑制;三是在数据增强里开启mosaic=1.0,模拟多猪密集排列。如果还漏,检查角落里那些漏检框是不是标注框画得过大,把旁边的猪也框了进来,导致训练时模型认为“角落不需要多个框”。必要时单独做一轮小目标增强,把包含小目标的图复制一份并放大后再喂给模型。

5.5 现象:换一台电脑推理结果和训练时对不上,边框乱飘

原因:绝大多数是预处理不一致。训练时imgsz=640,推理时也写640,但yolov8默认会把输入图letterbox到指定尺寸,如果你部署代码里忘了做letterbox,直接resize,图像发生了拉伸,检测框自然错位。另外,训练和推理的half精度设置如果不一致,模型权重从FP32转FP16时某些层出现精度损失,边界框就会抖动。

解决:先确认推理脚本用的是Ultralytics官方predict接口,内部已处理letterbox。如果是自己用ONNX或TensorRT的engine推理,必须自己实现letterbox和坐标反变换。验证方法非常简单:取一张训练时的验证图,用官方predict跑一次,把原图和模型输出框叠起来,框若完全重合,则流程正确;框偏几个像素,多半是预处理问题。我还习惯在推理时固定imgsz,不要输入端或大或小,传输时也保持同样尺寸,不然不同输入尺寸会导致模型输出分布变化。

以上五个坑没有一个需要换更贵的算法,全是数据、预处理和标注规范的事。我踩得最多的是第一个,因为每次换数据集都想当然地信任它的格式,“yolov8能加载”不等于“yolov8能训好”。

6. 进阶:从单帧识别到行为占比、异常提醒的最后一步

单帧检测只回答“这一帧里每头猪在干什么”,生产现场真正需要的是“这头猪今天喝了多久、睡了多久”。把yolov8的输出接一个按跟踪id聚合的状态机,是常规做法。我给个简易思路:对视频每3秒抽1帧,用IoU匹配或bytetrack把同一头猪的id固定下来;对同一个id的连续30帧预测标签做多数投票,得到一个稳定行为段;把每段时长累加进当天统计表。

import collections MAX_FRAMES = 30 tracker = {} # track_id -> deque of recent labels behavior_time = collections.Counter() def process_frame(detections): for track_id, label in detections: if track_id not in tracker: tracker[track_id] = collections.deque(maxlen=MAX_FRAMES) tracker[track_id].append(label) if len(tracker[track_id]) == MAX_FRAMES: stable = tracker[track_id].most_common(1)[0][0] behavior_time[stable] += 3 # 每3秒累加

这段代码的核心是滑动窗口:行为瞬间抖动时,窗口多数投票会忽略短时噪声,输出稳定状态。maxlen=30对应30个抽帧样本,如果抽帧频率是1fps,就是30秒窗口,覆盖猪舍里几分钟级别的行为切换足够。更精细的做法是给每头猪做一个状态转移,检测到变化连续5帧才切换,防止“喝一下马上抬头看”被记成两次喝水。

部署到边缘设备时,yolov8s导出为rknn或tensorrt后,单帧耗时大多在几十毫秒量级,加上硬件的多路视频能力,甚至能同时处理多个圈舍。我往往在导出前先在PC上把验证集的metrics跑一遍,再导板子跑同一批图,对比mAP差异;如果板端差太多,第一怀疑后处理NMS实现是否一致,不要一上来就怪模型量化。

现在拿到这种猪行为数据集,我的习惯是先做两件事:统计四类框的比例,看混淆矩阵里“喝”和“吃”的分裂程度;再按部署场景抽帧构一个“没见过的时间段”验证集。这两件事做完,92.6%这张成绩单还剩多少含金量,基本就心里有数了。先把数据和指标管好,模型不会差到哪去。希望帮到你。

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

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

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

立即咨询