简介面向养殖场智能化管理场景这套资源为生猪猪只行为状态检测提供了完整的YOLOv5训练权重与配套数据集。数据包含1000余张现场采集图像已按train/val/test划分并附带data.yaml配置可支持YOLOv5、YOLOv7、YOLOv8、YOLOv9等主流算法直接训练。同时提供PyQt界面程序方便将检测模型封装为桌面应用覆盖进食、站立、躺卧、攻击四类行为识别需求。资源包共约2000个文件包含txt格式标注、jpg图像、Python脚本、yaml配置等压缩后大小约274MB目录结构清晰适合算法学习、毕业设计或养殖场项目快速落地。目前已有447人浏览学习可作为猪只行为分析任务的训练基准与二次开发参考。1. 养殖场生猪行为状态检测YOLOv5 1000数据集 PyQt界面到底能凑出一套什么方案做养殖场生猪-猪只行为状态检测最省事的一条技术路线就是标题里这套组合YOLOv5作为检测算法1000张标注图作为训练数据集训练好的权重文件作为交付物再套一层PyQt界面让操作员打开视频或摄像头就能看到“趴卧、站立、走动、采食、饮水、骑跨”这些行为状态被实时框出来。它的核心价值是把一堆零散的监控素材变成一套可演示、可试运行、可给现场人员操作的桌面检测系统而不是让项目停在训练脚本和loss曲线里。这套组合适合做智慧养殖项目落地的开发者也适合研究生搭课题Demo或者设备商给客户做概念验证。1000张图规模不算大但也够用真正决定能不能落地的是行为类目定义、数据分布和训练参数这三件事有没有在第一周想清楚。做这类项目数据标注和调试界面往往会占掉一半以上的时间模型反而不是最费劲的部分。2. 行为检测的数据准备类别词典、抽帧与标注格式转换2.1 先定行为类别词典检测边界画在哪里行为状态检测在YOLOv5里并不是什么新算法本质上就是把“状态”当成类别做目标检测。画框之前必须先把行为标签词典定下来否则标到一半大概率返工。我一般会按俯视或斜视监控画面里能稳定看出来的动作来定类别常见做法是六个类趴卧、站立、走动、采食、饮水、骑跨。前五个是日常高频状态最后一个“骑跨”是为了兼容“争斗”这类短时行为。为什么不直接定“健康”或“异常”这种业务词因为YOLOv5拿到的是一次性的静态特征它很难判断猪是不是生病了但能稳定判断姿态。你要做的是让软件先告诉你“猪在哪、在干嘛”后续再结合时长统计去推断健康问题。类别之间必须有肉眼可分的信息趴卧和侧卧在俯视镜头下很难区分就不要拆成两类统一叫趴卧就完了。类别定义要用书面规范定死别口头交代。我一般会把判定标准直接整理成一张表贴在标注电脑旁边标注的时候随时翻。类别判定标准标注时注意趴卧躯干贴地腿部不承重下半身被遮挡但上半身可辨时按可见范围标站立四腿撑地框中猪只位置基本不动转身瞬间仍然算站立走动四腿移动且框中猪只位置明显变化被遮挡后从画面另一侧出现按新目标重新标采食头低向食槽或嘴部在食槽内活动只是站在食槽旁边闻一闻不算采食饮水口鼻接触饮水器或正在吸水分不清口鼻位置时不要硬标骑跨前肢搭在另一头猪的背部两人同时确认才算这类单人存疑就不标这类项目里“存疑不标”比“尽量多标”更重要。行为状态类别之间边界模糊标错一个框对训练的伤害比少一个框大得多尤其是在只有1000张图的情况下几十个脏标签就能把模型带偏。单人标注时如果遇到实在分不清的帧宁可跳过也不要创造一个噪声样本。2.2 从监控视频里抽帧图片来源与筛选1000张图一般不建议一张张拿相机去拍常见做法是直接用猪场的固定摄像头录视频再按采样间隔从视频里抽帧。我一般会把视频按时间段切分白天明亮段、傍晚暗段、开灯补光段、清扫后的空旷段、喂料时段各来一批。每个时间段隔10到15秒抽一帧抽到四五千张再做一轮人工筛选把模糊、严重遮挡、画面重复度过高的帧去掉最后保留1000到1500张左右。画质至少要720p最好是1080p。如果猪在画面里只有十几个像素模型根本学不到轮廓特征后面不管怎么调参都救不回来。尤其注意不要为了统一尺寸把所有图片都压到640宽训练时YOLOv5的输入尺寸可以是640也可以是1280提前把数据压到640等于白白丢掉原始细节。下面这段是视频抽帧脚本按秒间隔抽帧输出为JPG命名里带上视频名方便后面按视频来源划分训练集和验证集。import cv2 import os def extract_frames(video_path, out_dir, interval_sec10): os.makedirs(out_dir, exist_okTrue) cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_interval max(int(fps * interval_sec), 1) # 间隔多少帧抽一次 index 0 saved 0 while True: ok, frame cap.read() if not ok: break if index % frame_interval 0: base os.path.basename(video_path) name os.path.splitext(base)[0] save_path os.path.join(out_dir, f{name}_{saved:05d}.jpg) cv2.imwrite(save_path, frame) saved 1 index 1 cap.release() print(fvideo {video_path} - {saved} frames)逻辑说明cap.get(cv2.CAP_PROP_FPS)拿到视频实际帧率再乘以interval_sec得到“每抽一帧要跳过多少帧”。用取模判断当前帧是不是采样点比定时器更可靠因为OpenCV按帧读取时时间戳并不总是均匀的。参数说明interval_sec建议10到15间隔太短会导致连续帧几乎相同信息冗余大间隔太长又会漏掉一部分短时行为比如饮水这个动作可能只持续几秒。如果视频原始帧率低于15fps走动行为会严重跳变此时间隔要缩短否则一张图里全是半透明的运动残影。2.3 把标注结果转成YOLO格式并划分训练验证集拿到筛选好的图片之后就是标注。不管用哪种标注工具最终都要拿到每个目标框的左上角、右下角像素坐标和类别名称。很多图形标注工具导出的是JSON里面存的是多边形点列而不是YOLO需要的归一化矩形框所以中间必须加一步转换。转换的核心逻辑是把每个多边形取外接矩形当检测框再把像素坐标归一化到0到1之间按class_id x_center y_center width height的格式写成一个txt文件每行一个目标。矩形框会混入一点背景但对行为状态检测来说完全够用标起来也快很多没必要在这种任务上做语义分割。import json import os CLASS_MAP {lying: 0, standing: 1, walking: 2, eating: 3, drinking: 4, riding: 5} def convert_labelme_json(json_path, out_dir, img_width, img_height): with open(json_path, r, encodingutf-8) as f: data json.load(f) txt_name os.path.splitext(os.path.basename(json_path))[0] .txt out_path os.path.join(out_dir, txt_name) with open(out_path, w) as f_out: for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 防止坐标越界把归一化结果限制在 0~1 之间 x_center max(0.0, min(1.0, x_center)) y_center max(0.0, min(1.0, y_center)) w max(0.0, min(1.0, w)) h max(0.0, min(1.0, h)) f_out.write(f{CLASS_MAP[label]} {x_center:.6f} {y_center:.6f} f{w:.6f} {h:.6f}\n)参数说明CLASS_MAP里的类别顺序必须和后面训练时data.yaml里names的顺序完全一致这是最常见的翻车点很多人改过类别名但忘了同步索引结果模型输出和标签对不上。img_width和img_height必须用原始图片尺寸不能用训练时的输入尺寸。这个脚本只处理了没有方向角的正框如果标注工具导出的是旋转框需要先在标注阶段把旋转框关掉。txt目录建好之后还要做一次检查。YOLO训练时如果遇到空的txt文件或者标签全部丢失既不报错也不中断只会静默跳过那一张图。我自己就吃过这个亏训练完了对比一下发现训练集框数比标注时少了一截。简单检查法统计labels目录里所有txt的行数凡是为0的就是问题文件。划分训练集和验证集时不能单纯把1000张图随机打散因为同一段视频抽出来的连续帧内容高度相似随机划分会让验证集虚高看起来mAP是90%一换现场视频就掉到60%。正确做法是按视频来源分桶同一个视频抽出来的所有帧放在同一个桶里桶整体划入训练集或验证集。import os import random import shutil def split_by_video(image_dir, train_dir, val_dir, train_ratio0.9): video_buckets {} for name in os.listdir(image_dir): if not name.endswith(.jpg): continue video_key name.rsplit(_, 1)[0] # 按“视频名_编号”规则取视频名 video_buckets.setdefault(video_key, []).append(name) train_keys set(random.sample( list(video_buckets.keys()), int(len(video_buckets) * train_ratio) )) for key, names in video_buckets.items(): for name in names: txt_name os.path.splitext(name)[0] .txt src_img os.path.join(image_dir, name) src_txt os.path.join(image_dir, txt_name) dest_dir train_dir if key in train_keys else val_dir os.makedirs(dest_dir, exist_okTrue) shutil.copy(src_img, os.path.join(dest_dir, name)) if os.path.exists(src_txt): shutil.copy(src_txt, os.path.join(dest_dir, txt_name))逻辑说明name.rsplit(_, 1)[0]是按前面抽帧脚本的命名规则把videoA_00012.jpg切成videoA和00012取前半段作为视频标识。如果你改了命名规则这里也要跟着调整。脚本只做“复制不移动”目的是保留原始标注目录万一验证集效果太差还能回头重新划分。最后写一个data.yamlYOLOv5 训练时全靠它定位数据和类别信息。path: D:/pig_ai/pigs train: images/train val: images/val nc: 6 names: [lying, standing, walking, eating, drinking, riding]说明path建议写数据集根目录的绝对路径如果项目要在多台机器间搬动就改成相对路径。train和val是相对于path的目录名nc是类别数names的顺序就是模型输出类别索引的顺序必须与CLASS_MAP一致。3. 训练出一套能用的权重模型选型、命令与参数3.1 选哪个模型s起步别一上来就追求大模型YOLOv5 有 n、s、m、l、x 几个尺度档位做养殖场行为检测这个场景我一般建议从 s 起步。s 的参数量适中4GB显存的独立显卡就能跑推理速度也能保持在实时的边缘。n 更小适合纯CPU机器或嵌入式设备但小目标漏检率明显偏高猪在画面里占比不大时尤其明显。m 精度更好但训练时间和推理耗时都会上涨。千万别一上来就选l或x1000张数据集的体量根本喂不饱大模型参数量上去了损失照样降不下来最后得到的权重体积还大传给现场机器和设备调试都不方便。模型推荐显存适合场景yolov5s4G~8G中大型画面、常规实时检测首选yolov5n4G以下或CPU推理快速Demo、嵌入式设备yolov5m8G以上精度优先、帧率要求不高如果现场机器没有独立显卡只有CPUs模型在640分辨率下大约只能跑到几帧界面体验会非常差。这种情况我一般会权衡一下要么换成n模型要么接受帧率降低要么干脆离线处理视频而不是实时预览。这个取舍要在选型阶段就定下来不然做到界面联调时才发现帧率不够返工成本很高。3.2 训练命令与关键参数数据集和yaml准备好之后进入训练目录执行下面这条命令。这里假设你把YOLOv5的代码放在本地./yolov5目录下数据集放在D:/pig_ai/pigs。python train.py --data pig_behaviors.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 200 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name pig_v1参数说明--weights yolov5s.pt表示用预训练权重做初始化数据量少时用预训练权重收敛更稳。如果不想用预训练权重可以传--weights 从零训练但对1000张图来说不推荐。--img 640是输入分辨率训练时和检测时必须一致。如果画面里猪只目标小、占比低可以提升到 1180 或 1280显存占用会成倍上升batch要相应调小。--batch 16是批大小4GB显存建议降到88GB以上再考虑16或32。不要以为显存不够就硬调小模型能解决一切问题输入分辨率对稠密小目标的影响更直接。--epochs 200对1000张图来说是一个比较合适的范围。数据量少时训练到后期很容易过拟合所以不建议超过300。--hyp data/hyps/hyp.scratch-low.yaml是数据增强配置低增强方案在数据量不大时更稳定。很多人喜欢用默认的高增强策略数据集小的时候反而会把模型学乱loss曲线来回跳。参数常用值什么情况要改img640~1280猪只目标很小、漏检多就上调batch8/16/32显存不够就降不要降imgepochs100~300数据少时不要超过300hyplow优先后期过拟合明显再换medium/high3.3 从训练产物里找到权重并验证训练结束后去runs/train/pig_v1/weights/目录下找文件里面会有两个.pt文件。best.pt是验证集上指标最好的权重last.pt是最后一个epoch的权重。我基本只用best.pt因为1000张图这种规模下最后一个epoch往往已经过拟合用last反而会掉点。先用一张没参与训练的图片快速跑一次detect确认权重能被正确加载python detect.py --weights runs/train/pig_v1/weights/best.pt \ --source test_pig.jpg \ --conf 0.4 --iou 0.45说明--conf 0.4是置信度阈值低于这个值的框会被丢掉--iou 0.45是NMS时的IoU阈值数值越低越严格对重叠密集的猪栏场景可以适当降低到0.4能减少相邻猪只被合并成一个框的情况。检测结果默认写在runs/detect/exp目录里打开看一眼框是否贴合猪身是否有把采食框到猪屁股上的低级错误。图片测试通过后再找一段训练集里完全没出现的监控视频跑一遍看行为状态的连贯性和误判情况。图片测不出状态连续性问题视频会暴露大量“上一帧站立、下一帧趴卧、再下一帧又站立”的抖动。最后用验证集跑一次评估得到精确率、召回率和每个类别的mAP指标python val.py --data pig_behaviors.yaml \ --weights runs/train/pig_v1/weights/best.pt \ --img 640注意不要只看整体mAP。1000张图里趴卧、站立样本可能占80%骑跨只占很少一部分mAP会被大头类别抬得很高。要逐行看每一类的结果尤其关注骑跨这一类。如果某个类别precision和recall为零几乎可以断定是数据分布严重不均或者标注不干净跟模型结构没关系这时候回去补数据和纠标签比调模型更有效。4. 用PyQt做行为检测桌面界面从权重到可操作软件4.1 把检测封装成独立模块别把detect.py塞进界面做PyQt界面时最常见的错误是把YOLOv5的detect.py整个搬进界面代码main函数和界面主循环搅在一起后面改一个参数都要翻半天。常见做法是写一个Detector类把模型加载和单帧推理封装成一个独立的接口界面只需要持有这个类的实例。import cv2 import torch class BehaviorDetector: def __init__(self, weights_pathruns/train/pig_v1/weights/best.pt, devicecpu, conf0.4, iou0.45): self.device device # 从本地yolov5目录加载custom权重sourcelocal表示权重在本地 self.model torch.hub.load(./yolov5, custom, pathweights_path, sourcelocal) self.model.conf conf self.model.iou iou self.model.to(device) def detect(self, frame_bgr): results self.model(frame_bgr, size640) df results.pandas().xyxy[0] # 检测结果表 drawn results.render()[0] # 画好框的BGR图像 return df, drawn逻辑说明初始化时只加载一次权重不要放在每次检测调用的地方否则界面一打开就卡十几秒后面每次点检测都重新加载模型用户会直接放弃。torch.hub.load的第一个参数是本地YOLOv5代码目录不是空字符串也不是网上某个仓库地址。size640必须和训练时的--img一致否则模型虽然能跑但目标尺寸比例不对小目标框会偏大或偏小。Device参数要根据机器实际硬件来定。界面里可以做一个下拉框让用户选CPU还是GPU启动时自动打印当前设备和检测耗时方便排查“为什么这么慢”的问题。不要默认写死cuda:0因为现场很多机器根本没有独立显卡强行调用会直接崩溃。4.2 用QThread避免界面卡死PyQt界面点“开始检测”后按钮点了没反应或者视频窗口卡住几秒钟才动一下这是把推理写在了主线程里。检测是一个阻塞操作视频源一边读帧一边推理主线程的界面消息循环全部被卡住鼠标悬停、按钮点击都进不去。解决办法是把检测循环放到QThread后台线程里主线程只负责接收信号并刷新界面。from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectionThread(QThread): frame_ready pyqtSignal(object) # 发送画好框的图像 result_ready pyqtSignal(object) # 发送检测结果表 def __init__(self, detector, source): super().__init__() self.detector detector self.source source self.running False def run(self): cap cv2.VideoCapture(self.source) while self.running: ok, frame cap.read() if not ok: break df, drawn self.detector.detect(frame) self.frame_ready.emit(drawn) self.result_ready.emit(df) cap.release()参数说明source可以是摄像头编号比如0也可以是视频文件路径。running标志位用来控制线程正常退出界面点击“停止”时把它置为False不要用terminate()强制终止线程容易造成摄像头资源没释放、下次打开失败。界面里初始化线程并连接信号self.det_thread DetectionThread(self.detector, self.source) self.det_thread.frame_ready.connect(self.update_frame) self.det_thread.result_ready.connect(self.update_table)逻辑说明frame_ready信号把画完框的图像传回主线程配对的槽函数只负责显示result_ready把结果表传回去刷新统计信息。这样检测和界面显示互不阻塞CPU或GPU在后台跑推理时窗口仍然能拖动、按钮仍然能点击。4.3 BGR转RGB、表格刷新与阈值实时调节OpenCV读出来的视频帧是BGR顺序而PyQt的QImage默认按RGB解析。如果直接把OpenCV的ndarray塞给QImage画面里的猪就是偏蓝的。需要先做一次通道转换再构造QImage。from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import Qt def update_frame(self, frame_bgr): rgb cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, 3 * w, QImage.Format_RGB888) self.video_label.setPixmap( QPixmap.fromImage(qimg).scaled( self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) )参数说明QImage(rgb.data, w, h, 3 * w, QImage.Format_RGB888)里的3 * w是每行字节数省略它会在某些分辨率下出现画面斜切或错位。KeepAspectRatio保证图像不变形SmoothTransformation让缩放后的画面边缘更平滑。结果表刷新不要每帧setRowCount(0)再重新addItem那样画面会闪个不停。更稳定的做法是只更新类别计数和当前总帧数比如在界面底部放一个状态栏QLabel实时显示各行为类别的数量。表格里的明细数据保留最近100帧即可超出部分自动丢弃。阈值调节做成滑条会更直观。界面上的“置信度滑条”直接写回Detector实例的属性def on_conf_changed(self, value): conf value / 100 self.detector.model.conf conf self.conf_label.setText(fconf: {conf:.2f})逻辑说明运行时修改model.conf会即时生效不需要重启界面。这个滑条不是摆设现场测试时如果把阈值从0.4降到0.25后漏检的猪能被框出来说明模型本身学到了特征只是阈值卡得太严那最终部署就用0.25如果降到0.25还是框不出来那就不是阈值问题要回去看数据。5. 训练与界面落地的5个常见坑和排查5.1 标签混乱、类别不平衡、loss翻车第一个常见坑多个标注人员对“趴卧”和“侧卧”、“站立”和“走动”判定不一致训练时loss能降但验证集指标忽高忽低。现象是训练曲线的loss很平滑val指标却像锯齿一样来回跳。原因是标注时没有先对齐标准有人把趴卧标成站立有人把路过食槽的猪标成采食模型学到的类间特征相互矛盾。解决方法其实出现在标注之前先截几张典型画面把每个类别的判定标准写成文档发给所有标注人员并且指定一个人牵头复检每标完50张抽10张出来对照发现不一致立即纠偏。不要相信“标完再统一检查”这句话1000张图全部返工的成本很高。第二个坑稀疏类别直接被模型忽略。现象是训练完成后的混淆矩阵里“骑跨”这一行全部归到别的类或者precision和recall都是零。原因是1000张图里骑跨类别的框可能只有几十个相对于趴卧、站立的几千个框权重更新时贡献太少模型干脆把所有样本都预测成大头类别。解决方法是先统计每个类别的框数量写个脚本遍历所有标签文件按类别计数然后把数量少于100个的类别挑出来回到视频里专门补帧补标注或者把该类别的框在数据增强时做重复采样。另外这类行为定义本身就模糊的类别强烈建议在标注时只标“骑跨”这种僵持姿势不标“争斗”这种连续性动作。import os from collections import Counter label_dir labels/train counter Counter() 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: cls int(line.split()[0]) counter[cls] 1 print(counter)第三个坑训练中途loss突然变成NaN或者数值忽然飙到几十。现象是训练跑了几十个epoch控制台打印的loss从个位数直接跳到nan之后怎么等都恢复不了。原因主要集中在三处学习率设得太大某个标签文件的归一化坐标越界或为空以及用高增强策略时切图逻辑碰到小目标出问题。解决方法是先用--hyp data/hyps/hyp.scratch-low.yaml把增强强度降下来学习率如果手动改过就恢复到YOLOv5默认的0.01然后批量检查标签文件把越界坐标裁剪回0到1把空行和空txt全部清掉。5.2 界面卡死、画面偏蓝、CPU推理过慢第四个坑PyQt点击“开始”后整个窗口假死。现象是按钮按下后界面失去响应鼠标划过窗口都是沙漏图标过了好几秒才突然恢复。原因是检测循环直接写在了按钮的槽函数里这个槽函数阻塞在主线程上界面事件循环根本走不了。解决方法就是把推理挪进QThread像第4章写的那样让耗时操作全部在后台线程完成主线程只做信号处理和画面刷新。验证是否真正在线程里跑了可以打印推理调用处的线程ID能在控制台看到它和主线程ID不一致就算成功。第五个坑OpenCV图像直接显示出来猪是蓝色的。现象是检测框位置和文字都正确但整幅画面颜色发蓝发绿。原因是BGR和RGB通道顺序没有转换QImage按RGB解析了OpenCV的BGR数据红蓝通道互换。解决方法就是显示前先cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)然后再构造QImage。这个坑几乎每个写PyQt图像显示的人都会踩一次本质上不是YOLOv5的问题但排查起来往往以为是模型输出画坏了。第六个坑CPU推理慢到没法用模型加载还特别慢。现象是界面启动要十几秒视频画面像是幻灯片。原因是每次检测都重新加载模型或者把所有计算都放到了CPU上还同时开了640以上的分辨率。解决方法是把模型初始化放到界面构造函数里只做一次CPU机器建议直接用n模型不要再坚持s如果还得跑实时预览可以把输入分辨率降到480虽然精度略降但界面手感会好很多。另一个容易忽略的开销点是把每帧结果都写日志、刷新表格帧率会被这些附带操作拖低日志和表格更新要降频比如每秒只更新两次。6. 进阶验证把单帧检测升级成行为时序统计6.1 验证一版权重的固定动作拿到一版新权重后不要只盯着mAP看我习惯按固定动作走一遍。先跑val.py拿到整体mAP和每个类别的precision、recall然后找一个没参与训练的视频把置信度阈值降到0.25跑一遍看有没有低置信度但实际正确的框出现。如果有说明模型学进去了只是阈值太严部署时把conf调低即可如果没有说明对应类别的特征没学好得回头补数据和纠标签。这是在几分钟内判断一版权重值不值得继续投入的最快办法比反复调参强得多。6.2 从单帧行为检测升级为时序统计单帧检测回答的是“这一秒猪在干什么”养殖场现场更需要的是“这只猪趴了多久、采食了几次、今天有没有异常”。把单帧检测升级为时序统计不需要一开始就上复杂的目标跟踪算法固定摄像头场景下用相邻帧的IoU匹配就能应付大部分情况。def match_boxes(prev_boxes, cur_boxes, iou_thr0.3): matched {} for i, cur in enumerate(cur_boxes): best_j, best_iou -1, 0.0 for j, prev in enumerate(prev_boxes): iou compute_iou(prev, cur) if iou best_iou: best_j, best_iou j, iou if best_iou iou_thr: matched[i] best_j return matched逻辑说明prev_boxes是上一帧的检测框列表cur_boxes是当前帧的检测框列表对每个当前框找IoU最大的历史框超过阈值就认为还是同一头猪。这样不需要引入额外的跟踪模型却能给猪分配临时ID。拿到ID后连续多帧对同一个ID的行为类别取众数就能滤掉“站立一秒变趴卧再变站立”这种单帧抖动得到更稳定的行为序列。基于这个序列可以统计趴卧时长、采食频次、饮水间隔再结合时间维度判断“某头猪长时间不进食”这类业务信号。界面里加一个历史统计面板把每头猪的临时ID、当前状态、持续时长列出来对现场人员来说比实时画框更有价值。这套组合做完工具才真正从“检测Demo”变成“能回答养殖问题的软件”。我自己的习惯是每次训练完一版权重第一件事不是看mAP而是找一段现场视频把上面的时序统计逻辑跑一遍看输出的标签序列是否平滑。不平滑就回到对应的原始帧看数据问题多半是标签定义或数据分布的问题这比反复调参有效得多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?