
简介一套基于PytorchYOLOv5SlowFast的视频流实时动作检测算法源码支持多目标跟踪检测源自个人毕设项目答辩评分98分。面向计算机、通信、人工智能、自动化等相关专业学生、老师或从业者适用于期末课程设计、课程大作业、毕业设计等场景。压缩包共31个文件以22个Python脚本为主覆盖目标检测、SlowFast动作识别、可视化封装及deep_sort跟踪配置另含pbtxt动作标签映射、yaml参数配置、gif演示动图与readme说明文档整体仅7.21MB。代码经过调试可稳定运行目录结构清晰并附演示动图与说明便于快速理解与二次开发。目前已有164人学习下载具有较高的学习借鉴与实际改造价值。1. 视频流实时动作检测是个系统工程YOLOv5和SlowFast正好各管一半视频流动作检测和单帧目标检测最大的区别在于你不仅要回答“画面里有什么”还要回答“这个人正在做什么”。这两件事在计算机视觉里分属不同层级——目标检测是空间感知动作识别是时间感知。PytorchYOLOv5SlowFast这套组合之所以常见是因为它把问题拆成了两个可以独立优化的环节YOLOv5负责在每一帧上框出目标SlowFast负责对框出来的时序片段做动作分类之间再用多目标跟踪把跨帧的检测结果关联成轨迹。这样既避开了端到端视频模型难以训练的坑也让每一步都可以单独调参和替换。适合用它来解决的问题很具体监控视频里的人体行为分析、赛事视频里的动作判定、产线安全行为识别。它的核心价值不在于某一个模型多强而在于数据流的设计——检测、跟踪、识别三段各司其职实时性瓶颈可以被精确定位到某一环。接下来我会把这套从环境搭建到工程落地的完整路径讲清楚。2. 环境与依赖选型Pytorch版本、CUDA组合和模型权重的前置准备2.1 Pytorch 2.x CUDA 12.x的组合是当前的最稳选择做视频动作检测不比跑个MNIST显存占用和算力需求都高一个量级。CPU环境理论上能跑但SlowFast的3D卷积在CPU上的推理速度基本不可用。我一般建议直接用GPU环境起步当前比较稳妥的组合是Python 3.10.11、Pytorch 2.8.0配CUDA 12.1。这个组合包在pip和conda源里都已经有预编译的wheel不需要自己编译源码。安装命令用官方pip源就能满足大部分情况# 创建独立环境避免和已有项目冲突 conda create -n action_recognition python3.10.11 conda activate action_recognition # 安装Pytorch 2.8.0 CUDA 12.1版本 pip install torch2.8.0 torchvision0.19.0 torchaudio2.8.0 \ --index-url https://download.pytorch.org/whl/cu121 # 安装视频处理和跟踪依赖 pip install opencv-python opencv-contrib-python pip install cython lap numpy scipy这里有一个容易踩的坑opencv-python和opencv-contrib-python不能同时存在后者的内置跟踪器更多多目标跟踪的SORT算法依赖的就是contrib包里的工具。安装完成后用python -c import torch; print(torch.cuda.is_available())验证CUDA是否生效返回True才能继续。2.2 YOLOv5与SlowFast的版本选型逻辑YOLOv5官方仓库一直在迭代但做视频流场景不建议追最新版本稳定性和推理速度优先。YOLOv5 v6.0之后的检测头结构变化不大v7.0对小目标的召回率有提升但对视频流推理速度有轻微影响。我的建议是锁定一个具体tag来用git clone --branch v7.0 https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtSlowFast这边官方实现是Facebookresearch的slowfast仓库基于Pytorch的Video API构建。注意一点slowfast仓库的master分支可能和最新版Pytorch存在兼容问题建议直接安装特定的release版本git clone --branch v0.1 https://github.com/facebookresearch/slowfast.git cd slowfast python setup.py build develop提示安装slowfast之前先确保Pytorch版本不低于2.0因为它内部用到了一些新版API的注册机制版本太老会直接import报错。2.3 预训练权重文件的选择策略缺少预训练权重是整个项目跑不起来最常见的原因也是最容易被忽视的前置准备。YOLOv5的yolov5s.pt是最常用的起步权重14MB左右COCO数据集上mAP够用且推理速度快。如果你的场景不包含COCO的80类而是只关心人形目标建议用yolov5s.pt然后在自己的数据集上微调。SlowFast方面Kinetics-400上预训练的SlowFast_8x8_R50权重和模型结构是配套的不要混用不同帧率训练出来的权重。3. 检测器改造用YOLOv5输出的边界框构造时序输入3.1 YOLOv5检测器在视频流中的调用方式视频流场景和单张图片推理有个关键区别视频帧是连续队列如果每帧都做全量推理GPU利用率波动非常大。常见做法是引入一个帧采样策略——每隔N帧做一轮检测其余帧直接用轻量级跟踪器补位。N的取值取决于你的视频帧率30fps输入时N取2或3即每2到3帧检测一次跟踪器在中间帧做位置预测。以下是一个标准的YOLOv5推理封装import cv2 import torch # 加载模型时的关键参数设置 model torch.hub.load( ultralytics/yolov5, custom, pathweights/yolov5s.pt, force_reloadFalse ) model.conf 0.45 # 置信度阈值 model.iou 0.5 # NMS的IoU阈值 model.classes [0] # 只检测person类COCO中0为person cap cv2.VideoCapture(rtsp://your_stream_url) frame_skip 2 frame_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % frame_skip 0: # 转成RGB并归一化到0-255范围 results model(frame[:, :, ::-1]) detections results.xyxy[0].cpu().numpy() # detections列含义: x1, y1, x2, y2, confidence, class_id frame_idx 1参数说明model.conf控制漏检和误检的平衡0.45适合大多数场景。如果你的画面中目标较远、尺寸小需要降到0.3来减少漏检如果场景复杂、误检干扰大则上调到0.5以上。model.classes [0]把检测范围限定为person类这不仅提升推理速度还能大幅降低后续SORT跟踪的ID Switch概率。3.2 YOLOv5网络结构的实时性优化思路YOLOv5s是速度和精度的平衡点但在视频流场景可能仍然不够快。有一条常规优化路径在models/yolov5s.yaml中调整width_multiple和depth_multiple。分别改成0.5和0.33后对应的是YOLOv5n参数量降低到原来的四分之一左右。这个改动对动作检测系统的影响集中在box定位精度上而SlowFast本身不依赖高精度的框——只要你把目标大致框住时序特征提取就有足够的语义信息。另一个值得动手的地方是开启半精度推理model model.half() # FP16推理显存占用直接减半在2080Ti这类卡上推理耗时能缩短30%左右。代价是框的坐标精度有些损失但这个精度损失不会影响SlowFast分类结果。实测中这样做的人很多坑在于FP16推理下results.xyxy的返回值变成float16类型后面和跟踪器对接时要先.float().cpu().numpy()转换。4. SlowFast模型的输入构造与动作分类管道4.1 从检测框到时序片的Tube构建这是整个项目里最容易出错的一步。SlowFast不像图像分类那样接受单帧输入它需要的是[B, C, T, H, W]形状的视频片段。T代表时间维度的帧数8x8配置代表用8帧作为输入、每帧间隔8个采样步长。这意味着你要做的不是把连续帧直接堆叠而是先确定目标在时间轴上的位置——这个位置由跟踪器的轨迹点来维护。核心代码如下import torch from slowfast.models import build_model from slowfast.config.defaults import get_cfg import numpy as np # Clip构建类按轨迹ID聚合时序信息 class TubeBuilder: def __init__(self, clip_len8, frame_interval8): self.clip_len clip_len # 需要多少帧 self.frame_interval frame_interval # 采帧步长 self.buffer {} # 每个track_id对应的帧缓存 def build(self, track_id, frame, box): frame: 当前帧RGB, box: [x1,y1,x2,y2] 核心逻辑用固定长度的deque维护每个轨迹的最近N帧 if track_id not in self.buffer: from collections import deque self.buffer[track_id] deque(maxlenself.clip_len) # 裁剪并对齐到224x224 x1, y1, x2, y2 [int(v) for v in box] roi frame[y1:y2, x1:x2] roi cv2.resize(roi, (224, 224), interpolationcv2.INTER_LINEAR) self.buffer[track_id].append(roi) # 缓存不足时返回None if len(self.buffer[track_id]) self.clip_len: return None # 构造SlowFast需要的双路径输入 clip np.stack(self.buffer[track_id]) # [T, H, W, C] clip clip.transpose(3, 0, 1, 2) # [C, T, H, W] # Slow路径: 每4帧取1帧 slow_path clip[:, ::4, :, :] # Fast路径: 全帧率通道减半 fast_path clip[:, :, :, :] return { slow: torch.from_numpy(slow_path).float().unsqueeze(0), fast: torch.from_numpy(fast_path).float().unsqueeze(0) }这段代码的关键在于deque(maxlenclip_len)这个数据结构。它保证每个track_id只保留最近的8帧超出自动丢弃最早帧。slow_path的::4步长采样实现了SlowFast的核心思想Slow路径低帧率高通道Fast路径高帧率低通道。实际操作中deque的maxlen就是clip长度但显存足够时建议用10帧动作识别的准确率会明显提升。4.2 SlowFast模型的推理与标签映射模型部分的调用方式是固定的device torch.device(cuda if torch.cuda.is_available() else cpu) cfg get_cfg() cfg.merge_from_file(configs/Kinetics/SlowFast_8x8_R50.yaml) cfg.NUM_GPUS 1 model build_model(cfg) model.eval() model.to(device) # 加载预训练权重 checkpoint torch.load(weights/SlowFast_8x8_R50.pkl, map_locationdevice) model.load_state_dict(checkpoint[model_state]) with torch.no_grad(): slow_input tube[slow].to(device) fast_input tube[fast].to(device) # SlowFast网络会同时在两个路径上计算 preds model([slow_input, fast_input]) probs torch.softmax(preds, dim1) topk torch.topk(probs, k3) # topk.indices里的数字对应动作类别ID action_name label_map[topk.indices[0][0].item()]cfg.merge_from_file这一步不可省略slowfast仓库的模型结构参数大量依赖YAML配置文件直接build_model(cfg)不merge各个yaml会跑出随机结果。label_map是从Kinetics-400数据集的分类索引里读取如果你用的是自定义数据集训练的动作分类器需要在训练结束时保存一份class_to_idx的映射文件。4.3 SlowFast训练自己的数据集时的参数风格用UCF101这类公开数据集微调是常见路径。这个数据集有101类动作视频总量约13K对SlowFast来说偏小需要较强的数据增强多尺度裁剪、水平翻转、随机灰度和色彩抖动是基础组合。优化器方面常见做法是SGD带momentum初始学习率0.01配合cosine decaySOLVER: BASE_LR: 0.01 MOMENTUM: 0.9 WEIGHT_DECAY: 1e-4 MAX_EPOCH: 50 LR_POLICY: cosine DATA: TRAIN_CROP_SIZE: 224 NUM_FRAMES: 8 SAMPLING_RATE: 8 TRAIN_JITTER_SCALES: [256, 320] TRAIN_CROP_NUM_TEMPORAL: 1注意SAMPLING_RATE指每隔几帧采一帧NUM_FRAMES是采多少帧。两者相乘的乘积决定了动作识别能覆盖的时间跨度。乘积太小会识别不出慢动作太大会把不同动作混在一起。5. 多目标跟踪与动作识别之间的数据关联设计5.1 SORT跟踪器的集成方式与轨迹生命周期管理跟踪模块的作用是给每个检测框分配一个稳定的track_id这个id是TubeBuilder聚合帧缓存时的key。没有跟踪器的系统会是什么状态同一个目标每帧可能被识别成不同的boxTubeBuilder里面的deque会反复重建SlowFast拿到的时序数据从不同目标上混着来动作分类准确率直接崩塌。所以跟踪不只是为了画框它是时序输入的粘合剂。SORT是集成成本最低的多目标跟踪方案。它的核心是卡尔曼滤波加匈牙利算法不需要深度特征速度极快。实现代码如下from sort import Sort mot_tracker Sort(max_age5, min_hits3, iou_threshold0.3) # 每一轮检测后更新跟踪器 detections results.xyxy[0].cpu().numpy() # detections格式转换: [x1, y1, x2, y2, conf] det_input detections[:, :5] if len(detections) 0 else np.empty((0, 5)) tracked_objects mot_tracker.update(det_input) for obj in tracked_objects: x1, y1, x2, y2, track_id obj.astype(int) # 用track_id作为TubeBuilder的key tube tube_builder.build(track_id, rgb_frame, (x1, y1, x2, y2)) if tube is not None: action_name run_slowfast(tube)这里mot_tracker.update做的事情是用卡尔曼滤波预测上一帧目标在当前帧的位置然后和检测框做IoU贪心匹配。max_age5表示目标丢失后最多延续5帧轨迹等待重现min_hits3表示连续3帧命中才对外输出这个轨迹用来过滤掉单帧噪声检测。5.2 跟踪与识别频率解耦两级流水线的并发设计视频流场景中最常见的性能瓶颈我们在第1章留了个引子现在具体展开。如果每一帧同时跑YOLOv5和SlowFast算力翻倍。由于SlowFast每8帧才需要推理一次但YOLOv5可以每帧推理两者天然存在频率差。可以设计成双线程流水线import threading import queue det_queue queue.Queue(maxsize300) # 存放检测结果 tube_buffer {} # track_id - TubeBuilder实例 def detection_worker(): 独立线程只做YOLOv5检测不碰SlowFast while True: frame, frame_id frame_queue.get() results model(frame[:, :, ::-1]) det_queue.put((frame_id, results)) def recognition_worker(): 独立线程消费检测结果每8个检测轮次做一次动作识别 while True: frame_id, results det_queue.get() tracked mot_tracker.update(results.xyxy[0].cpu().numpy()) for obj in tracked: x1, y1, x2, y2, track_id obj.astype(int) # 关键优化只在目标旧位置足够远或间隔达到8帧时才推理 global_count 1 if global_count % tube_builder.frame_interval 0: tube tube_builder.build(track_id, frame_cache[frame_id], (x1, y1, x2, y2)) if tube: action run_slowfast(tube) draw_overlay(frame_cache[frame_id], action)这个设计的核心价值在于YOLOv5是同步阻塞的但检测完放入queue后线程立即去处理下一帧SlowFast的推理在另一个线程里异步执行不用等它输出就能继续检测。实测中两个模型叠加的FPS损失从加起来等于半个帧率变成只等于最慢的那个——通常SlowFast推理一次耗时80msYOLOv5单帧14ms流水线总吞吐量基本被SlowFast的下采样间隔8帧抵消掉了。5.3 多目标场景下的ID Switch问题与改进方向SORT最大的弱点是ID Switch频繁。当两个人交叉走过互相遮挡时卡尔曼滤波的预测框发生重合匈牙利匹配会互相纠缠导致track_id互换。这在动作检测系统里是致命的——track_id一旦交换TubeBuilder里deque存的8帧就混了两个人的动作时序识别结果变成乱七八糟的混合预测。一个不增加太多计算量的改进是给SORT加上外观特征即DeepSORT的思路但不在这个阶段引入重识别模型。中间态方案是在现有track_id的deque里存最后一个可见帧的外观向量用YOLOv5的倒数第二层特征输出即可匹配时对IoU加一个余弦相似度的加权项。YOLOv5的model.model[-1]是检测头取model.model[-2]的输出就是特征图# 提取检测特征用于外观辅助匹配 model.model[-2].register_forward_hook(lambda module, input, output: feature_cache.append(output))IoU匹配分数和外观相似度各占50%权重就能显著抑制遮挡场景的ID Switch问题。这个方案改动量小且不需要额外训练模型。6. 实时性能调优与工程落地的关键参数性能优化的目标不是单纯拉高FPS而是在保证动作识别准确率的前提下找到最经济的配置。以下是我在实践中总结的几个必须动手调整的参数组合。首先是下采样频率。SlowFast的SAMPLING_RATE8配合NUM_FRAMES8覆盖的是64帧跨度的动作语义。面对30fps的视频意味着需要2秒多的内容才能完成一次判断。如果场景里动作快比如跌倒检测、挥手调成SAMPLING_RATE4。但要注意采样率降低意味着时间感受野减半对那些持续2秒以上的慢动作如打斗前的对峙会判断不准。这个参数直接决定一个动作能被解构的时序粒度。其次是跟踪器的输出帧率与检测帧率的比例。我在第3章提到frame_skip即检测间隔。帧率是30fps时frame_skip3可以让YOLOv5以10fps运行这个频率对监控场景的人体运动足够。跟踪器在每帧都运行用卡尔曼滤波填补未检测帧的目标位置SORT的max_age在这个场景下建议设成10——间隔越大目标短暂离开画面后重新出现的轨迹保持能力越强。第三个是GPU算力分配与批处理。如果你的显卡显存大于12GB可以把多个Tube合成一个batch一次性喂给SlowFast# 将多个tube堆叠成batch slow_inputs torch.cat([t[slow] for t in active_tubes], dim0) fast_inputs torch.cat([t[fast] for t in active_tubes], dim0) with torch.no_grad(): batch_preds model([slow_inputs, fast_inputs])batch推理比逐个推理加起来的耗时短得多因为卷积的矩阵运算对batch维度天然友好。NVIDIA的TensorRT将YOLOv5和SlowFast都转成engine后整体FPS可以提升2倍左右但这要求你的部署环境与训练时的CUDA版本一致。最后的验证建议是启动后在终端打印一条周期性日志包含检测FPS、跟踪活跃目标数、SlowFast推理耗时三个指标。如果SlowFast推理耗时大于NUM_FRAMES * frame_interval / video_fps的倍数说明流水线在等待推理结果需要增大frame_interval或减小NUM_FRAMES直到流水线吞吐量恢复饱和。本文还有配套的精品资源点击获取