news 2026/9/13 11:21:39

ST-GCN骨骼动作识别:时空图卷积网络原理与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ST-GCN骨骼动作识别:时空图卷积网络原理与工程实践

简介:这是一份基于时空图卷积网络(ST-GCN)的骨骼动作识别毕业设计项目,面向计算机视觉、深度学习方向的本科或研究生,适合需要完成人体动作识别课题并希望快速跑通完整流程的开发者。项目以Python与PyTorch为核心,将人体骨骼结构建模为时空图,可识别行走、挥手、举重等动作,应用场景覆盖体育分析、医疗康复与智能交互。压缩包共91个文件,约52.56MB,主体为29个Python源码、13个YAML配置、12个Pyc编译文件,另有预训练模型、演示GIF与MP4视频、说明文档等,便于对照代码理解数据预处理、模型训练与可视化流程。目前已有159人学习下载。资源包含主程序、测试脚本、骨骼关键点绘制工具、OpenPose相关工具、NTU与Kinetics数据集处理脚本,以及多个预训练权重文件,并配有演示动图与视频,可直接体验识别效果,也为二次开发或论文复现提供了较为完整的工程骨架。

1. ST-GCN骨骼动作识别:这套工程直接拿来当毕设骨架

骨骼动作识别在体育分析、医疗康复、人机交互里都是热门方向,而ST-GCN(时空图卷积网络)是绕不开的基线模型。这个压缩包不是单文件论文代码,而是一套完整工程:NTU-RGB-D和Kinetics-skeleton的数据解析脚本、双流ST-GCN模型定义、训练入口、离线与实时demo、日志和可视化工具都在里面。对要做毕业设计的学生,改配置就能跑通训练和识别;对想快速验证图卷积想法的工程师,models目录下三份权重和AddEdge实验记录也有参考价值。下面从模型原理开始,逐层拆开每个环节的代码和参数。

2. 图卷积不是噱头:ST-GCN的建模逻辑与网络实现

2.1 为什么骨架动作要建模成图

动作识别常见的做法是把人体关键点坐标拼成一维向量,丢给LSTM或1D CNN。这样做的缺点是关节之间的结构关系被抹平了,比如左手腕和左肘的距离与左手腕和右膝的距离在特征里变得等价,模型必须靠数据硬学。ST-GCN把人体骨架定义成一张图:关节点是节点,骨骼是边,每一帧是图的一个快照,整个动作序列就是图在时间轴上的演化。空间上做图卷积聚合邻居信息,时间上做标准卷积,两个维度分开建模再堆叠,既保留拓扑又有序贯性。这个设计在NTU-RGB-D和Kinetics-skeleton上都验证过效果,作为毕业设计选题,论文好写、复现路径清晰。

2.2 邻接矩阵构造与图划分策略

图卷积的第一步是把骨骼图转成邻接矩阵。项目里net/graph.py干的就是这件事。以OpenPose的18个关键点为例,先定义哪些关节点物理相连,再生成带自环的邻接矩阵。常见做法是不直接用原始A矩阵,而是用归一化后的矩阵:

# net/graph.py 中构造归一化邻接矩阵的核心思路 import numpy as np def normalize_adjacency(A): """对邻接矩阵做对称归一化:D^{-1/2} A D^{-1/2}""" A = A + np.eye(A.shape[0]) # 加自环,让节点保留自身特征 D = np.diag(np.sum(A, axis=1)) # 度矩阵 D_inv_sqrt = np.linalg.inv(np.sqrt(D)) # D^{-1/2} return D_inv_sqrt @ A @ D_inv_sqrt # 返回 (num_node, num_node) 的归一化邻接矩阵 A = normalize_adjacency(raw_adjacency)

这里加自环的意义是让每个关节在卷积时能聚合到自己的特征,否则节点更新后只包含邻居信息,自身信息会丢失。对称归一化D^{-1/2} A D^{-1/2}是为了消除关节度数差异带来的尺度偏差,躯干中间的关节邻居多、末端关节邻居少,不归一化的话高度数节点特征数值会偏大。实际在OpenPose布局中num_node=18,而NTU-RGB-D数据集是25个关键点,换数据集时只需要改graph的布局定义和连接关系表。

ST-GCN原文里提出了几种图划分策略(unified、distance、spatial),项目默认用的是spatial策略:把每个关节的邻居分成三个子集——根节点本身、向心邻居(离骨架重心更近)、离心邻居(离重心更远)。每个子集给一个独立的卷积核,图卷积核张量形状是(K, C_out, C_in),其中K=3。代码上的差异就在get_adjacency_matrix返回的是带K维的矩阵,而不是普通的一层邻接矩阵。

2.3 时空卷积模块与双流融合

空间图卷积处理好每一帧,但动作本身是时序的,所以还需要时间维建模。net/st_gcn.py里的基本模块是“空间图卷积 + 时间卷积”串联:

# net/st_gcn.py 中 ST-GCN 基础模块的结构 class STGCNBlock(nn.Module): def __init__(self, in_channels, out_channels, A, stride=1): super().__init__() self.gcn = GraphConv(in_channels, out_channels, A) # 空间:3个子集图卷积 self.tcn = nn.Sequential( nn.Conv2d(out_channels, out_channels, kernel_size=(9, 1), padding=(4, 0)), # 时间:9帧窗口 nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True), ) def forward(self, x): # x 形状: (N, C, T, V),N批量、C通道、T帧数、V节点数 x = self.gcn(x) x = self.tcn(x) return x

这里有个容易忽略的细节:图卷积的输入输出在空间维(V)上长度不变,所以时间卷积的kernel只需要写成(9, 1),9是时间窗口,1是在空间维上不卷积。之所以不用LSTM而用一维卷积建模时间,是因为卷积可以并行计算、训练速度更快,而且9帧的感受野在堆叠多层后已经能覆盖完整的动作周期。项目里默认kernel_size=9, padding=4,保证时间维不缩水;如果处理高速动作,比如resource里的clean_and_jerk举重视频,可以把kernel加到11或13,代价是参数增加。

双流模型(st_gcn_twostream.py)是另一个关键设计。原始ST-GCN只输入关节坐标(joint),双流在此基础上增加骨骼向量分支:把相邻关节坐标相减得到骨骼向量,同样构造一个图,输入第二个ST-GCN分支。两个分支的输出在softmax之前做加权融合,权重作为可学习参数。这个改造对识别准确率的提升非常明显,在NTU-XSub上通常能比单流提高2到3个百分点,也是很多毕业设计把它作为基线的原因。配置文件中model_type: st_gcn_twostream就是切换到这个双流结构。

3. 从原始骨架文件到训练样本:数据链路怎么打通

3.1 NTU-RGB-D 骨架文件解析

NTU-RGB-D原始数据是.skeleton后缀的文本,每个文件对应一段动作视频。tools/ntu_read_skeleton.py负责把这种文本解析成Python对象。解析的关键在于理解它的层级结构:文件第一行是总帧数,之后每帧先有body个数,每个body又带25个关节,每个关节一行。常见做法是维护一个游标按行读取:

# tools/ntu_read_skeleton.py 解析单个 skeleton 文件的简化逻辑 def read_skeleton(file_path): with open(file_path, 'r') as f: lines = f.readlines() cur = 0 num_frames = int(lines[cur].strip()); cur += 1 frames = [] for _ in range(num_frames): num_bodies = int(lines[cur].strip()); cur += 1 bodies = [] for _ in range(num_bodies): # 每个 body 的信息头:关节数等字段 num_joints = int(lines[cur].strip()); cur += 1 joints = [] for _ in range(num_joints): parts = lines[cur].strip().split() joints.append([float(parts[0]), float(parts[1]), float(parts[2])]) cur += 1 bodies.append(joints) frames.append(bodies) return frames

这段代码保留了最核心的游标推进逻辑。实际NTU文件里body信息头不止关节数一个字段,还可能包含置信度和裁剪信息,解析时按固定索引取值即可,具体字段顺序以数据集说明为准。很多人在毕设答辩时被问到数据格式,答不出body header的结构会明显减分。ntu_read_skeleton.py处理的就是这段逻辑,替换数据集时只需要改关节数和字段索引。

3.2 从原始帧到图卷积输入

解析出的原始数据是变长的(帧数不同、人数不同),不能直接进batch。ntu_gendata.py的作用就是把原始骨架序列整理成固定长度(通常采样到300帧)的数组,没到300帧的补零,超过的均匀抽样,然后按train/val划分保存。生成的pkl文件里每条样本是dict,包含keypoint数组和label。在feeder.py中,训练时还会做数据增强:

# feeder/feeder.py 中随机旋转增强的典型实现 def random_rot(self, data): """绕Z轴随机旋转,幅度由magnitude参数控制,只作用于前3个坐标通道""" angle = np.random.uniform(-self.magnitude, self.magnitude) c, s = np.cos(angle), np.sin(angle) R = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]], dtype=np.float32) return np.einsum('ab,nctb->ncta', R, data)

einsum里的nctb对应批量N、通道C、时间T、空间节点数V,旋转矩阵在最后一维上做线性变换。注意增强只对坐标通道生效,如果输入还带了置信度或速度通道,需要先分离再旋转,否则会把非坐标信息也转进去。Kinetics数据集的路径类似,tools/kinetics_gendata.py接收的是已经用OpenPose从视频抽出的json骨架文件,处理逻辑和NTU版本基本一致,区别主要在关节数(18 vs 25)和类别数(400 vs 60)。Kinetics-skeleton的类别不是连续编号,label_name.txt就是做类别名和索引映射的,训练完拿它来翻译预测结果。

3.3 配置文件与超参数速查

torchlight是这个项目自己封装的配置与训练框架,config目录下按数据集和模型组织。以config/st_gcn.twostream/kinetics-skeleton下的train.yaml为例:

参数典型值含义与调整建议
in_channels3关节坐标维度,若加置信度改为2或3,加速度改为6
num_class400训练集动作类别数,换成自己的数据集必改
batch_size64显存不足时先减半再考虑换GPU
base_lr0.1SGD初始学习率,微调时降到0.01
step[30, 40]在第30、40个epoch学习率乘0.1
edge_importance_weightingTrue是否学习边权重矩阵,一般保持True

提示:base_lr设0.1对从头训练合理,但使用kinetics预训练权重微调时务必降到0.01或更低,否则loss很容易发散。

换自己的数据集时,第一优先改num_class,第二把step改成自己总epoch的一半和四分之三位置。训练指令不复杂:

# 以 NTU-XSub 双流配置启动训练 python main.py --config config/st_gcn.twostream/ntu-xsub/train.yaml

训练过程中所有中间结果由torchlight输出到work_dir目录,每个epoch会打印loss、top1和top5准确率。如果不想从头训练,models目录下的预训练权重可以直接用来评估或做迁移学习。

4. 训练、评估与两类Demo的正确打开方式

4.1 main.py 与 processor 的工作流

主入口main.py做的事情很纯粹:读配置、初始化模型和数据加载器、构建处理器processor/processor.py、开始训练或评估。识别场景对应recognition.py,它继承processor.py的基类,重写了train和test两个方法。对于只想跑通流程的人,不需要关心内部细节,只要知道训练状态会被序列化保存,断点续训时 torchlight 会在work_dir下找最新的checkpoint。如果中途断了,直接再执行一次相同命令,它会自动加载最近权重而不是从零开始。

评估单个权重文件可以用命令行直接指定模型参数:

# 用训练好的双流权重做 top-1 / top-5 评估 python main.py --config config/st_gcn.twostream/ntu-xview/test.yaml \ --weights models/OriginSTGCN.pt

注意评估时配置文件里的模型结构必须和权重文件对应,双流权重配单流配置会直接报key不匹配,报错信息里会列出缺失和多余的层名,对着层名检查是哪个环节配错了。

4.2 models 目录下三份权重怎么选

项目压缩包里带了三个.pt文件,三者的定位差别很大,选错会导致测试结果完全对不上:

权重文件训练数据适用场景
OriginSTGCN.ptNTU-RGB-D 单流原始模型复现原论文基线
AddEdgeSTGCN12345.ptNTU-RGB-D 加入边权重改进对比改进效果、毕设创新点演示
kinetics-st_gcn.ptKinetics-skeleton 预训练迁移到自己的动作数据集

使用时要看模型类型和后缀是否匹配:单流权重不能直接加载进双流模型,会报key不一致。Kinetics预训练权重加载到双流模型时,最后一层全连接维度不同,需要冻结前面的层、只替换最后的分类头。JustTest.py里就有快速验证代码,可以先把模型初始化成对应配置,再load_state_dict时设strict=False,这样只要关键层对得上,分类头维度不匹配也不会中断加载。

4.3 离线视频识别与实时摄像头演示

演示是毕业设计评分里权重很高的一环。项目给出两条演示路径:demo_offline.py处理录制好的视频,demo_realtime.py走摄像头实时识别,demo_old.py是早期版本的参考实现,不建议新代码基于它改。离线版本会先用OpenPose检测每帧的关键点,再把关键点序列整理成模型输入,最后把识别结果叠加在画面上。运行方式一般是:

# 识别 resource 目录下的太极拳视频 python processor/demo_offline.py --video resource/ta_chi.mp4 \ --weights models/kinetics-st_gcn.pt # 启动摄像头实时识别,按 q 退出 python processor/demo_realtime.py --weights models/kinetics-st_gcn.pt \ --cam_id 0

两个demo里最容易出问题的环节不是模型,而是OpenPose的依赖。它会拉取caffe和pyopenpose相关组件,环境装不对就会在导入阶段抛错,报错通常显示找不到openpose模块。我一般建议把关键点检测的耗时单独打点,如果一帧超过50ms就把输入分辨率降一档;实时演示时识别结果有1到2帧延迟是正常的,不用刻意追求零延迟。resource里的gif和demo_video.gif是之前跑通的样例输出,可以参照对比自己的画面效果。

5. 日志挖潜与AddEdge改进:让毕设多两个亮点

5.1 从 logData 和 log.txt 里找训练异常

work_dir/recognition/kinetics_skeleton/tmp/log.txt 记录了每次运行的完整日志,logData 里还保存了AddEdgeWeight_2.txt这样针对边权重实验的对比输出。检查训练是否正常,先看loss是否在step节点(比如第30个epoch)出现明显下降;如果下降发生在step之前,说明学习率偏小,可以把base_lr加大一档看看。top1准确率在第一个epoch后如果接近随机水平,要怀疑类别标签对齐出了问题,优先检查label_name.txt的映射表。NTU里还有部分样本骨架缺失,samples_with_missing_skeletons.txt列出了这些文件,评估时必须先过滤,否则会出现某一类准确率异常偏低或者loss跳成NaN。

5.2 DrawLine.py 做骨架时序可视化

DrawLine.py是个容易被低估的工具,它能把骨骼序列画成连线图并逐帧输出,适合生成论文插图。常见用法是把NTU的skeleton文件或pkl中的keypoint数组画成图像序列:

python DrawLine.py --input S001C001P001R001A051_w.gif \ --output skeleton_sequence.mp4

画图时注意关节连线关系要和graph.py里的neighbor_link保持一致,否则会出现手肘连线接到肩膀上的错误。对论文来说,可视化关键点叠加在原始视频上的效果(resource里的attention+prediction.png)比单独画骨架更有说服力,因为评委能直观看到OpenPose的检测结果和模型关注区域,注意力特征图可以用最后几层GCN输出的梯度做CAM类激活图叠加上去。

5.3 在原始ST-GCN上加边权重的落地方案

logData里的AddEdgeWeight_2.txt记录了给ST-GCN增加边权重的实验思路:原始模型已经有edge_importance_weighting,但它学习的是每个节点的权重对角矩阵,而AddEdge改成了对每条边也学一个标量权重,等价于在邻接矩阵上叠加一个可学习的边权矩阵。实现时只需要在GraphConv里增加一个与A同形状的nn.Parameter,forward时用A * W替代A,代码改动不超过十行。这个实验和OriginSTGCN.ptAddEdgeSTGCN12345.pt两份权重正好形成对照组,差分对比就是毕业论文的完整“改进-验证”闭环。记录文件名里的_2说明这是第二轮实验,第一轮的参数或结果记录可以在logData里顺藤摸瓜找到演变轨迹,答辩时这一过程讲清楚很加分。验证时记得用同一份test配置、同一个随机种子跑两个模型,不要只报最优数字,把两个权重在各类别上的准确率差值列成表格,能更清楚看到加边权重到底改善了哪几类动作。

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

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

快速选择算法与堆排序:高效解决数组第K大元素问题

1. 问题定义与算法选择在编程面试和日常开发中,"数组中的第K个最大元素"是一个经典问题。给定一个未排序的整数数组,我们需要找到其中第K个最大的元素。这个问题看似简单,但不同的解法在效率上差异巨大。最直观的解法是对数组进行排…

作者头像 李华
网站建设 2026/9/13 11:20:02

Sway 合约如何用 storage namespace 注解避免存储槽位冲突?

Sway 合约如何用 storage namespace 注解避免存储槽位冲突? 【免费下载链接】sway 🌴 Empowering everyone to build reliable and efficient smart contracts. 项目地址: https://gitcode.com/GitHub_Trending/sw/sway 在 Sway 中编写合约时&…

作者头像 李华
网站建设 2026/9/13 11:15:08

amis Avatar 头像组件完全指南:JSON 配置、变量绑定与事件交互

amis Avatar 头像组件完全指南:JSON 配置、变量绑定与事件交互 【免费下载链接】amis 前端低代码框架,通过 JSON 配置就能生成各种页面。 项目地址: https://gitcode.com/GitHub_Trending/am/amis Avatar 头像组件是 amis 低代码框架中用于展示用…

作者头像 李华
网站建设 2026/9/13 11:12:19

AI搜索时代GEO优化:提升品牌内容引用率的关键策略

1. 项目背景与行业痛点 在AI搜索逐渐取代传统搜索引擎的今天,云南泽森科技团队发现了一个关键的市场空白点。我们服务云南玉溪地区中小企业时,发现这些企业的品牌内容在豆包、通义千问等主流AI平台上的引用率普遍低于5%。这个数字背后反映的是一个行业级…

作者头像 李华
网站建设 2026/9/13 11:11:25

基于YOLOv5的苹果叶片病虫害智能检测系统开发

1. 项目背景与核心价值苹果种植业面临的最大挑战之一就是叶片病虫害的早期识别与防治。传统的人工检测方式存在效率低、主观性强、专业门槛高等问题。我们开发的这套基于YOLOv5的识别系统,能够在3秒内完成单张叶片图像的病虫害检测,准确率达到92%以上&am…

作者头像 李华