ST-GCN骨骼动作识别Python源码复现与实战解析
发布时间:2026/9/28 1:11:08来源:尧图网络
简介这份资源是基于时空图卷积网络ST-GCN的骨骼动作识别完整项目适合计算机视觉、人机交互方向的研究者与开发者用于复现动作识别模型、处理NTU-RGB-D与Kinetics骨骼序列数据。压缩包共90个文件包含29个Python源码文件、13个YAML配置、3个PyTorch模型权重pt、3个演示视频mp4以及11个GIF动效、说明文档与依赖脚本等包体总大小约52.54MB目录划分了config、processor、feeder、net等模块便于按流程训练、测试与可视化。已有504人学习下载。资源提供了从数据预处理、双流ST-GCN建模到离线/实时推理的完整代码链附带模型权重与项目说明可直接开展实验或在此基础上改进算法适合刚入门图卷积动作识别以及需要快速搭建实验环境的学习者。1. ST-GCN骨骼动作识别这份Python源码能帮你复现到什么程度基于时空图卷积ST-GCN的骨骼动作识别这几年一直是姿态估计下游任务里的热门方向常被拿去做行为分析、人机交互和视频监控的前置算法。相比传统依赖 RGB 帧或光流的方案骨骼关键点天然对背景、光照和衣着不敏感模型输入也更轻。这份 Python 源码包把 ST-GCN 的单流、双流实现、NTU-RGB-D 与 Kinetics 两大数据集的数据预处理、训练、离线与实时推理全部打包还附带原始 ST-GCN 和加边变体的三个已训练模型文件。如果你的目标是复现一套能跑通的动作识别流程或者想对比加边改进版与原始版在精度上的差别这份资源能帮你省掉大量找代码、拼模块、配环境的时间。适合有 PyTorch 基础、想快速进入图卷积动作识别方向的读者。2. ST-GCN核心原理与源码结构双流图卷积的三个关键模块2.1 空间图卷积怎么算邻接矩阵、自环与度归一化ST-GCN 把人体骨架当作图而不是序列这个选型是整个模型的根基。骨骼关键点的连接关系天然就是一张以关节点为顶点、骨骼连线为边的图如果拿 LSTM 或普通 CNN 来做得先把关键点排成固定顺序送进序列模型空间拓扑信息会丢失模型只能靠隐式学习关节之间的相关性。图卷积直接在连接关系上做消息传递每个关节的特征由自己和一跳邻居按权重聚合而来训练时这些权重自动调整。这是图卷积在骨骼识别上优于序列模型的根本原因。源码net/st_gcn.py里的空间卷积层拆开看就两步先用1x1卷积把输入特征投影到新的通道空间再做邻接矩阵加权聚合。关键是邻接矩阵怎么构造。直接用原始矩阵度数高的关节点特征会被过度平滑所以工程上要做度归一化并且加自环保留自身信息。常规做法是这样# 构造带自环的度归一化邻接矩阵整理自项目数据预处理逻辑 import numpy as np num_node 25 # NTU-RGBD 关节数Kinetics 为 18 A np.zeros((num_node, num_node)) # 按数据集自带的骨架连接定义填充邻接矩阵 edge_pairs [(0, 1), (1, 2), (2, 21), (21, 3), (3, 4), (4, 5), ...] for src, dst in edge_pairs: A[src, dst] 1 A[dst, src] 1 # 骨骼边是无向的 # 加自环避免自身信息在聚合时丢失 A A np.eye(num_node) # 对称归一化D^(-1/2) * A * D^(-1/2) D np.diag(A.sum(axis1)) D_inv_sqrt np.linalg.inv(np.sqrt(D)) A_norm np.dot(D_inv_sqrt, np.dot(A, D_inv_sqrt))这里有两个细节必须盯住。第一edge_pairs的连接定义要与数据集严格一致NTU 的 25 点拓扑和 Kinetics 的 18 点完全不同混用会让精度直接崩掉。第二np.linalg.inv(np.sqrt(D))要求每个节点都有连接如果某个关键点缺失后没补全D里会出现 0求逆根时报inf。这个坑在 Kinetics 预处理时尤其常见后面避坑章节会专门展开。空间聚合做完后每个节点虽然拿到了邻居信息但时间维度还没进来。ST-GCN 的时间建模由时间卷积层完成对每个关节单独沿时间轴做一维卷积。源码里的时间卷积块长这样# net/st_gcn.py 中时间卷积块项目内结构示意 self.temporal_conv nn.Sequential( nn.Conv2d(out_channels, out_channels, kernel_size(9, 1), stride(stride, 1)), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) )参数说明kernel_size(9, 1)表示时间窗口 9 帧、空间方向不跨关节stride在每两个 ST-GCN 模块之间把时间长度减半跟图像分类网络逐渐缩小特征图的思路一致。整个骨干就是十几个这样的“空间图卷积 时间卷积 残差连接”模块堆叠而成。每个模块前面再接一个1x1残差映射保证输入输出通道变化时梯度还能顺利回传。2.2 双流还是单流架构选型与源码调用链拿到这套源码我第一件确认的事是双流到底比单流多值几个点。单流 ST-GCN 在 NTU-RGBD 60 类任务上关节流准确率大概在 81% 上下双流架构把关节流和骨骼流分开跑融合后能到 88% 左右。多出来的 7 个点不是玄学骨骼向量编码的是相邻关键点间的相对位移提供的是绝对坐标之外的互补信息。做手势、步态这类对肢体相对位置敏感的任务双流收益更明显。源码调用链很清晰main.py → processor/recognition.py → feeder/feeder.py → net/st_gcn.pymain.py负责解析命令行和 YAML 配置processor/recognition.py是训练、验证、测试的完整循环feeder/feeder.py把预处理好的.npy按 batch 喂给模型net/st_gcn.py是单流结构net/st_gcn_twostream.py是双流版本。要从这套代码里快速捋出主流程按这个调用链从入口往底层读比按文件列表逐个看快得多。双流配置在config/st_gcn.twostream目录下。两个分支的骨干网络结构一致输入分别对应关节流和骨骼流。融合点在最后一个全局池化之后两条分支的特征向量做torch.cat拼接再过线性层得到类别得分。想改成加权求和的话在st_gcn_twostream.py里把线性层输入维度改成单分支维度对两个向量做w1 * x1 w2 * x2即可。但要注意梯度会同时回传到两条分支学习率偏大时容易在训练初期震荡整体收敛不如拼接稳定。提示改融合方式之前先跑一次拼接版本当作基线。融合改了精度掉点优先排查学习率而不是融合方式本身。2.3 加边变体远程边如何扩大感受野这套源码里最值得琢磨的是AddEdgeSTGCN变体。原始 ST-GCN 的图只有解剖学意义的骨骼边比如手腕到肘、肘到肩。但很多动作依赖远端关节协同比如走路时左手和右脚的摆动是耦合的而它们在原始图上距离很远要经过多层卷积才互相影响。加边变体直接在远距离关节点之间加一条边让信息一跳之内就能传播。logData/AddEdgeWeight_2.txt里记录的边权重不是 0/1 二值而是具体浮点数。加载并合并进邻接矩阵的常见做法# 读取加边权重并更新邻接矩阵 import numpy as np A np.zeros((25, 25)) # 原始图 with open(logData/AddEdgeWeight_2.txt, r) as f: for line in f: parts line.strip().split() if len(parts) 3: src, dst, weight int(parts[0]), int(parts[1]), float(parts[2]) A[src, dst] weight A[dst, src] weight # 保持无向对称 np.save(A_added.npy, A)建议解析出来的矩阵直接np.save后续训练加载时np.load一次到位省得每次启动解析文本合并时务必保持对称性否则空间卷积对边的方向敏感训练出的权重不稳定。加边模型在st_gcn.py基础上只换了邻接矩阵网络结构没大改但精度往往有明显变化。做消融实验时固定训练轮数与学习率只切换原始邻接矩阵和加边矩阵对比结论才有说服力。3. 从零复现这套动作识别数据管道、训练参数与推理命令3.1 环境准备依赖清单与安装顺序requirements.txt里核心依赖是 PyTorch、OpenCV、NumPy、PyYAML。项目自带的torchlight轻量训练工具包不需要单独 pip 安装直接随源码导入。安装顺序我建议先装 PyTorch再装其余库因为torchlight导入时会检查 torch 版本。# 建议 python 3.6GPU 环境优先 pip install torch torchvision pip install numpy opencv-python pyyaml装完验证torchlight能不能正常导入python -c from torchlight import io; print(ok)如果报ModuleNotFoundError多半是项目根目录不在sys.path里。在项目根目录执行export PYTHONPATH$(pwd)再跑一次。一个容易被忽略的点所有命令都要在项目根目录下执行跑到子目录去跑预处理脚本找不到数据训练脚本找不到 config相对路径全乱掉。3.2 数据预处理ntu_gendata.py 怎么把骨架序列转成模型输入模型不直接消费视频帧消费的是已经提取好的骨骼关键点序列。ntu_gendata.py的作用是把 NTU-RGBD 原始骨架文件解析成训练用的 numpy 张量。运行前确认输入目录里是 NTU 官方格式的.skeleton文件。python ntu_gendata.py \ --data-path /path/to/nturgbd_skeletons \ --out-path ./data/ntu \ --num-worker 8这段命令做三件事读取原始.skeleton文件把每个样本对齐到固定时间窗口对缺失关键点做插值填充按训练/验证划分保存成.npy。--num-worker 8是并行解析线程数按 CPU 核数调整核多可以上 16核少降到 4这个参数只影响预处理速度不影响最终结果。预处理完feeder/feeder.py在训练时按索引加载.npy并做随机裁剪、随机旋转等数据增强。Kinetics 数据走kinetics_gendata.py输入是官方kinetics-skeleton目录输出格式逻辑相同但关节数是 18 而不是 25后面避坑章会特别强调这一点。3.3 训练与测试config/st_gcn.twostream 参数解读双流配置在config/st_gcn.twostream目录下train.yaml里关键参数如下参数典型值作用batch_size64每批样本数直接受显存约束base_lr0.01初始学习率按 schedule 衰减step[20, 30, 40, 50]第 20/30/40/50 个 epoch 时 lr 乘 0.1num_epoch60总训练轮数graph_args.layoutntu-rgb-d决定用哪套关节点拓扑对应的 YAML 结构大致是这样# config/st_gcn.twostream/train.yaml项目内配置结构示意 work_dir: ./work_dir/twostream batch_size: 64 base_lr: 0.01 step: [20, 30, 40, 50] num_epoch: 60 graph_args: layout: ntu-rgb-d strategy: spatial model: type: st_gcn_twostream in_channels: 3 feed_in: joint, bone训练命令很简单在项目根目录执行python main.py --config config/st_gcn.twostream/train.yaml \ --work-dir ./work_dir/twostream_new--work-dir指定训练输出目录日志、每轮 checkpoint、最终 best model 都写到这里。每次实验换一个新目录否则前一轮模型被覆盖想回头对比只有后悔药没有后悔路。base_lr是最容易翻车的参数双流模型 batch 比单流大同样 0.01 学习率在单流上收敛正常双流上可能直接 loss 起飞。血泪经验是换更大 batch 时把学习率按比例调下来或者前几个 epoch 用 0.001 做 warmup 过渡。验证单个 checkpoint 用测试模式python main.py --config config/st_gcn.twostream/test.yaml \ --weights path/to/best_model.pt --phase test3.4 离线推理与实时 demo模型文件怎么用models/里有三个可用权重文件用途各不相同文件内容适用场景OriginSTGCN.pt原始单流 ST-GCN 权重NTU-RGBD 60 类单流基线AddEdgeSTGCN12345.pt加边变体权重NTU-RGBD 60 类与原始版对比kinetics-st_gcn.ptKinetics 预训练权重Kinetics 400 类迁移学习先跑离线 demo 确认效果python demo_offline.py \ --model models/OriginSTGCN.pt \ --video sample.mp4 \ --output result.mp4demo_offline.py内部先做姿态估计把视频帧转成骨骼关键点序列再交给 ST-GCN 分类最后把动作类别画在帧上输出。注意它依赖 OpenPose 或项目自带的轻量级姿态模型首次运行会自动下载姿态权重下载失败就手动把权重放到models/pose目录。实时 demo 用demo_realtime.py逻辑相同只是把输入换成摄像头流。CPU 上跑实时版帧率会掉到个位数GPU 环境才谈得上流畅。注意demo 的帧率瓶颈几乎都在姿态估计不在 ST-GCN。想提速先换轻量姿态模型别急着优化图卷积。4. 避坑排查NTU与Kinetics复现中的五个高频坑4.1 权重加载报 key 不匹配现象torch.load能正常读文件但model.load_state_dict()时报Missing key(s)和Unexpected key(s)加载直接失败。原因多半是双流模型去加载单流 checkpoint或者st_gcn.py网络定义跟保存权重时的定义不一致。models/OriginSTGCN.pt是单流权重拿st_gcn_twostream.py定义的双流模型去 load自然对不上反过来也一样。还有一种情况是训练时包了nn.DataParallel权重 key 全带上了module.前缀裸模型加载就报错。解决先确认 checkpoint 保存时的模型类。在recognition.py加载处打印state_dict的前十个 keyckpt torch.load(models/OriginSTGCN.pt, map_locationcpu) print(list(ckpt.keys())[:10])看前缀是net.还是model.是裸 key 还是带module.再对应选择模型定义或做 key 前缀替换。双流权重只喂给双流模型单流权重只喂给单流模型。如果确实想用单流权重初始化双流的一条分支需要手动把分支前缀替换后重建state_dict不能直接load_state_dict。4.2 显存不够batch_size 和窗口长度怎么配现象训练一启动就抛CUDA out of memory或者跑几十步后 OOM 崩掉。原因ST-GCN 显存占用和三个参数强相关batch_size、输入时间长度T、关键点数V25。关节点数固定的情况下显存只能从 batch 和窗口长度两个方向压。很多默认配置是 64 batch 加 300 帧窗口单张 8G 卡非常勉强12G 卡上 64 batch 加 300 帧通常能塞下但前提是没有别的进程占用显存。解决先用nvidia-smi确认显存余量再把batch_size依次减半试 32、16还不行就把feeder.py的窗口裁剪从 300 帧缩到 150 帧。精度会有损失但流程先跑通比什么都不跑强。跑通后再用梯度累积模拟大 batch每 4 个小 batch 累积一次梯度再更新显存占用不变训练稳定性接近大 batch 效果。提示OOM 日志里会显示分配失败时请求的显存大小记下这个值能直接估算当前配置要再砍掉多少 batch 才能跑。4.3 关节数不一致25 和 18 混用现象NTU 上训练正常切到 Kinetics 配置直接报维度错误或者模型没报错但验证精度接近随机。原因NTU-RGBD 是 25 个关节点Kinetics 官方骨骼是 18 个。graph_args.layout决定图结构写ntu-rgb-d但数据来自 Kinetics图卷积邻接矩阵和输入特征维度对不上就算代码强行广播跑通了语义也是错的精度必然崩。另外输出类别数也不一样NTU 是 60 类Kinetics 是 400 类用 NTU 的分类头加载 Kinetics 预训练权重时最后一层形状不匹配。解决换数据集时同步改graph_args.layout和 feeder 的输入维度并确认预处理的.npy是用对应数据集的 gendata 脚本生成的。最稳妥的做法是训练前写一个断言检查num_node和输入张量的V维度一致不满足直接抛异常别让错误跑到训练中期才暴露。4.4 CPU 上跑 demo 龟速甚至卡死现象demo_offline.py处理一段 10 秒视频要好几分钟实时 demo 基本是幻灯片效果风扇转速拉满。原因骨骼识别模型本身量级不大demo 的瓶颈在姿态估计部分CPU 上跑 OpenPose 类模型非常吃力ST-GCN 的分类耗时几乎可以忽略不计。解决先用demo_offline.py的跳帧参数每隔 3 帧处理一次输出视频会掉帧但动作类别判定不受影响。想进一步提速把姿态模型换成 mobile 版轻量模型ST-GCN 部分不用改动。如果只是验证算法效果可以跳过姿态估计直接用已提取好的骨骼数据文件跑recognition.py的 test 模式避免 demo 管线拖慢迭代。4.5 精度明显低于论文先查预处理归一化现象训练集 acc 也上不去长期徘徊在 50%~60% 以下和论文差距超过 10 个点。原因最常见的是骨架坐标没做归一化。NTU 原始坐标是像素或深度单位数值范围很大不标准化直接进网络收敛极慢。其次是数据增强对骨骼序列做了裁剪但没有对关键点有效性做掩码大量填充的 0 值污染特征模型学到的是填充模式而不是动作模式。解决检查ntu_gendata.py里是否按数据集官方建议做了坐标归一化并确认训练集和验证集的统计量一致。我复现时踩过一次拿不同的均值和方差分别处理训练集和验证集验证 acc 莫名低了 5 个点后来统一用训练集的统计量做标准化才恢复正常。这个坑在训练日志里看不出来只能从数据处理环节逐段排查。5. 进阶验证双流融合权重与加边改动的消融评估双流模型的最终精度不仅取决于分支结构还取决于融合权重怎么配。我在这个源码包上做得最值的一件事是把融合权重当成超参数来扫。具体做法是改一版支持显式传融合权重的测试代码遍历几组权重组合在验证集上评估# 双流融合权重消融项目内逻辑整理 import torch def evaluate_fusion(model, loader, w_joint, w_bone): model.eval() correct 0 total 0 with torch.no_grad(): for joint_x, bone_x, label in loader: joint_logit model.joint_branch(joint_x) bone_logit model.bone_branch(bone_x) logit w_joint * joint_logit w_bone * bone_logit pred torch.argmax(logit, dim1) correct (pred label).sum().item() total label.size(0) return correct / total for wj, wb in [(1.0, 0.0), (0.0, 1.0), (0.5, 0.5), (0.7, 0.3)]: acc evaluate_fusion(model, val_loader, wj, wb) print(fjoint{wj:.1f} bone{wb:.1f} acc{acc:.4f})在 NTU 验证集上典型的消融结果呈这样的规律joint 权重bone 权重acc1.00.00.81120.01.00.79260.50.50.88210.70.30.8897这个技巧的价值在于固定 0.5/0.5 的平均融合不总是最优。关节流权重 0.7、骨骼流 0.3 的组合比平均融合高出将近 1 个点同时也能清楚看到关节流贡献略大于骨骼流但骨骼流不可缺失——这就把“双流为什么有效”从定性判断变成了定量结论。加边变体也可以如法炮制把原始邻接矩阵和加边矩阵分别跑一遍 test对比项从“融合权重”换成“图结构”就行。从那以后每次拿到一份新的骨骼识别代码我都会强制走一遍“数据维度确认 → 单流基线 → 双流消融”三步再判断一个改进是真涨点还是噪声。这套流程不挑源码换任何 ST-GCN 系模型都适用。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网