深圳网站推广优化,泉州关键词优化软件,wordpress主题吧,深圳网站优化页面ST-GCN模型详解#xff08;openpose#xff09;
一、什么是ST-GCN呢
基于骨架的动作识别#xff08;Skeleton-Based Action Recognition#xff09;主要任务是从一系列时间连续的骨骼关键点#xff08;2D/3D#xff09;中识别出正在执行的动作。因为牵涉到骨骼框架这种…ST-GCN模型详解openpose
一、什么是ST-GCN呢
基于骨架的动作识别Skeleton-Based Action Recognition主要任务是从一系列时间连续的骨骼关键点2D/3D中识别出正在执行的动作。因为牵涉到骨骼框架这种图结构的输入采用GCN的方法逐渐成为了主流并取得了不错的效果。ST-GCN是基于动态骨骼的动作识别方法ST-GCN时空图卷积网络模型
论文链接https://arxiv.org/abs/1801.07455
Github 代码https://github.com/yysijie/st-gcn?
建议先看完GCN的相关知识图卷积神经网络-GCN_图卷积网络-CSDN博客
对GCN的总结就是GCN算法的步骤简单可总结为
1、初始化为每个节点分配初始特征表示。
2、邻居聚合对于每个节点将其自身特征与邻居节点的特征进行加权平均或拼接得到聚合后的特征。
3、特征转换对聚合后的特征进行线性变换以充分利用特征之间的关系。
4、非线性激活应用非线性激活函数如ReLU将线性变换后的特征映射到非线性空间。
5、循环迭代重复进行邻居聚合、特征转换和非线性激活的步骤直到达到所需的网络层数或收敛条件。
对于每个节点我们在聚类时从它的所有邻居节点处获取其特征信息当然也包括它自身的特征。
香港中大-商汤科技联合实验室的最新 AAAI 会议论文「Spatial Temporal Graph Convolution Networks for Skeleton Based Action Recognition」提出了一种新的 ST-GCN即时空图卷积网络模型用于解决基于人体骨架关键点的人类动作识别问题。该方法除了思路新颖之外在标准的动作识别数据集上也取得了较大的性能提升。
二、ST-GCN的各部分结构
1、数据输入结构
我们都知道一个动作都是连续的例如我们进行喝水这个动作那么从拿起水杯到喝完放下水杯着整个动作才能被称作为喝水这时候就需要对视频进行每一帧的骨骼点输出组装在一起形成一个完整的动作因此基于骨架的动作识别方法的一般输入为时间连续的人体骨架关键点。 这些关键点可以通过openpose进行姿态估计获取也可以手动标注。其数据维度一般为N, C, T, V, M 那么什么是openpose呢
2、openpose预处理
OpenPose 是一个标注人体的关节颈部肩膀肘部等连接成骨骼进而估计人体姿态的算法。作为视频的预处理工具我们只需要关注 OpenPose 的输出就可以了。 总的来说视频的骨骼标注结果维数比较高。在一个视频中可能有很多帧Frame。每个帧中可能存在很多人Man。每个人又有很多关节Joint。每一个关节又有不同特征位置、置信度。其数据维度一般为**N, C, T, V, M **
N代表视频的数量通常一个 batch 有 256 个视频其实随便设置最好是 2 的指数C代表关节的特征通常一个关节包含x,y,acc 等 3 个特征如果是三维骨骼就是 4 个x,y为节点关节的位置坐标acc为置信度。T 代表关键帧的数量一般一个视频有 150 帧。V 代表关节的数量通常一个人标注 18 个关节。M代表一帧中的人数一般选择平均置信度最高的 2 个人。
3、图划分策略
考虑到动作识别的特点作者并未使用单一的卷积核而是使用『图划分』将 A ^ \hat{A} A^ 分解成了 A 1 ^ , A 2 ^ , A 3 ^ \hat{A_{1}}, \hat{A_{2}}, \hat{A_{3}} A1^,A2^,A3^ A ^ \hat{A} A^表示的所有边如上图右侧所示
两个节点之间有一条双向边节点自身有一个自环 在ST-GCN这篇文章中作者的另一大创新点是通过对运动的分析引入了图划分策略即建立多个反应不同运动状态如静止离心运动和向心运动的邻接矩阵。作者在原文中提到其采用了三种不同的策略分别为
Uni-labeling即与跟根节点相邻的所有结点具有相同的label如下图b所示。Distance partitioning即根节点本身的label设为0其邻接点设置为1如下图c所示。Spatial configuration partitioning是本文提出的图划分策略。也就是以根节点与重心的距离为基准label0在所有邻接节点到重心距离中小于基准值的视为向节心点label1大于基准值的视为离心节点label2。什么意思呢对于一个根节点与它相连的边可以分为 3 部分图d。 第 1 部分连接了空间位置上比本节点更远离整个骨架重心的邻居节点黄色节点包含了离心运动的特征。第 2 部分连接了更为靠近重心的邻居节点蓝色节点包含了向心运动的特征。第 3 部分连接了根节点本身绿色节点包含了静止的特征。 使用这样的分解方法1 个图分解成了 3 个子图。卷积核也从 1 个变为了 3 个即 (1,18,18)变为 (3,18,18)。3 个卷积核的卷积结果分别表达了不同尺度的动作特征。要得到卷积的结果只需要使用每个卷积核分别进行卷积在进行加权平均和图像卷积相同。
代码如下
A []
for hop in valid_hop:a_root np.zeros((self.num_node, self.num_node))a_close np.zeros((self.num_node, self.num_node))a_further np.zeros((self.num_node, self.num_node))for i in range(self.num_node):for j in range(self.num_node):if self.hop_dis[j, i] hop:if self.hop_dis[j, self.center] self.hop_dis[i, self.center]:a_root[j, i] normalize_adjacency[j, i]elif self.hop_dis[j, self.center] self.hop_dis[i, self.center]:a_close[j, i] normalize_adjacency[j, i]else:a_further[j, i] normalize_adjacency[j, i]if hop 0:A.append(a_root)else:A.append(a_root a_close)A.append(a_further)
A np.stack(A)
self.A A这段代码的主要目的是根据给定的邻接矩阵 normalize_adjacency 和节点间的跳跃距离 self.hop_dis 创建多个权重矩阵。这些权重矩阵根据距离的不同对图进行划分最后将结果组合成一个三维张量 A。下面是这段代码的详细解释1. **初始化空列表 A**首先创建一个空列表 A 来存储权重矩阵。2. **循环遍历 valid_hop**对于给定的每个跳数valid_hop 列表中的每个跳数分别对图进行划分。3. **初始化权重矩阵**在每次循环中初始化三个权重矩阵 a_root、a_close 和 a_further它们都是与邻接矩阵尺寸相同的零矩阵。4. **遍历所有节点对**通过双层循环遍历所有节点对 (i, j)。- **根据跳数判断**对于给定的跳数 hop如果 self.hop_dis[j, i] 等于 hop则进行进一步分类- **a_root**如果 self.hop_dis[j, i] 等于 hop并且 j 和 i 到中心节点self.center的距离相同则将 normalize_adjacency[j, i] 赋值给 a_root[j, i]。- **a_close**如果 j 到中心节点的距离大于 i 到中心节点的距离则将 normalize_adjacency[j, i] 赋值给 a_close[j, i]。- **a_further**如果 j 到中心节点的距离小于 i 到中心节点的距离则将 normalize_adjacency[j, i] 赋值给 a_further[j, i]。5. **添加权重矩阵到列表**在每次循环中根据 hop 是否为 0将权重矩阵 a_root 添加到列表 A 中。如果 hop 不是 0则将 a_root a_close 和 a_further 分别添加到列表 A 中。6. **将列表转化为张量**使用 np.stack 将列表 A 转化为一个三维张量并将其赋值给 self.A。该代码主要通过对图的划分对节点对进行分类并创建多个权重矩阵。这些权重矩阵根据节点之间的距离和它们到中心节点的距离进行分类从而在神经网络中对不同跳数的节点进行不同权重的处理。4、GCN
从结果上看最简单的图卷积似乎已经能取得很好的效果了具体实现如下
def normalize_digraph(A):Dl np.sum(A, 0)num_node A.shape[0]Dn np.zeros((num_node, num_node))for i in range(num_node):if Dl[i] 0:Dn[i, i] Dl[i]**(-1)AD np.dot(A, Dn)return AD这个函数 normalize_digraph 用于对给定的有向图邻接矩阵 A 进行归一化处理。它使用一种基于节点的度的归一化方法具体过程如下1. **计算节点的出度**首先计算每个节点的出度即每个节点有多少条边从它出发。这通过计算矩阵 A 中每列的和即 np.sum(A, 0)得到并将结果存储在 Dl 中。2. **初始化度矩阵 Dn**创建一个零矩阵 Dn尺寸与邻接矩阵 A 相同。3. **填充度矩阵**对于每个节点 i如果节点 i 的出度大于 0则将 Dn[i, i] 设置为 Dl[i] 的倒数Dl[i]**(-1)。这意味着在度矩阵 Dn 中只对对角线元素节点的出度进行非零赋值。4. **对邻接矩阵进行归一化**计算邻接矩阵 A 与度矩阵 Dn 的点积得到归一化后的邻接矩阵 AD。这一步是关键的归一化操作。通过矩阵 A 与度矩阵 Dn 的点积操作邻接矩阵中的每个元素被它的源节点的出度进行归一化。5. **返回归一化后的邻接矩阵**最终返回归一化后的邻接矩阵 AD。通过这个归一化过程每个节点的出度被归一化到总和为 1这对于在神经网络中处理图数据时是一个重要的前处理步骤因为它有助于确保特征值和特征向量的稳定性。但是作者在实际项目中使用的图卷积公式是 aggre ( x ) D − 1 A X \operatorname{aggre}(x)D^{-1} A X aggre(x)D−1AX
化简如下 其实就是以边为权值对节点特征求加权平均。其中 A ^ D − 1 A \hat{A}D^{-1} A A^D−1A可以理解为卷积核。 加上上面的图划分策略我们可以可以写出带有k 个卷积核的图卷积表达式了 对 v求和代表了节点的加权平均对 k 求和代表了不同卷积核 feature map 的加权平均具体实现如下
代码如下
self.conv nn.Conv2d(in_channels,out_channels * kernel_size,kernel_size(t_kernel_size, 1),padding(t_padding, 0),stride(t_stride, 1),dilation(t_dilation, 1),biasbias)def forward(self, x, A):assert A.size(0) self.kernel_sizex self.conv(x)n, kc, t, v x.size()x x.view(n, self.kernel_size, kc//self.kernel_size, t, v)x torch.einsum(nkctv,kvw-nctw, (x, A))return x.contiguous(), A这段代码展示了一个卷积操作通过 nn.Conv2d 类和其前向传播函数 forward 方法的实现。在 forward 方法中输入是一个四维张量 x 和一个邻接矩阵 A。在代码中这个前向传播函数主要包括以下步骤1. **断言**使用 assert 语句检查邻接矩阵 A 的尺寸。确保 A 的第一个维度等于卷积核大小即 self.kernel_size。2. **卷积操作**通过调用 self.conv(x) 对输入张量 x 进行卷积操作得到卷积后的输出。3. **调整维度**卷积操作得到的输出 x 是一个四维张量 (n, kc, t, v)这里 n 是批次大小kc 是通道数乘以卷积核大小out_channels * kernel_sizet 是时间步数v 是节点数量。为了接下来的操作将 x 重整为一个五维张量 (n, self.kernel_size, kc // self.kernel_size, t, v)在其中 kc // self.kernel_size 代表的是原通道数。4. **爱因斯坦求和约定**使用 torch.einsum 函数根据爱因斯坦求和约定einsum对调整后的 x 和邻接矩阵 A 进行运算。运算的形式为 nkctv,kvw-nctw意思是将 x 的前两个维度和 A 的后两个维度进行矩阵乘法。得到的结果是一个四维张量。5. **返回结果**forward 方法返回连续化后的 x使用 contiguous() 方法确保 x 的连续性和邻接矩阵 A。这些步骤组合在一起展示了通过卷积操作与邻接矩阵来对输入张量进行处理并最终返回处理后的张量和邻接矩阵。5、TCN
GCN 帮助我们学习了到空间中相邻关节的局部特征。在此基础上我们需要学习时间中关节变化的局部特征。**如何为 Graph 叠加时序特征是图网络面临的问题之一。**这方面的研究主要有两个思路时间卷积TCN和序列模型LSTM。
ST-GCN 使用的是 TCN由于形状固定我们可以使用传统的卷积层完成时间卷积操作。为了便于理解可以类比图像的卷积操作。st-gcn 的 feature map 最后三个维度的形状为 (C,V,T) 与图像 feature map 的形状 (C,W,H) 相对应。 在图像卷积中卷积核的大小为『w』× 『1』则每次完成 w 行像素1 列像素的卷积。『stride』为 s则每次移动 s 像素完成 1 行后进行下 1 行像素的卷积 在时间卷积中卷积核的大小为『temporal_kernel_size』 ×『1』则每次完成 1 个节点temporal_kernel_size 个关键帧的卷积。『stride』为 1则每次移动 1 帧完成 1 个节点后进行下 1 个节点的卷积。
代码如下
padding ((kernel_size[0] - 1) // 2, 0)self.tcn nn.Sequential(nn.BatchNorm2d(out_channels),nn.ReLU(inplaceTrue),nn.Conv2d(out_channels,out_channels,(temporal_kernel_size, 1),(1, 1),padding,),nn.BatchNorm2d(out_channels),nn.Dropout(dropout, inplaceTrue),
)这段代码定义了一个名为 self.tcn 的 nn.Sequential 对象用于构建一个时序卷积神经网络TCN的一个层。这层由几个 PyTorch 模块组成包括批归一化、激活函数、卷积层、再批归一化和丢弃层。下面是对每个组件的解释1. **nn.BatchNorm2d**这是一个二维批归一化层。它会对输入张量在特征维度上进行批量归一化即在维度 1即通道数的维度上进行归一化。归一化有助于稳定训练过程。2. **nn.ReLU**这是一个激活函数层使用的是整形版的 ReLU 函数。ReLU 是一种常用的非线性激活函数。inplaceTrue 参数表示激活操作将直接对输入张量进行修改而不是创建一个新的张量。3. **nn.Conv2d**这是一个二维卷积层卷积核尺寸为 (temporal_kernel_size, 1)卷积步长为 (1, 1)填充为 padding。out_channels 是输出通道数与输入通道数一致因此该层的输入输出维度相同。卷积操作有助于捕获输入张量中的局部时空特征。4. **第二个 nn.BatchNorm2d**再次使用批归一化层以进一步稳定训练过程和提高模型性能。5. **nn.Dropout**这是一个丢弃层目的是在训练过程中随机地将一部分神经元的输出设置为 0以减少过拟合。dropout 参数控制丢弃的概率inplaceTrue 表示丢弃操作直接在输入张量上进行。总的来说这段代码定义了一个用于时序卷积神经网络的模块结合了批归一化、激活函数、卷积操作和丢弃以提高模型的稳定性和性能。通过 nn.Sequential 对象将这些层结合在一起形成一个顺序执行的结构。
6、Attention
作者在进行图卷积之前还设计了一个简易的注意力模型ATT
代码如下
# 注意力参数
# 每个 st-gcn 单元都有自己的权重参数用于训练
self.edge_importance nn.ParameterList([nn.Parameter(torch.ones(self.A.size()))for i in self.st_gcn_networks
])
# st-gcn 卷积
for gcn, importance in zip(self.st_gcn_networks, self.edge_importance):print(x.shape)# 关注重要的边信息x, _ gcn(x, self.A * importance)这段代码的主要目的是使用一系列堆叠的 ST-GCN空间时间图卷积网络层来处理输入数据 x同时考虑每个 ST-GCN 层的边权重 importance。以下是对代码的解释1. **self.edge_importance**这是一个 nn.ParameterList 对象包含多个可学习的参数。每个参数对应一个 ST-GCN 层并用于调整图的边权重。这些参数初始值为全 1 的张量大小与邻接矩阵 self.A 相同。这些权重参数用于在训练过程中学习边的重要性。2. **迭代堆叠的 ST-GCN 层**代码使用 zip 函数迭代遍历 self.st_gcn_networks 中的 ST-GCN 层和 self.edge_importance 中的权重参数。对于每一层代码首先打印当前输入数据 x 的形状。3. **关注重要的边信息**对于每个 st_gcn 层代码将当前的输入数据 x 与对应的权重参数 importance 相乘通过 self.A * importance 得到然后将乘积结果与 gcn 层进行操作。gcn 层是一个 ST-GCN 层它接受经过加权的邻接矩阵 self.A * importance 和输入数据 x然后对 x 进行卷积操作。卷积操作的结果返回并更新 x。4. **操作过程**在每次迭代中代码输出当前输入数据 x 的形状这有助于跟踪数据在网络中的流动和变化。在 st_gcn 层中x 会被调整为图结构的边重要性并通过卷积处理。每一层的输出结果作为下一层的输入数据。这段代码展示了如何在一个网络中使用多个 ST-GCN 层来处理输入数据并通过权重参数调整边的重要性。这种关注边权重的方式可以让模型更好地学习空间时间图结构的特征提高模型的预测性能。7、整体结构
时空图卷积分为空间图卷积和时间图卷积其中空间图卷积是核心部分。一个空间图卷积加上一个时间卷积就是一层一共10层但是第一层没有残差结构所以大部分文献都称它9层。原代码里的空间图卷积对应名称gcn,时间对应tcn。每一层的结构如下。 论文中的网络结构如下 首先我们先来看第一部分对输入矩阵进行归一化 self.data_bn nn.BatchNorm1d(in_channels * A.size(1))#函数data_bn的定义N, C, T, V, M x.size()
# 进行维度交换后记得调用 contiguous 再调用 view 保持显存连续
x x.permute(0, 4, 3, 1, 2).contiguous()
x x.view(N * M, V * C, T)
x self.data_bn(x)
x x.view(N, M, V, C, T)
x x.permute(0, 1, 3, 4, 2).contiguous()
x x.view(N * M, C, T, V)
这段代码是一种数据预处理步骤用于对输入数据 x 进行规范化和重排。数据的形状在不同的步骤中发生了变化。让我们分解这段代码来理解其功能和目的1. **输入数据**代码开始时输入数据 x 的形状为 (N, C, T, V, M)。这代表了一个五维张量其中- N样本数量batch size。- C通道数例如 RGB 图像中的红、绿、蓝三通道。- T时间步数时间维度表示时序数据。- V节点数例如表示人体姿态中的关节数。- M图形实例数量例如在多个人体实例的场景下M 表示实例的数量。2. **调整维度顺序**通过 x.permute(0, 4, 3, 1, 2)数据的维度顺序被调整为 (N, M, V, C, T)。这样做的原因是为了方便后续的重整和操作。3. **重整形状**通过 x.view(N * M, V * C, T)将数据重整为 (N * M, V * C, T) 形状。这种重整方式将批量和实例维度合并在一起方便后续的批量归一化batch normalization。4. **批量归一化**self.data_bn(x) 对数据应用了批量归一化。这是对数据进行归一化的一种常见方法有助于稳定和加速训练过程。5. **恢复形状**通过 x.view(N, M, V, C, T) 将数据恢复为原始的 (N, M, V, C, T) 形状。6. **调整维度顺序**再次通过 x.permute(0, 1, 3, 4, 2) 调整维度顺序为 (N, M, C, T, V)这样做可能是为了适应后续的网络层或操作。7. **重整形状**最后通过 x.view(N * M, C, T, V)将数据重新整形为 (N * M, C, T, V) 形状。归一化是在时间和空间维度下进行的 V×C 。也就是将一个关节在不同帧下的位置特征x 和 y 和 acc进行归一化。
这个操作的作用
关节在不同帧下的关节位置变化很大如果不进行归一化不利于算法收敛在不同 batch 不同帧下的关节位置基本上服从随机分布不会造成不同 batch 归一化结果相差太大而导致准确率波动。
然后通过 ST-GCN 单元交替的使用 GCN 和 TCN对时间和空间维度进行变换 # N*M(256*2)/C(3)/T(150)/V(18)
Input[512, 3, 150, 18]
ST-GCN-1[512, 64, 150, 18]
ST-GCN-2[512, 64, 150, 18]
ST-GCN-3[512, 64, 150, 18]
ST-GCN-4[512, 64, 150, 18]
ST-GCN-5[512, 128, 75, 18]
ST-GCN-6[512, 128, 75, 18]
ST-GCN-7[512, 128, 75, 18]
ST-GCN-8[512, 256, 38, 18]
ST-GCN-9[512, 256, 38, 18]代码如下
self.st_gcn_networks nn.ModuleList((st_gcn(in_channels, 64, kernel_size, 1, residualFalse, **kwargs0),st_gcn(64, 64, kernel_size, 1, **kwargs),st_gcn(64, 64, kernel_size, 1, **kwargs),st_gcn(64, 64, kernel_size, 1, **kwargs),st_gcn(64, 128, kernel_size, 2, **kwargs),st_gcn(128, 128, kernel_size, 1, **kwargs),st_gcn(128, 128, kernel_size, 1, **kwargs),st_gcn(128, 256, kernel_size, 2, **kwargs),st_gcn(256, 256, kernel_size, 1, **kwargs),st_gcn(256, 256, kernel_size, 1, **kwargs),
))# initialize parameters for edge importance weighting
if edge_importance_weighting:self.edge_importance nn.ParameterList([nn.Parameter(torch.ones(self.A.size()))for i in self.st_gcn_networks])
else:self.edge_importance [1] * len(self.st_gcn_networks)# ST-GCN与可学习的权重矩阵不断重复与堆叠
for gcn, importance in zip(self.st_gcn_networks, self.edge_importance):x, _ gcn(x, self.A * importance)这段代码定义了一个基于空间时间图卷积网络Spatial-Temporal Graph Convolutional Network, ST-GCN的模型并对输入数据 x 进行多层的 ST-GCN 操作。模型中使用了多个堆叠的 ST-GCN 层。以下是对代码的解释1. **self.st_gcn_networks**这是一个 nn.ModuleList 对象包含多个 st_gcn 模块。st_gcn 是空间时间图卷积网络ST-GCN的一个层每一层接受不同的输入和输出通道数以及不同的卷积内核和步长。模型通过将多个 st_gcn 层堆叠在一起形成完整的网络。2. **edge_importance_weighting**该变量控制是否对边缘进行权重调整。根据其值代码初始化了 self.edge_importance。如果 edge_importance_weighting 为 True则初始化一个可学习的权重参数列表 nn.ParameterList每个 st_gcn 层对应一个权重参数否则将 self.edge_importance 设置为包含一个权重为 1 的列表。3. **迭代堆叠的 ST-GCN 层**代码使用 zip 函数迭代遍历 self.st_gcn_networks 中的 ST-GCN 层以及 self.edge_importance 中的权重参数。对于每一层代码将当前的输入数据 x 与边权重 importance 相乘后作为输入并通过当前的 st_gcn 层进行操作。每一层的输出结果作为下一层的输入。4. **操作 gcn 层**在每次迭代中x 被传递到 gcn 层与边权重 importance 的乘积中计算结果被存储在 x 中作为下一层的输入。这种连续的操作使得输入数据 x 在多个 ST-GCN 层之间传递和处理。总体而言这段代码实现了一个多层 ST-GCN 网络模型使用可学习的权重矩阵对输入数据进行处理。这样能够在模型中自动学习边缘的重要性从而提高模型的性能。空间维度是关节的特征开始为 3时间的维度是关键帧数开始为 150。在经过所有 ST-GCN 单元的时空卷积后关节的特征维度增加到 256关键帧维度降低到 38。
个人感觉这样设计是因为人的动作阶段并不多但是每个阶段内的动作比较复杂。比如一个挥高尔夫球杆的动作可能只需要分解为 5 步但是每一步的手部、腰部和脚部动作要求却比较多。
最后使用平均池化、全连接层或者叫 FCN对特征进行分类具体实现如下 # self.fcn nn.Conv2d(256, num_class, kernel_size1)# global pooling
x F.avg_pool2d(x, x.size()[2:])
x x.view(N, M, -1, 1, 1).mean(dim1)
# prediction
x self.fcn(x)
x x.view(x.size(0), -1)这段代码展示了一个典型的卷积神经网络CNN中的最后部分用于对特征进行全局池化global pooling、预测分类以及将输出形状调整为合适的形式。让我们分解这段代码来理解其功能1. **定义卷积层**- self.fcn nn.Conv2d(256, num_class, kernel_size1): 定义了一个卷积层 self.fcn输入通道数为 256输出通道数为 num_class类别数量卷积核尺寸为 1x1。这是一种用于特征图变换的卷积层将特征映射到分类的类别空间。2. **全局池化**- x F.avg_pool2d(x, x.size()[2:]): 使用 PyTorch 的 F.avg_pool2d 函数对 x 进行全局平均池化。池化的核大小和步长等于特征图的空间维度因此整个特征图将被池化成一个单一的值。3. **调整形状**- x x.view(N, M, -1, 1, 1).mean(dim1): 这一步首先通过 x.view() 将张量 x 形状调整为 (N, M, -1, 1, 1)。这里的 -1 表示剩余的通道和特征图维度被折叠成一个通道。然后通过 mean(dim1) 对 M 维度求均值即将实例数目维度合并这种操作常用于多个实例的特征融合。4. **预测分类**- x self.fcn(x): 将 x 输入到 self.fcn 卷积层中执行分类操作。这一步的输出是 (N, num_class, 1, 1) 形状的张量。5. **调整形状**- x x.view(x.size(0), -1): 通过 view 操作将输出调整为 (N, num_class) 形状这是为了适应后续的损失函数或预测任务。Graph 上的平均池化可以理解为对 Graph 进行 read out即汇总节点特征表示整个 graph 特征的过程。这里的 read out 就是汇总关节特征表示动作特征的过程了。通常我们会使用基于统计的方法例如对节点求 max,sum,mean 等等。mean 鲁棒性比较好所以这里使用了 mean。
这些就是ST-GCN的整体的网络架构了
三、总结
ST-GCN应当具备能够从时空维度提取特征的能力其在GCN中的表现就是能够同时聚合时空维度的信息如下图所示。 其具体网络层如图所示 其具体可以分为以下步骤
步骤1引入一个可学习的权重矩阵与邻接矩阵等大小与邻接矩阵按位相乘。该权重矩阵叫做“Learnable edge importance weight”用来赋予邻接矩阵中重要边节点较大的权重且抑制非重要边节点的权重。步骤2将加权后的邻接矩阵与输入送至GCN中进行运算。同时作者还引入了残差结构一个CNNBN计算获得Res与GCN的输出按位相加实现空间维度信息的聚合。步骤3利用TCN网络实际上是一种普通的CNN在时间维度的kernel size1实现时间维度信息的聚合。
其具体结合openpose实现可参考用自建kinetics-skeleton行为识别数据集训练st-gcn网络流程记录_kinetics_skeleton数据集制作-CSDN博客
参考时空图卷积ST-GCN理论和代码详解-CSDN博客
深入理解时空图卷积ST-GCN-CSDN博客
(8 条消息) 如何评价ST-GCN动作识别算法 - 知乎 (zhihu.com)
本文章仅当做记录学习使用以便以后回顾