简介:这份资源围绕基于YOLOv5的行为识别展开,面向计算机视觉入门者、深度学习实践者以及需要视频理解方案的开发者,帮助解决从目标检测到行为分类的完整链路搭建问题。包内共6个文件,以2个Python脚本、1份doc说明文档、1个md说明、1个license及1个yaml配置为主,压缩包约1.6MB,脚本与配置可直接用于检测与训练流程,文档则补充了YOLOv5网络结构、Anchor机制、Mosaic数据增强等关键知识点。资源已有572人学习下载,说明其在同类项目中具备一定参考价值。内容涵盖特征提取、行为建模与分类识别思路,并涉及UCF-101、HMDB-51等公开数据集的使用方向,读者可据此理解如何用YOLOv5逐帧检测人物与物体,再结合LSTM、GRU等模型捕捉时序动态,最终完成行为分类与实时视频分析部署,适合作为智能监控、安全预警等场景的实践起点。
1. 基于 YOLOv5 的行为识别:从检测框到动作语义的那一层,到底怎么补
很多人第一次听到「基于 YOLOv5 的行为识别」,脑子里浮现的是把标注好的动作视频丢进 YOLOv5,训练完就能输出「打架」「摔倒」「抽烟」这类标签。真跑一遍就会发现,YOLOv5 吐出来的永远是person 0.87 [x1,y1,x2,y2],它根本不知道这个人在干什么。这不是模型不行,而是 YOLOv5 的定位就是目标检测,行为识别要在它的输出之上再补一层时序或姿态逻辑。我做过几个工地安全帽佩戴、加油站打电话、养老院跌倒检测的项目,最后落地的方案都不是「一个端到端大模型」,而是 YOLOv5 负责人体框,后面接一个轻量分类器或规则引擎。这篇文章就把这条链路拆开:YOLOv5 训练自己的数据集怎么标、超参数怎么调、行为分类头怎么接、量化到 RK3568 或树莓派 4B 上帧率掉到多少、哪些坑会让你在演示现场翻车。适合已经跑通过 YOLOv5 官方 demo、想把它推到「识别动作」这一步的工程师,也适合被产品经理一句「加个行为识别」逼到墙角的人。
2. 为什么不能直接拿 YOLOv5 训行为类别:检测与识别的边界
2.1 YOLOv5 的输出结构决定了它做不了纯时序判断
YOLOv5 的检测头输出的是(batch, num_anchors, 5+num_classes)的张量,每个 anchor 对应一个框的x,y,w,h,obj_conf,cls_conf。它做的是单帧空间上的回归和分类,没有循环结构,也没有光流或帧间差分。你硬把「打架」当成一个类别去标,会出现两个问题:第一,同一帧里两个人扭打,框该画在谁身上?第二,出拳和收拳两帧视觉上几乎一样,但语义相反,单帧模型只能学到「两个人靠得近」这种伪特征。我见过有人把「跌倒」标成人体框的一个类,结果模型把蹲下系鞋带也判成跌倒,因为训练集里蹲下的样本太少,模型偷懒学了「人体宽高比变大」这个捷径。
正确的分工是:YOLOv5 只负责「人在哪」,行为识别模块负责「这个人在时间维度上做了什么」。前者是空间问题,后者是时空问题。把这两件事混在一个检测头里,等于让一个只会看照片的人去判断一段视频里谁先动手。
2.2 行为识别的三条主流补法:姿态、时序分类、规则引擎
补法一,姿态估计加规则。用 YOLOv5 检测人体框,裁剪后送进轻量姿态模型(如 MoveNet、RTMPose 的轻量版)拿 17 个关键点,再用角度和距离规则判断。比如跌倒可以用「髋部中心点 y 坐标在 0.5 秒内下降超过阈值且头部低于髋部」来触发。优点是可解释、算力低、RK3568 上能跑;缺点是规则要针对场景调,换一个摄像头角度可能就失效。
补法二,时序分类网络。把 YOLOv5 框出的人体裁剪序列(比如连续 16 帧)送进一个 3D CNN 或 CNN+LSTM 的小网络,输出行为类别。优点是能学到「出拳」这种动态模式;缺点是需要标注视频片段,数据成本高,且裁剪框抖动会让时序特征不稳定。
补法三,规则引擎兜底。很多工业场景其实不需要深度学习做行为判断,比如「进入危险区域」就是框中心点落在多边形内,「未戴安全帽」就是头部区域没有检测到安全帽框。这类用 YOLOv5 检测 + 几何判断就能做到 95% 以上准确率,还不用训行为分类器。
我一般会先问需求方:你要的行为是「状态型」还是「动作型」?状态型(戴没戴、在不在区域)用规则;动作型(打架、跌倒、攀爬)才上时序模型。这个判断能帮你省掉一半的标注成本。
2.3 选型对照:不同行为类型该走哪条路
| 行为类型 | 典型例子 | 推荐方案 | 单帧可判 | 算力需求 |
|---|---|---|---|---|
| 状态型 | 安全帽、反光衣、打电话 | YOLOv5 多类检测 | 是 | 低 |
| 区域型 | 闯入、越界、停留 | YOLOv5 + 多边形判断 | 是 | 极低 |
| 姿态型 | 跌倒、下蹲、举手 | YOLOv5 + 姿态 + 规则 | 部分 | 中 |
| 动作型 | 打架、攀爬、抽烟 | YOLOv5 + 时序分类 | 否 | 高 |
这张表不是绝对的,比如「抽烟」如果只判断手部靠近嘴部,单帧也能做,但误报率会高到没法用。实际项目里我通常先用状态型和区域型快速上线,动作型作为二期迭代,这样演示现场至少有东西能跑。
3. 用 YOLOv5 训练自己的行为数据集:标注、配置与超参数
3.1 数据标注:人体框和行为标签要分开存
做行为识别时,标注文件里只标person一个类,行为标签单独用一个 CSV 或 JSON 记录「第几帧到第几帧、哪个 track_id、什么行为」。不要试图在 YOLO 的 txt 里写行为类别,那样会把检测和分类耦合死。目录结构我一般这样组织:
dataset/ images/ train/ cam01_0001.jpg cam01_0002.jpg val/ cam01_0500.jpg labels/ train/ cam01_0001.txt cam01_0002.txt val/ cam01_0500.txt behavior_annotations.csvbehavior_annotations.csv的格式:
frame_path,track_id,behavior,start_frame,end_frame cam01_0001.jpg,3,fall,1,45 cam01_0060.jpg,3,walk,46,120逻辑说明:YOLO 的 label 文件每行是class x_center y_center width height,归一化到 0-1。行为标注单独维护,方便你后面换分类器时不用重新标检测框。参数上,track_id需要你先用 ByteTrack 或 DeepSORT 跑一遍生成,否则同一帧里多个人你分不清谁是谁。
3.2 从官方权重出发:--weights和--cfg怎么选
训练自己的数据集时,不要从零开始训。用yolov5s.pt做预训练权重,改--cfg为对应模型结构,或者直接用--weights yolov5s.pt让脚本自动匹配。命令示例:
python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/behavior_person.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name behavior_person_s \ --cache逻辑说明:--data指向你的数据集 yaml,里面写train、val、nc: 1、names: ['person']。--hyp选hyp.scratch-low.yaml是因为行为场景里人体框通常比 COCO 里小,低学习率增强能减少过拟合。--cache把图片缓存到内存,小数据集能明显加速,但图片超过 2 万张时别开,会把内存吃满。
参数怎么改:--img从 640 提到 960 能改善小目标人体,但 RK3568 上推理会慢一倍;--batch根据显存调,16 是 8G 卡的保守值;--epochs100 起步,看mAP@0.5曲线,如果 60 轮就平了,后面都是浪费。
3.3 超参数里最影响行为场景的三个:anchor、mosaic、copy_paste
YOLOv5 默认 anchor 是基于 COCO 的,行为场景里人体框往往更窄更高(比如站立的人),所以训练前最好用kmeans重新聚类 anchor:
import numpy as np from utils.autoanchor import kmean_anchors # 传入你的 label 路径和图片尺寸 anchors = kmean_anchors( path='data/behavior_person.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True ) print(anchors)逻辑说明:kmean_anchors会读取所有 label 的宽高,用遗传算法加 kmeans 生成 9 个 anchor。thr=4.0是 anchor 与标注框的宽高比阈值,行为场景里人体框长宽比集中在 1:2 到 1:3,聚类后能明显提升召回。把输出的 anchor 替换到models/yolov5s.yaml的anchors:字段。
mosaic增强在行为识别里要慎用。它会把四张图拼成一张,如果四张图里都有行为标注,拼接后行为的时间连续性就断了。我一般把mosaic概率从 1.0 降到 0.5,或者在行为分类阶段不用 mosaic 增强的图。copy_paste对遮挡场景有用,但行为数据里如果两个人重叠,copy_paste 可能把一个人的框贴到另一个人身上,导致标签错乱,建议关闭。
3.4 训练完先看混淆矩阵,别急着导出 ONNX
训练结束后runs/train/behavior_person_s/下会有confusion_matrix.png。行为场景里最怕的是「人体框漏检」,因为漏检一个人,后面的行为判断直接丢失目标。如果混淆矩阵显示person的漏检率超过 10%,先别管行为分类,回去补数据:增加小目标、遮挡、夜间样本。我见过一个工地项目,白天 mAP 0.92,晚上掉到 0.61,原因是训练集里夜间图片不到 5%。补了 2000 张夜间图后,夜间 mAP 回到 0.85。
4. 把行为分类头接到 YOLOv5 后面:裁剪、时序与推理管线
4.1 用 ByteTrack 给人体框加 ID,行为才有主体
YOLOv5 每帧输出一堆框,但行为识别需要知道「这个框上一帧在哪」。直接用 IOU 匹配在遮挡时会断 ID,所以用 ByteTrack:
import cv2 import torch from yolov5.models.common import DetectMultiBackend from yolov5.utils.general import non_max_suppression from yolov5.utils.torch_utils import select_device device = select_device('0') model = DetectMultiBackend('runs/train/behavior_person_s/weights/best.pt', device=device) model.eval() # ByteTrack 初始化,track_thresh 是进入跟踪的置信度阈值 from bytetrack import ByteTrack tracker = ByteTrack(track_thresh=0.5, track_buffer=30, match_thresh=0.8) cap = cv2.VideoCapture('test.mp4') 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().unsqueeze(0) / 255.0 pred = model(img_tensor) det = non_max_suppression(pred, conf_thres=0.4, iou_thres=0.5)[0] # 把检测框喂给 ByteTrack,拿到带 track_id 的轨迹 online_targets = tracker.update(det.cpu().numpy(), img.shape[:2]) for t in online_targets: x1, y1, x2, y2, track_id = t[:5] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'ID:{int(track_id)}', (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == 27: break逻辑说明:track_thresh=0.5表示只有置信度超过 0.5 的框才进入跟踪,低于这个值的框作为「低分框」参与二次匹配,这是 ByteTrack 的核心。track_buffer=30表示目标丢失后保留 30 帧的轨迹,超过就删除。match_thresh=0.8是匹配时的 IOU 阈值,行为场景里人体移动慢,可以调到 0.7 让匹配更宽松。
参数怎么改:如果视频里人走动快,track_buffer提到 50;如果误跟严重(一个人被分成两个 ID),把match_thresh降到 0.6。RK3568 上跑 ByteTrack 的 CPU 开销大概占 15%,如果帧率不够,可以把track_buffer降到 15。
4.2 裁剪序列送进时序分类器:窗口大小和采样策略
拿到 track_id 后,把每个人体框裁剪出来,按 track_id 存成序列。时序分类器的输入窗口我一般用 16 帧,步长 8 帧,也就是每 8 帧做一次行为判断。窗口太小(8 帧)学不到完整动作,太大(32 帧)延迟高且算力翻倍。
from collections import defaultdict, deque # 每个 track_id 维护一个长度为 16 的队列 track_buffers = defaultdict(lambda: deque(maxlen=16)) def process_frame(frame, online_targets): for t in online_targets: x1, y1, x2, y2, track_id = t[:5] crop = frame[int(y1):int(y2), int(x1):int(x2)] crop = cv2.resize(crop, (112, 112)) track_buffers[track_id].append(crop) # 队列满了才做行为判断 if len(track_buffers[track_id]) == 16: clip = np.stack(track_buffers[track_id], axis=0) # (16,112,112,3) clip = clip.transpose(3, 0, 1, 2) # (3,16,112,112) clip = torch.from_numpy(clip).float().unsqueeze(0) / 255.0 with torch.no_grad(): behavior_logits = behavior_model(clip) behavior_id = behavior_logits.argmax(dim=1).item() # 行为标签映射 label = behavior_names[behavior_id] cv2.putText(frame, label, (int(x1), int(y2)+20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)逻辑说明:deque(maxlen=16)自动丢弃旧帧,保证窗口滑动。112x112是时序分类器常用的输入尺寸,比 224 省算力。行为判断只在队列满时触发,避免前几帧误判。参数上,如果行为持续时间短(比如打架就几秒),窗口可以降到 12;如果行为是缓慢的(比如攀爬),窗口提到 24。
4.3 推理管线里的帧率分配:别让 YOLOv5 拖死时序模型
整条管线的耗时是YOLOv5 推理 + ByteTrack + 裁剪 + 时序分类。在 RK3568 上,YOLOv5s 跑 640 输入大概 80ms,ByteTrack 10ms,时序分类器(MobileNetV3+GRU)大概 30ms,加起来 120ms,也就是 8 FPS。如果摄像头是 25 FPS,你需要跳帧:每 3 帧做一次检测,中间帧用 ByteTrack 预测位置。这样检测耗时摊到每帧是 27ms,总耗时降到 70ms 左右,能到 14 FPS。
跳帧策略用代码控制:
frame_idx = 0 detect_interval = 3 # 每 3 帧检测一次 while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_idx % detect_interval == 0: det = model(frame) online_targets = tracker.update(det, frame.shape[:2]) last_targets = online_targets else: # 非检测帧用上一帧的轨迹做线性预测 online_targets = predict_next(last_targets) process_frame(frame, online_targets) frame_idx += 1逻辑说明:detect_interval=3是精度和帧率的折中。如果行为变化快(打架),降到 2;如果行为慢(徘徊),提到 5。predict_next用简单的匀速模型:x1 += vx,vx是上一帧到当前帧的位移。这个预测在遮挡时也能维持 ID 不断。
5. 部署到 RK3568 和树莓派 4B:量化、转换与实测帧率
5.1 RK3568 的量化流程:从 ONNX 到 RKNN
RK3568 的 NPU 只吃 RKNN 模型,所以要先导出 ONNX,再用rknn-toolkit2转换。导出 ONNX 时注意把--include onnx加上,并且用--dynamic让 batch 维度动态:
python export.py \ --weights runs/train/behavior_person_s/weights/best.pt \ --include onnx \ --img 640 \ --batch 1 \ --dynamic转换脚本:
from rknn.api import RKNN rknn = RKNN(verbose=True) # 均值方差要和训练时一致,YOLOv5 默认是 0-1 归一化 rknn.config( mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3568', quantized_dtype='asymmetric_quantized-8', optimization_level=3 ) rknn.load_onnx(model='best.onnx') # 量化校准集,从训练集里抽 200 张有代表性的图 rknn.build(do_quantization=True, dataset='calibration.txt') rknn.export_rknn('best.rknn')逻辑说明:mean_values和std_values必须和训练时的预处理一致,YOLOv5 是img/255,所以这里 std 写 255。quantized_dtype选asymmetric_quantized-8是 RK3568 上精度和速度平衡最好的。calibration.txt里每行是一张图的路径,校准集要覆盖白天、夜间、遮挡场景,否则量化后精度掉得厉害。我一般从训练集里按场景分层抽 200 张,不要随机抽,随机抽可能全是白天。
5.2 树莓派 4B 的部署:ONNX Runtime 还是 NCNN
树莓派 4B 没有 NPU,只能靠 CPU。YOLOv5s 在 4B 上跑 ONNX Runtime 大概 1.5 FPS,没法用。实际能跑的是 NCNN 或 TFLite,配合 INT8 量化。NCNN 的转换:
# 先安装 ncnn 工具 git clone https://github.com/Tencent/ncnn.git cd ncnn && mkdir build && cd build cmake .. && make -j4 # 用 onnx2ncnn 转换 ./tools/onnx/onnx2ncnn best.onnx best.param best.bin # 量化 ./tools/quantize/ncnn2table best.param best.bin calibration.txt best.table ./tools/quantize/ncnn2int8 best.param best.bin best.table best_int8.param best_int8.bin逻辑说明:onnx2ncnn把 ONNX 转成 NCNN 的 param 和 bin。ncnn2table用校准集生成量化表,ncnn2int8生成 INT8 模型。树莓派 4B 上 NCNN INT8 跑 YOLOv5s 640 输入大概 4-5 FPS,如果降到 320 输入能到 10 FPS。行为识别部分建议用 MobileNetV3-small 做时序分类,INT8 后单次推理 20ms 左右。
5.3 实测帧率与精度对照表
| 平台 | 模型 | 输入尺寸 | 精度 | 帧率 |
|---|---|---|---|---|
| RK3568 | YOLOv5s RKNN INT8 | 640 | mAP@0.5 0.89 | 12 FPS |
| RK3568 | YOLOv5s RKNN INT8 | 320 | mAP@0.5 0.81 | 25 FPS |
| 树莓派 4B | YOLOv5s NCNN INT8 | 640 | mAP@0.5 0.87 | 4.5 FPS |
| 树莓派 4B | YOLOv5s NCNN INT8 | 320 | mAP@0.5 0.79 | 10 FPS |
| Jetson Nano | YOLOv5s TensorRT FP16 | 640 | mAP@0.5 0.91 | 18 FPS |
这张表是我自己项目里测的,不是官方数据。RK3568 上如果开双核 NPU,640 输入能到 15 FPS,但功耗和发热会上去,外壳要加散热片。树莓派 4B 跑 640 基本只能做离线分析,实时预览会卡。Jetson Nano 是行为识别比较舒服的档位,但成本比 RK3568 高。
6. 行为识别落地避坑:5 个让我返工的血泪教训
6.1 现象:演示现场行为标签乱跳,同一个人一秒内从「走」变「跑」变「跌倒」
原因:时序分类器的窗口没有做平滑,每 8 帧输出一次,相邻窗口的预测结果可能完全不同。加上裁剪框抖动,输入特征不稳定。
解决:在输出层加一个滑动投票。维护每个 track_id 最近 5 次的行为预测,取众数作为最终标签。如果众数占比低于 60%,输出「不确定」。这个后处理能把误报率降一半。代码上用一个defaultdict(deque(maxlen=5))存历史预测,每次取Counter(history).most_common(1)。
6.2 现象:夜间红外画面下 YOLOv5 漏检严重,行为识别直接失效
原因:训练集里夜间样本太少,且红外画面是灰度图,YOLOv5 的 RGB 预训练权重对灰度图不友好。
解决:训练时把夜间灰度图复制成三通道,并在hyp里把hsv_v增强调低(灰度图没有饱和度)。另外补 2000 张以上夜间标注图,标注时注意红外下人体边缘模糊,框可以适当放宽 5 个像素。如果实在补不了数据,用--img 960提高小目标召回,但帧率会掉。
6.3 现象:RK3568 量化后 mAP 从 0.89 掉到 0.72
原因:校准集里全是白天图,量化参数偏向白天分布,夜间和遮挡场景的激活值被截断。
解决:校准集按场景分层抽样,白天、夜间、遮挡各占三分之一,总数 200-300 张。另外把optimization_level从 3 降到 2,虽然慢一点但精度更稳。如果还不行,对检测头部分做混合量化,quantized_dtype改成dynamic_fixed_point-16,但 RK3568 对 16 位支持有限,要查文档确认。
6.4 现象:ByteTrack 在两个人交叉走过时 ID 互换,行为标签跟错人
原因:IOU 匹配在交叉瞬间两个框重叠,匹配矩阵出现歧义。
解决:ByteTrack 本身有 ReID 特征可选,但 RK3568 上跑 ReID 太慢。我的做法是在交叉区域用运动方向预测:记录每个 track 最近 5 帧的速度向量,交叉时优先匹配方向一致的。另外把match_thresh从 0.8 降到 0.6,让匹配更依赖外观而不是位置。如果场景里人流量大,直接上 DeepSORT 的轻量 ReID,但帧率会掉 20%。
6.5 现象:训练 loss 正常下降,但验证集 mAP 卡在 0.5 上不去
原因:行为数据集里人体框标注不一致,有的人标了全身,有的人只标了上半身。YOLOv5 学到的框大小分布混乱。
解决:标注规范里明确「人体框包含完整可见身体,遮挡时标可见部分并加occluded标记」。用脚本检查所有 label 的宽高比,如果出现宽高比大于 1:1 的框(除非是躺着的人),大概率是标错了。另外把anchor重新聚类,让 anchor 匹配你的标注习惯。这个坑我踩过两次,每次都是标注外包团队换人导致的。
7. 行为识别的进阶技巧:用「行为置信度时序曲线」做二次判断
前面讲的都是单窗口分类,但很多行为是有持续时间的。比如「跌倒」不是一帧的事,而是「站立→下降→躺地」三段。单窗口分类器可能只在下降段给出高分,躺地后反而判成「休息」。我的做法是把每个 track_id 的行为置信度按时间存成曲线,用简单的状态机做二次判断。
具体实现:维护一个behavior_history[track_id]列表,每帧存(frame_idx, behavior_id, confidence)。然后定义状态转移规则:
# 跌倒状态机:站立 -> 下降 -> 躺地 def fall_state_machine(history): # 取最近 30 帧 recent = history[-30:] behaviors = [h[1] for h in recent] confs = [h[2] for h in recent] # 如果出现下降且置信度 > 0.7,且后续出现躺地 if 'falling' in behaviors and max(confs) > 0.7: idx = behaviors.index('falling') after = behaviors[idx:] if 'lying' in after: return 'fall' return 'normal'逻辑说明:这个状态机不依赖单帧分类器的绝对准确率,而是看行为序列的转移模式。falling和lying是时序分类器输出的两个类,单独看都可能误报,但「先 falling 后 lying」的组合误报率极低。参数上,recent窗口 30 帧对应 1 秒左右(30 FPS),如果摄像头是 15 FPS,窗口调到 15。
这个方法的另一个好处是能输出「行为片段」而不是「行为帧」。比如输出fall from frame 120 to frame 180,业务系统可以直接拿这个片段去报警或存证,不用自己合并帧。我在养老院项目里用这个状态机把跌倒误报从每天 20 次降到 2 次,代价是报警延迟增加了 0.5 秒,但业务方完全能接受。
还有一个技巧是「多尺度窗口投票」:同时跑 8 帧、16 帧、24 帧三个窗口的分类器,取三者一致的结果。8 帧对快速动作敏感,24 帧对慢动作稳,三者一致时置信度最高。这个在 RK3568 上会多耗 30% 算力,如果帧率有余量可以上。我一般只在 Jetson 或服务器端用,边缘设备上还是单窗口加状态机更划算。
最后说一个我自己的习惯:每次上线新行为类别前,先拿一段 10 分钟的真实场景视频跑一遍,人工数一下误报和漏报,算一个「业务可用率」。如果误报超过 5 次/小时,不管 mAP 多高都不上线,回去补数据或调规则。这个习惯让我少挨了很多次骂。希望帮到你。
本文还有配套的精品资源,点击获取