
简介基于时空图卷积ST-GCN的骨骼动作识别Python毕业设计项目面向计算机视觉方向的学生适用于毕业设计、期末大作业或课程设计。包含完整源代码与文档说明代码注释详细、部署简单新手也能较快掌握。资源共90个文件以Python脚本、YAML配置文件、GIF演示、文本说明及模型权重为主体压缩包约52.61MB涵盖了训练、数据预处理、离线与实时演示等模块。已有354人学习下载。项目还提供多种预训练模型.pt、网络结构prototxt、Shell脚本及MP4演示视频便于理解从骨架数据构建到模型推理的全流程也可直接用于复现和二次开发。整体目录设计清晰含工具脚本、特征处理、模型定义等分层模块是一套结构完整、可操作性强的动作识别参考实现。1. 基于时空图卷积ST-GCN的骨骼动作识别这份源码怎么用起来骨骼动作识别一直是毕业设计里“看着难、做起来有套路”的方向。你不需要处理原始图像也不需要跑目标检测只需要拿到人体关节点坐标序列就能用图卷积把“谁和谁有关联”学出来。ST-GCNSpatial Temporal Graph Convolutional Network是 2018 年提出的经典方案把人体骨架建模成时空图节点是关节边是骨骼连接和帧间连接在 NTU-RGBD 这类标准数据集上效果稳定源码可读性也比后来的 transformer 系模型友好得多。这份资源正好是标准 PyTorch 实现代码带注释目录里有训练好的模型权重、NTU 预处理脚本和可视化 demo适合做课程设计、期末大作业、毕业设计出成果。你下载后要做的不是读论文而是把环境搭起来跑通一版训练或推理再往里塞自己的数据。我用一个下午把它完整过了一遍后面几章把关键模块和实际操作路径都拆给你。2. 时空图卷积的核心设计与工程落地从邻接矩阵到 PyTorch 实现2.1 为什么骨架动作识别要用图卷积而非普通 CNN普通二维卷积天然假设输入是欧几里得结构的数据比如图像是规则的网格卷积核在局部窗口里滑动。人体骨架是一组关节点的拓扑结构任意两个关节之间的空间关系不是靠像素距离判断的而是靠骨骼连接。如果硬把 25 个关节点的坐标拼成一张二维图卷积核很难学到“手腕和手肘是相邻的、手腕和左脚踝不是相邻的”这种信息。图卷积做的事情就是提前把邻接关系写死在邻接矩阵里让卷积操作只在存在边的节点对上传播消息。ST-GCN 在时间维度上也没偷懒它的时间卷积是在每个关节自己的时间序列上做的空间图卷积负责聚合同一帧内相邻关节的信息时间卷积负责聚合同一关节跨帧的信息两部分交替堆叠。空间图卷积的核心公式是Y A_hat X W其中 A_hat 是由邻接矩阵 A 归一化后得到的矩阵X 是输入特征W 是可学习的权重。这个公式在源码里对应 net/st_gcn.py 中st_gcn_block的核心计算逻辑。实现上它没有直接做对称归一化而是用了A_hat D^(-1/2) * A * D^(-1/2)的套路代码层面用torch.matmul和逐元素乘法完成。这里的输出通道、卷积核大小、padding 策略都会直接影响模型参数量和感受野。源码里默认是 9 层 ST-GCN Block 堆叠通道数从 64 起步逐层翻倍到 256这种结构在 NTU 60 个动作类别上能跑出约 80% 以上的 Top-1 准确率作为毕业设计实验足够撑场面。2.2 环境准备与依赖说明先跑通再谈改进拿到源码后最忌讳一上来就看代码细节要先搭环境。我建议用 Python 3.7 或 3.8PyTorch 1.4 到 1.10 的范围内这个项目都能跑新版 PyTorch 2.x 也能兼容但要注意部分 API 在最新版里改了名字比如torch.norm的行为差异实在不行就按默认 requirements 安装。源码根目录下有 requirements.txt里面是 PyTorch、numpy、opencv-python 这类基础依赖。bash cd st-gcn pip install -r requirements.txt如果公司或实验室网络受限用国内镜像源安装更快bash pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple装完后用python -c import torch验证 PyTorch 是否可用。注意这个依赖文件只解决核心依赖demo_offline.py 里如果要跑 openpose 提取骨架还需要额外装 openpose 的环境那个比较复杂我一般会直接喂自带骨架数据来绕过去。2.3 模型结构选型单流还是双流取决于你的数据net 目录下有两个模型文件st_gcn.py 是原始的单流模型输入是单份骨骼数据的坐标序列st_gcn_twostream.py 是双流模型可以同时接受骨骼坐标流和骨骼长度/角度等辅助信息流。双流的动机很简单单流只用了坐标位置而骨骼的长度、相邻骨骼间的夹角这些特征其实也携带动作判别信息比如“举手”和“挥手”在坐标上很接近但肘关节角度变化模式差异明显。双流模型的实现思路是两条分支各自走 ST-GCN最后把特征拼接后接分类头。源码里用torch.cat实现特征融合再通过nn.Linear输出到动作类别数。我的建议是如果你的毕业设计题目定的是“动作识别”先跑通单流如果你想写“改进”相关的工作双流是一个低成本且稳定的改进方向不需要改模型主体只需改数据加载部分的特征拼接方式。2.4 这份资源里的预训练模型该用哪个、怎么用models 目录下有三个权重文件OriginSTGCN.pt、AddEdgeSTGCN12345.pt、kinetics-st_gcn.pt。第一个是标准 ST-GCN 在 NTU-RGBD 上训练的结果第二个看起来是在原始模型基础上做了邻接矩阵的改动训练时加了额外的边权重第三个是在 kinetics-skeleton 数据集上预训练的模型。从命名和训练日志目录里的 AddEdgeWeight_2.txt 来看第二个权重更接近“改进模型”的产物。我习惯先加载 OriginSTGCN.pt 做 baseline确认整个推理链路没问题后再加载 AddEdgeSTGCN12345.pt 对比效果。加载方式是在 processor/recognition.py 里指定权重文件路径然后跑 demo_offline.py。如果手头没有训练好的数据直接用这两个模型做离线推理完全可行它们对应的类别数都是 60也就是 NTU-RGBD 的 60 个动作类别。3. 数据加载与预处理细节骨架是怎么变成模型输入的3.1 从 NTU 原始数据到模型能吃的张量NTU-RGBD 原始数据是每个样本一个.skeleton文件里面存的是每帧每人的 3D 关节点坐标。模型不能直接读这种格式tools/ntu_gendata.py 就是负责把原始数据转成 numpy 数组的预处理脚本。它读入每个样本的关节坐标、置信度然后按人数和帧数重新组织成固定维度的张量默认输出格式是(N, C, T, V, M)分别代表样本数、坐标通道数x, y, z 三维坐标、时间帧数、关节点数25、人物数最多 2 人。这个维度排布在源码里到处用很多新手栽在这里。pyTorch 里网络的输入维度写的是(N, C, T, V, M)而普通视频模型是(N, C, T, H, W)维度含义完全不同。你看代码时遇到 shape 不匹配八成是在预处理阶段维度就没对上。bash python tools/ntu_gendata.py --data-path /path/to/nturgbd_skeletons --out-dir ./data处理完后 data 目录下会生成 train 和 val 两个子目录里面是.npy文件feeder 在训练时会按序加载这些文件。3.2 feeder 是怎么把磁盘数据变成训练 batch 的feeder/feeder.py 是数据加载的关键模块它的职责是给定样本索引和标签文件从磁盘读取 npy 数据做随机裁剪、随机旋转等数据增强然后组装成 batch 返回。源码里值得看的是数据增强的实现它对骨架坐标做了随机旋转绕三个轴的某几个角度还会随机裁剪时间序列这些操作对提升泛化能力比调网络结构更直接。我在跑实验时会把debug参数打开让 feeder 在每次 epoch 结束后打印一批样本的 label 分布检查类别是否均衡。如果某个动作类别样本数远大于其他类别需要考虑数据增强增强该类样本或做加权采样否则模型会对高频类别产生偏见。feeder 的__getitem__方法里还处理了 padding 逻辑当两个人物出现在同一段视频里时它会把第二个人的坐标对齐到第一个人的身体坐标系。3.3 用可视化脚本验证你的数据预处理是否正确判断预处理有没有做对的唯一可靠方法是直接可视化。项目里有 pose 和 DrawLine 相关文件配合 feeder/tools.py 可以把关节坐标画回图像上检查骨架是否和动作一致。我跑 demo_offline.py 时会用 opencv 把每帧的骨架连接线画到输出视频里连接关系用 DrawLine 里的规则比如手腕连手肘、手肘连肩膀、脚踝连膝盖这些固定的骨骼对。如果画出来的骨架在关键帧上是合理的人形结构预处理基本没问题。python # 骨架可视化核心思路给定关节坐标按固定连接对画线 import numpy as np import cv2 def draw_skeleton(frame, joints, edges): for edge in edges: start, end edge pt1 (int(joints[start][0]), int(joints[start][1])) pt2 (int(joints[end][0]), int(joints[end][1])) cv2.line(frame, pt1, pt2, (0, 255, 0), 2) return frameedges 列表由骨骼连接定义给出25 个关节对应 24 条骨骼边画出来的结果如果不连着就是坐标通道顺序搞错了——x 和 y 互换是常见低级错误。3.4 用训练好的模型跑离线推理验证全链路处理器 processor/recognition.py 是推理入口它读取权重文件加载模型然后对每个样本做前向计算输出各类别得分。demo_old.py 和 demo_offline.py 两个演示脚本的区别是前者用的是老接口后者是后来重构的版本。我在跑离线推理时先用 demo_offline.py 指定数据集里的一个样本输出它的 top-5 预测类别和对应得分确认权重和类别映射正确后再改造成批量推理脚本。python # 推理脚本改造思路加载模型、前向、取 top-k import torch from net.st_gcn import Model model Model(num_class60, in_channels3) checkpoint torch.load(models/OriginSTGCN.pt, map_locationcpu) model.load_state_dict(checkpoint[model_state_dict] if model_state_dict in checkpoint else checkpoint) model.eval() with torch.no_grad(): output model(sample_tensor) top_k torch.topk(output, k5)加载模型时看到的 checkpoint 是完整状态字典还是嵌套的键取决于保存方式源码里两种都出现过先打印键名再 load 是最稳的做法。4. 避坑与常见问题环境、维度和权重加载的三类翻车点4.1 权重加载报错 size mismatch现象load_state_dict报size mismatch错误信息指向st_gcn.blocks.0....这类键名。原因模型的num_class或in_channels和训练时不一致。这份资源里有的权重是 60 类 NTU 上训的有的是 kinetics 数据集上训的kinetics 是 400 类如果你把 kinetics 预训练权重直接加载到 num_class60 的模型里最后一层分类头的输出维度对不上前面的 block 层则可能因为输入通道不同而报尺寸错误。解决先打印掉多余键或缺失键确认权重属于哪个数据集。用filter方式加载跳过不匹配的层python pretrained_dict torch.load(models/kinetics-st_gcn.pt) model_dict model.state_dict() pretrained_dict {k: v for k, v in pretrained_dict.items() if k in model_dict and v.shape model_dict[k].shape} model_dict.update(pretrained_dict) model.load_state_dict(model_dict)只更新匹配的层分类头保留随机初始化后面微调时会自己收敛。4.2 模型输出全是同一类别概率几乎等于 1现象跑任何输入预测结果都是同一个类别置信度很高。原因大概率是类别映射表没对齐。NTU 的 60 类动作顺序和模型训练时的顺序在源码和权重里有对应关系但你在别处下载的类别文件如果版本不同比如源码里用的是旧的 A 方案而权重是在 B 方案上训的就会出这种问题。解决找一份源码自带的类别标签文件直接用它作为映射表。不要从网上随意替换。如果类别全对还是输出同类别那就是数据归一化的问题检查输入是 0-255 范围还是已经归一化到 -1 到 1ST-GCN 训练时一般用 z-score 归一化输入原始坐标值差异太大会导致所有样本经过编码后分布一致。4.3 进 demo_realtime 时总是报空数据现象demo_realtime.py启动后读取摄像头画面但控制台输出显示检测不到骨骼。原因实时 demo 需要前置的姿态估计模块输出关节点坐标源码里虽然有这个脚本但它只负责把姿态估计结果喂给 ST-GCN姿态估计本身依赖 openpose 或类似的外部程序。很多下载者在没有姿态估计环境的情况下直接跑自然拿不到输入。解决先跑通 offline demo用数据集自带骨骼。实时部分如果要展示我建议下载 openpose 的 CPU 版把输出的 json 里关节点坐标接进 feeder 的接口。这个过程比较耗时作为毕设演示可以提前录一段带骨骼可视化的视频现场放视频比现场跑摄像头稳定得多。4.4 训练时显存爆掉batch size 只能设 4现象训练到第三四个 epoch 时CUDA out of memory只能把 batch size 降到很小训练速度很慢。原因ST-GCN 的时间维度默认是 300 帧空间卷积在每一层都会对全图节点做消息传递中间特征图占用显存较大。解决不需要买新卡先把训练时长序列裁剪到 150 帧或 200 帧在配置文件里改num_frame然后控制batch_size为 8 或 16。再不行把模型里部分层的residual机制去掉可以减少一些显存开销。这两种方法对精度影响不明显却能让你在 6GB 显存上完成训练。4.5 验证集准确率高但测试集翻车现象验证集 85%测试集只有 60%差距巨大。原因NTU-RGBD 官方评测有两种划分方式cross-subject按人物划分和 cross-view按视角划分两种划分下准确率差异天然不同。源码默认可能用的是 cross-view你测试时如果用 cross-subject 的方式切数据就会出现这种巨大落差的“翻车”现场。解决确认源码里数据划分方式与你的实验目标一致。如果做课程设计建议直接沿用源码默认划分并明确标注如果毕设要求更高就两个划分都跑一遍在论文里对比这样反而多一个分析维度。5. 把这份资源吃透从复现实验到改自己的动作识别 demo加载好训练好的模型不算完毕设答辩时老师最常问的是“这个模型你改了什么”。这份资源里 AddEdgeWeight_2.txt 和 AddEdgeSTGCN12345.pt 暗示了一个很可行的改进思路在原始邻接矩阵上增加训练可学习的边权重。原始 ST-GCN 的邻接矩阵是固定的所有同类型连接共享相同的权重分配而改进版可以针对“手肘到手腕”这类关键边单独调整注意力强度让模型在“喝水”和“打电话”这类胳膊动作上更敏感。要复现这个改进你需要做的是在 net/st_gcn.py 中给图卷积的归一化邻接矩阵乘一个可学习的掩码矩阵并在训练过程中把它加入梯度流。刻意练习这一步的核心是把A_hat变成A_hat * M其中 M 的初始化可以全为 1也可以在训练前手动指定哪些边的权重更高。训练时加一个 graph 正则项限制 M 不会偏离初始值太多否则模型训练不稳定。这一改动在代码上只需要加几行python self.graph_att nn.Parameter(torch.ones_like(A_hat)) A_hat A_hat * self.graph_att把self.graph_att注册为模型的参数后训练时梯度会自动作用于这些边权重靠近实际动作语义的边会在训练中逐渐获得更高的数值。训练完把graph_att打印出来你可以直接分析模型关注了哪些骨骼连接这个内容写进论文里非常加分因为不止有实验数据还有可解释性的讨论。双流模型 st_gcn_twostream.py 的进阶用法更直接——把骨骼坐标流和骨骼速度流分别输入两条 ST-GCN 分支然后拼接特征。速度流可以通过坐标帧间差分得到不需要额外标注代码实现就是在 feeder 里对坐标序列做一阶差分python velocity coords[:, :, 1:, :, :] - coords[:, :, :-1, :, :]拼接时注意时间维度会少一帧需要补零对齐。双流模型对“快慢”类动作的识别提升特别明显比如慢走和快走这种仅靠单帧空间结构几乎无法区分的动作速度流能提供关键判别信号。如果你要脱离 NTU 数据集用自己录的视频做 demo路径是这样的先跑 openpose 提取每帧 25 个关节点坐标把每帧坐标都存成 json然后写一个 feeder 读取你的 json 并改成模型输入的格式。这里面最大的坑在于 openpose 输出的关节顺序和 NTU 的 25 关节顺序不一致写转换函数时一定要先画图验证每个关节点的索引对应关系不要盲猜。做完这个流程你的 demo 就完全跑在自己的数据上了。从那以后我每次跑这个项目都会先花 10 分钟检查数据维度和 checkpoint 键名确定没踩坑才开始训练。做毕业设计最怕的不是代码跑不通而是跑通了但说不清哪里是哪里。这份资源代码注释比较全配合预训练权重和预处理脚本完全可以作为独立完成的课题交差。希望帮到你。本文还有配套的精品资源点击获取