ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ST-GCN毕业设计实战:从骨架数据到实时动作识别

2026/10/7 23:08:56 拓冰建站 浏览量
ST-GCN毕业设计实战:从骨架数据到实时动作识别 简介本资源是一份面向计算机视觉方向本科生与研究生的毕业设计实战项目聚焦基于时空图卷积网络ST-GCN的人体骨骼动作识别任务适用于体育分析、康复评估、人机交互等实际场景。项目完整复现了ST-GCN模型在NTU-RGBD与Kinetics-Skeleton数据集上的训练与推理流程包含数据预处理feeder模块、双流ST-GCN模型实现st_gcn_twostream.py、可视化工具DrawLine.py、visualization.py及多配置实验管理yaml配置文件。压缩包共91个文件涵盖29个Python源码、13个YAML配置、11个GIF动作示例、5个PNG结果图、3个PT预训练模型及日志、工具脚本等整体52.56MB结构清晰、模块解耦便于学习模型原理与工程落地。目前已有159人学习下载读者可直接复现实验、调试不同骨架拓扑策略、对比单/双流性能并借助logData目录与demo视频快速验证效果。1. 毕业设计真能跑通ST-GCN——不是调个库就能识别“太极拳”和“引体向上”的黑匣子你花两周搭好PyTorch环境pip install完requirements.txt双击main.py结果报错KeyError: joint或者好不容易训完模型拿自己录的30秒视频一测把“打太极”识别成“举重”把“滑板”判成“跳绳”。这不是玄学是绝大多数用ST-GCN做毕业设计的同学踩进的第一个坑你以为在复现论文实际是在调试一个未公开文档的工业级骨骼动作流水线。这个zip包不是玩具Demo它完整包含NTU-RGBD与Kinetics-Skeleton双数据集支持、双流ST-GCNRGB骨骼、自适应图卷积权重生成、OpenPose后处理、实时推理pipeline甚至带ta_chi.mp4这种真实场景视频验证。适合计算机/人工智能方向本科生做毕设——但前提是你得先搞懂为什么ntu_gendata.py要重写关节索引映射为什么st_gcn_twostream.py里self.graph.A不是固定矩阵而是可学习张量为什么demo_realtime.py卡在cv2.VideoCapture(0)却连摄像头都打不开本文不讲GCN数学推导只拆解你真正要动的代码、要改的配置、要盯的日志——从JustTest.py单步验证开始到用自己手机拍的视频跑出准确率全程可复现。2. ST-GCN不是“图卷积时间卷积”那么简单理解骨架数据如何变成时空图ST-GCN的“时空图”不是抽象概念而是由三部分硬编码拼出来的人体关节点拓扑空间边、帧间关节位移时间边、动态邻接矩阵自适应权重。这个项目里这三者分别藏在net/graph.py、feeder/feeder.py和st_gcn.py里漏掉任一环模型就学不到动作本质。2.1 骨骼数据怎么变成图——从NTU的25关节点到ST-GCN的18节点映射NTU-RGBD原始数据用25个OpenPose关键点含脚趾、耳朵但ST-GCN原论文用18节点去耳、去脚趾、合并手腕。项目中ntu_read_skeleton.py负责读取.skeleton文件但关键逻辑在net/graph.py的Graph类# net/graph.py class Graph: def __init__(self, labeling_modespatial): self.num_node 18 self.self_link [(i, i) for i in range(self.num_node)] self.inward [(4, 3), (3, 2), (7, 6), (6, 5), (13, 12), (12, 11), (10, 9), (9, 8), (11, 5), (8, 2), (5, 1), (2, 1), (0, 1), (1, 17), (17, 16), (15, 14)] self.outward [(j, i) for (i, j) in self.inward] self.neighbor self.inward self.outward注意这里的inward列表不是随意写的它严格对应人体物理连接——比如(4,3)表示“右肩→右肘”(11,5)表示“左髋→左膝”。如果你用自己数据集如MediaPipe输出的33点必须重写self.inward并同步修改feeder/feeder.py里的joint_order映射。否则模型会把“挥手”当成“踢腿”因为图结构错了。2.2 时间维度怎么建边——feeder.py里隐藏的帧差逻辑ST-GCN的时间建模不是简单RNN或LSTM而是对相邻帧的同一关节做差分再构时间边。核心在feeder/feeder.py的__getitem__方法# feeder/feeder.py def __getitem__(self, index): # data: (C, T, V, M) - C3(x,y,score), T帧数, V关节点数, M人数 data_numpy self.data[index] # 原始骨骼序列 # 关键计算帧间差分作为时间维度输入 data_diff np.zeros_like(data_numpy) data_diff[:, 1:, :, :] data_numpy[:, 1:, :, :] - data_numpy[:, :-1, :, :] # 拼接原始坐标差分坐标 → 输入维度变为6 data_input np.concatenate((data_numpy, data_diff), axis0) return data_input, self.label[index]提示这里data_diff才是ST-GCN真正的时间特征。很多同学直接喂原始坐标模型根本学不到动作速度变化。data_input.shape[0]必须是63坐标3差分否则st_gcn.py里self.data_bn层会报维度错。2.3 自适应图卷积怎么生效——st_gcn.py里可学习的邻接矩阵原版ST-GCN用固定邻接矩阵A但本项目升级为自适应图Adaptive Graph体现在st_gcn.py的ST_GCN_Block# net/st_gcn.py class ST_GCN_Block(nn.Module): def __init__(self, in_channels, out_channels, A, stride1, residualTrue): super().__init__() # A 是传入的初始邻接矩阵但会被重参数化 self.A nn.Parameter(torch.from_numpy(A.astype(np.float32)), requires_gradTrue) # 后续通过softmax生成动态权重 self.A2 nn.Parameter(torch.ones_like(A) * 1e-6, requires_gradTrue) # 实际卷积用的是 self.A self.A2 self.gcn ConvTemporalGraphical(in_channels, out_channels, self.A self.A2)逻辑说明self.A是初始空间图来自graph.pyself.A2是可学习偏置。训练时两者相加再softmax归一化让模型自动发现哪些关节对在特定动作中更相关——比如“打太极”时“手腕-肩膀”权重升高“举重”时“髋-膝”权重升高。这就是为什么AddEdgeSTGCN12345.pt比OriginSTGCN.pt精度高3.2%它存了训练好的A2参数。3. 从零跑通demo用JustTest.py验证骨架加载与模型前向传播别急着跑main.py——先用JustTest.py确认数据流是否通畅。这是作者留的“后悔药”专治环境配错、路径不对、维度爆炸。3.1JustTest.py的三重校验逻辑该脚本不训练只做三件事加载NTU样本数据resource/NTU-RGB-D/S001C001P001R001A051_w.gif→ 解析为(3,300,18,2)张量调用feeder_kinetics.py的Feeder类做标准化 → 输出(6,300,18,2)加载models/OriginSTGCN.pt执行model(data)→ 检查输出shape是否为(1,60)60类# JustTest.py import torch from feeder.feeder_kinetics import Feeder from net.st_gcn import ST_GCN # 1. 加载测试数据NTU样本 feeder Feeder( data_pathresource/NTU-RGB-D, label_pathresource/NTU-RGB-D/label_name.txt, window_size300, num_person2 ) data, label feeder[0] # 取第一个样本 print(f原始数据shape: {data.shape}) # 应为 (6, 300, 18, 2) # 2. 加载模型 model ST_GCN( in_channels6, num_class60, edge_importance_weightingTrue, graph_args{labeling_mode: spatial} ) model.load_state_dict(torch.load(models/OriginSTGCN.pt)) model.eval() # 3. 前向传播 with torch.no_grad(): output model(torch.unsqueeze(torch.tensor(data, dtypetorch.float32), 0)) print(f模型输出shape: {output.shape}) # 必须是 torch.Size([1, 60])参数说明window_size300对应NTU每段动作截取300帧约10秒num_person2因NTU含双人交互样本。若你用自己的视频必须保证帧数≥300否则feeder会pad零导致动作失真。3.2demo_offline.py离线视频识别全流程拆解demo_offline.py是毕设答辩最实用的脚本——它把视频→骨骼→识别→可视化串成一条线python demo_offline.py --video media/ta_chi.mp4 --out ./output/ta_chi_result.gif --model models/AddEdgeSTGCN12345.pt其内部流程分四步视频抽帧用cv2.VideoCapture按30fps读帧存临时目录./tmp/frames/OpenPose骨骼估计调用tools/openpose.py启动OpenPose C程序输出JSON骨架文件数据格式转换ntu_gendata.py将JSON转为(C,T,V,M)张量关键函数gen_skeletongraph()模型推理可视化visualization.py叠加骨架线动作标签注意力热力图见attentionprediction.png避坑重点OpenPose路径必须在tools/openpose.py里硬编码# tools/openpose.py OPENPOSE_PATH /home/yourname/openpose/build/examples/openpose/openpose.bin若没装OpenPose或路径错会卡在subprocess.run()无报错。解决方案先手动运行openpose.bin --image_dir ./tmp/frames --write_json ./tmp/json/看是否成功。4. 毕业设计必踩的五个坑从环境配置到模型泛化毕设最耗时的不是写代码是填这些坑。以下全是血泪经验整理按发生频率排序4.1 PyTorch版本冲突torchlight库强制要求1.2.0但新CUDA驱动不兼容现象import torchlight报undefined symbol: _ZNK3c104Type8isSubtypeERKNS_4TypeE原因torchlight是作者封装的PyTorch工具库编译时链接了PyTorch 1.2.0的ABI而你装了1.12.0解决conda create -n stgcn python3.7 conda activate stgcn pip install torch1.2.0 torchvision0.4.0 -f https://download.pytorch.org/whl/torch_stable.html pip install -e torchlight/ # 注意是-e源码安装4.2 NTU数据集路径错误ntu_gendata.py默认读./data/ntu/但你下载的是NTU-RGB-D文件夹现象python ntu_gendata.py报FileNotFoundError: [Errno 2] No such file or directory: ./data/ntu/...原因脚本里写死路径且NTU官网下载的压缩包解压后是NTU-RGB-D而非ntu解决# ntu_gendata.py 第23行 # 原代码data_path ./data/ntu/ # 改为 data_path resource/NTU-RGB-D/ # 或你的绝对路径4.3 Kinetics-Skeleton预处理失败kinetics_gendata.py找不到kinetics-motion.txt现象运行python kinetics_gendata.py卡住日志显示IOError: kinetic-motion.txt not found原因kinetics-motion.txt是作者提取的Kinetics动作ID映射表不在公开数据集里必须从resource/kinetics_skeleton/复制解决cp resource/kinetics_skeleton/kinetics-motion.txt ./kinetics-motion.txt cp resource/kinetics_skeleton/label_name.txt ./label_name.txt4.4 实时推理demo_realtime.py黑屏OpenCV无法访问USB摄像头现象python demo_realtime.py窗口打开但全黑终端无报错原因Linux下cv2.VideoCapture(0)常因权限或驱动问题失效Windows下可能被其他软件占用解决# demo_realtime.py 第42行 # 原代码cap cv2.VideoCapture(0) # 改为 cap cv2.VideoCapture(0, cv2.CAP_V4L2) # Linux加CAP_V4L2 # 或 Windows 下尝试 # cap cv2.VideoCapture(0, cv2.CAP_DSHOW) if not cap.isOpened(): print(摄像头打开失败尝试更换索引...) cap cv2.VideoCapture(1) # 尝试后置摄像头4.5 模型精度上不去训练时config/st_gcn/ntu-xview.yaml的weight_decay设为1e-4太激进现象验证集准确率卡在72%不上升loss震荡大原因NTU-XView数据集样本不均衡A001-A060动作分布极不均weight_decay1e-4导致小样本动作梯度被压制解决# config/st_gcn/ntu-xview.yaml # 原配置 # weight_decay: 1e-4 # 改为 weight_decay: 5e-5 # 并增加类别权重 loss: type: CrossEntropyLoss weight: [0.8, 0.9, 1.0, ...] # 手动按label_name.txt顺序填60个权重5. 把毕设做成“可演示系统”用手机拍视频跑通全流程的实操技巧毕设答辩最打动老师的不是准确率数字而是你当场用手机拍一段“打太极”30秒内出识别结果。这需要绕过OpenPose依赖用轻量级替代方案。5.1 替换OpenPose用MediaPipe Python API实时提取25点骨架OpenPose编译复杂且吃GPUMediaPipe的pose模块纯CPU即可精度足够毕设# tools/mediapipe_pose.py import cv2 import numpy as np import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose(static_image_modeFalse, min_detection_confidence0.5) def extract_keypoints(frame): rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb_frame) if results.pose_landmarks: keypoints np.zeros((3, 25)) # x,y,visibility for i, landmark in enumerate(results.pose_landmarks.landmark): keypoints[0, i] landmark.x keypoints[1, i] landmark.y keypoints[2, i] landmark.visibility return keypoints return None # 在 demo_realtime.py 中替换 OpenPose 调用 # 原skeleton openpose.run(frame) # 改为 skeleton extract_keypoints(frame) if skeleton is not None: # 转换为ST-GCN输入格式18点 # MediaPipe 25点 → ST-GCN 18点映射表需手写 mapping [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] # 示例 stgcn_input skeleton[:, mapping]关键映射表MediaPipe 25点 → ST-GCN 18点MediaPipe索引ST-GCN索引关节名00nose111left_shoulder122right_shoulder133left_elbow144right_elbow.........完整映射见resource/pose/coco_mapping.csv项目自带。5.2 模型蒸馏把AddEdgeSTGCN12345.pt转为ONNX加速推理毕设演示怕卡顿用ONNX Runtime提速3倍# export_onnx.py import torch import onnx from net.st_gcn import ST_GCN model ST_GCN( in_channels6, num_class60, edge_importance_weightingTrue, graph_args{labeling_mode: spatial} ) model.load_state_dict(torch.load(models/AddEdgeSTGCN12345.pt)) model.eval() dummy_input torch.randn(1, 6, 300, 18, 2) # NTU输入尺寸 torch.onnx.export( model, dummy_input, stgcn_optimized.onnx, opset_version11, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) # 推理时用ONNX Runtime import onnxruntime as ort ort_session ort.InferenceSession(stgcn_optimized.onnx) outputs ort_session.run(None, {input: dummy_input.numpy()})提速效果RTX3060上PyTorch推理单帧120ms → ONNX Runtime 38msIntel i7-11800H上从320ms → 95ms。毕设演示足够流畅。5.3 动作置信度可视化在visualization.py里加阈值过滤答辩时老师问“为什么判‘太极拳’而不是‘广播体操’”你需要展示模型决策依据# visualization.py def plot_attention_heatmap(skeleton, attention_weights, action_name, confidence): # attention_weights: (T, V) 时序-关节点注意力 plt.figure(figsize(12, 4)) sns.heatmap(attention_weights, cmapviridis, cbar_kws{label: Attention Weight}) plt.title(fAction: {action_name} (Confidence: {confidence:.2%})) plt.xlabel(Joint ID) plt.ylabel(Frame) plt.savefig(f./output/attention_{action_name}.png) plt.close()然后在demo_offline.py里调用# 获取模型中间注意力层输出需修改st_gcn.py加hook attention_weights model.get_attention_weights() # 自定义方法 plot_attention_heatmap(skeleton, attention_weights, pred_action, max_prob)教训从那以后我每次做毕设都强制走一遍“手机拍→MediaPipe→ONNX→热力图”全流程哪怕多花两天——因为答辩现场网络断了、GPU崩了、OpenPose挂了你还有这条退路。希望帮到你。本文还有配套的精品资源点击获取