基于骨骼点的动作识别 (Skeleton-based Action Recognition) 旨在从一系列时间连续的人体骨骼点中识别正在执行的动作。相较于 RGB 帧或光流,人体骨骼这一模态与人体动作天然更密切,且更加紧凑。
因此,人体骨骼模态在各类动作识别任务中有广泛的应用。基于骨骼点的动作识别的动作识别,往往具有比基于其他模态的算法更轻量,更具泛化性的特点。当然,由于骨骼点所包含信息的局限性,基于骨骼点的算法很难对一些与物体或场景关系紧密的动作进行有效识别,可以说有利有弊。
目前对于基于骨骼点的动作识别这一任务, 主流的公开数据集有 NTURGB+D,NTURGB+D 120,Kinetics-400 等,下表总结了这些数据集的相关信息及特点。
MMAction2 中提供了以下大部分数据集由 HRNet 提取的 2D 人体关键点,这种方式提取的人体骨骼质量较高,在各个数据集上都可以取得良好的性能。
MMAction2 链接:
https://github.com/open-mmlab/mmaction2/tree/master/tools/data/skeleton
在骨骼动作识别中,GCN 一族的方法占据主流地位。在基于 GCN 的方法出现之前,早期的深度学习方法将一段时间内的骨骼点坐标构造为向量, 作为 RNN,LSTM 的输入来预测行为类别。这类方法的能力较为有限,它们没有显式地利用骨骼点之间的空间关系,这些信息对于理解人类动作非常重要。
由于骨骼点属于图结构的输入, 而图卷积 (GCN) 比较擅长建模图数据, 因此采用 GCN 的方法成为主流, 例如 ST-GCN, 2s-AGCN, ShiftGCN 等等。
本文内容
ST-GCN
2s-AGCN
PoTion
PoseC3D
1. ST-GCN
ST-GCN 是将 GCN 应用在基于骨骼点的动作识别的开山之作, 这里首先介绍一下 ST-GCN 整体的网络结构, 如下图所示, 首先对视频采用 OpenPose 等算法进行姿态估计, 并构造骨骼点的时空图。
接着利用多层时空图卷积(ST-GCN)逐渐在图上生成更高级别的特征图,最后通过 Softmax 激活函数预测出属于每类动作的概率大小。
数据预处理
OpenPose 是一个标注人体的关节(颈部,肩膀,肘部等),连接成骨骼,进而估计人体姿态的算法。输入网络的一个 batch 可以用一个5维矩阵(N, C, T, V, M)表示:
- N 代表视频的数量,例如一个 batch 中有 32 个视频,则 N 等于 32;
- C 代表骨骼点的维度,通常一个骨骼点包含 3 个维度:
二维骨骼点:(x, y, score),其中 (x, y) 表示骨骼点坐标,score 表示骨骼点的置信度(针对模型预测的骨骼点,若为 ground truth,score 可以缺省);三维骨骼点:(x, y, z),即为骨骼点的三维坐标;
- T 代表一个样本所采的帧数,对于 GCN 方法,这个值通常等于最长训练视频的长度,如训练集中最长视频含有 300 帧,则 T 设置为 300。对于更短的视频,通常采用循环的方式补齐帧数;
- V 代表骨骼点的数量,如果是 NTU-RGB+D 数据集,V 等于 25,如果是使用 OpenPose 进行姿态估计,V 等于 18;
- M 代表一帧中的人数,根据情况选择,对于 NTU-RGB+D 数据集一般为 2;
上述数据在输入 ST-GCN 之前需要先进行标准化操作,具体地说,就是标准化每个骨骼点在所有帧上的坐标值。
N, C, T, V, M = x.size() # N 3 300 25(17) 2
x = x.permute(0, 4, 3, 1, 2).contiguous() # N M V C T
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) # Nx2 3 300 25(17)
其中的 data_bn 定义如下:
self.data_bn = nn.BatchNorm1d(in_channels * A.size(1))
网络结构
ST-GCN 网络主要包括10个 block 和一个分类层, 每个 block 包括图卷积 GCN 和时间卷积 TCN。
self.st_gcn_networks = nn.ModuleList((
st_gcn_block(in_channel, 64, kernel_size, 1, residual=False, **kwargs0),
st_gcn_block(6, 64, kernel_size, 1, **kwargs),
st_gcn_block(64, 64, kernel_size, 1, **kwargs),
st_gcn_block(64, 64, kernel_size, 1, **kwargs),
st_gcn_block(64, 128, kernel_size, 2, **kwargs),
st_gcn_block(128, 128, kernel_size, 1, **kwargs),
st_gcn_block(128, 128, kernel_size, 1, **kwargs),
st_gcn_block(128, 256, kernel_size, 2, **kwargs),
st_gcn_block(256, 256, kernel_size, 1, **kwargs),
st_gcn_block(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)
# fcn for prediction
self.fcn = nn.Conv2d(256, num_class, kernel_size=1)
这里简单介绍一下图卷积 GCN 的步骤:
- 根据图结构建立一个邻接矩阵 A, 并对其进行归一化, 得到 A ;
- 对图输入 X 进行普通的二维卷积, 得到 XW ;
- 将归一化的邻接矩阵 A 和卷积提取的特征 XW 进行聚合, 得到 AXW 作为输出。
def forward(self, x, A):
x = 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 分解为 3 个子矩阵, 分别表达向心运动、离心运动和静止的动作特征。
具体地说,对于一个根节点,与它相连的边可以分为 3 部分, 如下图所示:
第 1 部分连接了空间位置上比本节点更远离整个骨架重心的邻居节点(黄色节点),包含了离心运动的特征。
第 2 部分连接了更为靠近重心的邻居节点(蓝色节点),包含了向心运动的特征。
第 3 部分连接了根节点本身(绿色节点),包含了静止的特征。
TCN 其实是一种普通的 CNN, 在时间维度上使用 kernel_size > 1 的卷积核进行时间维度信息的聚合。
同时还引入残差结构计算得到 res, 并与 tcn 的输出相加作为一个 block 的输出。
def forward(self, x, A):
res = self.residual(x)
x, A = self.gcn(x, A)
x = self.tcn(x) + res
return self.relu(x), A
在网络的最后, ST-GCN 使用全局平均池化以及 1x1 卷积输出预测类别:
# global pooling
x = F.avg_pool2d(x, x.size()[2:])
x = x.view(N, M, -1, 1, 1).mean(dim=1)
# prediction
x = self.fcn(x)
x = x.view(x.size(0), -1)
return x
2. 2s-AGCN
2s-AGCN 是对 ST-GCN 的改进, 发表在 CVPR2019, 论文提出了一个双流自适应图卷积网络, 主要的创新点有两个:
-提出了自适应的图卷积;
-使用双流网络利用骨骼点的一阶和二阶信息。
先回顾一下 ST-GCN 的整体公式, 如下所示:
Wk 是权重, Ak 是邻接矩阵,Mk 是注意力掩码, Kv 是子图的个数, 一般为 3 (静止,向心,离心)。
ST-GCN 的注意力掩码是与邻接矩阵直接相乘, 这会造成一个问题: 如果邻接矩阵 Ak 里面部分元素为 0, 无论 Mk 对应元素为何值, 最后结果都为 0。
对于类似“行走”动作,手和腿的联系很大,但是手和腿没有直接相连,所以效果不好。其次, ST-GCN 只利用了骨骼点数据的一阶特征(骨骼点坐标), 没有利用骨骼点的二阶特征(关节的长度和方向)。
针对第一个问题, 2s-AGCN 提出的自适应图卷积的整体公式如下:
相比 ST-GCN, 2s-AGCN的邻接矩阵是三个部分之和。
第一部分Ak ST-GCN 与 ST-GCN 的邻接矩阵 Ak 相同,
第二部分 Bk 是一个可训练的 N x N 矩阵, 它不仅能表示两个骨骼点之间是否存在联系, 而且能表示联系的强弱,
第三部分Ck 是针对每个样本学习一个特有的图, 使用高斯嵌入函数来捕捉两个骨骼点之间的相似性,通过 Softmax 处理生成 0-1 的概率。
针对上述第二个问题, 作者提出了双流法, 一个分支输入骨骼点的坐标信息, 另一个分支输入骨骼 (bone) 的长度和方向. 长度和方向看起来比坐标要复杂,其实很简单。
首先寻找一个人体骨骼的重心,就是人胸腔部分作为中心点,因为每个骨骼都有两个点,把靠近中心点的骨骼点看做源骨骼点,远离中心点的骨骼点看做目标骨骼点。骨骼就是从一个点指向另外一个点的向量,向量的长度就是骨骼的长度,向量的方向就是骨骼的方向。
因为骨骼 (bone) 是两个骨骼点 (skeleton) 组成,而且没有环状的骨骼点图,所以骨骼点数比骨骼数多 1 个。这里添加一个值为 0 的空骨骼,这样骨骼点数与骨骼数就相同,网络也相同。最后双流法的框架如下图。
除了基于 GCN 的方法外, 还有一些工作使用 CNN-based 的方法解决基于骨骼点的行为识别任务, 例如 PoTion(2D-CNN), PoseC3D(3D-CNN) 等。
3. PoTion
PoTion 是一种基于 2D-CNN 的骨骼点行为识别算法, 基于实时姿态估计算法 [4] 提取每一帧的姿态信息,获得骨骼点的热力图, 接着根据帧所在的时间维度给热力图上色, 然后针对每一个关节 ( joint ), 把所有帧上的热力图相加作为输入传给一个包含 6 个卷积层和一个 FC 层的 2D-CNN 网络进行行为识别预测。
PoTion 方法还可以与传统双流网络 (RGB 和 Optical Flow) 相结合, 提高动作识别的性能. 但是不足的地方在于该算法采用上色的聚合方式会带来一定的信息丢失。
4. PoseC3D
PoseC3D 是一种基于 3D-CNN 的骨骼点行为识别算法。 跟基于 GCN 的方法不同的是, PoseC3D 基于提取好的 2D 姿态, 生成 K x H x W 的二维关键点热图 ( K 是骨骼点的数量), 再堆叠视频中T帧热图 构成 K x H x T x W 的 3D 热图作为骨骼点模态的输入。
同时, PoseC3D 还将骨骼点模态与其他模态(如 RGB 的特征)进行融合, 从而得到更好的识别效果。PoseC3D 网络整体结构如下:
PoseC3D-SlowFast 包含两个分支, 分别处理 RGB 和骨骼点模态, RGB 分支具有低帧率以及更大的网络宽度,骨骼点分支具有高帧率和更小的网络宽度。两分支间存在双向连接,以促进模态间的特征融合。模型将两分支的预测结果融合,作为最终的预测。
目前 MMAction2 中 skeleton-based 的行为识别算法在各 benchmark 数据集上和 SOTA 的结果对比如下:
MMAction2 现在已经支持 ST-GCN 和 PoseC3D 算法, 后续还会支持更多行为识别算法以及与 skeleton 相关的任务算法, 敬请期待~~
同时, 有兴趣了解更多视频内容理解的内容可前往 Github , MMAciton2欢迎大家关注 star, fork ~
MMAction2 链接:
https://github.com/open-mmlab/mmaction2/tree/master/tools/data/skeleton
Reference
[1] Spatial Temporal Graph Convolutional Networks for Skeleton-Based Action Recognition
[2] PoTion: Pose MoTion Representation for Action Recognition
[3] Two-Stream Adaptive Graph Convolutional Networks for Skeleton-Based Action Recognition
[4] Realtime multi-person 2D pose estimation using part affinity field
[5] Revisiting Skeleton-based Action Recognition