ARTICLE DETAIL

资讯详情

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

基于CNN的人体姿态与动作识别系统:从热图回归到时序建模实战

基于CNN的人体姿态与动作识别系统:从热图回归到时序建模实战 简介基于CNN的人体姿态与动作识别系统是一套完整可运行的Python深度学习项目源码主要面向计算机相关专业正在准备毕业设计、课程设计或期末大作业的学生也适合有一定Python基础、希望深入理解深度学习实战的开发者与爱好者。资源共6个文件以5个Python脚本和1个Markdown说明文档组成覆盖姿态检测、模型训练、模型测试、动作数据获取等核心环节同时附有项目说明文档对目录结构和调用关系进行梳理便于快速上手与二次开发。压缩包体积仅7KB代码精简但流程闭环已通过运行测试并获得导师高度认可评审平均分达96.5分。目前已有205人学习下载口碑良好。通过研读与实操该项目读者可掌握基于CNN的人体关键点检测与动作分类的完整流程理解数据获取、网络搭建、模型训练与评估的代码实现还可根据自身需求进行功能扩展为毕设、课设或项目初期立项提供扎实参考。若下载后遇到运行问题可私信联系作者安排远程教学。1. 基于 CNN 的人体姿态与动作识别系统能解决什么谁该往下看只看标题会以为基于 CNN 识别人体姿态和动作只是一套模型的工程化封装真正动手做起来你会发现源码包的价值不在那一堆 .py 文件里而在训练数据的组织方式以及模型串联起来的那条链路。这个系统要解决的是两件事第一从单目图像里估计出人体关键点通常是 17 个关节第二把连续帧的关键点序列映射成动作类别比如走路、举手、跌倒。它适合我这种要把它接进业务系统的人安防告警、运动分析、交互应用都能用。拿到这套源码先确认网络骨架能不能适配你的摄像头视角和多人人数再决定改数据还是改模型。2. 姿态和动作识别的 CNN 架构选型热图回归、PAF 与时序建模2.1 图像为什么用 CNN关键点为什么用热图回归一开始做这个系统时我也怀疑过既然关键点只是十几个 (x,y) 坐标直接用全连接网络回归不就行了实际一跑就露馅了。图像是二维网格结构用全连接网络把像素展平空间相邻关系全丢参数还会爆炸一张 256×256 的图展平是六万维向量第一层就要千万级参数而且图里的人物平移几个像素全连接网络的输出就剧烈变化。业界为什么图像处理用 CNN 而不用前馈神经网络答案就是两个词局部连接和权值共享。CNN 靠局部感受野和卷积核共享把平移等变性带进来这也是这套系统最终沿着 CNN 路线走的原因。姿态估计的具体做法是“热图回归”。模型不是直接输出坐标而是为每个关键点生成一张 H×W 的置信度图峰值所在位置就是关键点。相比直接回归 x,y 数值热图对标注噪声和遮挡更宽容多个人同时出现时也能在图上形成多个峰值。下面的最小模型能展示这种输出形态输入三通道图像输出 17 个通道的热图分布。# 最小姿态热图模型展示 CNN 的输出形态不是完整网络 import torch import torch.nn as nn class PoseHeatmapCNN(nn.Module): def __init__(self, num_joints17): super().__init__() self.backbone nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 特征图缩到 1/2 nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 缩到 1/4 nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 缩到 1/8 ) self.head nn.Conv2d(128, num_joints, 1) def forward(self, x): return self.head(self.backbone(x)) # B,17,H/8,W/8模型输出是原始分辨率 1/8 的特征图训练时把标注关键点也缩放到 1/8 尺寸再算损失推理时做一次上采样回到原图取 argmax 得到像素坐标。这个 resize 过程最容易犯的错是忘记同步缩放后面避坑章节会详细展开。真实项目里不会用这么浅的网络HRNet、Hourglass 只是把主干换成了更复杂的高分辨率或编解码结构但输出头基本都沿用这种热图回归方式。2.2 先检出人再定位还是先定位再组装两个路线的取舍姿态估计算法里存在两条路线标题里没有限定但选错方向会让多人场景的难度直线上升。自上而下先跑目标检测把每个人裁出来单独估计关键点代表是 AlphaPose 和 HRNet。它精度高适合人少且需要动作细节的业务代价是推理时间随人数线性增长。自下而上先预测所有关键点热图再通过 PAF 关联向量把关键点拼成骨架OpenPose 是典型代表。它速度稳定人数增加影响小但在拥挤场景容易拼错骨架。我在实际项目里定过一条经验摄像头覆盖区域最多 3 个人用 OpenPose 足够超过 3 人或存在大量重叠就改成目标检测加 HRNet 的结构。选型代表方法推理速度多人重叠工程复杂度自上而下HRNet、AlphaPose随人数下降较好多一个检测器自下而上OpenPose、PAF基本稳定骨架易串线分组后处理复杂这个选择直接影响下游动作识别。动作特征来自骨架序列骨架一旦串线后面的分类器再强也没用。所以我拿到项目说明后的第一件事不是看模型结构而是看它默认走的哪条路线把自己的摄像头角度下几十段测试视频喂进去压一遍多人场景再决定要不要改架构。2.3 动作识别不是帧级 CNN时序特征层放在哪决定了系统上限关键点逐帧提取之后动作识别就变成一个序列分类问题。传统做法是拿 3D CNN 在视频帧上做时空卷积但这样做对数据量和算力要求都很高而且和姿态估计耦合严重。最落地的方式是让姿态 CNN 先把每帧压缩成骨架特征再用一个轻量时序网络分类。也就是说CNN 在系统里负责“看到哪里”时序网络负责“发生了什么”。如果动作持续时间短、类别拆得细我会先用两层 LSTM序列长度取 30 帧特征维度由上一阶段的归一化坐标、角度和置信度拼接而成。跑通后再考虑 ST-GCN把骨架按人体自然连接和时序连接建图用图卷积做分类。ST-GCN 对肢体关系建模更准但吃样本数据不够时容易过拟合。这里的关键是不要一上来就追最复杂模型先用 CNN 把空间特征拿稳再讨论用 LSTM 还是图网络做时序。3. 从零搭建可运行的姿态动作系统环境、数据与主流程3.1 Python 虚拟环境与 CUDA 对应关系拿到源码后第一件事不是看模型而是把环境固定下来。这类项目常见组合是 Python 3.8、PyTorch 1.8 到 2.x、CUDA 11.x。显卡驱动较新时直接装对应 CUDA 版本的 PyTorch 就行。下面这条命令我每次开项目都会落成 requirements.txt 的一部分避免重装时重复踩坑。# 创建虚拟环境并安装依赖 conda create -n pose_action python3.8 -y conda activate pose_action pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pycocotools numpy scipy参数说明--index-url指定 CUDA 11.8 的 PyTorch 安装源如果不支持 11.8装 cu113 或 cu121 也可以关键是torch.version.cuda要和显卡驱动匹配。纯 CPU 机器装默认版本只能跑推理调试训练会很痛苦。pycocotools用来做 COCO 数据格式解析Windows 上安装失败时先装 C Build Tools 再 pip install这是常见坑。我还建议把输入分辨率、热图尺寸、sigma、数据集路径、模型保存目录全部写进一个 config.py不要散落在各个脚本里。下面是常用配置模板参数含义写在注释里。# 全局配置所有子模块统一引用避免传参错乱 INPUT_SIZE 256 # 模型输入边长 HEATMAP_SIZE 64 # 热图分辨率通常是输入尺寸 1/4 或 1/8 SIGMA 2 # 高斯核标准差按热图尺寸设定 BATCH_SIZE 16 BASE_LR 1e-3 POSE_WEIGHTS checkpoints/pose_model.pth ACTION_WEIGHTS checkpoints/action_lstm.pth把参数集中管理后换业务场景时只需要改配置文件数据加载和训练代码不用动。这也是这类系统从 demo 走向产品结构化的第一步。3.2 数据接入COCO 格式和自定义数据怎么改姿态数据最常用的是 COCO 和 MPIICOCO 有 17 个关键点顺序固定鼻子、左右眼、左右耳、左右肩、左右肘、左右腕、左右髋、左右膝、左右踝。动作识别的标签则在关键点之外额外定义一个 action_id。如果业务数据是自定义的我强烈建议别另搞一套格式直接仿照 COCO 的 JSON 结构把每一帧的标注统一成 keypoints 数组加 action 字段。下面的函数把 COCO 格式的标注转换成模型需要的热图张量同时保留不可见关键点的处理逻辑。# 把 COCO keypoints 转换成 17 通道热图 import numpy as np def keypoints_to_heatmap(kps, img_h, img_w, heatmap_h, heatmap_w, sigma): # kps: 51 长度数组每 3 个元素为一组 [x, y, visibility] ht np.zeros((17, heatmap_h, heatmap_w), dtypenp.float32) kps kps.reshape(-1, 3) scale_h heatmap_h / img_h scale_w heatmap_w / img_w for c in range(17): x, y, v kps[c] if v 0: continue # 不可见关键点通道置零 cx, cy x * scale_w, y * scale_h yy, xx np.mgrid[0:heatmap_h, 0:heatmap_w] ht[c] np.exp(-((xx - cx) ** 2 (yy - cy) ** 2) / (2 * sigma ** 2)) return ht注意visibility为 0 时热图对应通道必须全零否则模型会去学习预测一个没有标注的位置。我见过不少训练代码没做这个处理导致模型把不可见关键点输出在所有图像的中心位置。sigma的取值要和热图尺寸挂钩64×64 热图设 2128×128 设 4不要直接照抄公开代码里的数值。3.3 主流程代码骨架与训练超参数设置整个系统的主流程可以拆成四段预处理、姿态推理、骨架特征提取、动作分类。动作分类需要时间维度所以实际实现里会在姿态推理后面增加一个固定长度的骨架序列缓冲。下面这段是单帧流程的骨架方便理解数据流向。# 主流程单帧推理返回动作类别 import torch from collections import deque buf deque(maxlen30) def run_frame(model_pose, model_action, frame): x preprocess(frame) # B,3,256,256 with torch.no_grad(): hm model_pose(x) # B,17,64,64 kps heatmap_to_keypoints(hm) # 17,3 feat build_skeleton_feature(kps) # D, buf.append(feat) if len(buf) buf.maxlen: return None # 缓存未满不下判断 seq np.stack(buf, axis0) # 30,D seq torch.from_numpy(seq).float().unsqueeze(0) # B,T,D logits model_action(seq) action_id logits.argmax(dim1).item() return action_idpreprocess在这里不只是 resize 和归一化还要把 BGR 转 RGB、像素值除以 255并按训练时的 mean/std 做标准化。heatmap_to_keypoints是把模型输出的热图做 argmax 得到坐标再把缩放比例乘回去。build_skeleton_feature在下一章展开。这一段能跑通说明数据流没有断点之后要做的只是提升精度和速度。训练超参数方面我最常用的底线配置是Adam 优化器初始学习率 1e-3cosine 衰减batch size 16输入尺寸 256热图尺寸 64。如果 50 个 epoch 内 loss 还在明显下降就继续训练不用硬卡在预设 epoch 上。4. 核心代码实现热图标签、骨架特征与动作分类器4.1 热图标签生成与损失函数设置第 3 章的代码已经覆盖了标签生成的完整流程这里把损失函数单拎出来说因为它直接决定模型能不能收敛。热图回归最直接的损失是 MSEPyTorch 里就是nn.MSELoss。但热图里大部分区域是零正负样本比例失衡模型容易整体趋于保守。处理办法有两个给峰值区域加权重或者用空间加权 MSE。# 空间加权 MSE 损失加强关键点中心区域 class WeightedMSELoss(nn.Module): def __init__(self, weight_bg0.1): super().__init__() self.weight_bg weight_bg def forward(self, pred, target): # target 是热图标签按标签激活值构造权重 weight torch.ones_like(target) * self.weight_bg weight[target 0] 1.0 loss torch.mean(weight * (pred - target) ** 2) return lossweight_bg设为 0.1意思是背景区域对损失的贡献只有峰值区域的十分之一。这样模型不会把大量注意力浪费在空白区域收敛速度明显更快。实际训练时我还习惯把模型输出的 1/8 分辨率热图先上采样到标签分辨率再算 loss梯度对标注更敏感。上采样用F.interpolate注意align_cornersFalse否则坐标会差出半个像素。4.2 骨架特征提取与归一化让分类器不再依赖摄像头距离关键点坐标直接送进去模型学到的很可能是绝对位置和“人在画面里的大小”。换一个摄像头或者人离镜头远近不同动作识别马上就崩。所以构造骨架特征时核心是去掉平移和尺度。我的做法是以左右髋中点作为骨盆中心把全部关键点坐标对齐到该中心再用骨盆宽度作为尺度单位把坐标归一化到近似相同的量级。# 构造尺度不变的特征同时拼接关节角度提高鲁棒性 def build_skeleton_feature(kps): # kps: 17x3 [x, y, confidence] hip_center (kps[11, :2] kps[12, :2]) / 2 scale np.linalg.norm(kps[11, :2] - kps[12, :2]) 1e-8 norm_kps (kps[:, :2] - hip_center) / scale angles joint_angles(norm_kps) # 计算主要关节夹角 feature np.concatenate([ norm_kps.flatten(), # 17*2 维坐标 angles, # 10 维角度 kps[:, 2] # 17 维置信度 ]) return feature.astype(np.float32)scale加了一个极小值防止除零尤其当人物侧躺导致左右髋重叠时这一步能避免特征爆炸。joint_angles一般计算四肢的肩、肘、胯、膝夹角角度天然对尺度不敏感和坐标特征组合效果更好。置信度也要拼进去当模型认为某个关键点不可靠时分类器应该看到这个不确定性信号。这个特征维度大约六七十维LSTM 完全接得住。4.3 动作分类LSTM 起步、ST-GCN 进阶有了每帧的骨架特征后动作分类只需要一个轻量时序网络。整个系统把姿态特征提取放在 CNN 上最后一步的时序层通常是循环网络或图网络这一点在项目说明里会写得很清楚。我一般先实现一个双层 LSTM数据量少的时候比一上来就做 ST-GCN 更稳。# 简单可靠的动作分类基线双层 LSTM class ActionLSTM(nn.Module): def __init__(self, feature_dim, hidden_dim128, num_layers2, num_classes6): super().__init__() self.lstm nn.LSTM(feature_dim, hidden_dim, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_dim, num_classes) self.dropout nn.Dropout(0.3) def forward(self, x): # x: B,T,F out, _ self.lstm(x) # out: B,T,H out self.dropout(out[:, -1, :]) return self.fc(out)feature_dim要和build_skeleton_feature的返回维度严格一致num_classes根据动作字典来定。hidden_dim设 128 是经验值序列长度 30、动作类别少于 20 时足够继续加大会过拟合。训练时注意把每个视频片段作为一个样本不要跨视频拼接序列否则时序上下文会断掉。如果 LSTM 效果到不了业务要求再考虑 ST-GCN。ST-GCN 的输入是节点特征张量和邻接矩阵节点数量就是关键点数量时序边描述帧与帧之间的连接。实现时需要把骨架编排成B,C,T,V的张量并用人体骨骼连接和跨帧连接两个矩阵组成图结构。调参难度比 LSTM 高但对应的是动作识别精度的上限提升。5. 避坑与排查训练到部署的五个高频问题5.1 骨架串线多人场景下关键点拼错现象画面里有两个人交错或遮挡时输出骨架会把 A 的手接到 B 的胳膊上动作分类跟着乱掉。原因自下而上方法在关键点分组阶段PAF 匹配做的是积分匹配行人重叠越严重关联向量的置信度就越低贪心匹配就容易选错边。解决先调推理参数把paf_threshold从默认 0.5 提高到 0.7过滤掉低置信度连接如果还是错就切换到目标检测加单人姿态估计的链路。代码层面就是先检测再裁剪# 多人场景切换到自上而下链路目标检测姿态裁剪 for box in detect_person(frame, conf_threshold0.5): person crop_resize(frame, box, (256, 256)) hm pose_model(person) keypoints heatmap_to_keypoints(hm)这样做把“一个图里多人”转成“一个框里一个主要人物”骨架串线问题基本消失代价是最多跟踪 5 个人时帧率会明显下降需要根据实际性能再权衡。5.2 热图 loss 不降或乱跳模型输出与标签没对齐现象训练早期 loss 下降到零点几就不动了热图峰值在可视化里是糊的关键点永远偏在某个固定方向。原因最常见是模型输出尺寸不同步。模型输出原始分辨率 1/8 的热图标签却在原图尺寸上生成两者直接算 MSE等于让网络在错误坐标系里学一个模糊映射。解决训练时把输出上采样到标签尺寸或者在生成标签时就生成到 1/8 分辨率的热图上。排查时先加一个形状断言问题会在训练第一步暴露。# 训练前打印各张量尺寸确认匹配 assert out.shape[-2:] label.shape[-2:], heatmap size mismatch尺寸一致还是抖动就检查标签的坐标归一化是否出错例如把 256 尺寸的坐标直接缩放到 0 到 1然后当作像素坐标生成高斯导致中心被压到角落。5.3 换个视角动作就误判归一化只做了一半现象训练时同一动作在正对摄像头时识别准确换成斜 45° 视角动作被误判成另一类。原因特征层面减掉了骨盆中心和尺度但没有处理旋转。同一动作在不同视角下的骨架角度变化很大模型容易记住训练集的视角分布。数据采集没覆盖多视角时模型天然是脆弱的。解决数据增强里加入围绕人体中心的空间旋转旋转范围 ±20°让模型看到更多角度如果已有真实业务数据每类动作至少补充两个视角。骨架特征层面的随机旋转同样有效相比图像旋转更不伤关键点对齐。5.4 视频推理帧率太低慢的往往不是卷积本身现象一秒钟只能跑两三帧整个系统看起来像幻灯片。原因姿态网络输入分辨率太大、PyTorch eager mode 在 CPU 上跑、视频解码和预处理用单线程这三个因素叠加帧率当然上不去。大量项目在 1080p 原图上直接跑模型其实很没必要关键点识别依赖的是人体尺度不是画面里的每根毛发。解决先把输入降到 640再裁剪出人体区域推理用半精度浮点 autocast 包住。下面这段是半精度推理的写法# FP16 推理在支持半精度的 GPU 上能提速 30% 以上 with torch.no_grad(), torch.cuda.amp.autocast(): heatmaps pose_model(frames)如果还要更快就把模型导出 ONNX 再走 TensorRT落地到 C 部署。先用这三步优化推理耗时通常能降到原来的三分之一。5.5 显存不足调小 batch 后反而更慢现象8GB 显存训练到两步就 OOM把 batch size 从 16 改成 4速度反而明显下降GPU 利用率只有 20%。原因batch size 过小时GPU 计算单元没被喂满大量时间花在 CPU 预处理、数据拷贝和内核启动上。OOM 的根源往往是激活值占用而不是模型参数个数。解决优先降低输入分辨率而不是 batch。把 256×256 降到 224×224显存会明显松动然后开足数据加载并行度。如果分辨率不能再降就用梯度累积模拟大 batch。# 显存受限时梯度累积等效扩大 batch optimizer.zero_grad() for step, batch in enumerate(dataloader): loss compute_loss(batch) / accum_steps loss.backward() if (step 1) % accum_steps 0: optimizer.step() optimizer.zero_grad()accum_steps取 2 到 4学习率也要同步放大否则收敛变慢。显存还是不够时再用torch.utils.checkpoint把激活值换到显存外但要接受训练时间显著拉长的代价。6. 进阶与验证把系统压到实时再用客观指标验收结果6.1 半精度与 TensorRTFP16 不只推理能用训练也可以用混合精度把显存占用压掉一半。PyTorch 的 autocast 配合 GradScaler 是最成熟搭配多数姿态网络不需要改结构。TensorRT 转换则是把训练好的模型先导出 ONNX再转成 TensorRT engine固定输入分辨率 256×256 后延迟通常能降到原来的一半以下。这块需要额外处理动态轴问题建议推理阶段固定输入尺寸能省去很多编译时的不确定性。6.2 关键点平滑、PCKh 与混淆矩阵实时推理时关键点抖动会造成动作分类在边界帧来回跳。给关键点加一阶滤波是个很实用的技巧不需要重新训练。代码如下# 指数移动平均平滑关键点 alpha 0.6 smoothed None def smooth_keypoints(kps): global smoothed if smoothed is None: smoothed kps.copy() else: smoothed alpha * kps (1 - alpha) * smoothed return smoothedalpha 在 0.5 到 0.7 之间取动作节奏快就贴近 0.5慢动作就贴近 0.8。平滑带来的副作用是延迟动作分类对延迟敏感所以别把 alpha 调太高。部署前必须用客观指标验收姿态部分用 PCKh动作部分至少给每个类别算准确率和召回率最好画混淆矩阵。没有这套验证模型演示看起来再炫进入业务后所有状态都会暴露出来。我现在的习惯是每次训练完都把关键点可视化视频和混淆矩阵一起拷进项目目录下一次改参数时直接对比。这套流程帮我避开了很多玄学调参希望帮到你。本文还有配套的精品资源点击获取
返回列表