| import numpy as np
|
| from enum import Enum
|
|
|
| class BodyIdentifier(Enum):
|
| INDEX_FINGER_DIP_right = 0
|
| INDEX_FINGER_MCP_right = 1
|
| INDEX_FINGER_PIP_right = 2
|
| INDEX_FINGER_TIP_right = 3
|
| MIDDLE_FINGER_DIP_right = 4
|
| MIDDLE_FINGER_MCP_right = 5
|
| MIDDLE_FINGER_PIP_right = 6
|
| MIDDLE_FINGER_TIP_right = 7
|
| PINKY_DIP_right = 8
|
| PINKY_MCP_right = 9
|
| PINKY_PIP_right = 10
|
| PINKY_TIP_right = 11
|
| RING_FINGER_DIP_right = 12
|
| RING_FINGER_MCP_right = 13
|
| RING_FINGER_PIP_right = 14
|
| RING_FINGER_TIP_right = 15
|
| THUMB_CMC_right = 16
|
| THUMB_IP_right = 17
|
| THUMB_MCP_right = 18
|
| THUMB_TIP_right = 19
|
| WRIST_right = 20
|
| INDEX_FINGER_DIP_left = 21
|
| INDEX_FINGER_MCP_left = 22
|
| INDEX_FINGER_PIP_left = 23
|
| INDEX_FINGER_TIP_left = 24
|
| MIDDLE_FINGER_DIP_left = 25
|
| MIDDLE_FINGER_MCP_left = 26
|
| MIDDLE_FINGER_PIP_left = 27
|
| MIDDLE_FINGER_TIP_left = 28
|
| PINKY_DIP_left = 29
|
| PINKY_MCP_left = 30
|
| PINKY_PIP_left = 31
|
| PINKY_TIP_left = 32
|
| RING_FINGER_DIP_left = 33
|
| RING_FINGER_MCP_left = 34
|
| RING_FINGER_PIP_left = 35
|
| RING_FINGER_TIP_left = 36
|
| THUMB_CMC_left = 37
|
| THUMB_IP_left = 38
|
| THUMB_MCP_left = 39
|
| THUMB_TIP_left = 40
|
| WRIST_left = 41
|
| RIGHT_SHOULDER = 42
|
| LEFT_SHOULDER = 43
|
| LEFT_ELBOW = 44
|
| RIGHT_ELBOW = 45
|
|
|
| class Graph():
|
| """ The Graph to model the skeletons extracted by the openpose
|
|
|
| Args:
|
| strategy (string): must be one of the follow candidates
|
| - uniform: Uniform Labeling
|
| - distance: Distance Partitioning
|
| - spatial: Spatial Configuration
|
| For more information, please refer to the section 'Partition Strategies'
|
| in our paper (https://arxiv.org/abs/1801.07455).
|
|
|
| layout (string): must be one of the follow candidates
|
| - openpose: Is consists of 18 joints. For more information, please
|
| refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose#output
|
| - ntu-rgb+d: Is consists of 25 joints. For more information, please
|
| refer to https://github.com/shahroudy/NTURGB-D
|
|
|
| max_hop (int): the maximal distance between two connected nodes
|
| dilation (int): controls the spacing between the kernel points
|
|
|
| """
|
|
|
| def __init__(self,
|
| layout='openpose',
|
| strategy='uniform',
|
| max_hop=1,
|
| dilation=1):
|
| self.max_hop = max_hop
|
| self.dilation = dilation
|
|
|
| self.get_edge(layout)
|
| self.hop_dis = get_hop_distance(
|
| self.num_node, self.edge, max_hop=max_hop)
|
| self.get_adjacency(strategy)
|
|
|
| def __str__(self):
|
| return self.A
|
|
|
| def get_edge(self, layout):
|
| if layout == 'openpose':
|
| self.num_node = 18
|
| self_link = [(i, i) for i in range(self.num_node)]
|
| neighbor_link = [(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), (15, 0), (14, 0), (17, 15), (16, 14)]
|
| self.edge = self_link + neighbor_link
|
| self.center = 1
|
| elif layout == 'ntu-rgb+d':
|
| self.num_node = 25
|
| self_link = [(i, i) for i in range(self.num_node)]
|
| neighbor_1base = [(1, 2), (2, 21), (3, 21), (4, 3), (5, 21),
|
| (6, 5), (7, 6), (8, 7), (9, 21), (10, 9),
|
| (11, 10), (12, 11), (13, 1), (14, 13), (15, 14),
|
| (16, 15), (17, 1), (18, 17), (19, 18), (20, 19),
|
| (22, 23), (23, 8), (24, 25), (25, 12)]
|
| neighbor_link = [(i - 1, j - 1) for (i, j) in neighbor_1base]
|
| self.edge = self_link + neighbor_link
|
| self.center = 21 - 1
|
| elif layout == 'ntu_edge':
|
| self.num_node = 24
|
| self_link = [(i, i) for i in range(self.num_node)]
|
| neighbor_1base = [(1, 2), (3, 2), (4, 3), (5, 2), (6, 5), (7, 6),
|
| (8, 7), (9, 2), (10, 9), (11, 10), (12, 11),
|
| (13, 1), (14, 13), (15, 14), (16, 15), (17, 1),
|
| (18, 17), (19, 18), (20, 19), (21, 22), (22, 8),
|
| (23, 24), (24, 12)]
|
| neighbor_link = [(i - 1, j - 1) for (i, j) in neighbor_1base]
|
| self.edge = self_link + neighbor_link
|
| self.center = 2
|
| elif layout == 'mediapipe':
|
| self.num_node = 25
|
| self_link = [(i, i) for i in range(self.num_node)]
|
| neighbor_1base = [(20, 18), (18, 16), (20, 16), (16, 22), (16, 14), (14, 12),
|
| (19, 17), (17, 15), (19, 15), (15, 21), (15, 13), (13, 11),
|
| (12, 11), (12, 24), (24, 23), (23, 11),
|
| (10, 9),
|
| (0, 4), (4, 5), (5, 6), (6, 8),
|
| (0, 1), (1, 2), (2, 3), (3, 7)]
|
| neighbor_link = [(i - 1, j - 1) for (i, j) in neighbor_1base]
|
| self.edge = self_link + neighbor_link
|
| self.center = 10
|
|
|
| elif layout == "mediapipe_two_hand":
|
| self.num_node = 46
|
| self_link = [(i, i) for i in range(self.num_node)]
|
| neighbor_1base = [(BodyIdentifier.WRIST_left.value, BodyIdentifier.THUMB_CMC_left.value),
|
| (BodyIdentifier.THUMB_CMC_left.value, BodyIdentifier.THUMB_MCP_left.value),
|
| (BodyIdentifier.THUMB_MCP_left.value, BodyIdentifier.THUMB_IP_left.value),
|
| (BodyIdentifier.THUMB_IP_left.value, BodyIdentifier.THUMB_TIP_left.value),
|
|
|
| (BodyIdentifier.WRIST_left.value, BodyIdentifier.INDEX_FINGER_MCP_left.value),
|
| (BodyIdentifier.INDEX_FINGER_MCP_left.value, BodyIdentifier.INDEX_FINGER_PIP_left.value),
|
| (BodyIdentifier.INDEX_FINGER_PIP_left.value, BodyIdentifier.INDEX_FINGER_DIP_left.value),
|
| (BodyIdentifier.INDEX_FINGER_DIP_left.value, BodyIdentifier.INDEX_FINGER_TIP_left.value),
|
|
|
| (BodyIdentifier.INDEX_FINGER_MCP_left.value, BodyIdentifier.MIDDLE_FINGER_MCP_left.value),
|
| (BodyIdentifier.MIDDLE_FINGER_MCP_left.value, BodyIdentifier.MIDDLE_FINGER_PIP_left.value),
|
| (BodyIdentifier.MIDDLE_FINGER_PIP_left.value, BodyIdentifier.MIDDLE_FINGER_DIP_left.value),
|
| (BodyIdentifier.MIDDLE_FINGER_DIP_left.value, BodyIdentifier.MIDDLE_FINGER_TIP_left.value),
|
|
|
| (BodyIdentifier.MIDDLE_FINGER_MCP_left.value, BodyIdentifier.RING_FINGER_MCP_left.value),
|
| (BodyIdentifier.RING_FINGER_MCP_left.value, BodyIdentifier.RING_FINGER_PIP_left.value),
|
| (BodyIdentifier.RING_FINGER_PIP_left.value, BodyIdentifier.RING_FINGER_DIP_left.value),
|
| (BodyIdentifier.RING_FINGER_DIP_left.value, BodyIdentifier.RING_FINGER_TIP_left.value),
|
|
|
| (BodyIdentifier.WRIST_left.value, BodyIdentifier.PINKY_MCP_left.value),
|
| (BodyIdentifier.PINKY_MCP_left.value, BodyIdentifier.PINKY_PIP_left.value),
|
| (BodyIdentifier.PINKY_PIP_left.value, BodyIdentifier.PINKY_DIP_left.value),
|
| (BodyIdentifier.PINKY_DIP_left.value, BodyIdentifier.PINKY_TIP_left.value),
|
|
|
|
|
| (BodyIdentifier.WRIST_right.value, BodyIdentifier.THUMB_CMC_right.value),
|
| (BodyIdentifier.THUMB_CMC_right.value, BodyIdentifier.THUMB_MCP_right.value),
|
| (BodyIdentifier.THUMB_MCP_right.value, BodyIdentifier.THUMB_IP_right.value),
|
| (BodyIdentifier.THUMB_IP_right.value, BodyIdentifier.THUMB_TIP_right.value),
|
|
|
| (BodyIdentifier.WRIST_right.value, BodyIdentifier.INDEX_FINGER_MCP_right.value),
|
| (BodyIdentifier.INDEX_FINGER_MCP_right.value, BodyIdentifier.INDEX_FINGER_PIP_right.value),
|
| (BodyIdentifier.INDEX_FINGER_PIP_right.value, BodyIdentifier.INDEX_FINGER_DIP_right.value),
|
| (BodyIdentifier.INDEX_FINGER_DIP_right.value, BodyIdentifier.INDEX_FINGER_TIP_right.value),
|
|
|
| (BodyIdentifier.INDEX_FINGER_MCP_right.value, BodyIdentifier.MIDDLE_FINGER_MCP_right.value),
|
| (BodyIdentifier.MIDDLE_FINGER_MCP_right.value, BodyIdentifier.MIDDLE_FINGER_PIP_right.value),
|
| (BodyIdentifier.MIDDLE_FINGER_PIP_right.value, BodyIdentifier.MIDDLE_FINGER_DIP_right.value),
|
| (BodyIdentifier.MIDDLE_FINGER_DIP_right.value, BodyIdentifier.MIDDLE_FINGER_TIP_right.value),
|
|
|
| (BodyIdentifier.MIDDLE_FINGER_MCP_right.value, BodyIdentifier.RING_FINGER_MCP_right.value),
|
| (BodyIdentifier.RING_FINGER_MCP_right.value, BodyIdentifier.RING_FINGER_PIP_right.value),
|
| (BodyIdentifier.RING_FINGER_PIP_right.value, BodyIdentifier.RING_FINGER_DIP_right.value),
|
| (BodyIdentifier.RING_FINGER_DIP_right.value, BodyIdentifier.RING_FINGER_TIP_right.value),
|
|
|
| (BodyIdentifier.WRIST_right.value, BodyIdentifier.PINKY_MCP_right.value),
|
| (BodyIdentifier.PINKY_MCP_right.value, BodyIdentifier.PINKY_PIP_right.value),
|
| (BodyIdentifier.PINKY_PIP_right.value, BodyIdentifier.PINKY_DIP_right.value),
|
| (BodyIdentifier.PINKY_DIP_right.value, BodyIdentifier.PINKY_TIP_right.value),
|
|
|
|
|
| (BodyIdentifier.RIGHT_SHOULDER.value, BodyIdentifier.RIGHT_ELBOW.value),
|
| (BodyIdentifier.RIGHT_ELBOW.value, BodyIdentifier.WRIST_right.value),
|
|
|
| (BodyIdentifier.RIGHT_SHOULDER.value, BodyIdentifier.LEFT_SHOULDER.value),
|
|
|
| (BodyIdentifier.LEFT_SHOULDER.value, BodyIdentifier.LEFT_ELBOW.value),
|
| (BodyIdentifier.LEFT_ELBOW.value, BodyIdentifier.WRIST_left.value)]
|
|
|
| neighbor_link = [(i, j) for (i, j) in neighbor_1base]
|
| self.edge = self_link + neighbor_link
|
| self.center = BodyIdentifier.RIGHT_SHOULDER.value
|
|
|
|
|
| else:
|
| raise ValueError("Do Not Exist This Layout.")
|
|
|
| def get_adjacency(self, strategy):
|
| valid_hop = range(0, self.max_hop + 1, self.dilation)
|
| adjacency = np.zeros((self.num_node, self.num_node))
|
| for hop in valid_hop:
|
| adjacency[self.hop_dis == hop] = 1
|
| normalize_adjacency = normalize_digraph(adjacency)
|
|
|
| if strategy == 'uniform':
|
| A = np.zeros((1, self.num_node, self.num_node))
|
| A[0] = normalize_adjacency
|
| self.A = A
|
| elif strategy == 'distance':
|
| A = np.zeros((len(valid_hop), self.num_node, self.num_node))
|
| for i, hop in enumerate(valid_hop):
|
| A[i][self.hop_dis == hop] = normalize_adjacency[self.hop_dis ==
|
| hop]
|
| self.A = A
|
| elif strategy == 'spatial':
|
| 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
|
| else:
|
| raise ValueError("Do Not Exist This Strategy")
|
|
|
|
|
| def get_hop_distance(num_node, edge, max_hop=1):
|
| A = np.zeros((num_node, num_node))
|
| print(edge)
|
| for i, j in edge:
|
| A[j, i] = 1
|
| A[i, j] = 1
|
|
|
|
|
| hop_dis = np.zeros((num_node, num_node)) + np.inf
|
| transfer_mat = [np.linalg.matrix_power(A, d) for d in range(max_hop + 1)]
|
| arrive_mat = (np.stack(transfer_mat) > 0)
|
| for d in range(max_hop, -1, -1):
|
| hop_dis[arrive_mat[d]] = d
|
| return hop_dis
|
|
|
|
|
| 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
|
|
|
|
|
| def normalize_undigraph(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]**(-0.5)
|
| DAD = np.dot(np.dot(Dn, A), Dn)
|
| return DAD |