尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ST-GCN骨骼动作识别实战:从原理到二次开发的完整指南

ST-GCN骨骼动作识别实战:从原理到二次开发的完整指南 简介一套基于时空图卷积ST-GCN的骨骼动作识别Python项目利用图卷积网络对三维骨骼序列进行时空建模实现动作类别判断。面向计算机、电子信息等专业学生及深度学习爱好者可作为课程设计、期末大作业或毕业设计的参考实现适合具备一定Python和深度学习基础、愿意独立调试的读者。压缩包共90个文件约52.55MB其中29个Python源码负责核心逻辑13个YAML配置用于参数调整3个PyTorch模型权重可直接加载验证另有MP4演示视频、gif动画等可视化素材与Markdown说明文档结构清晰。代码模块覆盖数据加载、模型搭建、训练与评估等环节可直接运行附带的预训练权重能快速验证识别效果项目说明文档与环境依赖清单、模型下载脚本为部署提供了指引。目前已有586人学习下载适合作为深度学习动作识别方向的入门与进阶参考资料。1. 骨骼动作识别选型为什么 ST-GCN 是拿到 python 源码后最值得先跑通的基线动作识别项目最怕的就是“换场景就废”同一个动作背景纹理一变或者机位稍微偏一点基于 RGB 视频的模型精度就会断崖式下跌。骨骼动作识别则只依赖人体关键点坐标不关心衣服纹理和背景天然对视角和遮挡更鲁棒。ST-GCN时空图卷积网络是这一领域公认的基线模型用图结构建模关节连接、用时间卷积捕捉运动节奏。这个 python 源码包正好把 ST-GCN 从数据预处理、模型定义到训练推理完整串在了一起NTU 和 Kinetics 两条数据管线、单流双流模型和预训练权重都是现成的。适合做课程设计、期末大作业也适合想快速上手图卷积动作识别的从业者。接下来我按原理、部署、推理、踩坑和二次开发的顺序把这个包彻底拆一遍。2. 时空图卷积原理与代码对应st_gcn.py 里从邻接矩阵到双流模型的参数链条2.1 骨架图是怎么构建的邻接矩阵和 st_gcn.py 的图卷积实现ST-GCN 的核心假设是人体骨架可以被建模成一张图每个关节点是节点骨骼连接是边。图卷积做的事情就是让每个节点聚合邻居节点的特征。代码里这个“图”不是动态生成的而是在初始化阶段算好一个邻接矩阵A后续所有层的空间卷积都复用这个矩阵。# net/utils/st_gcn.py 中关于图卷积的关键逻辑 # A 是预处理的邻接矩阵shape 为 (num_node, num_node) # 在 StGCN 的每一层中输入特征先做 1x1 卷积再与邻接矩阵相乘 def _normalize_digraph(A): Dl np.sum(A, 0) num_node A.shape[0] Dn np.zeros((num_node, num_node)) for i in range(num_node): if Dl[i] 0: Dn[i, i] Dl[i] ** (-1) AD np.dot(A, Dn) return AD这里_normalize_digraph做的是按列归一化目的是让每个节点聚合邻居特征时不会因为邻居数量不同而产生数值偏差。实际操作中原包会根据hop_dis计算每个节点在max_hop范围内的邻居关系生成带自环的邻接矩阵。max_hop一般取 1也就是只看直接相连的关节取 2 就会把隔一个关节的间接邻居也纳入聚合范围感受野变大但参数量和过拟合风险同时上升。我在拿到这个包后习惯先不急着训练而是打印A的 shape 和稀疏度。如果发现A全零或者 shape 和num_node对不上一定是前面的edge定义和实际关节数不匹配。这个包里有info目录里面保存了 NTU 和 Kinetics 两种骨架的连接关系定义换数据集时必须同时换图定义和 feeder 的关节映射两者缺一个模型就跑不出合理结果。2.2 时间维度的卷积kernel_size 和 stride 对识别精度的影响空间图卷积处理的是“单帧内关节之间的关系”但动作识别本质上要看“关节随时间怎么运动”。ST-GCN 在空间卷积之后接一维时间卷积沿着帧维度滑动。st_gcn.py里的kernel_size参数默认是 9意味着每一层时间卷积覆盖 9 帧的上下文。# st_gcn.py 中时间卷积的典型结构 self.tcn nn.Sequential( nn.Conv2d( out_channels, out_channels, kernel_size(9, 1), # 时间维度卷积核为 9 stride(1, 1), padding(4, 0), # padding 保证帧数不缩减 ), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue), )注意padding(4, 0)是为了让时间维度长度保持不变这样多层的输出帧数能对上后续全连接层需要的特征维度。如果你要自己调kernel_size必须同步改 padding否则运行到一半会报维度错误。这种维度错误在代码里表面上是 tensor 尺寸不匹配实际原因大概率是你改了模型参数但没同步改 padding。经验上kernel_size9对 NTU 数据集是比较稳的默认值。调大到 13 能捕捉更长的动作序列但对实时推理帧率有影响调小到 5 适合短促动作如挥手、踢腿但连续动作容易丢上下文。训练自己的数据时我建议先固定kernel_size9把精力放在数据预处理上动作识别的大头误差通常来自骨骼数据本身的噪声。2.3 单流与双流模型骨骼流是关节流的差分信息增强这个包里的st_gcn_twostream.py对应双流模型。单流只送关节坐标每个关节的 x、y双流额外送骨骼向量对每条边用子关节坐标减去父关节坐标得到向量。骨骼向量描述的是肢体朝向和长度和关节坐标互补比如“手臂抬起”这个动作关节流看到的是手的位置变化骨骼流看到的是手臂向量从向下变成向上信息更直接。# feeder.py 中计算骨骼特征的常见写法 # data 是 (num_frame, num_joint, 2) 的关节坐标 bone_data np.zeros_like(data) for v in range(num_joint): parent parent_index[v] if parent ! -1: bone_data[:, v, :] data[:, v, :] - data[:, parent, :]代码里parent_index是预先定义好的父子关系表NTU 骨架的 25 个关节每个都有明确的父节点。这里有一个容易忽略的细节如果父节点坐标存在噪声骨骼向量会被放大因为差分运算会叠加两倍的噪声。所以双流训练时数据增强策略要保守一些关节流可以做的平移扰动骨骼流上最好别做否则误差会非常难看。双流模型在推理时会对关节流和骨骼流的预测分数做加权融合默认权重是 50/50。实际项目中我发现关节流权重稍高一点效果更稳原因是骨骼向量对骨架提取器比如 OpenPose的抖动更敏感。如果你用的是这个包里的预训练权重先别动融合权重跑通之后再做消融。2.4 三个预训练权重怎么选OriginSTGCN.pt 和 AddEdgeSTGCN12345.pt 的边界models目录下有OriginSTGCN.pt、AddEdgeSTGCN12345.pt和kinetics-st_gcn.pt三个权重文件。kinetics-st_gcn.pt是在 Kinetics-Skeleton 上训练的类别是 400 类日常动作适合做通用特征提取。OriginSTGCN.pt对应原始 ST-GCN 在 NTU-RGB-D 上的分类结果类别是 60 类。AddEdgeSTGCN12345.pt是别人加了自定义边权重之后重新训练的模型用途上和原始模型等价但如果你要加载这个权重模型定义里必须包含对应的边权重参数层否则加载时会出现 missing key。我一般这样选跑 demo 验证流程用kinetics-st_gcn.pt因为类别覆盖面广随便一个动作视频都能命中一个近似类别做 NTU 相关课程设计用OriginSTGCN.pt想对比边权重改进效果才用AddEdgeSTGCN12345.pt。加载权重时注意strictFalse的问题需要具体看日志如果是测试阶段直接加载完整权重就用默认strictTrue它会帮你检查模型结构和权重文件是否完全一致。3. 复现三部曲环境依赖、NTU 数据生成与配置文件的完整部署流程3.1 环境安装requirements.txt 里的依赖并不是越多越好这个包的根目录有requirements.txt但我的习惯是只装核心依赖不照单全收。核心依赖就这几个PyTorchCPU 或 GPU 版、numpy、opencv-python、tqdm、pyyaml。项目里用到的torchlight目录其实是自带的轻量工具模块不需要额外安装如果你看gpu.py会发现它自己做 GPU 显存查询依赖 pynvml那也只需要在 GPU 机器上装。# 建议的安装方式先建虚拟环境再装依赖 python -m venv stgcn_env source stgcn_env/bin/activate pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy opencv-python tqdm pyyaml pip install pynvml # 仅在 GPU 环境下需要PyTorch 版本建议不低于 1.8因为这个包里的st_gcn.py使用了nn.Conv2d和nn.BatchNorm2d等标准模块没有依赖新版特性版本太新反而可能出现 API 弃用警告但不影响运行。CPU 机器也能跑推理只是实时 demo 会很卡训练就别想了。我踩过的一个小坑是 opencv 的版本和 numpy 的兼容问题opencv-python 新版本对 numpy 版本有硬性要求如果装完出现np.float报错说明 opencv 和 numpy 版本冲突把两者都升级到最新版即可。这种问题在日志里看起来像是代码 bug实际上是环境问题先查版本再查代码。3.2 ntu_gendata.py 数据生成输入原始数据输出 feeder 能吃的 npzntu_gendata.py负责把 NTU-RGB-D 的原始骨骼数据转换成模型训练需要的格式。NTU 原始数据是.skeleton文件里面保存每一帧每个关节的三维坐标和置信度。gendata脚本要做的事可以概括成三步读原始文件、按固定格式组织成帧序列、保存为.npz。kinetics_gendata.py做的事情类似只是输入输出格式对应 Kinetics 数据集。# 以 NTU 数据处理为例运行方式 python ntu_gendata.py --data_dir /path/to/ntu_raw_skeleton \ --output_dir ./data/ntu \ --mode train关键参数是mode。train 模式会按训练集划分文件列表val 模式则用验证集划分。这个包在NTU-RGB-D目录下存放了标准的样本划分文件如果你没有原始数据集至少要把目录结构保留好让 feeder 能找到路径。换个说法ntu_gendata.py本身不下载数据它只负责转换数据源必须你自己准备。转换完成后生成的.npz文件结构大概是data、label两个键data的 shape 是(num_samples, num_frame, num_joint, 3)。这里第三个维度是 3 是因为 NTU 原始数据是三维坐标。但模型输入通常只取二维坐标feeder 里会做一次切片只保留前两维。这个地方如果直接拿原始数据喂模型维度会对不上而 feeder 已经帮你处理好了前提是你不要自作聪明去改 feeder 的这段逻辑。3.3 config 目录训练参数和模型路径如何对齐这个包的config目录下能看到st_gcn.twostream和st_gcn两个配置组。配置以 yaml 格式保存训练时main.py会读取 yaml 里的参数并组装模型。一个最关键的配置项是weights它指向预训练权重的路径。# config/st_gcn.twostream/train.yaml 中的典型配置片段 model_args: in_channels: 3 num_class: 60 edge_importance_weighting: true num_layers: 10 train_args: batch_size: 64 base_lr: 0.1 step: [30, 40] device: 0 weights: models/OriginSTGCN.pt这里in_channels: 3对应关节坐标加置信度一共三个通道如果你的数据只有二维坐标需要改成 2 或者保留置信度维度变成 3这个必须和 feeder 输出对齐。num_class对应类别数NTU 是 60Kinetics 是 400。edge_importance_weighting是 ST-GCN 的一个核心特性每个图卷积层会学习一个与邻接矩阵同形状的注意力权重矩阵对边的重要性加权。这个选项建议保持true因为它是 ST-GCN 相对早期 GCN 的重要改进。我拿到任何动作识别源码包后的第一个动作永远是先检查weights路径和模型定义是否对应再跑训练。因为这个包同时提供了三个模型权重而且st_gcn.py和st_gcn_twostream.py是两套模型定义如果你用双流配置去加载单流权重报错信息会很直接但如果你用单流配置去加载双流权重里的关节流部分报错信息就可能变得很隐晦。4. 训练与推理实操demo_offline 和 demo_realtime 的三种运行方式4.1 离线推理参数跑通一个视频文件需要哪些输入demo_offline.py是离线推理入口输入一段视频输出动作类别和置信度。这个流程看起来简单实际链路很长视频先经过骨架提取模型拿到每帧关键点坐标再经过 ST-GCN 分类最后把结果写回视频帧上。包里的pose目录和recognition目录就是这两个阶段的代码实现。# 离线推理的标准命令行 python demo_offline.py \ --video ./resource/demo_asset/sample_video.mp4 \ --weights ./models/kinetics-st_gcn.pt \ --config ./config/st_gcn/kinetics.yaml \ --output ./work_dir/result.mp4我在本地跑这个命令时最常遇到的错误是权重路径和配置文件不一致。比如视频是日常动作但配置文件里num_class: 60加载kinetics-st_gcn.pt这个 400 类权重就会在最后的全连接层维度上报错。所以无论从哪个 demo 入口进去都需要先想清楚这个视频对应的动作类别空间是什么。离线推理的输出视频其实包含了两层信息画面左上角标的动作名以及每一帧上关键点的绘制连线。如果你想看模型内部的特征图processor/recognition.py里有 debug 相关的选项可以输出中间层的结果图。这个功能对排查“为什么模型把 A 动作识别成 B 动作”非常有用比如很多时候是骨架提取阶段把左右手搞反了导致后续分类全乱。4.2 实时摄像头推理demo_realtime.py 的帧率瓶颈和参数调整demo_realtime.py走的是摄像头输入流程和离线版一致差别在于每帧都要跑骨架提取和分类速度压力大很多。如果你的机器没有 GPU这个 demo 基本只能看到幻灯片级别的帧率因为骨架提取模型本身就是个深度网络CPU 上单帧推理可能需要几百毫秒。# 实时推理命令采集摄像头 0 python demo_realtime.py \ --cam_id 0 \ --weights ./models/kinetics-st_gcn.pt \ --config ./config/st_gcn/kinetics.yamlcam_id是摄像头编号笔记本一般内置摄像头是 0外接摄像头可能是 1 或 2。如果启动后有窗口但一片黑大概率是摄像头编号选错了可以写两行代码列出当前机器所有可用摄像头编号。实时 demo 的识别延迟主要卡在骨架提取上常见做法是每隔 N 帧才做一次骨架提取中间帧直接复用上一帧的结果这样能把帧率提升 N 倍代价是动作响应有延迟。我在调实时 demo 时发现另一个容易被忽略的细节视频帧的缩放比例。骨架提取模型通常输入是固定尺寸比如 256x256如果你的摄像头分辨率是 1920x1080代码会把帧压缩再送入模型输出关键点坐标需要缩放回原分辨率才能画对位置。这个缩放逻辑如果写错骨架线条会明显错位但不是完全乱这时候别急着怀疑模型先检查坐标映射。4.3 训练自己的数据main.py 和 processor.py 的调用链训练入口是main.py它会读取 yaml 配置实例化 feeder、模型和 processor然后进入训练循环。processor 里定义了每个 epoch 的训练和验证逻辑包括学习率调整、模型保存和日志打印。整个调用链不复杂但参数传递链路很长改配置时容易顾此失彼。# 从头训练一个 ST-GCN 模型 python main.py --config ./config/st_gcn.twostream/train.yaml训练开始前需要确认两件事一是 feeder 能正常加载预处理后的数据文件二是有足够的显存。这个包的models目录下已经有现成权重所以我建议的路径是先用预训练权重做推理确认流程没问题再尝试微调最后才从头训练。从头训练 NTU 上的 60 类模型在单张 24GB 显存的卡上大概需要几十个小时没有这个资源的同学直接跑预训练权重做迁移学习就够了。训练过程的日志在work_dir目录下生成每轮会打印 loss 和 top1 准确率。如果你发现验证准确率很低且不升先检查训练集和验证集是否重叠了这是数据划分的问题如果 loss 下降正常但验证波动很大那是过拟合减少训练轮数或者调小 batch size 比加正则更直接。5. 避坑记录权重加载失败、数据路径错误与显存不足的五个案例5.1 现象加载 .pt 权重时报 key 不匹配的 RuntimeError现象运行demo_offline.py或训练脚本加载权重时报错Missing key(s) in state_dict或者unexpected key有时还带一堆size mismatch。原因这个包里有多个模型权重OriginSTGCN.pt对应单流模型st_gcn.pyAddEdgeSTGCN12345.pt对应加了边权重的变体kinetics-st_gcn.pt对应 Kinetics 数据集的类别数。用错配置文件会导致模型结构里的全连接层输出维度与权重文件不一致边权重相关层缺失也会触发这个错误。解决先确认你用的配置文件和权重文件是一套。单流模型加载单流权重双流配置加载双流权重。如果确认对应关系无误但还是报错打印一遍模型的state_dict的 key和权重的 key 逐个对照定位到具体层后再决定是改代码还是换权重。我最后的一招是干脆不用预训练权重用随机初始化跑通流程排除权重文件本身损坏的可能。5.2 现象ntu_gendata.py 报找不到输入文件或路径为空现象运行数据预处理脚本时提示FileNotFoundError或者输出目录为空日志显示读取到的文件列表长度为 0。原因脚本里的--data_dir参数指向了错误路径或者目录下没有.skeleton文件。另一种可能是你在 Windows 上运行但路径分隔符处理逻辑是 Linux 风格。解决先用ls或资源管理器确认数据目录下文件确实存在再检查脚本里的文件后缀过滤逻辑。Windows 环境下建议把路径里的反斜杠转成正斜杠或者在命令行传参时用绝对路径。这个坑属于低级的路径问题但最容易让人在代码层面瞎找半天。5.3 现象demo_offline 跑完输出视频打不开或者全是黑帧现象推理过程没有报错日志正常打印了每个动作的类别和置信度但输出的 mp4 文件用播放器打开是黑屏或者文件损坏。原因opencv 的 VideoWriter 对编码器有平台依赖。在 Linux 上默认用的 mp4v 编码在某些环境下可能没有正确注册导致写出来的文件没有关键帧播放器无法解码。解决换用 avi 格式输出或者指定更通用的编码器。代码里输出格式是在文件后缀和写入参数里控制的把输出路径从.mp4改成.avi通常能立刻解决。如果还有问题检查输出视频的分辨率是否和 VideoWriter 初始化时传入的分辨率一致不一致也会导致文件打不开。5.4 现象训练时显存不足batch size 调到 16 还是 OOM现象训练脚本开始后不久显存占用直接拉满报CUDA out of memory。原因ST-GCN 虽然模型不大但中间特征图的尺寸和num_frame、num_joint以及层数直接相关。如果你把num_frame设置的很大而没减少 batch size显存会很快爆掉。多个进程同时占用 GPU 也会造成这种情况。解决双管齐下。一是调低batch_size到 8 或 4 并同步调低base_lr因为 batch size 减小后学习率不变会导致收敛不稳二是检查是否有其他程序占用显存用nvidia-smi看训练前把不用的进程清掉。如果显卡只有 8GB 显存建议num_frame不要超过 300层数保持默认的 10 层。5.5 现象实时 demo 窗口黑屏但进程没退出现象demo_realtime.py能启动窗口但画面全黑日志也没有报错。原因摄像头索引不对或者摄像头被其他程序占用。索引不对时 opencv 的VideoCapture(0)不会报错只会持续返回空帧表现就是黑窗。解决先用一行代码验证摄像头编号是可以出画面的再传入正式命令。如果摄像头被占用关掉占用程序或者换个编号。这个是我做过好几次的体感式操作黑窗第一反应查摄像头第二反应查帧的缩放映射基本不涉及模型本身。6. AddEdgeWeight 二次开发边权重修改、微调与验证技巧这个包里有一个AddEdgeWeight_2.txt和对应的AddEdgeSTGCN12345.pt主题就是给骨架图加入可学习的边权重。原始 ST-GCN 的edge_importance_weighting已经做到了全局的边加权但它是所有层共享一个权重矩阵粒度不够细。AddEdgeWeight 的思路是给每一层都配备独立的边权重矩阵让浅层关注躯干连接、深层关注四肢末端的精细运动。# 在 st_gcn.py 中扩展边权重的一个常见实现 # 原始代码中每个 ST-GCN Block 的 attention 矩阵是全局共享的 # 扩展后为每一层单独创建权重矩阵 self.edge_importance nn.ParameterList([ nn.Parameter(torch.ones_like(A) * 0.01) for _ in range(num_layers) ])其实这个改进的原理并不复杂骨架图里的边重要性在不同层级完全不同。肩膀和脊柱的连接在浅层网络中重要性最高因为它们决定了人体整体姿态而手指和脚趾的关节连接在深层网络中才体现出区分度。全局共享权重相当于强行让所有层用同一套重要性判断限制了模型表达能力。每层独立的边权重矩阵让网络自己按层级去学这也是AddEdgeSTGCN12345.pt比OriginSTGCN.pt有提升空间的原因。我做微调时的习惯是不从头训练加载OriginSTGCN.pt权重然后把新增的edge_importance参数的学习率调得比主干网络大 5 倍冻结主干的前几层只训练后半部分和图卷积层。这样能在较少数据和较短时间内看到改进效果。验证这个改动的最直接方法不是只看 top1 准确率而是分别对同一批测试视频跑OriginSTGCN.pt和微调后的模型输出每个类别的置信度分数看分数分布是否更集中。如果改进有效正确类别的置信度分数会系统性提高错误类别的分数会下降。我通常会写一个小脚本统计两个模型在验证集上的置信度均值、方差以及 top1 和 top5 的差值变化。另一个实用技巧是可视化边权重矩阵的热力图。训练收敛后把edge_importance参数导出来观察哪些边在浅层被赋予了高权重哪些在深层才被激活。如果发现躯干边的权重在深层依旧很高说明模型没有学到你预期的层级结构可以尝试调整层数或者在时间卷积部分的感受野上做点手脚。我从这个包里学到的最大教训就是模型改进必须和可视化验证同步进行不然你根本不知道权重矩阵学到的到底是什么。从那以后我拿到任何图卷积模型都会先做一次边权重的可视化再谈训练调参这一步能省掉大量盲目尝试的时间。希望这套流程对你跑通这个项目也能派上用处。本文还有配套的精品资源点击获取
返回列表