☰
ST-GCN毕业设计实战:从骨架数据到实时动作识别
2026/10/7 23:08:51 网站建设 项目流程

简介:本资源是一份面向计算机视觉方向本科生与研究生的毕业设计实战项目,聚焦基于时空图卷积网络(ST-GCN)的人体骨骼动作识别任务,适用于体育分析、康复评估、人机交互等实际场景。项目完整复现了ST-GCN模型在NTU-RGB+D与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-RGB+D与Kinetics-Skeleton双数据集支持、双流ST-GCN(RGB+骨骼)、自适应图卷积权重生成、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-RGB+D原始数据用25个OpenPose关键点(含脚趾、耳朵),但ST-GCN原论文用18节点(去耳、去脚趾、合并手腕)。项目中ntu_read_skeleton.py负责读取.skeleton文件,但关键逻辑在net/graph.py的Graph类:

# net/graph.py class Graph: def __init__(self, labeling_mode='spatial'): 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) -> C=3(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), axis=0) return data_input, self.label[index]

提示:这里data_diff才是ST-GCN真正的时间特征。很多同学直接喂原始坐标,模型根本学不到动作速度变化。data_input.shape[0]必须是6(3坐标+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, stride=1, residual=True): super().__init__() # A 是传入的初始邻接矩阵,但会被重参数化 self.A = nn.Parameter(torch.from_numpy(A.astype(np.float32)), requires_grad=True) # 后续通过softmax生成动态权重 self.A2 = nn.Parameter(torch.ones_like(A) * 1e-6, requires_grad=True) # 实际卷积用的是 self.A + self.A2 self.gcn = ConvTemporalGraphical(in_channels, out_channels, self.A + self.A2)

逻辑说明:self.A是初始空间图(来自graph.py),self.A2是可学习偏置。训练时两者相加再softmax归一化,让模型自动发现哪些关节对在特定动作中更相关——比如“打太极”时“手腕-肩膀”权重升高,“举重”时“髋-膝”权重升高。这就是为什么AddEdgeSTGCN12345.pt比OriginSTGCN.pt精度高3.2%:它存了训练好的A2参数。


3. 从零跑通demo:用JustTest.py验证骨架加载与模型前向传播

别急着跑main.py——先用JustTest.py确认数据流是否通畅。这是作者留的“后悔药”,专治环境配错、路径不对、维度爆炸。

3.1JustTest.py的三重校验逻辑

该脚本不训练,只做三件事:

  1. 加载NTU样本数据(resource/NTU-RGB-D/S001C001P001R001A051_w.gif)→ 解析为(3,300,18,2)张量
  2. 调用feeder_kinetics.py的Feeder类做标准化 → 输出(6,300,18,2)
  3. 加载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_path='resource/NTU-RGB-D', label_path='resource/NTU-RGB-D/label_name.txt', window_size=300, num_person=2 ) data, label = feeder[0] # 取第一个样本 print(f"原始数据shape: {data.shape}") # 应为 (6, 300, 18, 2) # 2. 加载模型 model = ST_GCN( in_channels=6, num_class=60, edge_importance_weighting=True, 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, dtype=torch.float32), 0)) print(f"模型输出shape: {output.shape}") # 必须是 torch.Size([1, 60])

参数说明:window_size=300对应NTU每段动作截取300帧(约10秒),num_person=2因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

其内部流程分四步:

  1. 视频抽帧:用cv2.VideoCapture按30fps读帧,存临时目录./tmp/frames/
  2. OpenPose骨骼估计:调用tools/openpose.py启动OpenPose C++程序,输出JSON骨架文件
  3. 数据格式转换:ntu_gendata.py将JSON转为(C,T,V,M)张量,关键函数gen_skeletongraph()
  4. 模型推理+可视化:visualization.py叠加骨架线+动作标签+注意力热力图(见attention+prediction.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 python=3.7 conda activate stgcn pip install torch==1.2.0 torchvision==0.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.txt

4.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_decay=1e-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编译复杂且吃GPU,MediaPipe的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_mode=False, min_detection_confidence=0.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索引关节名
00nose
111left_shoulder
122right_shoulder
133left_elbow
144right_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_channels=6, num_class=60, edge_importance_weighting=True, 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_version=11, 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 38ms;Intel 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, cmap='viridis', cbar_kws={'label': 'Attention Weight'}) plt.title(f"Action: {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挂了,你还有这条退路。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询