ARTICLE DETAIL

资讯详情

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

行为识别三件套实战:YoloV5+SlowFast+DeepSort全流程解析

行为识别三件套实战:YoloV5+SlowFast+DeepSort全流程解析 简介面向行为识别实战学习者的综合项目资源包整合YoloV5、SlowFast与DeepSort三大模块构建“实时检测—动作识别—目标跟踪”的视频处理流水线。适用于目标检测、动作识别、多目标跟踪等方向的研究者与开发者帮助解决单一SlowFast难以实时标注目标、复杂场景跟踪易丢失等问题。包体共180个文件以Python脚本(.py)、YAML配置(.yaml)、Markdown笔记、Shell脚本、Dockerfile及预训练权重(.pt/.t7)等为主内容涵盖完整源码、环境配置、示例视频与模型文件压缩包大小约994.4MB结构清晰便于按模块部署和二次开发。目前已有963人学习下载。资源将YoloV5的逐帧检测结果接入SlowFast进行动作分类再利用DeepSort完成持续跟踪相比单独使用SlowFast这套方案能在每一帧输出标注目标并保持较高跟踪精度。解压后可直接对照目录阅读源码适合搭建自己的行为识别系统也适合作为课程设计或论文实验的参考基础。1. 行为识别实战第二天YoloV5SlowFastDeepSort三件套的价值在哪行为识别实战第二天场景很直接你把一段监控视频丢给系统要的不是“这段视频里有人在运动”而是“第几秒、画面哪个位置、这个人在做什么动作”。只跑SlowFast做不到这一点它只给一个视频级的动作类别定位不了“谁在动”。这个YoloV5SlowFastDeepSort三件套框架把目标检测、动作识别、目标跟踪串成流水线YoloV5框出每帧目标SlowFast分析检测框裁剪出的时序片段判断动作DeepSort跨帧维持目标ID。它解决的是实时行为告警、监控视频结构化、体育动作统计这类落地问题。适合手上有视频数据、想快速搭出行为识别Demo或者准备做实时行为分析的工程师。2. 三个模型的分工与耦合检测框如何变成动作标签2.1 为什么是YoloV5SlowFast而不是只跑SlowFast先聊一个很现实的问题单独跑SlowFast到底缺什么。SlowFast本质上是一个视频分类网络输入一整段clip输出一个动作类别在UCF101、Kinetics这类数据集上确实打得出成绩但拿到实际视频里就有硬伤。第一它不告诉你动作主体在画面哪个位置第二它不做跨帧目标关联一段三分钟的视频它输出的还是整段视频级别的动作概率“谁在动、在哪动、动了几秒”这三个信息全丢。这也是我接到行为识别需求时第一反应是找检测器的原因。YoloV5在前面把每一帧的目标框出来后面的SlowFast和DeepSort只需要围绕这些框工作全图计算被压缩成区域计算开销直接降了一截。从这个角度看三件套不是三个模型堆一起而是把问题重新切分检测解决“是什么在哪”识别解决“正在做什么”跟踪解决“同一个目标持续了多久”。如果你做过实时告警类项目会发现这三个问题缺了任何一个输出都没法直接用。单独SlowFast只能拿来跑学术指标拿去做监控结构化在现场根本用不上因为你要的告警信息是“某人在某个位置开始进入危险区域”这个粒度只有检测加跟踪才给得出。这里还要说清楚一个容易被带跑的节奏SlowFast不是不能处理多目标视频而是它的训练范式决定了它擅长的是视频片段级别的判断。你把一段多人的画面直接喂进去它输出的动作类别是混合概率你没法知道是谁的动作得分最高。我见过有同学直接把SlowFast接到ROI检测后面以为输出就是目标框对应的动作结果把整个clip塞进去了翻车翻得很彻底。正确的做法是让SlowFast只看到“单个目标的历史裁剪区域”这个前提决定了整个数据流怎么设计。2.2 数据流设计每帧检测、片段识别、跨帧跟踪的时序关系整个系统的数据流是这么走的视频帧按顺序进入YoloV5YoloV5输出这一帧的检测框坐标和置信度检测框带着时间戳进入一个环形缓冲区攒够一个clip长度后交给SlowFastSlowFast输出该目标的动作类别与此同时DeepSort接收YoloV5的检测结果用卡尔曼滤波预测轨迹用ReID特征匹配前后帧的同一个目标最终每一帧都输出带目标ID、动作标签、检测框的完整标注。这三个模块不是各跑各的时间基准必须对齐否则会出现“跟踪框在动、动作标签还停在上一秒”的割裂感。时间对齐这块我踩过坑。常见做法是维护一个以目标ID为key的滑动窗口队列每个目标保存最近N帧的裁剪图只有队列里的帧数攒够了才触发一次SlowFast推理。SlowFast推理本身是有延时的所以通常让动作标签“滞后”几帧输出而不是强行实时。实战里我会把检测线程和识别线程分开检测线程负责每帧前置推理识别线程异步消费缓冲区达到“检测实时、识别准实时”的效果。如果强行走同步视频帧率会被SlowFast的推理时间拖死720P的输入都可能掉到2fps以下。这里有一个很关键的设计点DeepSort的轨迹管理需要跟SlowFast的标签回填解耦。DeepSort只管“这个框是谁”不关心动作标签动作标签是SlowFast算完后回填到对应目标ID上的。如果每次检测都硬要等SlowFast的结果再更新跟踪器整个系统会把跟踪问题放大成识别问题——一旦SlowFast卡一下跟踪也跟着崩。我的做法是跟踪器独立运行动作标签异步覆盖到目标的历史轨迹里这样即使SlowFast推理失败目标ID和位置信息仍然是可用的。2.3 检测框裁剪与clip组织SlowFast真正吃进去的是什么SlowFast的输入不是原图而是YoloV5检测框裁剪出来的目标区域序列。具体组织方式固定clip长度常见的是16帧或32帧每帧在检测框基础上加一定比例的padding然后resize到SlowFast要求的输入尺寸我这边用的是224×224最后按[C, T, H, W]的维度堆叠成输入张量。这里有两个细节直接决定识别效果值得单独拿出来讲。第一个是裁剪margin。检测框是贴合目标的但动作本身经常超出检测框边界比如“举手”这个动作手可能大部分时间都在框外。我一般会把检测框外扩20%到30%再裁剪具体比例要看应用场景动作幅度大的用1.3倍动作幅度小的用1.1倍。这个参数用好了识别精度能涨三四个点用不好就是黑匣子式的玄学掉点。第二个是帧采样策略。如果原始视频是30fps16帧的clip只覆盖约0.5秒很多慢动作根本识别不出来。我会在clip里做间隔采样比如隔一帧取一帧这样16帧能覆盖1秒左右动作周期比较长的场景甚至可以隔两帧。但采样间隔过大会导致动作特征断裂这个需要经验去调。场景clip长度采样间隔覆盖时域适用动作快速动作跌倒、挥手16帧0逐帧约0.5秒持续时间短、特征突变中等动作行走、跑步16帧隔1帧约1秒有周期性但周期短慢动作打斗、攀爬32帧隔1-2帧1.5-2秒动作周期长、变化平缓clip长度和采样间隔不是越大越好。我做过一组对照32帧逐帧采样的效果并不比16帧间隔采样好多少但推理耗时会翻倍。上面的表可以直接抄作业跑自己的数据集时先按中间的参数起跑再根据识别结果微调。3. 环境配置与镜像构建conda、Dockerfile与setup.cfg的三层依赖3.1 用conda复现yolov5环境torch版本和CUDA必须一起锁先把最容易让人翻车的环节放在前面这个项目下载下来之后第一件事不是跑demo而是把环境钉死。YoloV5和SlowFast对torch版本都有要求而torch版本跟CUDA是强绑定的跨版本混用经常会遇到“模型加载成功、一跑就段错误”或者“算子不匹配、推理直接挂”的问题。我自己的做法是先用conda建一个独立环境Python版本固定在3.8然后把torch、torchvision、CUDA的对应版本一起锁死。conda create -n action python3.8 -y conda activate action # cu113对应CUDA 11.3这套组合在YoloV5和SlowFast上验证过 pip install torch1.11.0 torchvision0.12.0 --index-url https://download.pytorch.org/whl/cu113 pip install -r requirements.txt这里的核心逻辑是YoloV5源码在torch 1.7到1.13之间表现稳定SlowFast对torch 1.9以上的兼容性比较好取交集之后选择1.11.0是一个比较稳妥的中间值。requirements.txt里通常会有opencv-python、numpy、scipy、tqdm这些基础依赖但注意numpy版本不要升到2.x因为很多老代码里的np.int这种别名在NumPy 2.x里已经被移除了装上之后import阶段就报错。提示装完环境第一时间跑一次随机张量前向确认模型能推理再上视频不然你分不清是环境问题还是数据问题。还有一个我反复跟人强调的习惯装完环境后先用随机输入验证YoloV5和SlowFast能不能正常前向不要直接拿视频跑。因为一旦出问题你分不清是环境问题还是数据问题排查成本直接翻倍。验证通过之后再上视频你会省掉大量无意义的debug时间。3.2 Dockerfile多架构解读cpu版与arm64版分别解决什么问题项目里带了好几个Dockerfile文件名分别是Dockerfile、Dockerfile-arm64、Dockerfile-cpu。这个组织方式很有用因为这些镜像的构建目标完全不同对应完全不同的使用场景。压缩包里那几个.DS_Store是macOS产生的隐藏文件直接忽略即可。文件镜像基础适用场景主要差异DockerfileCUDA基础镜像有NVIDIA GPU的开发机带CUDA运行时和cudnn推理速度最快Dockerfile-cpupython:3.8-slim无GPU的服务器、演示环境去掉CUDA依赖纯CPU推理Dockerfile-arm64arm64基础镜像树莓派5、RK3588这类ARM板需要处理部分算子的交叉编译cpu版和arm64版出现频率很高尤其现在不少人是想在树莓派5上部署自己训练的YoloV5模型做边缘端的行为识别。需要注意的是SlowFast在CPU上的推理速度非常不乐观我一个200帧的测试clip在i5上跑完要接近半分钟所以纯CPU部署更适合做“先录后析”的离线分析实时性要求高的场景还是得上GPU或者NPU。# 构建CPU版镜像 docker build -f Dockerfile-cpu -t action-detection:cpu . # 在ARM机器上构建arm64版镜像 docker build -f Dockerfile-arm64 -t action-detection:arm64 .# Dockerfile-cpu 最小可运行镜像结构 FROM python:3.8-slim WORKDIR /workspace COPY . . RUN pip install --no-cache-dir -r requirements.txt pip install -e . CMD [python, demo.py]构建镜像时有个小坑如果宿主机是x86直接docker build会默认构建x86架构的镜像要构建arm64版需要在ARM架构的机器上执行或者配置buildx做交叉构建。不少人在这步翻车构建完发现镜像在ARM板上拉不了白花半小时。我一般会在ARM板上直接构建避免交叉编译带来的算子兼容问题。3.3 setup.cfg依赖声明把SlowFast的关键包版本固定下来setup.cfg在这个项目里承担的是包元数据和依赖声明职责比requirements.txt优先级更高。用pip install -e .安装项目时pip会优先读取setup.cfg里的install_requires把核心依赖钉住。这里建议专门整理出来看一眼因为很多依赖版本冲突都出在这一层。[options] python_requires 3.8 install_requires torch1.9.0 torchvision0.10.0 opencv-python4.5.0 numpy1.19.0 scipy1.5.0 av8.0.0 psutil5.8.0 tqdm4.60.0这里面有个值得注意的点是av库也就是PyAVSlowFast的视频解码和帧提取依赖它。很多人配环境时漏掉av结果跑到视频读取那一步报ModuleNotFoundError前面的环境全白配了。另外setup.cfg里的依赖是下限约束不锁上限这意味着如果某些依赖出了大版本更新比如numpy 2.xinstall的时候会自动给你装最新版反而引入兼容性风险。实战中我通常会在安装完后手动把numpy固定在1.24以下。把这三层搞定环境基本就稳了conda管Python版本、pip管torch系列、setup.cfg管项目依赖。之后不管是本地开发还是丢进Docker跑都是同一套依赖基线不会再出现“我本地能跑、docker里跑不了”这种经典的部署难题。4. 核心代码串讲YoloV5检测到DeepSort跟踪的完整数据流4.1 YoloV5推理与后处理conf阈值和NMS参数怎么调YoloV5在这套框架里是前处理器它的输出质量直接决定了后面SlowFast和DeepSort的表现。代码入口通常是这样需要在yolov5源码目录下运行# yolo_detector.py - YoloV5推理与后处理 import cv2 import torch from utils.general import non_max_suppression from utils.augmentations import letterbox def yolo_infer(frame, model, conf_thres0.4, iou_thres0.45, img_size640): # letterbox保持宽高比并填充到640x640 img, ratio, pad letterbox(frame, new_shapeimg_size, stride32) img img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB且变为CHW img torch.from_numpy(img.copy()).float() / 255.0 img img.unsqueeze(0).to(next(model.parameters()).device) with torch.no_grad(): pred model(img)[0] # 模型输出的原始预测 # NMS后处理返回筛选后的检测框 det non_max_suppression(pred, conf_thres, iou_thres)[0] return det # 每行: [x1, y1, x2, y2, conf, cls]这段代码的逻辑是先把输入帧通过letterbox处理到640×640的尺寸保持宽高比不变然后做BGR到RGB的通道转换和HWC到CHW的维度转换模型推理得到原始预测后用non_max_suppression做后处理去掉重叠框和低置信度框。conf_thres控制保留多少检测框iou_thres控制重叠框的合并力度。参数上我的经验值conf_thres设0.4到0.5之间太低了会有大量低质量框进入后面的跟踪模块导致DeepSort的匹配矩阵里混入噪声太高了会漏检真正的小目标。iou_thres设0.45到0.5对密集人群场景可以下调到0.4让重叠的人体框更激进地合并。后处理这个环节看着不起眼实际上它决定了整个pipeline的上限——框漏了SlowFast没有输入框乱了DeepSort的ReID特征全是对不齐的目标。4.2 SlowFast推理视频片段张量的构造与预测SlowFast这部分值得单独讲它的输入是“一个目标的历史裁剪框序列”而不是整帧画面。我一般维护一个按目标ID分组的环形缓冲区目标出现后把每一帧的裁剪图塞进去攒够clip长度后触发一次推理。# slowfast_engine.py - 从检测框历史构造clip并完成推理 import numpy as np import torch import cv2 def build_clip_tensor(frame_history, clip_len16, spatial_size224, margin_scale1.2): # frame_history: 每个元素是(帧, 检测框) # 先按margin_scale外扩检测框再裁剪并resize crops [] for frame, box in frame_history: x1, y1, x2, y2 [int(v) for v in box] w, h x2 - x1, y2 - y1 cx, cy (x1 x2) // 2, (y1 y2) // 2 nw, nh int(w * margin_scale), int(h * margin_scale) x1, y1 max(0, cx - nw // 2), max(0, cy - nh // 2) x2, y2 min(frame.shape[1], cx nw // 2), min(frame.shape[0], cy nh // 2) crop frame[y1:y2, x1:x2] crop cv2.resize(crop, (spatial_size, spatial_size)) crops.append(crop) # 组合成 [C, T, H, W] 归一化张量 clip np.stack(crops).transpose(3, 0, 1, 2).astype(np.float32) clip torch.from_numpy(clip / 255.0).unsqueeze(0) # 加batch维度 return clip def slowfast_predict(model, clip_tensor, class_names): with torch.no_grad(): pred model([clip_tensor]) # SlowFast接收list输入 # 取softmax后的类别得分并映射到动作标签 probs torch.softmax(pred[0], dim1)[0] top_idx int(torch.argmax(probs)) return class_names[top_idx], probs[top_idx].item()这段代码里最关键的是margin_scale和spatial_size两个参数。margin_scale控制外扩范围1.2是中等动作的起点配置spatial_size是SlowFast的输入分辨率这个值不要随意增大因为SlowFast的时空卷积核是按224设计预训练的超过这个尺寸反而会引入预训练分布之外的信息。注意SlowFast的模型输入是list而不是单张量因为slow和fast两条路径的帧率采样不同结构上决定了输入必须是列表形式。还有一点容易忽略推理时机。我这里的做法是每个目标攒够了clip_len帧才触发一次推理目标被跟丢之前不会重复推理避免同一个动作被反复计算。推理结果回填到目标ID对应的轨迹上后续帧直接复用最近的标签直到下一次clip更新。4.3 DeepSort跟踪更新卡尔曼滤波与动作标签回填DeepSort在整套系统里扮演的是“粘合剂”角色它的输入是YoloV5的检测框和ReID外观特征输出是稳定的目标ID。核心更新逻辑如下# deepsort_wrapper.py - 检测结果接入DeepSort跟踪器 from deep_sort.deep_sort.tracker import Tracker from deep_sort.deep_sort.detection import Detection def update_tracker(tracker, det_output, frame, extractor): # det_output: [x1, y1, x2, y2, conf, cls] 的二维数组 detections [] for row in det_output.cpu().numpy(): x1, y1, x2, y2, conf, cls row bbox [x1, y1, x2 - x1, y2 - y1] # deep_sort需要xywh格式 # 用ReID模型提取外观特征 feat extractor(frame, bbox) detections.append(Detection(bbox, conf, feat)) tracker.predict() # 卡尔曼滤波预测上一轮轨迹的新位置 tracker.update(detections) # 级联匹配或IoU匹配 return tracker.tracks # 每个track带稳定ID这段代码的细节在于bbox格式转换YoloV5输出的是[x1,y1,x2,y2]DeepSort内部用的是[x,y,w,h]直接喂进去会全错位。ReID特征由extractor提取常见做法是加载一个预训练的deep_sort模型权重对检测框区域做外观特征编码这个特征跟卡尔曼滤波的位置预测一起参与匹配。级联匹配会优先匹配最近帧确认过的轨迹这样能有效处理遮挡后的ID恢复。动作标签回填这块我一般放在tracker.update之后遍历tracks每个track拿到自己对应的目标ID然后从SlowFast的结果表里查这个ID最近一次的动作标签覆盖到当前帧的绘制结果上。这里要注意标签回填不能阻塞跟踪更新我会用一个字典缓存每个ID的最近动作标签SlowFast推理是异步的标签落后几帧是允许的。整套数据流跑下来输出的每一帧就同时有了“框、ID、动作”三个维度的信息这就是行为识别落地最需要的结构化数据。5. 避坑指南行为识别三件套的常见问题定位5.1 现象模型加载成功一跑推理就段错误现象代码走到了model(frame)这一步进程直接崩掉没有任何Python报错终端里只留下Segmentation fault。原因torch和CUDA版本不匹配或者CUDA编译算子跟不上显卡驱动。最常见的是python环境里装了torch 2.x而项目依赖的预编译算子还是按1.x编译的加载权重时内存布局对不上。解决回到conda环境重新锁版本先卸载torch再装回1.11.0对应CUDA版本验证方法很简单跑一次python -c import torch; print(torch.zeros(1).cuda())这一行能过模型推理基本不会因为环境崩。5.2 现象检测框抖动导致SlowFast动作标签跳变现象同一个目标在连续帧里的检测框忽大忽小SlowFast输出的动作标签在“站立”和“走动”之间反复横跳。原因YoloV5对同一目标在不同帧的检测框并不稳定框的边界变化直接改变了裁剪区域内容SlowFast吃到的时序输入被污染。解决拉大margin外扩比例让动作主体尽量稳定地落在裁剪区域内同时给SlowFast输出加一个缓冲区连续三帧输出相同标签才更新相当于做一次时域滤波。我实际测试下来这个“三帧确认”机制能把标签跳变率从30%压到5%以下。5.3 现象DeepSort跟踪ID频繁切换现象画面里一个人走着走着ID从1变成3过两秒又变成7轨迹断成很多截。原因ReID特征在动作剧烈变化时失真或者卡尔曼滤波的预测方差没调好遮挡后级联匹配匹配不到历史轨迹直接把旧轨迹丢弃开新轨迹。解决调低级联匹配里的max_age参数效果不明显真正有效的是提高ReID特征在距离计算中的权重让外观特征占比更大。另一个土办法是给轨迹加最短存活时间ID成立前必须有连续5帧匹配从源头抑制短暂噪声目标开新ID。5.4 现象CPU推理慢到每秒0.3帧现象在无GPU机器上整个pipeline跑起来像幻灯片每秒连一帧都处理不完。原因检测和识别全跑在CPU上YoloV5本身在CPU上只能到2-4fps而SlowFast的时空卷积在CPU上是灾难级的两者叠加直接拖垮整条流水线。解决做四件事。一是缩小检测输入尺寸640降到416二是检测抽帧每2帧检测一次三是SlowFast的clip只对已确认跟踪的目标ID触发四是把识别部分做成异步。这一套下来CPU模式能从0.3fps提到1.5fps左右虽然谈不上实时但已经能支撑离线批处理。5.5 现象行为类别顺序错位现象识别出来的标签总是比真实动作慢半拍或者把“走”识别成“跑”还特别稳定。原因SlowFast模型的类别映射用的是自己数据集的class_names顺序而项目里加载类别文件时没有对齐索引另外clip覆盖时域太短周期性动作的特征没有被完整包含。解决下载权重时把类别列表单独导出成json推理时按下标索引而不是硬编码顺序clip长度按第2章表格里的“慢动作”档位重设并让类别定义和数据集标签严格对应。这个坑纯属细节但一旦踩了就是全量输出全错跟推理性能没关系。6. 进阶调优用yolov5超参数和deepsort改进把精度再往上顶6.1 yolov5超参数调整的三个杠杆用yolov5训练自己的数据集时超参数是最直接的杠杆。我自己固定项目的做法是先不碰模型结构只动三个超参数。第一个是训练时hyp里的lr0从默认0.01调到0.005对数据量小于2000张的小数据集非常友好避免loss前期震荡第二个是image size训练用640推理也用640不要训练时640推理时416mAP会在换尺寸时掉两三个点第三个是anchoryolov5会自动计算数据集的anchor但autoanchor在极端长宽比的类别上效果不稳定我会在训练前手动导出anchor并固定不依赖动态结果。还有yolov5源码里的augmentation参数mosaic和mixup对小样本数据提升明显但光照变化大的监控场景里mosaic过于激进反而掉点这个要根据数据分布做取舍不是无脑全开。6.2 deepsort改进把ReID和运动模型分开调deepsort改进这块我踩过不少坑。经典deepsort在密集场景最大的问题是ID Switch率高常见方案是换更强的ReID骨干网络或者把级联匹配改成贪心匹配。我会先量化问题再动手跑一段标注了ground truth的测试视频统计ID Switch次数和MOTA指标如果ID Switch高但MOTA不错说明主要问题在特征换ReID网络最有效如果MOTA也低说明检测框质量就有问题回头调YoloV5的conf阈值。最后分享一个收尾习惯每次把这套行为识别pipeline跑通后我都会固定用同一段包含遮挡、快速运动、多人交叉的视频做回归测试记录三个指标——检测mAP、动作识别accuracy、ID Switch次数。从那以后我每次改任何参数都强制走一遍回归测试参数有没有改进一眼就能看出来而不是靠肉眼盯着画面猜。这套验证流程算是我在这类项目里最大的血泪经验也希望帮到你。本文还有配套的精品资源点击获取
返回列表