ARTICLE DETAIL

资讯详情

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

人体动作识别实战:MediaPipe关键点提取与DTW/LSTM时序分类

人体动作识别实战:MediaPipe关键点提取与DTW/LSTM时序分类 简介基于Mediapipe与动态时间规整DTW、长短期记忆网络LSTM的人体动作识别配套资源包面向计算机视觉与动作识别方向的开发者和学习者适用于智能监控、虚拟现实、健身指导等技术实践。资源共139个文件压缩包约11.04MB核心包含120个npy姿态数据文件、8个Python源代码、8个mp4动作演示视频另有h5训练模型、md说明文档等覆盖从人体姿态估计、动作序列匹配到时序分类的完整代码与数据支撑。目前已有1222人学习下载。通过该资源可深入理解Mediapipe关键点提取流程、DTW处理变长序列的匹配思路以及LSTM门控机制在动作特征学习中的作用并可将三个模块串联成一套可运行的识别管线配合演示视频直观验证效果。内容组织清晰适合一边阅读说明一边复现代码npy数据文件便于特征复用h5模型可直接用于推理测试是快速上手人体姿态识别与动作分类组合方案的实用参考资料。 去年有一个动作识别项目找上门需求很具体摄像头拍一段人体动作系统实时判断出这个人是在“挥手”“下蹲”还是“走路”。当时我第一反应就是上mediapipe——Google这套开源方案做人体姿态识别确实太方便了33个骨骼关键点开箱即用不需要自己训练检测网络跑起来还非常快。但姿态识别只是第一步真正难的是怎么把“连续几帧的关键点序列”变成“动作类别”这部分我用动态时间规整算法DTW和LSTM两条路线各实现了一版整个过程踩了不少坑也积累了一些经验。这篇就把这个项目从选型到落地的完整思路写出来重点拆解两种动作识别方案的核心细节和工程实践给正在做同类项目的人一个能直接参考的模板。先把我做的方案放在前面整体流程分三步。第一步用mediapipe Pose模块从视频帧中提取人体33个关键点坐标第二步对关键点序列做归一化和滑窗预处理形成统一长度的特征序列第三步用DTW做模板匹配或者用LSTM训练分类模型完成动作识别。两条路线我都跑通了效果各有优劣。为什么同时做两种方案因为DTW适合小样本场景录几个模板就能跑LSTM则需要带标注的大批量数据但鲁棒性更好。工程上先用DTW验证概念再上LSTM做正式版本这种节奏很务实。1. 项目整体设计与方案选型思路1.1 为什么选mediapipe而不是OpenPose以前做人体姿态识别主流方案是OpenPose。OpenPose精度不错但模型很大在普通CPU上跑起来只有个位数帧率部署到边缘设备基本不现实。mediapipe Pose走的是轻量化路线模型文件才几十MB在CPU上就能跑到接近实时的速度在GPU上更是毫无压力。更重要的是它的API设计很友好几行代码就能拿到一帧图像里所有人体的关键点坐标还自带追踪优化对连续视频帧的处理稳定性比OpenPose更好。实际测试下来mediapipe在光照正常、单人入镜场景下关键点的抖动控制得不错尤其是四肢末端手、脚的识别精度在可接受范围内。当然它也有缺点——在多人遮挡、快速运动、暗光环境下会掉点漏检这些我们在后面“常见问题”部分会专门说。1.2 姿态识别和动作识别是两码事很多刚入门的读者会把“姿态识别”和“动作识别”混为一谈这里必须先分清。人体姿态识别是单帧空间层面的任务它回答的是“这一帧里人的关节在什么位置”而人体动作识别是时间序列层面的任务它回答的是“连续几帧里这个人正在做什么动作”。我的项目里mediapipe负责第一层也就是姿态识别输出每一帧的骨骼关键点DTW和LSTM负责第二层也就是动作识别。两件事分开处理最大的好处是模块解耦——就算以后换掉底层的姿态检测模型上面动作识别的逻辑基本不用动。1.3 整体架构和技术流程整个系统的技术架构按数据流向可分为四个模块数据采集层调用摄像头或读取视频文件逐帧送入mediapipe。关键点提取层mediapipe Pose输出每帧33个关键点的归一化坐标x、y、z加上可见性visibility。特征预处理层提取出关键点后不直接裸用先做坐标系归一化、填补缺失点、按滑动窗口分割成动作片段。动作分类层DTW相似度匹配或LSTM循环神经网络分类输出的类别就是最终的人体动作识别结果。之所以把预处理单独拎出来讲是因为我在这方面吃过亏——直接把原始坐标丢给模型准确率会掉得很明显后面详细展开。2. mediapipe人体姿态识别核心要点2.1 mediapipe Pose到底输出了什么mediapipe Pose的输出是33个关键点每个关键点由x、y、z和visibility四个值组成。x、y是归一化到图像宽高的二维坐标z是以髋部中心为原点、以大概肩宽为尺度的相对深度。visibility代表这个关键点在画面中被检测到的置信度范围从0到1值越低越可能被遮挡。我在实际代码里给pose模块的参数是这样设的import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, # 视频流模式开启相邻帧追踪 model_complexity1, # 0/1/2三档1是精度和速度的平衡点 min_detection_confidence0.5, min_tracking_confidence0.5, ) cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: for idx, lm in enumerate(results.pose_landmarks.landmark): x, y, z lm.x, lm.y, lm.z visibility lm.visibility # 这里按需收集关键点 # 显示或保存结果 cap.release()一个很关键的点mediapipe的输入图像必须是RGB顺序。因为OpenCV的VideoCapture读出来是BGR如果直接丢给pose.process视觉上看起来好像没大问题但对检测精度是有微妙影响的颜色通道错了会让部分关键点置信度下降。这个坑我踩过一次排查了一下午。2.2 关键点坐标归一化与缺失处理拿到33个关键点坐标后不能直接作为动作特征。原因是原始坐标受人体在画面中的位置和距离影响太大——人离摄像头近肩宽坐标的绝对值就大人站在画面左侧或右侧绝对坐标整体偏移也很大。如果不做归一化模型学到的是“人在画面哪个位置、离镜头多远”这类无关信息。我的做法是以骨盆中心左右髋关节点连线的中点作为原点把所有关键点坐标平移到以这个原点为基准的坐标系中再以左右肩关节点距离作为单位长度做缩放。这样即使人从画面左边走到右边、或者靠近镜头又远离镜头特征值是稳定的。关键点缺失也是经常出现的情况尤其是手臂抬起来或者转身时手肘和脚踝容易被身体挡住。候选做法有两种一是直接丢弃该帧这在连续动作中太浪费二是用前后帧的线性插值填补更平滑。我采用的是插值法实测在短暂遮挡2到3帧的情况下对准确率几乎没有负面影响。3. 基于DTW的人体动作识别实现3.1 DTW核心原理两条时间序列怎么对齐DTW全称Dynamic Time Warping最早是语音识别领域用来匹配不同语速下同一段话的算法核心解决的是“两个序列长度不一样、速度快慢不一样怎么算相似度”的问题。举个例子。一个人第一遍挥手用时3秒第二遍挥手用时2秒如果直接逐帧算欧氏距离两次挥手的相似度会很低但实际这是同一个动作。DTW的思路就是允许时间轴弯曲对齐——把其中一个序列的一部分拉伸去匹配另一个序列的对应位置从而找到两条序列整体上最相似的对齐路径。具体计算方法是动态规划。用seq1和seq2分别表示模板序列和待识别序列构建一个m乘n的距离矩阵其中矩阵第i行第j列的元素是seq1第i帧和seq2第j帧特征向量的欧氏距离。然后从矩阵左上角累加最小路径代价到右下角最终累加值就是两条序列的DTW距离。路径必须满足三个约束边界约束起点和终点固定、连续性约束只能相邻匹配、单调性约束不能回头匹配。3.2 模板选择与动作切分DTW这个方案本质上就是模板匹配模板库的质量直接决定识别效果。以“挥手”为例我录了10次不同速度、不同幅度的挥手每一次都截取出一段长度为几十到上百帧的关键点序列。模板不能直接用第一条录像因为个体动作细节差异太大。更好的做法是对10次序列做帧数重采样统一长度后逐点取平均生成一个“平均挥手模板”。动作切分也是一个容易出问题的环节。连续视频流里要知道“动作从哪一帧开始到哪一帧结束”。我用了一个简单但有效的二合一策略先设置关键点位移的滑动窗口计算连续30帧内手腕关节的平均位移量当位移超过阈值时判断为动作开始当位移连续低于阈值超过一定帧数判断为动作结束。这套逻辑在周期性和突发性动作上都够用比硬分割要稳得多。另外一个重要参数是Sakoe-Chiba带宽限制。不加限制时DTW可能会产生特别疯狂的对齐路径——序列里完全无关的两段被强行匹配上。加上带宽限制只允许在对角线附近一定宽度的区域内搜索既能防止过度扭曲也能大幅减少计算时间。我在项目里选的带宽是序列长度的10%实测效果比较理想。3.3 一个可以跑的DTW距离计算核心代码import numpy as np def dtw_distance(seq1, seq2, window_ratio0.1): m, n len(seq1), len(seq2) window max(int(m * window_ratio), 1) dp np.full((m 1, n 1), np.inf) dp[0, 0] 0.0 for i in range(1, m 1): start max(1, i - window) end min(n, i window) for j in range(start, end 1): cost np.linalg.norm(seq1[i - 1] - seq2[j - 1]) dp[i, j] cost min(dp[i - 1, j], # 删除 dp[i - 1, j - 1], # 匹配 dp[i, j - 1]) # 插入 return dp[m, n]识别时把待识别的动作序列和模板库里每个模板分别计算DTW距离取最小距离对应的类别作为判定结果。还有一个重要细节是加判定阈值——如果最小距离也超过一个阈值说明这个动作不在模板库里这时候宁可判为“未知动作”也不要硬套一个错误结果。我在项目里把这个阈值设为模板内部平均距离的1.8倍这个系数需要针对具体动作微调。DTW方案最大的优势是标注成本低每个动作录几遍、取平均模板就够了不需要构建几百上千条带标签的数据集。它的劣势也很明显对动作变体比如从左挥手变成右挥手泛化能力有限且逐帧算距离在高帧率视频下耗时较高。这就引出了LSTM方案。4. 基于LSTM的人体动作识别实现4.1 数据如何准备好喂给LSTMLSTM方案需要构建带标签的数据集。我的做法是同时用摄像头录制和MoTion数据增强录制时每组动作做20到50遍每一遍自动截断成固定长度的序列序列长度取60帧大约2秒。对于滑动窗口切出的动作片段如果长度超过60帧就随机采样截取如果不足60帧则用重复首尾帧的方式填充。特征维度上我没有把全部33个关键点都塞进去。只取了动作识别中最具判别力的上半身关节点左肩、右肩、左肘、右肘、左腕、右腕、左髋、右髋一共8个关键点每个关键点取x、y、z三个坐标所以每帧的特征向量是24维。为什么舍弃腿部和脚踝因为我的应用场景主要是上肢动作和简单下肢动作腿部关键点的抖动更大、遮挡更常见特征多了反而引入噪声。标签标注我强烈建议不要记在脑子里建议录制时就按动作分目录存放后续代码根据目录名自动打标签data/ 挥手/ hand_wave_001.npy hand_wave_002.npy 下蹲/ squat_001.npy squat_002.npy 行走/ walk_001.npy保存的.npy文件就是预处理后长度为60帧、维度为24的特征序列数据加载时再分训练集、验证集、测试集比例7比2比1。4.2 模型搭建与训练流程LSTM模型结构我用了两层LSTM加一个全连接分类头。第一层LSTM返回完整序列return_sequencesTrue第二层只返回最后一步输出中间加了Dropout防止过拟合。这个结构对一维时间序列分类非常经典。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout, Input model Sequential([ Input(shape(60, 24)), # 60帧每帧24维坐标 LSTM(64, return_sequencesTrue), Dropout(0.3), LSTM(32), Dropout(0.3), Dense(32, activationrelu), Dense(num_classes, activationsoftmax) ]) model.compile( optimizeradam, losscategorical_crossentropy, metrics[accuracy] )训练时设置两个回调一个EarlyStopping监控验证集loss连续10轮不下降就停止训练并恢复最佳权重一个ReduceLROnPlateau验证loss进入平台期后学习率自动乘0.5。这两个回调对模型收敛效果影响巨大不加的话训练曲线波动幅度会大很多。我训练的batch_size设为32初始学习率0.001训练不到100轮就收敛了验证集准确率达到了94%。从损失曲线看LSTM在前20轮快速下降后面逐渐平缓没有明显的过拟合迹象说明60帧序列加两层LSTM的容量对本项目的数据量是合适的。4.3 推理阶段的降噪技巧训练好模型之后如果直接把每一帧的滑动窗口结果都输出为预测类别会出现一种很常见的现象——标签在相邻帧之间抖动比如第50帧是“挥手”第55帧突然变成“下蹲”第60帧又变回“挥手”。这是因为单个窗口内的特征可能刚好落在分类边界上。解决这类波动我推荐多数投票法。维护一个长度15的队列每隔5帧滑动一次对队列内所有窗口的预测结果做众数统计用众数作为当前时刻的最终动作标签。实测下来这能让输出结果在时间维度上稳定非常多。顺便说一句LSTM对输入序列的顺序极度敏感样本的类别顺序必须打乱不能让一个batch里全是同一种动作否则模型会学到batch内的偏置。用tf.data的shuffle或者sklearn的train_test_split时都要注意设置shuffle。5. DTW和LSTM的横向对比与选型建议整个项目做完之后我把DTW和LSTM两条路线放在一起做了一个横向对比适用于大多数人体动作识别场景。对比维度DTWLSTM数据需求每个动作录5到10个模板每个动作需要几十到上百条样本标注成本低录制时按动作分类目录存即可中高需要标签明确、样本量大训练时间无需训练建模板即可需要GPU或较长CPU时间推理速度依赖模板库大小实时性尚可单次推理毫秒级实时性强动作变体泛化弱速度/幅度变化大会降低匹配度较好能学习动作内在时序特征代码复杂度核心逻辑不超过50行模型训练管道较复杂最佳适用场景快速验证、小样本原型、固定动作识别正式产品、多类别大样本动作识别我的整体建议是如果项目刚起步、数据量有限先用DTW把整条管道跑通它能让团队快速验证动作切分、特征选择、模板库设计等核心问题这些经验在切到LSTM后完全复用。当数据积累到一定程度再训练LSTM替换DTW分类层形成增量优化节奏。两条路线本质上是互补的不代表谁更先进。6. 常见问题与排查技巧实录6.1 动作识别准确率不稳定怎么办最常见的原因有三个关键点归一化做得不到位、动作切分的窗口边界不准确、模板/训练数据本身质量参差不齐。排查顺序建议先可视化——把每一帧输入画面、提取出的关键点骨架、预测结果一起叠加显示。如果画面骨架没有问题而预测错误重点检查特征归一化和数据标注如果骨架本身就是歪的、丢失关键点则要从mediapipe参数和图像质量下手。这类可视化手段也是我在项目中最常用的调试方式。6.2 mediapipe关键点抖动导致误判关键点抖动的来源主要是光照变化、运动模糊和遮挡。开启动态追踪能缓解一部分另外在特征层面对关键点坐标做滑动平均平滑处理也很有效。我用的窗口大小是5帧权重视实际需要调整。过度平滑会损失动作特征让快速动作变得迟钝所以不要为了去掉噪声而把窗口开得过大。6.3 多人同时出现在画面里怎么处理mediapipe Pose本身支持同时检测多个人会返回多个pose_landmarks结果。但如果同时识别多人动作复杂度会上升很多——首先要区分哪个关键点属于哪个人然后逐一分析。我的建议是限制项目范围单人场景就锁定画面中置信度最高的一人设定bbox筛选逻辑多人场景干脆改用mediapipe的detection接口先做目标框检测再对每个目标框单独跑姿态识别。多人追踪涉及跨帧身份匹配工作量不会小。6.4 数据增强对动作识别有没有用有用但幅度需控制。我对样本做了三种增强改变关键点坐标的整体尺度、沿水平方向镜像翻转必须成对处理左右关节标签、添加幅度较小的随机高斯噪声模拟摄像头采集误差。通过镜像翻转相当于把“迈左腿”变成“迈右腿”数据量直接翻倍这对LSTM模型的泛化能力有明显提升。但要注意镜像翻转只适用于左右对称的动作像“右手握拳”这种强方向性动作不能盲目翻转。做这个项目最大的体会是人体动作识别链路里姿态识别这一层的成熟度已经很高真正的设计重心应该放在时序建模和数据工程上。每一种算法都有它合理的应用场景DTW和LSTM不是替代关系是互相配合的双保险。希望这篇落地经验分享能帮你绕过我踩过的那些坑如果你也在MediaPipe、DTW、LSTM这条技术路线上做动作识别欢迎交流。本文还有配套的精品资源点击获取
返回列表