news 2026/10/7 5:25:56

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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目录存放数据集 info;kinetics-motion.txt是 kinetics 侧预处理后的索引文件;resource/demo_asset和demo_media是演示用的媒体资源。AddEdgeWeight_2.txt这个文件从命名看,是作者做「加边」实验时记录的额外边权重信息,后面会单独讲它的用途。

这一层结构对应了 ST-GCN 工程的标准套路:原始数据不直接进网络,必须先做一次离线转换,把官方格式的骨架序列转成统一形状的 numpy 张量,再通过 feeder 在训练时按帧读入。两条数据管线各自独立,NTU 走ntu_gendata.py + feeder.py,kinetics 走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/nturgb+d_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_pickle=True) label = np.load("./data/NTU-RGB-D/train_label.npy", allow_pickle=True) print(data.shape) # 期望看到 (N, C, T, V, M) print(label.shape) print(np.unique(label)) # 类别 id 范围

这里N是样本总数,C一般是 3(x, y, z),T是时间帧数,V是 25,M是人物数。如果你的输出里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
坐标通道 C3(x,y,z)3(x,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 策略时K=3,对应向心、离心、静止三类邻居。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.py:joint 流与 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, dim=1) + F.softmax(bone_out, dim=1) 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 的结构,是 baseline;
  • AddEdgeSTGCN12345.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 不降反升,或直接显存溢出。

现象:训练跑了十几个 epoch,loss 还在原地打转,或者启动训练就报 CUDA out of memory。原因:window_size太大、batch_size太大、num_worker开得过多,三者叠加把显存撑爆了。解决:先把batch_size降到 8,window_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(dim=1).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,这个差异就能成为你毕设论文里「拓扑改进」章节的素材——不是玄学,是你自己复现出来的对比数据。如果差异不明显,说明这段序列本身对额外边不敏感,换一段涉及手脚协调的动作再试,比如踢腿、挥手这类需要远端关节联动的动作。从那以后我每次拿到带预训练权重的骨架识别工程,第一件事永远是先跑通离线推理链路,再对比不同权重的输出差异,因为推理链路不通,后面所有实验都等于在摸黑。这个顺序看似简单,能省下大量排查时间,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 5:25:55

GPT-6模型家族选型与成本控制实战指南

1. GPT-6模型家族全景与选型思路拆解先说个背景。最近连续接了三个GPT-6相关的落地项目,发现一个很共性的问题:大家不是不会调接口,而是卡在最开始的“选型”上。GPT-6已经不是单一模型,而是一个覆盖多个规模、多种定位的家族&…

作者头像 李华
网站建设 2026/10/7 5:25:20

苹果设备端AI能力解析:1.6M参数背后的物理与工程逻辑

1. 这张表不是“性能排行榜”,而是苹果AI落地的路线图最近Apple官网悄然上线了一份名为《On-device AI Capabilities by Device》的公开文档,标题直白得不像苹果风格——“设备端AI能力对照表”。没有发布会、没有 keynote、甚至没配一张宣传图&#xff…

作者头像 李华
网站建设 2026/10/7 5:25:12

ROS机械臂导纳控制实战:从六维力传感器到柔顺操作

1. 项目概述:为什么导纳控制不是“加个力传感器就完事”的玄学导纳控制这个词,在ROS机械臂开发圈里常被当成高级操作的代名词,但实际落地时,90%的人卡在第一步——连“导纳”到底在控制器里干了什么都说不清楚。我带过三届机器人方…

作者头像 李华
网站建设 2026/10/7 5:24:19

智能体批量交付质量难?试试V模型工程化落地

最近一个月我连续参与了几批智能体项目的技术评审,一个现象特别明显:单看Agent的Demo,个个都能眼前一亮;一旦要求同批交付三五个Agent,团队就开始互相甩锅——写Prompt的说模型不稳定,做后端的说工具调用老…

作者头像 李华
网站建设 2026/10/7 5:24:18

DeepSeek Harness桌面版知识库工作流迁移实战与插件选型指南

1. 为什么我把主力知识库工作流迁到了 DeepSeek Harness 桌面版先说结论:DeepSeek Harness 桌面版不是一个"又一个 AI 客户端",它更像是一层把大模型能力、本地文件系统、插件生态和知识库工具串起来的胶水层。我用了大概三周时间,…

作者头像 李华