YOLOv5行为识别实战:从动作基元到边缘部署
简介基于YOLOv5的行为识别资源包面向计算机视觉学习者与开发者旨在解决视频连续帧中人体目标检测与行为分类问题。资源结合YOLOv5单阶段检测模型的高速度与高精度特性介绍Anchor机制、Mosaic数据增强等关键点并给出基于LSTM/GRU对运动轨迹建模的识别思路适合用于智能监控、安防预警等实时场景。包内共6个文件包含train.py与detect.py训练检测脚本、data.yaml数据配置、项目说明Markdown文档以及详细技术解析Word文档压缩包仅1.6MB内容紧凑。资源从特征提取、行为建模到分类识别梳理完整流程项目文件结构简洁便于直接对照代码理解行为识别流程并二次开发。目前已有572人学习/下载适合作为入门YOLOv5行为识别的快速实践参考。1. 行为识别不必等姿态估计YOLOv5 直接干活的适用边界把一个摄像头对准工位、路口或仓库一角目标不是“检出人和物”而是判断“这个人正在做什么”——这是行为识别最朴素的定义。YOLOv5 作为一阶段目标检测器本身没有时序建模能力却被大量这类项目选为行为识别的主干模型不是因为 YOLO 进化出了视频理解头而是因为从业者把“行为”重新拆解成了“可被检测的中间状态”。这套思路在固定机位、目标尺度相对稳定、行为类别明确可控的场景里比引入 SlowFast、3D CNN 甚至姿态估计更轻、更快、更容易落地。本文讲的是这套被验证过多次的工程路线用 YOLOv5 训练“动作基元类别”在推理端把单帧检测结果组合成带状态转移的行为判断。适合的是有内置摄像头、算力有限、又需要实时告警或统计的工业与安防场景。读完你能明白为什么检测模型敢碰行为识别、数据标注该怎么设计、那组超参数怎么调以及在树莓派 5 这类边缘设备上部署时真正卡脖子的是什么。2. 行为识别为什么能用 YOLOv5从网络结构到任务重定义2.1 YOLOv5 的网络结构与“只有空间信息”这个前提YOLOv5 的主干是 CSPDarknet核心是 C3 模块它将特征图沿通道分成两路一路经过 Bottleneck 堆叠另一路直连最后拼接。这种结构在保持梯度丰富度的同时把计算量压了下来Neck 部分用了 PANet 结构把高层语义特征和浅层空间细节反复融合所以小目标检测能力稳定Head 是解耦程度不高的多尺度输出分别对应 80×80、40×40、20×20 的特征图匹配大、中、小三类目标。但这一切都是针对单帧图像的。YOLOv5 没有 LSTM没有注意力时序模块也没有光流输入它输出的是一个 tensor每个预测框包含x, y, w, h, conf, cls。这意味着直接用 YOLOv5“识别行为”在结构上不成立除非我们把行为识别重新定义成“对行为关键状态的单帧检测 跨帧逻辑判断”。这也是本文所有方案的前提YOLOv5 负责空间时间维度由工程逻辑补齐。2.2 行为拆解成动作基元比姿态估计更稳的降维方案常见误区是行为识别必须先做姿态估计把骨骼关键点喂给 LSTM 或 ST-GCN。这个路线学术上漂亮但在真实项目里代价很高。姿态估计对遮挡敏感标注成本按关键点计费且行为类别稍有变化就要重训整套流程。我一般会建议客户先回答一个问题这个行为能不能拆成“人出现在区域 A 手里有物体 X 持续超过 T 秒”能拆就用 YOLOv5 做“动作基元检测”。比如识别“打电话”不建模“手举到耳边”这个动作而是检测“手机”这个物体与“人”的紧密邻接关系识别“跌倒”可以检测“人躺倒”与“人站立”两个状态框的宽高比突变识别“离岗”检测“工位上没人”和“人出现在休息区”两个区域事件。每个动作基元都是一个独立的检测类别行为则由这些基元的时序组合来定义。这样的好处有三个标注是框级别而非关键点级别单帧检测的 AP 只要够用行为判断的准确率就基本可控推理链路里 YOLOv5 只跑一遍后续是纯逻辑操作。代价是行为拆解需要人工设计且不适合开放域行为——比如“聚会氛围”“打架前兆”这类模糊概念不适合用这套方案硬做。2.3 类别设计与正负样本YOLOv5 训练自己的数据集前最该想清楚的事行为识别的质量在标注阶段就已经锁定了。如果你把“接电话”拆成“人 手机”那么 YOLOv5 的类别里就只需要person和phone不需要calling。反过来如果你想要直接检测“接电话”这个整体语义那标注框必须画在“正在接电话的人”身上但“正在”这个时态怎么在静止帧里定义两个标注员可能标出完全不同的框。我建议的类别设计表如下行为目标动作基元检测类别逻辑判断条件打电话person, phonephone 框中心在 person 框上半部且重叠帧连续 3 秒跌倒person_down, person_standingperson_down 连续出现且其宽高比 1离岗person, workstationworkstation 区域内无 person 框超过 T 秒闯入person, restricted_zoneperson 框中心进入 restricted_zone 多边形注意一个细节person_down和person_standing不是两个“行为”而是两个“状态”。YOLOv5 负责区分状态逻辑层负责判断状态转移。负样本方面每个动作基元类别至少要收集“不像但容易混淆”的样本比如扫把拖把当作 person_down抱枕当作 phone否则训练出来的模型会在真实场景里高置信度误报。3. 真刀真枪训练YOLOv5 环境配置到行为识别专属调参3.1 本地环境与项目结构conda 创建虚拟环境是第一步YOLOv5 的环境配置是老生常谈但仍然是最多人卡住的第一关。推荐用 conda 建独立环境避免把系统 Python 搅乱。官方代码库拉下来后目录里最关键的是data/、models/、train.py、detect.py。行为识别项目需要在data/下新建自己的 yaml 文件指向数据集路径和类别列表。conda create -n yolo5 python3.9 -y conda activate yolo5 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt mkdir -p datasets/behavior/images/train datasets/behavior/images/val datasets/behavior/labels/train datasets/behavior/labels/valrequirements.txt里核心依赖是 torch、torchvision 和 opencv-python。如果你的机器是较新的 NVIDIA 显卡注意安装对应 CUDA 版本的 torch不要在 conda 里默认装 CPU 版。数据集目录按images和labels分开放YOLOv5 的 dataloader 会自动按前缀匹配同名文件。3.2 标注格式与数据集划分从 labelimg 到 YOLO txt 的完整链路行为识别的标注比通用目标检测更容易犯“画大框”的毛病。比如标注phone类别时很多人把整个手部画进去导致模型学到的是“手”而不是“手机”。正确做法是框紧贴物体边缘。标注工具用 labelimg 或 labelme 都行但必须导出 YOLO 格式也就是每个 txt 文件里一行一个目标的class_id x_center y_center width height坐标值是相对图像宽高的归一化浮点数。一个常见的数据集划分比例是 8:1:1但行为识别建议 val 集不要低于 10%。因为行为识别关注的是状态之间的混淆如果 val 集太小confusion matrix 上看不出person_down和person_standing之间的真实错误率。划分时可以写个脚本按目录打散./yolov5/data/scripts 里没有现成脚本的话自己用 shuf 命令 find datasets/behavior/images -name *.jpg | shuf | head -200 val_list.txt find datasets/behavior/images -name *.jpg | shuf | tail -1600 train_list.txtYOLOv5 的 train.py 接受的是数据集目录结构而非文件列表上面的命令只是说明随机化的思路。更规范的做法是用 sklearn 的train_test_split写一个 Python 脚本把同摄像头的连续帧全部划入同一侧防止数据泄漏——这在行为识别里尤其关键因为相邻帧几乎一模一样如果不按视频切片而是按帧随机划分val 精度会虚高到骗过自己。3.3 超参数选择yolov5s 起步重点盯住 mosaic、lr0 和 batch-size 三件套行为识别任务里目标往往不大比如远处的手机或趴下的整人所以模型尺寸我建议从yolov5s起步而不是一上来就yolov5l。参数量越大对小数据集越容易过拟合行为识别数据集通常只有几千到几万张不如把精力放在数据质量和超参上。以训练“打电话 跌倒”这类双基元模型为例命令行长这样python train.py --data behavior.yaml --weights yolov5s.pt --img 640 \ --batch 16 --epochs 100 --cache ram --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/behavior --name exp1参数说明--img 640YOLOv5 默认训练尺寸是 640不要为了“看得更清”盲目改成 1280计算量翻 4 倍推理速度也翻 4 倍。行为识别要的是实时不是竞赛刷分。--batch 16如果显存不够优先降 batch 而不是降分辨率。batch 太小会导致 BN 层统计不稳定特别是类别少、正样本稀疏的行为识别场景。--hyp hyp.scratch-low.yaml官方默认低增强策略加了较多正则适合中小数据集。如果你数据集超过 5 万张可以换成hyp.scratch-high.yaml里的增强强度。--cache ram把图像缓存进内存省去每轮 epoch 的磁盘 IO。这在行为识别的大批量连续帧上有肉眼可见的速度提升。epochs 100 只是及格线关键看 val 集的 mAP 曲线是否在最后 20 个 epoch 还在上升。如果 50 轮就收敛了说明任务太简单如果 100 轮还在缓慢上升说明数据多样性不够不是训练时间不够的问题。3.4 训练日志与模型选型从 results.png 里读出行为识别特有的隐患每次训练结束runs/behavior/exp1目录下会生成results.png里面有train/obj_loss、val/obj_loss、mAP_0.5等曲线。对于行为识别我重点看两个地方。第一是val/cls_loss是否持续抖动。如果抖动幅度大说明视频帧的连续性导致 val 集和 train 集分布错位需要回头检查数据划分是否真的按视频切片隔开了。第二是mAP_0.5:0.95和mAP_0.5的差值。行为识别的动作基元类别通常外观差异大手机 vs 人所以 mAP_0.5 高很容易但边界框的定位精度影响后续的逻辑判断。比如 phone 的框偏了半个身位“phone 中心是否在人框上半部”这个逻辑就会误判。如果0.95指标低于0.5指标 20 个点以上说明标注框的一致性差需要回头修标注而非继续调参。选模型时我的经验是行为识别项目优先跑yolov5s作为基线然后在边缘设备上测帧率。如果树莓派 5 跑 NCNN 的 yolov5s 只能到 8 FPS 而业务要求 15 FPS就换yolov5n不要轻易上 TensorRT 以外的优化手段——因为行为识别的逻辑层还需要额外算力做目标跟踪和状态机。4. 从检测到行为在线行为识别的推理链路与状态机设计4.1 单帧检测只是前菜目标跟踪是行为识别的隐形骨架很多人跑通 YOLOv5 的 detect.py 就以为行为识别开始了结果发现同一目标在帧间被反复改变 ID行为逻辑完全没法稳定判断。YOLOv5 本身没有跟踪能力每一帧的输出都是独立的因此必须外挂一个多目标跟踪模块。常见做法是用 ByteTrack 或 DeepSORT前者轻量且适合密集场景后者在遮挡严重的场景更稳定。有了跟踪 ID行为识别才能从“这帧有 phone”升级为“ID 3 这个人手持 phone 已经 2 秒”。如果跳过多目标跟踪直接做逐帧检测你就得处理“第 15 帧检测到 phone第 17 帧丢失第 19 帧又出现”这种碎片逻辑状态机压根无从谈起。信任度逻辑要放在跟踪器输出的轨迹上而不是原始检测框上。4.2 状态机驱动行为判别把 YOLOv5 输出变成长时行为判断行为识别最实用的工程范式是有限状态机。把每个动作基元类别对应到状态定义合法的状态转移边不满足转移条件就维持当前状态。这比用规则堆 if-else 要健壮得多也比直接训练一个时序分类器更容易解释和调试。以“离岗检测”为例状态机定义如下class WorkstationStateMachine: def __init__(self, max_absent_seconds60): self.absent_count 0 self.max_absent max_absent_seconds * 25 # 假设 25 FPS self.state occupied def update(self, detections, workstation_box): person_in_zone False for *xyxy, conf, cls in detections: if int(cls) 0: # person 类别 if is_inside(xyxy, workstation_box): person_in_zone True break if person_in_zone: self.absent_count 0 if self.state absent: self.state occupied else: self.absent_count 1 if self.absent_count self.max_absent: self.state absent return self.state注意代码里max_absent_seconds * 25是帧数阈值直接写秒数容易忽视帧率的波动。更稳的做法是用时间戳做差值而不是累加帧计数否则摄像头掉帧会导致“离岗时间被拉长”。状态机的好处是行为切换只能走有限的路径你不可能一个逻辑分支在一个 tick 内同时输出“在场”和“离岗”天然抑制了抖动。4.3 行为识别的在线推理脚本从摄像头取帧到输出行为事件完整的在线行为识别流程是读取视频流 → YOLOv5 推理 → 跟踪器更新 → 状态机判断 → 事件回调。下面是一段核心骨架代码容易直接改到自己的项目里。import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression model attempt_load(runs/behavior/exp1/weights/best.pt, map_locationcpu) model.eval() cap cv2.VideoCapture(0) # 改为 RTSP 地址即可接网络摄像头 while cap.isOpened(): ret, frame cap.read() if not ret: break img cv2.resize(frame, (640, 640)) img_tensor torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 img_tensor img_tensor.unsqueeze(0) with torch.no_grad(): pred model(img_tensor)[0] det non_max_suppression(pred, conf_thres0.4, iou_thres0.45) # det 里每个框交给跟踪器和状态机处理…YOLOv5 的官方 detect.py 已经把预处理封装好了实际项目中建议直接调用DetectMultiBackend和non_max_suppression这些工具函数而不是自己手动 preprocess。这里贴手动写法是为了说明流程的每一步。conf_thres在行为识别场景不要设置太低0.4 起步比较合理因为动作基元的误报代价远高于漏检——漏检最多让状态转移慢几帧误报会让离岗告警直接失效。5. 树莓派 5 上的模型部署自己训练的模型要过 NCNN 这一关5.1 为什么是 NCNN 而不是 TensorRT边缘设备的行为识别选型逻辑树莓派 5 的 CPU 是博通的四核 A76没有独立的 NVIDIA GPU所以 TensorRT 直接排除。常见的部署路径是 NCNN。NCNN 是腾讯开源的神经网络前向推理框架对 ARM 架构做了针对性优化支持 fp16 和 int8 量化。树莓派 5 上跑 YOLOv5s 的 NCNN 模型实测能到 10~15 FPS配合状态机的逻辑判断足够覆盖园区门岗、仓库工位一类不要求极高速率的场景。优点很明确NCNN 的模型转换链路成熟yolov5s.pt → ONNX → NCNN三步走社区踩坑记录多遇到问题能搜到解法。相比起来OpenVINO 在树莓派上不是首选因为 OpenVINO 主要优化 Intel 平台ARM 上性能优势不明显。5.2 从 PyTorch 权重到树莓派可执行文件完整转换流程与参数设置在 PC 上完成转换然后把模型文件拷贝到树莓派 5 上运行。第一步是导出 ONNX这里有几个关键参数要设置python export.py --weights runs/behavior/exp1/weights/best.pt --img 640 --batch 1 --include onnx导出时注意--opset默认值是 12NCNN 对 opset 11 和 12 支持最稳如果遇到不支持的算子回退到--opset 11再试。--dynamic参数不要开NCNN 部署时输入尺寸必须固定否则推理图优化无法生效。拿到 ONNX 后用 NCNN 的转换工具链onnx2ncnn best.onnx best.param best.bin这个命令来自 ncnn 仓库的 build/tools/onnx 目录需要先编译 ncnn。转换完成后建议立即用ncnnoptimize做一次网络结构优化去掉没用的分支ncnnoptimize best.param best.bin best_opt.param best_opt.bin 0最后一个数字0代表 fp321代表 fp16。树莓派 5 的 CPU 支持 fp16 加速但不一定收益明显建议先跑 fp32 测基准再对比 fp16。5.3 推理部署时的关键配置线程数、输入格式与后处理差异树莓派 5 上跑 ncnn 的 YOLOv5 推理时线程数是最容易踩的坑。四核 A76 并不代表开 4 个线程最快——实测跑 NCNN 推理时num_threads2往往比 4 更快因为树莓派 5 的内存带宽不足以喂满四个核的并行计算需求线程开多了反而引入调度开销。需要现场实测不要盲信核心数。NCNN 端的前处理也必须改PyTorch 的推理是 BGR 读图后转 RGB、除以 255而 NCNN 的Mat::from_pixels可以指定PIXEL_BGR2RGB省去手工循环拷贝。后处理尤其要小心 NCNN 输出的维度是1 × 25200 × 85这个 25200 对应 3 个尺度的 anchor 总数(80×8040×4020×20)×385 是5 类别数的拼接。很多人在转换后识别结果全乱就是因为后处理把 85 当成了 6单类别项目里容易犯的错。用一份验证过的解码函数不要从零写。我在实际项目里会直接在 PC 上用 ncnn 的 C 接口跑一遍输出和 PyTorch 的结果对比框重合度达标后再交叉编译到树莓派能在部署阶段筛掉绝大多数转换问题。6. 行为识别模型跑不起来时的四个排查点从数据泄漏到后处理尺寸失配行为识别的 YOLOv5 项目最终失败往往不是模型结构问题而是几个隐蔽细节。第一个是数据泄漏前面提到过连续视频帧里第 1 帧和第 5 帧几乎一样如果随机拆分数据集模型相当于“记住了”同一段画面的不同副本val mAP 会异常高一旦换到新摄像头画面就崩。验证方法是把 val 集精度和训练集精度的差距拉出来看如果差不到 2 个点大概率泄漏了要对视频按时间切片分组再拆分。第二个是类别不均衡导致的行为漏报。行为识别里“正常行为”的样本远多于“异常行为”比如 10000 帧里 9900 帧是“工位有人”只有 100 帧是“离岗”。YOLOv5 的class weights选项可以缓解但更推荐的做法是从数据侧解决——裁出离岗帧做复制增强并用 mosaic 增强让模型见过足够的正样本。不要指望 loss 函数自动帮你扭转这种极端不均衡。第三个是后处理尺寸失配。YOLOv5 的输入尺寸是 640×640但摄像头原始分辨率可能是 1920×1080。如果推理前直接 resize 成 640×640检测框坐标回到原图时需要按原始宽高比换算且 resize 会破坏宽高比导致目标畸变。建议用 letterbox 补边而不是简单 resize。NCNN 的输入同理Mat::from_pixels_resize处理不当会让小目标“手机”直接消失。第四个是推理延时的分阶段拆解。行为识别系统卡顿未必是 YOLOv5 推理慢也可能是 OpenCV 的VideoCapture从 RTSP 拉流阻塞了主线程。我在项目里一定是把视频解码、YOLOv5 推理、跟踪状态机分别放进不同线程并用带超时控制的队列连接。判断瓶颈的方法简单粗暴先注释掉推理只测摄像头帧率再注释掉状态机只测跟踪逐层定位。树莓派 5 上如果推理已有 15 FPS 但整体管线只有 8 FPS问题基本在视频解码的线程模型上而不是模型本身。改完线程后行为识别的在线流畅度才能与离线测试的指标对得上这步不做完前面所有训练和转换的功夫都白费。本文还有配套的精品资源点击获取