简介这是一套基于PytorchYOLOv5SlowFast的视频流实时动作检测项目源码支持多目标跟踪检测源自作者毕业设计答辩98分适合计算机、人工智能、自动化等专业学生及开发者用于课程设计、毕业设计或算法进阶。资源包共31个文件压缩包7.21MB核心为22个Python脚本配合2个pbtxt配置文件、2个类别/标签txt、1个YAML配置及2个GIF演示动图另有说明文档与readme目录结构清晰。当前已有164人浏览学习代码经调试测试可运行适合新手跟着demo动图快速理解推理流程。项目整合了YOLOv5目标检测、SlowFast动作识别与DeepSort跟踪包含ava动作标签、coco类别名等配套文件可直接用于视频流实时检测实验学习者可在此基础上调整模型和参数拓展到行为分析、安全监控等场景。1. 视频流里的动作检测难在“同时”两个字把一张静态图片里的人物框出来YOLO 系检测器早就做到了给一段短视频判断里面的人在“跑”还是“走”SlowFast 这类动作识别模型也能完成。但把两者拼成一条对视频流实时处理的管线麻烦立刻出现检测器只在单帧上工作动作识别却需要连续几十帧的时间上下文而中间还要保证同一个人的框不会因为一两帧漏检就“消失”否则动作识别的输入就断了。这个项目解决的就是这个衔接问题。它用 YOLOv5 做逐帧目标检测、DeepSORT 做跨帧身份关联、SlowFast 在滑动窗口里做动作分类三个阶段各司其职最终输出带动作标签的跟踪轨迹。对正在做毕设、课程设计或者刚接触视频理解方向的同学来说这套代码把“检测—跟踪—识别”三段式管线完整串了起来跑通它之后换成自己的数据集和场景就有了清晰的抓手。2. YOLOv5 做检测器单帧定位的性能与精度取舍先看检测层。YOLOv5 在整个管线里承担最基础的职责每一帧画面里有哪些目标主要是人各自在什么位置。这个阶段只回答“在哪”不回答“在做什么”所以对检测器的要求首先是稳定——漏掉一帧后面的跟踪和识别都会受影响。2.1 为什么选用 YOLOv5 而不是更新的检测器虽然 YOLOv8、RT-DETR 等新模型在精度上已有提升但 YOLOv5 在这套项目里依然合理。一方面是 PyTorch 生态下的部署资料极其密集换 COCO 预训练权重、调整 anchor 或做 TensorRT 加速都有大量现成案例另一方面v5 的模型结构在推理延迟上非常可控s/m 系列在 GTX 1660 级别的显卡上就能跑到实时帧率。对于动作识别这种需要“持续不断喂帧”的上游模块检测器占用推理时间越短留给 SlowFast 的计算预算就越宽裕。2.2 检测器核心推理代码的拆解项目中yolo_slowfast.py和slowfast_detection.py分别对应两条运行路径前者侧重完整流程后者更贴近接口化调用。以常见的视频帧推理为例YOLOv5 的调用方式如下import torch import cv2 # 加载预训练模型num_classes 需与权重匹配 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 # 检测置信度阈值 model.iou 0.5 # NMS 的 IoU 阈值 model.classes [0] # 只保留 person 类别COCO 中类别 0 为 person cap cv2.VideoCapture(demo/input.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 返回的 results 对象内含检测框、置信度、类别 results model(frame, size640) # 提取 [x1, y1, x2, y2, conf, class] 格式的数组 dets results.xyxy[0].cpu().numpy() # 只保留 person 目标交给后续跟踪器 person_boxes dets[dets[:, 5] 0] for box in person_boxes: x1, y1, x2, y2, conf box[:5] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.imshow(detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里有两个参数直接影响后续管线的质量。conf0.4是置信度阈值调低会召回更多漏检目标但也会把背景误检当作人传入跟踪器调高则减少误检但跟踪轨迹可能频繁中断。classes[0]限定了只检测 person这是因为动作识别模型训练时只面对人的行为类别把其他物体框交给 SlowFast 没有意义。imgsz640是推理分辨率输入尺寸越大小目标检出越好但推理耗时线性上升。我的经验是720p 视频流用 640 足够1080p 且人员密集时可以试 960前提是 GPU 显存和延迟预算许可。2.3 检测器输出归一化与跟踪器的衔接从 YOLOv5 拿到的边界框坐标是像素绝对值如果送入跟踪器前不做任何处理当输入视频分辨率变化时跟踪器的距离度量会不一致。常见做法是在交给 DeepSORT 之前把框坐标归一化到[0, 1]区间或者统一缩放到固定尺寸。项目源码中selfutils目录下的工具函数就承担了这类转换工作使用顺序一般为检测器原始框 - 坐标格式对齐 - 送入 DeepSORT update()。这一步看起来很琐碎但确实是最容易出 bug 的地方——YOLOv5 输出的中心坐标形式与 DeepSORT 需要的(x1, y1, x2, y2)不一致时跟踪器会直接报错或产生无意义的匹配结果。3. DeepSORT 多目标跟踪把断帧拼成连续轨迹检测器给出的是每一帧独立的框没有任何帧间关联。如果直接对每个框单独做动作识别那么目标一旦暂时被遮挡或检测丢失身份就变了。多目标跟踪就是解决这个问题的中间层DeepSORT 在这里负责把同一目标在不同帧的检测框串联成一条轨迹并为每条轨迹分配稳定的 ID。3.1 级联匹配策略与代价矩阵DeepSORT 的核心是匈牙利算法加级联匹配。它不直接对全部轨迹和全部检测做全局匹配而是优先匹配“最近被更新过”的轨迹再逐步放宽条件去匹配较久未更新的轨迹。这么做是为了降低 ID Switch 的频次。匹配时的代价由两方面组成一是马氏距离度量检测框和轨迹预测位置的运动一致性二是外观特征的余弦距离由一个小型 ReID 网络提取的外观向量计算。最终的代价矩阵是两者的加权和代价矩阵 外观距离权重 × 余弦距离 运动距离权重 × 马氏距离外观距离的权重通常更高因为行人外观在短时间段内变化不大而运动模型在摄像头抖动或目标突然转向时会明显失真。deep_sort/configs目录下的配置文件里就包含了这些权重和阈值建议按实际视频场景调而不是照搬默认值。3.2 轨迹生命周期管理的关键状态DeepSORT 里的每一条轨迹都经历三个阶段tentative不确定、confirmed确认、deleted删除。新检测框先产生 tentative 轨迹只有连续若干帧都能被匹配到才升级为 confirmed反之一条 confirmed 轨迹如果连续多帧没有匹配到任何检测框会进入missed计数超过max_age之后被删除。理解这个生命周期对调优很重要因为动作识别只处理 confirmed 轨迹的框误判阶段tentative的框往往不稳定位置跳动大送进 SlowFast 会引入噪声。下面是一段典型的 DeepSORT 状态更新逻辑代码结构在项目deep_sort目录中对应实现# 以 tracker.update 为核心模拟一帧的处理流程 from deep_sort import DeepSort # 构造跟踪器max_age 控制轨迹丢失后保留的最大帧数 deepsort DeepSort( model_pathdeep_sort/checkpoint/ckpt.t7, # ReID 特征提取权重 max_dist0.2, # 特征最大余弦距离超过则拒绝匹配 max_age30, # 轨迹连续 30 帧无匹配则删除 n_init3, # 连续 3 帧匹配才确认轨迹 nn_budget100 # 外观特征库容量超限则淘汰旧特征 ) # 每帧得到检测框后调用 # bboxes: [x1, y1, x2, y2] 格式的二维数组 # confs: 每个框的检测置信度 outputs deepsort.update(bboxes, confs, frame) # outputs 中每个元素为 [x1, y1, x2, y2, track_id]max_age是本项目中最值得调整的参数之一它直接影响动作识别的稳定性。max_age太小时短暂遮挡就会导致轨迹删除重新生成的轨迹会拿到新 ID动作识别就必须重新积累帧max_age太大时目标离开画面很久后轨迹仍残留可能与新进入画面的目标错误关联。在广场、地铁站这类遮挡频繁的场景我一般会把max_age设置在 45 到 60 之间。3.3 与检测器之间的关键耦合点置信度传递检测器输出的置信度不只是用于画框的它在 DeepSORT 中同样参与匹配。项目源码里通常会对置信度低的检测框做一个过滤——常见的做法是低于阈值的框直接丢弃或者保留但降低其参与匹配的优先级。我在使用时会以conf 0.4为界低于这个值的框直接不送入跟踪器因为低置信度框大概率是误检或严重遮挡的局部框强行送入反而会污染轨迹的外观特征库。这样做的副作用是漏检变多所以max_age就得相应调大两者需要联动而非单独调优。4. SlowFast 动作识别理解“在做什么”的时间网络有了稳定的跟踪轨迹最后一个核心模块就是 SlowFast。与图像分类不同动作识别必须同时利用空间信息和时间动态。SlowFast 的设计思路非常直接用两条分支分别处理慢速、高分辨率的语义信息和快速、低分辨率的运动信息再通过侧向连接融合。4.1 双路径结构的直观理解与选型理由Slow 路径以较低的帧率采样比如每秒 4 帧通道数较多负责捕捉目标的静态外观和场景语义Fast 路径以较高的帧率采样比如每秒 32 帧通道数较少通常是 Slow 的 1/8负责捕捉幅度较大的快速运动。Fast 路径不处理颜色信息输入是灰度图甚至仅仅是帧差计算量更小。侧向连接把 Fast 路径的运动特征送入 Slow 路径让 Slow 路径感知“这个人动得快还是慢”。这个设计契合了人类视觉系统的双通道假说在 AVA、Kinetics 等数据集上表现非常稳定。在本项目中需要识别的是“走、跑、挥手、蹲下”这一级别的动作SlowFast 的预训练权重已经覆盖常见类别。若想识别自定义动作需要收集视频片段裁剪并微调这一步推荐使用 UCF101 或自定义数据集先热热身——这也是网上“ucf101数据集实战如何用pytorch提升视频动作分类准确率”这类教程的价值所在训练流程与项目内的微调逻辑可以直接复用。4.2 把轨迹盒变成 SlowFast 输入的关键操作跟踪器输出的是每一帧的二维框而 SlowFast 要求输入是多帧的连续视频片段。项目中的基本做法是以当前帧为基准维护一个长度为clip_len的队列队列内存储同一track_id在不同帧对应位置的图像块。队列攒够帧数后组装成一个 5 维张量[B, C, T, H, W]送入模型。这段数据流水线的代码逻辑如下import torch import torchvision.transforms as T # SlowFast 常用的输入预处理 mean [0.45, 0.45, 0.45] std [0.225, 0.225, 0.225] transform T.Compose([ T.ToTensor(), T.Normalize(mean, std) ]) def build_clip_tensor(track_patch_queue): track_patch_queue: deque 存储同一 track_id 最近 N 帧的裁剪图 返回形状为 [1, 3, T, H, W] 的张量 T len(track_patch_queue) # 时间维长度 H track_patch_queue[0].shape[0] # 裁剪图高度 W track_patch_queue[0].shape[1] # 裁剪图宽度 clip torch.zeros(1, 3, T, H, W) for t in range(T): frame track_patch_queue[t] tensor transform(frame) # [3, H, W] clip[0, :, t, :, :] tensor return clip # SlowFast 前向推理 # model 为 SlowFast 网络实例 with torch.no_grad(): # 模型输出维度为 [1, num_classes]对应每帧动作类别 preds model(clip) action_id torch.argmax(preds, dim1).item()这段代码中有几个要点容易出错。裁剪图必须按同一track_id的框坐标从原帧中截取且所有帧的裁剪尺寸要保持一致——通常把检测框缩放为正方形推荐224x224因为 SlowFast 预训练权重输入就是 224。队列长度建议取 32 的倍数SlowFast 的 Backbone 包含时域下采样输入帧数不是 4 的倍数时会报维度错误。如果帧率是 30 FPS32 帧约等于 1 秒的动作片段识别“走路”这类周期性动作完全够用。4.3 SlowFast 关键超参数速查项目配置和模型定义中涉及以下超参数调参时优先关注标粗的部分参数项目中的常见取值作用与调整依据alpha4Fast 路径相对 Slow 路径的帧率倍率调大增强运动感知但增大计算量tau8Slow 路径的采样间隔帧率低时调小以保留足够帧数beta8Fast 路径通道数与 Slow 路径通道数的比值一般固定为 1/8clip_len32输入片段总帧数取决于动作持续时长动作剧烈时可用 16 减少延迟stride2滑动窗口的时间步长步长越小识别越平滑但推理次数增加num_classes80动作类别数与ava_action_list.pbtxt或coco_names.txt对应ava_action_list.pbtxt文件是类别名称与 ID 的映射表当模型预测结果与预期动作对不上时首先检查这个文件中类别的排列顺序是否与训练时一致。这是一个非常隐蔽但后果很严重的问题——不同版本的 AVA 预训练模型类别顺序不完全相同直接替换权重而不同时更新类别文件会导致“跑”被识别成“挥手”之类的荒谬输出。4.4 与检测器级联时的真实性能瓶颈SlowFast 的推理耗时远高于 YOLOv5 检测器这在实时系统中是绕不开的瓶颈。以输入clip_len32、分辨率224x224为例在 RTX 3060 上单条轨迹的单次推理约为 30 至 60 毫秒。如果画面里有 5 条轨迹而每条轨迹都有独立的滑动窗口那么一帧的总推理时间会膨胀到数百毫秒。项目代码中通常会做两个优化一是仅对confirmed状态的轨迹进行推理二是共享同一个 SlowFast 推理批。若你的场景中并发目标很多建议把多个轨迹的片段拼成一个 batch 一次性前向传播GPU 利用率会显着提升这也是本项目中slowfast_detection.py比yolo_slowfast.py更追求工程化的原因所在。5. 整条管线组装与实测调优技巧把三块技术串起来之后真正决定项目能否稳定跑完一场视频的是帧率匹配、队列管理和验证方法。5.1 帧率适配让三套模型各跑各的节奏YOLOv5 检测器理论上可以做到 30 FPS 实时推理但 SlowFast 由于滑动窗口的存在每个 snippet 内部存在大量重复计算——相邻两个窗口之间有 30 帧重叠时重叠部分被反复前向传播了好几次。最直接的优化是让三套模型不要以相同帧率运行检测器每帧都跑DeepSORT 每帧都更新但 SlowFast 每隔 4 帧才触发一次新片段推理。中间间隔的帧直接沿用前一个窗口的动作预测结果这是因为人类动作在 100 毫秒级的时间尺度上通常不会发生类别跳变。# 帧率解耦伪代码识别器降频运行 frame_count 0 action_results {} while cap.isOpened(): ret, frame cap.read() frame_count 1 # 1. YOLOv5 每帧检测 dets yolo_detect(frame) # 2. DeepSORT 每帧更新轨迹 tracks deepsort.update(dets, frame) # 3. SlowFast 每隔 4 帧才刷新一次动作结果 if frame_count % 4 0: for track_id, track in tracks.items(): snippet assemble_snippet(track.patch_queue) action_results[track_id] slowfast_infer(snippet) # 4. 当前帧直接复用最近一次结果 for track_id, action in action_results.items(): if track_id in tracks: draw_action(frame, tracks[track_id].box, action)代码中frame_count % 4 0就是一个典型的时间分片策略。在 30 FPS 视频中动作结果每 4 帧刷新一次即每 133 毫秒更新一次在人的视觉感知中依然流畅而 SlowFast 的实际推理次数减少到原来的 1/4。若想进一步降低延迟可以把窗口重叠率从 0.5 降到 0.75——也就是频繁触发推理但不建议低于 0.5否则识别结果抖动会非常明显。5.2 用torch.cuda.synchronize()量出真实耗时排查性能瓶颈时直接使用时间戳测量的结果往往不可靠。PyTorch 的 CUDA 算子默认是异步的普通的time.time()测量得到的只是 CPU 提交任务的时间GPU 实际计算还在后面排队。正确的测速方式是在每次前向传播后显式调用同步函数import time # 前面代码忽略这里只展示计时方法 model model.cuda().eval() frame frame.cuda() for _ in range(10): # 预热 GPU 算子 with torch.no_grad(): _ model(frame) torch.cuda.synchronize() t0 time.time() with torch.no_grad(): _ model(frame) torch.cuda.synchronize() t1 time.time() print(f单次推理耗时: {(t1 - t0) * 1000:.2f} ms)注意torch.cuda.synchronize()必须在time.time()之前和之后各调用一次否则统计区间内可能混入 GPU 处理上一帧遗留的任务。若耗时远高于预期优先排查是否误用了 CPU 推理或者 PyTorch 与 CUDA 版本不匹配导致算子回退到 CPU 实现。5.3 验证方法不要只看 GIF 演示项目附带的demo目录里有两张动图作为演示但那是别人跑通的结果自己修改代码后必须构造可重复的验证步骤。先准备一段自己录制的视频画面中包含单个人做两种以上动作例如走几步然后挥手。运行项目后检查三点第一跟踪 ID 在动作切换时是否保持不变第二动作标签的切换是否与画面动作时间点一致第三按q键退出时程序是否能正常释放摄像头或视频资源——很多毕设答辩现场演示出现卡死都是因为最后忘了cap.release()或者 GPU 显存没有释放。把这三项逐条确认后再把自己的视频片段换成 UCF101 里的标准数据集来测试模型对不同尺度、不同光照的鲁棒性数据集中部分类别与预训练权重覆盖的类别高度重合很适合验证阶段做基准比对。本文还有配套的精品资源点击获取
