ARTICLE DETAIL

资讯详情

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

ST-GCN骨骼动作识别工程实战:数据链路、图卷积与双流模型解析

ST-GCN骨骼动作识别工程实战:数据链路、图卷积与双流模型解析 简介这是一份基于时空图卷积ST-GCN的骨骼动作识别Python毕业设计项目面向计算机相关专业学生可用于毕业设计、课程设计及期末大作业。项目提供完整源代码与配套文档代码含详细注释新手也能按步骤理解并部署。资源包共90个文件涵盖Python源码、yaml训练配置、预训练模型权重、演示动图与视频、数据处理脚本及说明文档大小52.61MB目录区分模型、工具、处理器等模块便于按需学习。目前已有354人学习下载项目为个人原创高分成果曾获导师认可综合评分98分。下载后简单配置即可运行既能帮助理解ST-GCN动作识别原理也可直接作为课设/毕设答辩演示或在此基础上扩展改进。1. python 毕业设计里的 ST-GCN 骨骼动作识别这份工程能直接跑重点在四条链路做骨骼动作识别方向的毕业设计很多人卡在同一个地方论文里 ST-GCN 的图卷积公式读得懂但真要去训练一个能用的模型光是把骨架数据组织成张量就要折腾很久。这份基于时空图卷积ST-GCN的骨骼动作识别工程不是只有模型代码的 demo而是从原始骨架数据解析、图卷积网络实现、双流分支设计、训练入口到离线/实时推理的完整项目。解压之后目录里能看到 feeder、processor、net、torchlight、config、models 这些标准工程分层还自带三个预训练权重文件。对做 python 毕业设计、课程设计的人而言最值钱的是它能让你跳过「从零搭训练管线」的过程直接沿着数据 → 网络 → 训练 → 推理这条链路把项目跑通再在这个基础上改网络结构。适合人群很明确需要交代码和文档的在校生以及想快速复现 ST-GCN 做对比实验的研究者。2. 把原始骨架数据喂进图卷积ntu_gendata 与 kinetics_gendata 两条预处理链路2.1 从 zip 结构先读懂数据侧文件的作用打开工程压缩包第一眼容易被一堆文件劝退但数据侧的线索其实很清晰。根目录下有ntu_gendata.py和kinetics_gendata.py两个数据处理脚本分别对应 NTU-RGB-D 和 kinetics-skeleton 两套公开数据集feeder目录里是feeder.py和feeder_kinetics.py两个加载器NTU-RGB-D信息目录和kinetics_skeleton目录存放数据集 infokinetics-motion.txt是 kinetics 侧预处理后的索引文件resource/demo_asset和demo_media是演示用的媒体资源。AddEdgeWeight_2.txt这个文件从命名看是作者做「加边」实验时记录的额外边权重信息后面会单独讲它的用途。这一层结构对应了 ST-GCN 工程的标准套路原始数据不直接进网络必须先做一次离线转换把官方格式的骨架序列转成统一形状的 numpy 张量再通过 feeder 在训练时按帧读入。两条数据管线各自独立NTU 走ntu_gendata.py feeder.pykinetics 走kinetics_gendata.py feeder_kinetics.py。它们的产物形状都是(C, T, V, M)即通道数、时间帧数、关节点数、人物数只是 C、V、M 的具体值不同。2.2 跑通 ntu_gendata.py从 .skeleton 到 (N, C, T, V, M) 的标准化张量NTU-RGB-D 官方发布的骨架文件是.skeleton格式每个文件按帧记录人体关节的三维坐标和置信度。ntu_gendata.py做的事就是把这些文本式骨架数据解析成模型能直接消费的数组。文件命名里有官方编码规则比如S001C001P001R001A001这串从中能解析出 subject、camera、setup、repetition 和 action class划分 cross-subject 或 cross-view 训练/验证集时全靠它。常见跑法是先下载好 NTU 原始骨架目录再执行转换python ntu_gendata.py \ --data_path /path/to/nturgbd_skeletons \ --out_folder ./data/NTU-RGB-D--data_path要指向存放所有.skeleton文件的根目录不是某个样本文件--out_folder是输出目录脚本会在这里生成训练集和验证集的张量文件以及 label 索引。转换完成后数据侧的输出是形状为(C, T, V, M)的序列其中C对应坐标维度x、y、z 加置信度V对 NTU 来说是 25 个关节点M是视频中最多出现的人数不足的用 0 填充。生成结果后建议顺手打印一段数据的 shape 验证一下import numpy as np data np.load(./data/NTU-RGB-D/train_data.npy, allow_pickleTrue) label np.load(./data/NTU-RGB-D/train_label.npy, allow_pickleTrue) print(data.shape) # 期望看到 (N, C, T, V, M) print(label.shape) print(np.unique(label)) # 类别 id 范围这里N是样本总数C一般是 3x, y, zT是时间帧数V是 25M是人物数。如果你的输出里C是 4 或 2说明工程版本里带了置信度通道或用了二维坐标后续接网络时要注意in_channels必须和这里对齐。2.3 feeder 层参数window_size、p_interval 与数据增强的取舍数据转换完只是第一步真正决定训练效果的是 feeder 层怎么采样。打开feeder/feeder.py核心参数有这么几个window_size控制时间窗长度num_person是最大人物数num_point是关节点数p_interval是采样区间[0.95, 1]random_choose、random_move、random_shift是数据增强开关。p_interval的设计逻辑是原始视频帧数不固定不能直接塞进 batch所以统一做 resize 到window_size。[0.95, 1]表示从序列的 95% 到 100% 区间内随机截取这个「留一点尾巴」的采样策略在实际动作识别里比从头硬截效果好因为很多动作的判别信息出现在动作结束阶段。训练阶段random_choose会随机挑选起始帧random_move会对坐标做小幅平移增强对相机视角的鲁棒性。这几个开关在毕设答辩里很值得展开讲因为它是「数据不影响模型结构但直接影响精度」的典型例子。kinetics 侧的feeder_kinetics.py逻辑类似区别在于 kinetics 骨架的关节点是 18 个而不是 25 个且每帧人数不固定预处理时要做更多的人数对齐和缺失关节填充。两条管线的差异可以归纳成一张对比表对比项NTU-RGB-Dkinetics-skeleton关节点数 V2518坐标通道 C3x,y,z3x,y,z数据格式.skeleton解析JSON 序列解析类别数60 或 120400 左右数据加载器feeder.pyfeeder_kinetics.py跑通数据链路的标志是用DataLoader加载一个 batch 后能拿到形状正确的(N, C, T, V, M)张量和对应的 label。到这一步模型还没碰但整个工程的「入口」已经通了。3. 图卷积算子与双流设计st_gcn.py 里邻接矩阵和残差块到底怎么算3.1 ConvTemporalGraphical用一维卷积模拟图卷积的形态与参数打开net/st_gcn.py第一眼看到的是ConvTemporalGraphical这个类。名字里有 Graph但实现里并没有真正的高斯图卷积而是用二维卷积实现的输入特征形状是(N, C, T, V)经过一个 1×1 卷积把通道从C映射到C_out然后通过A矩阵做邻域聚合。这样设计的原因很实际——图卷积的数学形式是Y A X W其中X是节点特征W是权重矩阵A是带自环的邻接矩阵。把W展开成 1×1 卷积把A作为固定的掩码乘到特征上就能借用 CNN 的并行计算能力同时又保留图结构的信息。st_gcn_block是这个文件的另一个关键类它把图卷积、时间卷积、残差连接、Dropout 和 ReLU 组装成一个基本块。时间卷积用的是普通Conv2d沿 T 维度滑动窗口捕捉帧间关系。前向过程大致是def forward(self, x, A): # x: (N, C, T, V)A: (K, V, V) res self.residual(x) x self.conv_t(x) # 图卷积沿 V 维度用 A 聚合 x self.conv_temporal(x) # 时间卷积沿 T 维度提取帧间特征 x x res # 残差连接 x self.relu(x) return x这里A的形状是(K, V, V)K是图划分的子集数NTU 默认用 distance 策略时K3对应向心、离心、静止三类邻居。A在初始化时就会做归一化D^{-1/2} A D^{-1/2}其中D是度矩阵这一步的作用是避免不同节点因为度数不同导致特征尺度不一致。很多新手直接拿原版A用不归一化loss 震荡得非常厉害问题就出在这。3.2 图的划分策略与 Attention为什么骨骼不适合直接用 CNN骨骼数据天然是图结构25 个关节点通过骨头连接空间关系由连接决定而不是由图像的像素网格决定。如果用普通 CNN卷积核的平移不变性假设会被打破——手在画面左边和在画面右边是同一个动作但像素位置完全不同CNN 需要大量数据才能学到这种平移不变性。图卷积用邻接矩阵替代卷积核节点之间的关系不再依赖绝对位置而是依赖拓扑连接这让它在关节角度变化、人体位移的场景下更鲁棒。st_gcn.py里还有一个容易被忽略的组件通道注意力模块SE 结构。它的作用是先对特征做全局池化再经过两个全连接层生成每个通道的权重乘回原特征。这个操作对骨骼识别尤其重要因为不同动作类别往往依赖不同关节的通道——踢腿动作更依赖腿部关节的响应挥手动作更依赖手臂关节。注意力机制相当于给网络一个「按需放大某部分关节特征」的能力这是纯 GCN 结构不具备的。3.3 st_gcn_twostream.pyjoint 流与 bone 流的合并逻辑net/st_gcn_twostream.py里的双流设计是这个工程比原版 ST-GCN 更进一步的地方。单流模型只输入关节坐标双流模型则额外输入骨骼信息。骨骼特征的构造逻辑是每一根骨头的特征等于它两端关节坐标的差即bone joint_child - joint_parent。这样网络同时看到「关节在哪」和「骨头怎么连」两个视角精度通常能比单流高 2 到 4 个百分点。双流的合并逻辑在工程里是这样写的# joint 流输出和 bone 流输出形状相同 joint_out model_joint(data_joint) bone_out model_bone(data_bone) # 两流各自过 softmax 再取平均 final_out F.softmax(joint_out, dim1) F.softmax(bone_out, dim1) final_out final_out / 2注意这里的细节两个流是独立的网络不是共享权重各自有完整的st_gcn_block堆叠。最后不是对 logits 直接相加而是对 softmax 后的概率相加再归一化这样避免某一流的数值范围压过另一流。训练时两个流可以联合训练也可以分开训再融合工程里默认走的是联合训练。如果显存不够可以先只训 joint 流再把 bone 流网络的权重用 joint 流的初始化这在原版实验里也被证明是可行的迁移技巧。4. 训练与权重复现config 解析、torchlight 入口和三个预训练 pt 的选择4.1 config yaml 的层级结构model、feeder、optimizer 三块分别控制什么工程用 torchlight 作为训练框架配置文件放在config/st_gcn和config/st_gcn.twostream目录下每个实验场景对应一个 yaml。先读懂 yaml 的三层结构训练基本就成功了一半model: type: st_gcn_twostream # 模型类名对应 net/st_gcn_twostream.py args: in_channels: 3 num_class: 60 num_point: 25 graph_args: strategy: spatial dropout: 0.5 feeder: type: feeder.feeder.Feeder args: data_path: ./data/NTU-RGB-D/train_data.npy label_path: ./data/NTU-RGB-D/train_label.npy window_size: 64 p_interval: [0.95, 1] optimizer: type: SGD args: lr: 0.1 momentum: 0.9 weight_decay: 0.0001 step: [30, 40]model.args里的in_channels必须和ntu_gendata.py输出的通道数一致num_class对应数据集类别数NTU 的 cross-subject 划分是 60 类。graph_args.strategy决定图的划分方式spatial表示按空间距离划分为三个子集这是 NTU 上最常用的策略。feeder.args里的data_path指向第 2 章生成的文件window_size设 64 意味每条序列会被采样到 64 帧这个值不是越大越好帧数过多会导致显存占用翻倍。4.2 从 main.py 到 processor.py训练循环与 checkpoint 逻辑torchlight 的入口在processor/main.py它会解析命令行参数再读取 yaml 配置。启动训练的标准命令是python processor/main.py \ --config config/st_gcn.twostream/nturgbd-cross-subject/train.yaml \ --work-dir ./work_dir/ntu_twostream--config指向要用的 yaml--work-dir是 checkpoint 和日志的输出目录。torchlight 的命令行优先级高于配置文件意味着如果你临时想改学习率不用编辑 yaml直接加--optimizer.args.lr 0.01就能覆盖。processor/processor.py里封装了train()和test()两个核心方法。train()的循环逻辑是每个 epoch 里取一个 batch 的数据和 label前向算出 loss反向传播更新权重每隔save_interval个 epoch 保存一次 checkpoint。test()则是在验证集上跑前向输出 top1 / top5 准确率和混淆矩阵。对毕设来说混淆矩阵是答辩时的加分项它能直观说明哪些动作类别容易被混淆比如「坐下」和「蹲下」在关节坐标上非常接近网络分不清是正常的这时候可以针对性看数据标注是否有问题。4.3 三个 pt 权重怎么选原始拓扑、加边拓扑与 kinetics 迁移models目录下有三个预训练权重这是整个工程里最容易被忽略但也最实用的资源OriginSTGCN.pt原始图拓扑下训练出的模型对应原版 ST-GCN 的结构是 baselineAddEdgeSTGCN12345.pt加边拓扑下训练出的模型作者在原版邻接矩阵的某些远处关节对之间额外加了边用 1、2、3、4、5 五组加边组合训练kinetics-st_gcn.pt在 kinetics-skeleton 上预训练过的权重类别数是 400。加载权重要注意对应关系OriginSTGCN.pt和AddEdgeSTGCN12345.pt是单流还是双流、输入通道和类别数是多少都要和当前加载的模型实例匹配。最容易翻车的操作是拿 kinetics 权重直接加载到 NTU 模型上因为最后一层全连接维度一个是 400 一个是 60会报size mismatch。常见做法是加载 kinetics 权重后用torch.load拿到 state_dict把最后一层fc的权重删掉或重新初始化只保留前面图卷积层的参数做迁移起点pretrained torch.load(./models/kinetics-st_gcn.pt) model_dict model.state_dict() pretrained {k: v for k, v in pretrained.items() if k in model_dict and fc not in k} model_dict.update(pretrained) model.load_state_dict(model_dict)这样操作保留了 400 类上学到的骨骼特征表达能力同时让最后一层适配 NTU 的 60 类。从工程里get_models.sh的存在可以推断这些权重本来也可以通过网络下载获取压缩包直接带上省去了联网下载的麻烦。reference_model.txt应该是作者记录的模型来源或实验备忘做文档时可以直接参考它的格式来写自己的实验记录。5. 常见问题与排查部署这类 ST-GCN 工程最容易翻车的 6 个地方5.1 高频报错逐条拆从 KeyError 到 size mismatch问题一运行ntu_gendata.py报文件名解析 KeyError。现象脚本跑到某个样本时抛出 KeyError说找不到对应的类别或 subject 信息。原因NTU 原始文件命名不合规范或者--data_path指到了错误的目录层级导致文件名里解析不出S、A这些关键字段。解决确认原始数据目录里是.skeleton文件直接平铺在根目录下而不是又套了一层子目录如果再报错直接打印出出错的文件名对照官方命名规范检查是不是下载了不完整的样本。问题二加载预训练权重报size mismatch或unexpected key。现象load_state_dict报错提示某几个 key 的维度对不上甚至直接说unexpected key in state_dict。原因用了 kinetics 的权重去加载 NTU 模型或者把单流权重加载到双流模型。解决先打印模型 state_dict 的 key再和权重文件的 key 做对比for k in model.state_dict(): print(k, model.state_dict()[k].shape)确认类别数、输入通道、双流结构都对齐后再加载。如果只是想用预训练权重跑一下 demo就挑对应的权重文件别混用。问题三demo_realtime.py跑不起来摄像头黑屏或 import 报错。现象启动实时 demo 后画面出不来或者抛出某个姿态估计库的 ImportError。原因实时 demo 依赖外部的姿态估计模型把摄像头画面转成骨骼坐标这个 zip 里没有包含这部分模型权重。解决先用demo_offline.py或recognition.py跑通离线推理链路确认模型本身没问题实时部分单独安装姿态估计依赖或者改用离线视频推理完成毕设演示没必要在答辩现场赌摄像头识别成功率。问题四训练时 loss 不降反升或直接显存溢出。现象训练跑了十几个 epochloss 还在原地打转或者启动训练就报 CUDA out of memory。原因window_size太大、batch_size太大、num_worker开得过多三者叠加把显存撑爆了。解决先把batch_size降到 8window_size降到 32确认能正常跑一个 epoch再逐步加回去。降低p_interval到[0.8, 1]也能显著缩短序列长度减轻显存压力。5.2 数据与配置层面的隐蔽问题路径、环境与显存问题五kinetics_gendata.py跑完但kinetics-motion.txt是空的。现象转换脚本执行成功没有报错但生成的 motion 索引文件没有内容后续 feeder 加载时读不到数据。原因kinetics 原始 JSON 里某些帧没有检测到人体或者关键字段缺失导致运动特征提取时被静默跳过。解决转换前先检查原始 JSON 的字段完整性写一小段脚本统计哪些样本缺失关节坐标把它们剔除后再跑转换这属于数据清洗范畴但骨架数据集里这个问题很常见。问题六PyTorch 版本太新导致接口不兼容。现象安装最新版 PyTorch 后运行报module torch has no attribute xxx或者某些 deprecated 接口警告刷屏。原因这份工程的代码基于较早的 PyTorch 版本写的部分 API 在新版本里被移除或改名。解决最省事的方案是创建一个独立环境装torch 1.10左右的版本跑通后不要随意升级。如果必须在 2.x 环境下跑就把报错的 API 按官方迁移文档手动改掉但这种改动容易引入新问题建议直接锁版本。6. 收尾技巧离线验证与边权调试的正确姿势6.1 先离线验证再谈实时recognition.py 的调用顺序拿到工程后我的习惯是先不碰 demo不碰训练先用recognition.py把「加载模型 → 读一段骨骼序列 → 输出预测类别」这条最短链路走通。recognition.py里封装了模型的组装和推理逻辑调用时注意模型的输入形状要和训练时一致# 伪代码示意实际以工程内接口为准 model build_model(config) model.load_state_dict(torch.load(./models/OriginSTGCN.pt)) model.eval() with torch.no_grad(): # data: 预处理好的 (1, C, T, V, M) 骨骼序列 logits model(data) pred_class logits.argmax(dim1).item() print(预测动作类别:, pred_class)这里关键点是model.eval()必须调用否则 BN 层和 Dropout 的行为会不一致导致同一段序列每次推理结果都不同。如果一个 batch 里只有一段序列也要保持五维输入的形状缺一维会直接报维度错误。6.2 边权调试的进阶动作对比原版与加边权重这个工程相比原版 ST-GCN 多了一个值得玩的点OriginSTGCN.pt和AddEdgeSTGCN12345.pt的对比就是一组天然的控制实验。从文件命名看作者在原始邻接矩阵之外对某些关节点对额外增加了边然后重新训练。AddEdgeWeight_2.txt记录的就是这些加边的权重信息DrawLine.py负责把加边后的图结构可视化。我建议拿到项目后做一件事加载两个权重对同一段骨骼序列分别推理把两次的 softmax 输出打印出来对比。你会看到原始模型在某个类别上的置信度是 0.6加边模型可能会变成 0.75这个差异就能成为你毕设论文里「拓扑改进」章节的素材——不是玄学是你自己复现出来的对比数据。如果差异不明显说明这段序列本身对额外边不敏感换一段涉及手脚协调的动作再试比如踢腿、挥手这类需要远端关节联动的动作。从那以后我每次拿到带预训练权重的骨架识别工程第一件事永远是先跑通离线推理链路再对比不同权重的输出差异因为推理链路不通后面所有实验都等于在摸黑。这个顺序看似简单能省下大量排查时间希望帮到你。本文还有配套的精品资源点击获取
返回列表