单词

  1. cascade: 级联
  2. telepresence:“网真”
  3. occlusions: 遮挡
  4. anatomical: 解剖
  5. amenable: 有义务的
  6. skeletal: 骨骼的
  7. kinematic: 运动
  8. egocentric: 以自我为中心
  9. monocular: 单眼的
  10. robust: 健壮的
  11. Methodology: 方法
  12. vertices: 顶点
  13. ablation: 消融
  14. Metrics: 指标

小小科普

参考: 图卷积神经网络

图卷积神经网络:

  • 简单的来说就是其研究的对象是图数据(Graph),研究的模型是卷积神经网络;

    Euclidean data:

  • Euclidean data最显著的特征就是有规则的空间结构,比如图片是规则的正方形栅格,比如语音是规则的一维序列。而这些数据结构能够用一维、二维的矩阵表示,卷积神经网络处理起来很高效;

    Non Euclidean data:

  • 推荐系统、电子交易、计算几何、脑信号、分子结构等抽象出的图谱;

    作者的一些思考

    难点

  • 手或者目标对象的遮挡问题;

  • 第一人称相机拍摄过程中引入极大的相机的抖动;

    如何实现估计

  • 对手和物体进行整体把握,不宜分别估计;

    • 手和物体在交互过程中是有相互制约的;
    • 对手姿态的估计,同样可以为物体的属性提供线索;
    • 物体的姿态同样限制握住它的手的姿态;
  • 在估计过程时,将手以及物体表征为一个单独的图;
  • 产生3D姿态估计是,结合第一人称和第三人称视频数据(???);
    • 首先估计2D关键点和物体边界;
    • 然后在二维姿态估计的基础上,分层次联合恢复深度信息(3D);
    • 基于检测的模型在检测2D关键点方面效果非常好,但是由于三维检测时的高度非线性以及巨大的输出空间,使得基于回归的模型在三维检测方面更流行;
  • 模型分两步走:

    • 首先是2D关键点以及物体边界估计;
    • 然后是2D到3D的转换,改进Adaptive Graph U-Net实现;
    • 这两步可以分开进行训练,2D到3D转换网络使用了大量合成数据(一个数据集)进行训练;

      方法分析

      image.png

      步骤

      从上图可以看出来,模型至少经过了三步:初步估计2D关键点,2D关键点提纯,2D转3D;
      初步估计2D关键点
  • 采用ResNet10提取特征,并估计初始的2D关键点;

2D关键点提纯

  • 将坐标与图像特征连接起来作为3层图卷积的输入图的特征,利用邻域特征的力量来估计更好的二维位姿;

2D转3D

  • Adaptive Graph U-Net;

    图像编码与图卷积

  • 采用轻量级ResNet(ResNet10)作为图像编码器,其为每张图片输出2048D的特征向量,然后采用一个全连接层产生初始的2D关键点数据;(回归出2D关键点)

  • 将2048D的Image feature和每个关键点坐标进行连接产生多个2050D的特征,这些特征将被用于产生节点;
  • 利用3层结构的自适应图卷积网络,结合临近信息对2D关键点进行修改;
  • 最后将修改后的关键点输入到自适应U-Net中实现维度的转换;

    Adaptive Graph U-Net

    原版的U-Net尚未了解…
    将问题转为图问题的原因:

  • 能够利用临近信息;

  • 更能够发现手和物体的关系;

image.png

图卷积

之前了解过Graph U-Net,但是其中的gPool和gUnpool在此处不再实用,因为此处的graph为稀疏图,采用该论文中的gPool,基本上会导致节点的孤立。

图卷积操作

此映射的作用是:将临接节点的信息不断汇总到本节点。
image.png
其中:image.png,image.png,HOPE-Net: A Graph-based Model for Hand-Object Pose Estimation_2020CVPR - 图6
此公式和卷积运算的关系可以参考GCN论文!(卷积运算可以最终近似推出此公式)
其中一个任务就是确定临接矩阵,当然可以选择普通的,按照关键点实际连接情况进行临接矩阵的确定。但是,通过实验发现,让网络自行学习临接矩阵更优(此类操作严格来说使得该矩阵不再是临接矩阵)。所以Adaptive graph convolution需要更新HOPE-Net: A Graph-based Model for Hand-Object Pose Estimation_2020CVPR - 图7

运算实现代码

image.png实现

  1. # A_hat is trainable
  2. A_hat = Parameter(torch.eye(29).float().cuda(), requires_grad=True)
  3. # ......
  4. # A_hat: batch * 29 * 29
  5. batch, N = A_hat.shape[:2]
  6. # D_hat: batch * 29
  7. D_hat = (torch.sum(A_hat, 1) + 1e-5) ** (-0.5)
  8. L = D_hat.view(batch, N, 1) * A_hat * D_hat.view(batch, 1, N)
  9. # 此处的矩阵运算与数学意义上的不同,但是显然和上式公式是等价的。但是相对于矩阵乘法来说降低了运算量

HOPE-Net: A Graph-based Model for Hand-Object Pose Estimation_2020CVPR - 图9卷积运算实现

  1. def forward(self, X, A):
  2. batch = X.size(0)
  3. #A = self.laplacian(A)
  4. # A_hat: batch * 29 * 29
  5. A_hat = A.unsqueeze(0).repeat(batch, 1, 1)
  6. X = self.fc(torch.bmm(self.laplacian_batch(A_hat), X))
  7. if self.activation is not None:
  8. X = self.activation(X)
  9. return X

注:nn.Linear函数好像和之前印象中的存在偏差!(不需要进行“拉直”操作)
(之前理解的Linear不对)
gPool和gUnpool实现

  1. class GraphPool(nn.Module):
  2. def __init__(self, in_nodes, out_nodes):
  3. super(GraphPool, self).__init__()
  4. self.fc = nn.Linear(in_features=in_nodes, out_features=out_nodes)
  5. def forward(self, X):
  6. # 是对节点数进行pool,所以需要transpose
  7. X = X.transpose(1, 2)
  8. X = self.fc(X)
  9. X = X.transpose(1, 2)
  10. return X

Graph U-Net实现

  • 就是先不断地利用conv和pool不断降低节点数和feature大小;
  • 然后利用conv和unpool不断增加节点数和feature的大小;
  • 显然由于这是矩阵运算,所以对于feature输出size的控制相对于图片卷积来说方便很多;

    1. class GraphUNet(nn.Module):
    2. def __init__(self, in_features=2, out_features=3):
    3. super(GraphUNet, self).__init__()
    4. self.A_0 = Parameter(torch.eye(29).float().to(device), requires_grad=True)
    5. self.A_1 = Parameter(torch.eye(15).float().to(device), requires_grad=True)
    6. self.A_2 = Parameter(torch.eye(7).float().to(device), requires_grad=True)
    7. self.A_3 = Parameter(torch.eye(4).float().to(device), requires_grad=True)
    8. self.A_4 = Parameter(torch.eye(2).float().to(device), requires_grad=True)
    9. self.A_5 = Parameter(torch.eye(1).float().to(device), requires_grad=True)
    10. self.gconv1 = GraphConv(in_features, 4) # 29 = 21 H + 8 O
    11. self.pool1 = GraphPool(29, 15)
    12. self.gconv2 = GraphConv(4, 8) # 15 = 11 H + 4 O
    13. self.pool2 = GraphPool(15, 7)
    14. self.gconv3 = GraphConv(8, 16) # 7 = 5 H + 2 O
    15. self.pool3 = GraphPool(7, 4)
    16. self.gconv4 = GraphConv(16, 32) # 4 = 3 H + 1 O
    17. self.pool4 = GraphPool(4, 2)
    18. self.gconv5 = GraphConv(32, 64) # 2 = 1 H + 1 O
    19. self.pool5 = GraphPool(2, 1)
    20. self.fc1 = nn.Linear(64, 20)
    21. self.fc2 = nn.Linear(20, 64)
    22. self.unpool6 = GraphUnpool(1, 2)
    23. self.gconv6 = GraphConv(128, 32)
    24. self.unpool7 = GraphUnpool(2, 4)
    25. self.gconv7 = GraphConv(64, 16)
    26. self.unpool8 = GraphUnpool(4, 7)
    27. self.gconv8 = GraphConv(32, 8)
    28. self.unpool9 = GraphUnpool(7, 15)
    29. self.gconv9 = GraphConv(16, 4)
    30. self.unpool10 = GraphUnpool(15, 29)
    31. self.gconv10 = GraphConv(8, out_features, activation=None)
    32. self.ReLU = nn.ReLU()
    33. def _get_decoder_input(self, X_e, X_d):
    34. return torch.cat((X_e, X_d), 2)
    35. def forward(self, X):
    36. # X: batch * 29 * 2
    37. X_0 = self.gconv1(X, self.A_0)
    38. X_1 = self.pool1(X_0)
    39. X_1 = self.gconv2(X_1, self.A_1)
    40. X_2 = self.pool2(X_1)
    41. X_2 = self.gconv3(X_2, self.A_2)
    42. X_3 = self.pool3(X_2)
    43. X_3 = self.gconv4(X_3, self.A_3)
    44. X_4 = self.pool4(X_3)
    45. X_4 = self.gconv5(X_4, self.A_4)
    46. # X_5: batch * 1 * 64
    47. X_5 = self.pool5(X_4)
    48. global_features = self.ReLU(self.fc1(X_5))
    49. global_features = self.ReLU(self.fc2(global_features))
    50. X_6 = self.unpool6(global_features)
    51. X_6 = self.gconv6(self._get_decoder_input(X_4, X_6), self.A_4)
    52. X_7 = self.unpool7(X_6)
    53. X_7 = self.gconv7(self._get_decoder_input(X_3, X_7), self.A_3)
    54. X_8 = self.unpool8(X_7)
    55. X_8 = self.gconv8(self._get_decoder_input(X_2, X_8), self.A_2)
    56. X_9 = self.unpool9(X_8)
    57. X_9 = self.gconv9(self._get_decoder_input(X_1, X_9), self.A_1)
    58. X_10 = self.unpool10(X_9)
    59. X_10 = self.gconv10(self._get_decoder_input(X_0, X_10), self.A_0)
    60. return X_10

    Graph Pooling

    采用全连接层,并将其作用于feature matrix的transpose;
    特点:

  • 全连接层kernel综合所有特征;

  • 输出想要个数的节点数;

    Graph Unpooling

    同样采用全连接层,于Pooling操作类似,同样作用于feature matrix的transpose产生预期数量的节点数,最好再将其transpose回来。

    Loss Function

    loss由三部分组成:
    image.png
    分别是:初始的2D loss,提纯的2D loss以及3D loss。
    loss计算时采用Mean Squared Error

    1. outputs2d_init, outputs2d, outputs3d = model(inputs)
    2. loss2d_init = criterion(outputs2d_init, labels2d)
    3. loss2d = criterion(outputs2d, labels2d)
    4. loss3d = criterion(outputs3d, labels3d)
    5. loss = (lambda_1)*loss2d_init + (lambda_1)*loss2d + (lambda_2)*loss3d

    实现细节

    模型训练

  • 针对不同步骤的模型进行分开训练;

  • 数据增强的方式提高模型的健壮性;