
简介面向人体姿态识别与动作分类任务这份配套资料包集中演示了Mediapipe姿态估计、动态时间规整DTW序列匹配和LSTM时序建模的组合应用。资源共139个文件压缩包仅11.04MB主体包括120个npy格式的姿态特征数据、8个py格式的完整训练与推理脚本、8个mp4演示视频以及h5权重模型、README说明文档等便于对照源码复现识别流程并快速验证效果。已有1200余人学习下载。从内容结构看npy数据覆盖了不同动作样本的姿态序列py脚本串联起Mediapipe关键点提取、DTW模板匹配和LSTM分类器训练mp4视频则直观展示采集与测试场景适合计算机视觉初学者、算法工程师以及智能健身、安全监控等应用开发者参考。通过运行该项目读者可以掌握姿态关键点提取、序列对齐打分与时序分类的完整链路并基于内置模型和数据进行二次扩展与优化。 最近在做一个动作识别的项目技术方案正好踩在“传统时序匹配深度学习”的结合点上用mediapipe提取人体骨骼关键点再用DTW和LSTM两条路线分别做动作分类。项目难度不算高但把这两套方法完整跑通、对比、再融合的过程很有代表性。这篇就把整体思路、关键代码和踩坑记录整理出来给正在做姿态识别或者动作分类的朋友做参考。1. 项目总体方案与选型思路1.1 为什么选mediapipe做姿态识别人体动作识别的第一步是把视频里的人和动作“数字化”。行业内有很多方案比如OpenPose、AlphaPose、MediaPipe我最终选了MediaPipe主要看中三点。一是部署成本低。MediaPipe的Pose模块在CPU上就能跑实时推理单帧延迟在普通笔记本上能到30ms以内。我的项目初期是在本地摄像头跑后期要迁移到边缘设备这个特性很重要。二是输出稳定性。MediaPipe输出的是33个人体关键点的归一化坐标x, y, z关键点定义和COCO风格基本一致。相比自己训练关键点检测模型它的跨平台一致性做得很好省掉了大量数据标注和模型训练的功夫。三是生态成熟。MediaPipe同时支持Python、Android、Web同一个Pose模型可以多端复用。动作识别真正上线时往往要覆盖手机端选它风险最小。这里要补充一点MediaPipe的关键点坐标分为x、y、z三个分量但z并不代表真实深度而是以髋部中心为原点的相对深度单位和x/y不一致。这会在后续特征工程里带来不少麻烦下面会专门说。1.2 为什么同时用DTW和LSTM两条路线确定了用MediaPipe获取关键点之后动作识别层我特意对比了两类算法。DTW动态时间规整属于传统时序匹配算法核心能力是计算两条长度不一致的序列之间的相似度。动作数据天然存在“同一个人做同一动作速度快慢不同、骨架大小不同”的问题DTW能从时间轴上“伸缩”地比较序列非常适合处理这类变长匹配。LSTM则是循环神经网络的代表能够学习时序数据中的长期依赖关系。对于“坐下”这种包含连贯骨骼运动轨迹的动作LSTM能隐式学到动作的时序特征不需要手工设计距离度量。之所以两条路线都做是因为它们在项目中的定位完全不同DTW适合小样本、模板明确、需要实时响应的场景LSTM适合样本充足、动作种类多、需要泛化能力的场景。两者不是替代关系而是互补关系。项目后期我还试了分数融合效果比单独使用任意一个都要稳。1.3 系统整体架构与数据流整个系统分为四个模块视频输入与关键点提取、动作序列预处理、DTW分类器、LSTM分类器。数据流是这样的摄像头或视频文件逐帧输入经过MediaPipe Pose得到每帧的33个关键点坐标把连续帧的关键点组合成动作序列然后经过滑动窗口切分和归一化最后分别送入DTW模板库或LSTM模型做分类预测。我先用Python脚本从视频里批量抽帧存成关键点序列的npy文件再离线训练LSTM。实时预测时则直接用摄像头输入每10帧滑动一次窗口输出当前动作类别。这样开发和测试能够解耦训练数据也能复用。2. 环境准备与mediapipe姿态识别落地2.1 环境依赖与安装我的开发环境是Python 3.9、OpenCV 4.5、MediaPipe 0.10。安装命令很简单pip install opencv-python mediapipe numpy如果要做LSTM训练还得装TensorFlowpip install tensorflow这里有个容易踩的坑MediaPipe不同版本的API有细微差异比如0.9.0之后Pose接口的参数改名了。建议直接装最新版网上很多教程是基于旧版写的照着抄经常报参数错误。2.2 基于摄像头/视频的姿态提取核心代码姿态提取的核心逻辑并不复杂关键是把MediaPipe的输出正确转成自己的数据格式。我封装了一个工具函数import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, min_detection_confidence0.5, min_tracking_confidence0.5 ) def extract_keypoints(video_path): cap cv2.VideoCapture(video_path) sequences [] while cap.isOpened(): ret, frame cap.read() if not ret: break frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(frame_rgb) if results.pose_landmarks: h, w frame.shape[:2] keypoints [] for lm in results.pose_landmarks.landmark: keypoints.append([lm.x * w, lm.y * h, lm.z * w]) sequences.append(keypoints) cap.release() return np.array(sequences)逐帧存原始像素坐标是有意为之像素坐标受摄像头分辨率影响但特征归一化时我会再转成相对坐标所以原始数据怎么存都行保留越多原始信息越好。2.3 关键点选择与置信度处理MediaPipe的33个关键点里不是每个都适合作为动作分类特征。比如面部关键点对肢体动作几乎无信息量反而引入噪声。我最终只保留了四肢和躯干的22个关键点相当于在源头做了一次特征筛选。另一个关键点是置信度过滤。当人体部分遮挡时MediaPipe会输出低置信度的关键点这些点的坐标噪声极大。我的做法是当某个关键点的visibility低于0.5时用前后帧的同位置点线性插值补上。def interpolate_missing(points, visibility, threshold0.5): points points.copy() for i in range(1, len(points)-1): if visibility[i] threshold: points[i] (points[i-1] points[i1]) / 2.0 return points这么做的前提是动作帧率足够高前后帧差异不大。如果视频本身只有15帧以下这个方法的误差就会很大。3. 动作序列特征工程与数据集构建3.1 归一化与相对坐标系转换这一步是整个项目里对最终精度影响最大的一个环节。MediaPipe输出的关键点是基于图像尺寸的归一化坐标但它没有消除人体骨架大小、站在画面不同位置的差异。如果直接用原始坐标送入模型模型会把“人离摄像头远近”也当成特征导致同一种动作因为距离不同被判成不同类别。解决办法是把每个点都转换到以髋部中心为原点的相对坐标系。髋部中心我用左右髋的中点近似然后以两肩距离作为尺度参考做等比例缩放。这样处理后同一个人在不同位置、不同距离做同一动作特征序列基本一致。def normalize_pose(landmarks): hip_center (landmarks[23] landmarks[24]) / 2.0 shoulder_left, shoulder_right landmarks[11], landmarks[12] scale np.linalg.norm(shoulder_left - shoulder_right) normalized [] for lm in landmarks: normalized.append((lm - hip_center) / scale) return np.array(normalized)注意z坐标的处理。MediaPipe的z值本身是相对坐标单位和x/y不一致不能直接除scale。我的经验是在归一化之后单独给z乘一个较小的权重比如0.2降低它对分类的干扰。因为大部分动作在侧向深度上的信息量远低于平面上的信息量。3.2 滑动窗口与动作序列切分连续的摄像头流不能直接进模型需要切成固定长度的窗口。我用的窗口策略是窗口长度30帧相当于1秒的30fps视频滑动步长10帧。每个窗口提取一次特征。这里有一个平衡点窗口太短动作信息不完整窗口太长实时性差延迟高。实际测试中30帧对“蹲下”“举右手”“走路”这类动作足够对更复杂的多阶段动作比如“鞠躬后坐下”可能需要60帧。可以先做预实验对不同动作计算关键点序列的持续时间分布再定窗口长度。3.3 数据集的采集与标签设计数据集中每个样本的标签是动作类别。我采集了6类动作站立、走路、坐下、举起右手、弯腰、踢腿。每个动作由一个人重复20次每次持续3秒钟共采集3个人最终得到360组动作样本。采集时最容易忽略的问题是数据要覆盖不同人和不同速度。如果只采集自己一个人的动作模型会严重过拟合到个人的运动习惯。后来我专门让身高差异较大的同事也录了一遍模型在跨人测试中的准确率提升了约10个百分点。数据存储格式如下每个样本是一个numpy数组形状为(30, 66)30是窗口长度66是22个关键点乘以3维坐标。标签用one-hot编码与LSTM的softmax输出对齐。4. DTW动作识别的实现细节4.1 DTW原理与匹配流程DTW的核心思想是对两条序列逐点计算距离然后找一条从左上角到右下角的最短路径。路径的含义是“把序列A的第i帧对齐到序列B的第j帧”允许一对多或者多对一从而消除时间长度不一致带来的影响。实际使用中我先为每个动作构造一个“标准模板”。做法是把该类动作的所有训练样本按时间轴做平均得到一条代表该类动作的模板序列。待测序列与每个模板计算DTW距离距离最小的类别就是预测结果。4.2 DTW核心代码实现DTW的实现本身很经典直接照搬动态规划即可import numpy as np def dtw_distance(seq1, seq2): n, m len(seq1), len(seq2) dp np.full((n1, m1), np.inf) dp[0, 0] 0 for i in range(1, n1): for j in range(1, m1): cost np.linalg.norm(seq1[i-1] - seq2[j-1]) dp[i, j] cost min(dp[i-1, j], dp[i, j-1], dp[i-1, j-1]) return dp[n, m]这里用欧氏距离作为单帧两向量之间的距离度量。需要注意的是seq中每一行是当前帧的66维特征向量。在Python里最耗时的部分是这个双重循环我实测30帧序列对比一个模板大概需要5ms6个模板共30ms对于离线识别够用实时场景有点勉强。优化的思路是用矩阵运算一次算出所有单帧距离再在GPU或向量化层面做动态规划。4.3 阈值判定与误报控制DTW分类只输出“距离最近的模板”但“最近的模板”不等于“就是该动作”。如果待测序列和所有模板的距离都很大说明这根本是没见过的动作。此时如果依然输出最近标签就会把任意随机动作强行归为训练集中的某一类这是DTW最容易出现的误报。我的方案是给每个模板设定一个拒识阈值。阈值根据训练集内同类样本与该模板的DTW距离分布来定公式是threshold mean_distance 2 * std_distance待测序列与该模板的距离如果超过阈值直接判定为“未知动作”。这里用均值加两倍标准差大约覆盖95%的正常波动。阈值设得越严误报越少但漏报也会增加。实际项目中我倾向于“宁漏勿错”毕竟动作分类错比不识别更影响体验。5. LSTM动作识别的实现细节5.1 模型结构与参数设计LSTM模型我采用了两层LSTM加两层全连接的结构。输入形状是(30, 66)第一层LSTM返回完整序列第二层只返回最后一个时间步的输出再接两个全连接层最后softmax输出6个类别概率。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential([ LSTM(64, input_shape(30, 66), return_sequencesTrue), Dropout(0.3), LSTM(32, return_sequencesFalse), Dropout(0.3), Dense(32, activationrelu), Dense(6, activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy])LSTM单元数量设置成64和32是在精度和过拟合之间折中的结果。单元数太少模型学不到复杂时序模式太多则在小样本集上很容易过拟合。Dropout放在每层LSTM之后随机丢弃30%的节点明显提升了测试集上的表现。5.2 训练过程与调参心得训练数据一共360个样本按7:2:1划分为训练集、验证集和测试集。这里有个关键细节同一人的同一动作样本不能同时出现在训练集和测试集里否则会严重高估模型效果。我按“人”拆分数据保证测试集里的人从未出现在训练过程中。训练时设置了早停机制监视验证集loss连续5个epoch不下降就停止训练。实际训练中大约在第20个epoch收敛准确率稳定在95%以上。batch_size我用了32lr用了默认的0.001其他超参数没有过多搜索。这里分享一个调参经验如果发现训练集精度很高但验证集精度低第一件事不是调模型结构而是检查数据增广和归一化。我最初就是在数据归一化上做错了导致跨人测试效果崩了。5.3 模型评价与实时预测集成模型评价除了整体准确率我还会看每个类别的召回率。某些动作比如“站立”和“坐下”在过渡阶段非常相似容易被误判。我统计了混淆矩阵发现误判主要集中在“站立”和“坐下”的起止帧。这其实和数据标注的边界模糊有关不完全是模型的问题。实时预测时我把模型导出为tflite格式再加上滑动窗口每次预测使用最近30帧的关键点数据。整个流程在CPU上大约每100ms能输出一次预测结果基本满足实时应用要求。导出的方式converter tf.lite.TFLiteConverter.from_keras_model(model) tflite_model converter.convert()6. DTW与LSTM对比、融合与踩坑实录6.1 实测结果对比我在相同测试集上对比了两条路线的表现。准确率方面LSTM达到96%左右DTW约88%。LSTM明显更强但它的强建立在充足训练样本的基础上。当我只保留每个人5次动作数据时DTW的准确率是82%LSTM降到74%说明LSTM在小样本下发挥不出优势。实时性方面DTW每帧消耗大约30msLSTM约10ms。这里有个反直觉的点LSTM虽然计算量大但TensorFlow的推理引擎优化得好反而是DTW的Python双重循环成为瓶颈。如果能把DTW换成C或用numba加速二者差距会缩小。适用场景上DTW非常适合动作种类少、样本少、需要快速上线的项目LSTM适合动作种类多、样本充足、追求泛化效果的项目。如果你的动作是固定的几个姿势用DTW就够了完全没必要上深度学习。6.2 融合策略与最终效果项目最后我尝试了分数融合把DTW的“距离相似度”和LSTM的“类别概率”结合起来。做法是把DTW距离转换成相似度分数然后和LSTM概率做加权平均similarity 1.0 / (1.0 dtw_dist) final_score 0.35 * similarity 0.65 * lstm_prob权重0.35和0.65是通过网格搜索确定的。融合后准确率提升到97.5%左右更重要的是拒识效果变得更稳当动作属于未知类别时LSTM会输出一个比较平均的概率分布DTW则给出一个偏大的距离两者的分数都低就能正确触发“未知动作”判定。6.3 实操中遇到的最多的三个坑第一个坑是MediaPipe的z坐标。不同距离下z值的分布完全不同我一开始没处理LSTM训练时loss降到0.1但验证集准确率始终上不去排查了很久才意识到是z轴权重问题。最终方案是归一化后对z乘0.2问题随即消失。第二个坑是动作边界难切分。用户在“弯腰”动作开始前必然有一小段“站立”的过渡帧这部分包含在窗口里会拉低准确率。我最后参考了别人的做法对每个动作模板的首尾各截掉10%的帧只保留动作最核心的区间效果立竿见影。第三个坑是数据集采集时动作速度太规范。我录数据时很谨慎每个动作都做得又慢又标准结果模型在真实环境里遇到快节奏动作时经常误判。后来我特意加入了一些“快做”和“做得随意”的样本模型鲁棒性才提上来。这个项目跑完之后我最大的感受是技术选型不是越新越好而是要看数据量、实时性和部署环境。先用DTW这类传统方法快速跑通流程验证可行性再上LSTM提升上限最后用融合策略弥补各自短板是成本最低也最稳妥的推进路径。如果你正在做类似的动作识别项目建议照着这个顺序走一遍能少走不少弯路。本文还有配套的精品资源点击获取