@ignix-social/creator 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/dist/box-kit/comfyui/locks/linux-x64.txt +2409 -0
- package/dist/box-kit/comfyui/locks/macos-arm64.txt +2243 -0
- package/dist/box-kit/comfyui/locks/windows-x64.txt +2322 -0
- package/dist/box-kit/comfyui/recast/LICENSE-WAN.txt +201 -0
- package/dist/box-kit/comfyui/recast/people.py +115 -0
- package/dist/box-kit/comfyui/recast/persons.py +90 -0
- package/dist/box-kit/comfyui/recast/prep.py +204 -0
- package/dist/box-kit/comfyui/recast/wan_pose_draw.py +1368 -0
- package/dist/box-kit/comfyui/recast/wan_pose_utils.py +1160 -0
- package/dist/box-kit/comfyui/recast/wan_utils.py +227 -0
- package/dist/box-kit/comfyui/workflows/infinitetalk.v1.json +94 -0
- package/dist/box-kit/comfyui/workflows/wan22-i2v-14b.v1.json +112 -0
- package/dist/box-kit/comfyui/workflows/wan22-i2v.v1.json +62 -0
- package/dist/cli.cjs +23811 -0
- package/dist/core.cjs +63459 -0
- package/dist/sfx-kit/achievement.wav +0 -0
- package/dist/sfx-kit/applause.wav +0 -0
- package/dist/sfx-kit/aww.wav +0 -0
- package/dist/sfx-kit/boing.wav +0 -0
- package/dist/sfx-kit/bonk.wav +0 -0
- package/dist/sfx-kit/boo.wav +0 -0
- package/dist/sfx-kit/boom.wav +0 -0
- package/dist/sfx-kit/camera-shutter.wav +0 -0
- package/dist/sfx-kit/cash-register.wav +0 -0
- package/dist/sfx-kit/cheer.wav +0 -0
- package/dist/sfx-kit/clap-hit.wav +0 -0
- package/dist/sfx-kit/click.wav +0 -0
- package/dist/sfx-kit/clock-tick.wav +0 -0
- package/dist/sfx-kit/coffee-pour.wav +0 -0
- package/dist/sfx-kit/coin.wav +0 -0
- package/dist/sfx-kit/coins.wav +0 -0
- package/dist/sfx-kit/correct.wav +0 -0
- package/dist/sfx-kit/countdown-beep.wav +0 -0
- package/dist/sfx-kit/crickets.wav +0 -0
- package/dist/sfx-kit/ding.wav +0 -0
- package/dist/sfx-kit/door-bell.wav +0 -0
- package/dist/sfx-kit/door-knock.wav +0 -0
- package/dist/sfx-kit/error.wav +0 -0
- package/dist/sfx-kit/flash.wav +0 -0
- package/dist/sfx-kit/gasp.wav +0 -0
- package/dist/sfx-kit/glitch.wav +0 -0
- package/dist/sfx-kit/heartbeat.wav +0 -0
- package/dist/sfx-kit/honk.wav +0 -0
- package/dist/sfx-kit/impact.wav +0 -0
- package/dist/sfx-kit/laugh.wav +0 -0
- package/dist/sfx-kit/level-up.wav +0 -0
- package/dist/sfx-kit/message-received.wav +0 -0
- package/dist/sfx-kit/message-sent.wav +0 -0
- package/dist/sfx-kit/notification.wav +0 -0
- package/dist/sfx-kit/ooh.wav +0 -0
- package/dist/sfx-kit/page-turn.wav +0 -0
- package/dist/sfx-kit/paper-crumple.wav +0 -0
- package/dist/sfx-kit/pen-write.wav +0 -0
- package/dist/sfx-kit/phone-vibrate.wav +0 -0
- package/dist/sfx-kit/pop-clean.wav +0 -0
- package/dist/sfx-kit/pop.wav +0 -0
- package/dist/sfx-kit/punch.wav +0 -0
- package/dist/sfx-kit/record-scratch.wav +0 -0
- package/dist/sfx-kit/reverse-cymbal.wav +0 -0
- package/dist/sfx-kit/rimshot.wav +0 -0
- package/dist/sfx-kit/riser-short.wav +0 -0
- package/dist/sfx-kit/sad-trombone.wav +0 -0
- package/dist/sfx-kit/scroll.wav +0 -0
- package/dist/sfx-kit/slide-whistle.wav +0 -0
- package/dist/sfx-kit/snap.wav +0 -0
- package/dist/sfx-kit/sparkle.wav +0 -0
- package/dist/sfx-kit/splat.wav +0 -0
- package/dist/sfx-kit/squeak.wav +0 -0
- package/dist/sfx-kit/stamp.wav +0 -0
- package/dist/sfx-kit/stomp.wav +0 -0
- package/dist/sfx-kit/sub-drop.wav +0 -0
- package/dist/sfx-kit/success.wav +0 -0
- package/dist/sfx-kit/swipe.wav +0 -0
- package/dist/sfx-kit/swoosh-down.wav +0 -0
- package/dist/sfx-kit/swoosh-up.wav +0 -0
- package/dist/sfx-kit/tap.wav +0 -0
- package/dist/sfx-kit/tape-stop.wav +0 -0
- package/dist/sfx-kit/thud.wav +0 -0
- package/dist/sfx-kit/timer-end.wav +0 -0
- package/dist/sfx-kit/toggle.wav +0 -0
- package/dist/sfx-kit/typing-mobile.wav +0 -0
- package/dist/sfx-kit/typing-slow.wav +0 -0
- package/dist/sfx-kit/typing.wav +0 -0
- package/dist/sfx-kit/whoosh-fast.wav +0 -0
- package/dist/sfx-kit/whoosh-heavy.wav +0 -0
- package/dist/sfx-kit/whoosh-soft.wav +0 -0
- package/dist/sfx-kit/wow.wav +0 -0
- package/dist/sfx-kit/wrong.wav +0 -0
- package/dist/sfx-kit/zipper.wav +0 -0
- package/dist/sfx-kit/zoom-in.wav +0 -0
- package/package.json +32 -4
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
|
2
|
+
# Modified by Ignix (2026-10-05) for Ignix Creator: file renamed, unchanged. Apache-2.0 (see LICENSE-WAN.txt).
|
|
3
|
+
import warnings
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import List
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def box_convert_simple(box, convert_type='xyxy2xywh'):
|
|
11
|
+
if convert_type == 'xyxy2xywh':
|
|
12
|
+
return [box[0], box[1], box[2] - box[0], box[3] - box[1]]
|
|
13
|
+
elif convert_type == 'xywh2xyxy':
|
|
14
|
+
return [box[0], box[1], box[2] + box[0], box[3] + box[1]]
|
|
15
|
+
elif convert_type == 'xyxy2ctwh':
|
|
16
|
+
return [(box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1]]
|
|
17
|
+
elif convert_type == 'ctwh2xyxy':
|
|
18
|
+
return [box[0] - box[2] // 2, box[1] - box[3] // 2, box[0] + (box[2] - box[2] // 2), box[1] + (box[3] - box[3] // 2)]
|
|
19
|
+
|
|
20
|
+
def read_img(image, convert='RGB', check_exist=False):
|
|
21
|
+
if isinstance(image, str):
|
|
22
|
+
if check_exist and not osp.exists(image):
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
img = Image.open(image)
|
|
26
|
+
if convert:
|
|
27
|
+
img = img.convert(convert)
|
|
28
|
+
except:
|
|
29
|
+
raise IOError('File error: ', image)
|
|
30
|
+
return np.asarray(img)
|
|
31
|
+
else:
|
|
32
|
+
if isinstance(image, np.ndarray):
|
|
33
|
+
if convert:
|
|
34
|
+
return image[..., ::-1]
|
|
35
|
+
else:
|
|
36
|
+
if convert:
|
|
37
|
+
img = img.convert(convert)
|
|
38
|
+
return np.asarray(img)
|
|
39
|
+
|
|
40
|
+
class AAPoseMeta:
|
|
41
|
+
def __init__(self, meta=None, kp2ds=None):
|
|
42
|
+
self.image_id = ""
|
|
43
|
+
self.height = 0
|
|
44
|
+
self.width = 0
|
|
45
|
+
|
|
46
|
+
self.kps_body: np.ndarray = None
|
|
47
|
+
self.kps_lhand: np.ndarray = None
|
|
48
|
+
self.kps_rhand: np.ndarray = None
|
|
49
|
+
self.kps_face: np.ndarray = None
|
|
50
|
+
self.kps_body_p: np.ndarray = None
|
|
51
|
+
self.kps_lhand_p: np.ndarray = None
|
|
52
|
+
self.kps_rhand_p: np.ndarray = None
|
|
53
|
+
self.kps_face_p: np.ndarray = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if meta is not None:
|
|
57
|
+
self.load_from_meta(meta)
|
|
58
|
+
elif kp2ds is not None:
|
|
59
|
+
self.load_from_kp2ds(kp2ds)
|
|
60
|
+
|
|
61
|
+
def is_valid(self, kp, p, threshold):
|
|
62
|
+
x, y = kp
|
|
63
|
+
if x < 0 or y < 0 or x > self.width or y > self.height or p < threshold:
|
|
64
|
+
return False
|
|
65
|
+
else:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def get_bbox(self, kp, kp_p, threshold=0.5):
|
|
69
|
+
kps = kp[kp_p > threshold]
|
|
70
|
+
if kps.size == 0:
|
|
71
|
+
return 0, 0, 0, 0
|
|
72
|
+
x0, y0 = kps.min(axis=0)
|
|
73
|
+
x1, y1 = kps.max(axis=0)
|
|
74
|
+
return x0, y0, x1, y1
|
|
75
|
+
|
|
76
|
+
def crop(self, x0, y0, x1, y1):
|
|
77
|
+
all_kps = [self.kps_body, self.kps_lhand, self.kps_rhand, self.kps_face]
|
|
78
|
+
for kps in all_kps:
|
|
79
|
+
if kps is not None:
|
|
80
|
+
kps[:, 0] -= x0
|
|
81
|
+
kps[:, 1] -= y0
|
|
82
|
+
self.width = x1 - x0
|
|
83
|
+
self.height = y1 - y0
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def resize(self, width, height):
|
|
87
|
+
scale_x = width / self.width
|
|
88
|
+
scale_y = height / self.height
|
|
89
|
+
all_kps = [self.kps_body, self.kps_lhand, self.kps_rhand, self.kps_face]
|
|
90
|
+
for kps in all_kps:
|
|
91
|
+
if kps is not None:
|
|
92
|
+
kps[:, 0] *= scale_x
|
|
93
|
+
kps[:, 1] *= scale_y
|
|
94
|
+
self.width = width
|
|
95
|
+
self.height = height
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_kps_body_with_p(self, normalize=False):
|
|
100
|
+
kps_body = self.kps_body.copy()
|
|
101
|
+
if normalize:
|
|
102
|
+
kps_body = kps_body / np.array([self.width, self.height])
|
|
103
|
+
|
|
104
|
+
return np.concatenate([kps_body, self.kps_body_p[:, None]])
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def from_kps_face(kps_face: np.ndarray, height: int, width: int):
|
|
108
|
+
|
|
109
|
+
pose_meta = AAPoseMeta()
|
|
110
|
+
pose_meta.kps_face = kps_face[:, :2]
|
|
111
|
+
if kps_face.shape[1] == 3:
|
|
112
|
+
pose_meta.kps_face_p = kps_face[:, 2]
|
|
113
|
+
else:
|
|
114
|
+
pose_meta.kps_face_p = kps_face[:, 0] * 0 + 1
|
|
115
|
+
pose_meta.height = height
|
|
116
|
+
pose_meta.width = width
|
|
117
|
+
return pose_meta
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def from_kps_body(kps_body: np.ndarray, height: int, width: int):
|
|
121
|
+
|
|
122
|
+
pose_meta = AAPoseMeta()
|
|
123
|
+
pose_meta.kps_body = kps_body[:, :2]
|
|
124
|
+
pose_meta.kps_body_p = kps_body[:, 2]
|
|
125
|
+
pose_meta.height = height
|
|
126
|
+
pose_meta.width = width
|
|
127
|
+
return pose_meta
|
|
128
|
+
@staticmethod
|
|
129
|
+
def from_humanapi_meta(meta):
|
|
130
|
+
pose_meta = AAPoseMeta()
|
|
131
|
+
width, height = meta["width"], meta["height"]
|
|
132
|
+
pose_meta.width = width
|
|
133
|
+
pose_meta.height = height
|
|
134
|
+
pose_meta.kps_body = meta["keypoints_body"][:, :2] * (width, height)
|
|
135
|
+
pose_meta.kps_body_p = meta["keypoints_body"][:, 2]
|
|
136
|
+
pose_meta.kps_lhand = meta["keypoints_left_hand"][:, :2] * (width, height)
|
|
137
|
+
pose_meta.kps_lhand_p = meta["keypoints_left_hand"][:, 2]
|
|
138
|
+
pose_meta.kps_rhand = meta["keypoints_right_hand"][:, :2] * (width, height)
|
|
139
|
+
pose_meta.kps_rhand_p = meta["keypoints_right_hand"][:, 2]
|
|
140
|
+
if 'keypoints_face' in meta:
|
|
141
|
+
pose_meta.kps_face = meta["keypoints_face"][:, :2] * (width, height)
|
|
142
|
+
pose_meta.kps_face_p = meta["keypoints_face"][:, 2]
|
|
143
|
+
return pose_meta
|
|
144
|
+
|
|
145
|
+
def load_from_meta(self, meta, norm_body=True, norm_hand=False):
|
|
146
|
+
|
|
147
|
+
self.image_id = meta.get("image_id", "00000.png")
|
|
148
|
+
self.height = meta["height"]
|
|
149
|
+
self.width = meta["width"]
|
|
150
|
+
kps_body_p = []
|
|
151
|
+
kps_body = []
|
|
152
|
+
for kp in meta["keypoints_body"]:
|
|
153
|
+
if kp is None:
|
|
154
|
+
kps_body.append([0, 0])
|
|
155
|
+
kps_body_p.append(0)
|
|
156
|
+
else:
|
|
157
|
+
kps_body.append(kp)
|
|
158
|
+
kps_body_p.append(1)
|
|
159
|
+
|
|
160
|
+
self.kps_body = np.array(kps_body)
|
|
161
|
+
self.kps_body[:, 0] *= self.width
|
|
162
|
+
self.kps_body[:, 1] *= self.height
|
|
163
|
+
self.kps_body_p = np.array(kps_body_p)
|
|
164
|
+
|
|
165
|
+
self.kps_lhand = np.array(meta["keypoints_left_hand"])[:, :2]
|
|
166
|
+
self.kps_lhand_p = np.array(meta["keypoints_left_hand"])[:, 2]
|
|
167
|
+
self.kps_rhand = np.array(meta["keypoints_right_hand"])[:, :2]
|
|
168
|
+
self.kps_rhand_p = np.array(meta["keypoints_right_hand"])[:, 2]
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def load_from_kp2ds(kp2ds: List[np.ndarray], width: int, height: int):
|
|
172
|
+
"""input 133x3 numpy keypoints and output AAPoseMeta
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
kp2ds (List[np.ndarray]): _description_
|
|
176
|
+
width (int): _description_
|
|
177
|
+
height (int): _description_
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
_type_: _description_
|
|
181
|
+
"""
|
|
182
|
+
pose_meta = AAPoseMeta()
|
|
183
|
+
pose_meta.width = width
|
|
184
|
+
pose_meta.height = height
|
|
185
|
+
kps_body = (kp2ds[[0, 6, 6, 8, 10, 5, 7, 9, 12, 14, 16, 11, 13, 15, 2, 1, 4, 3, 17, 20]] + kp2ds[[0, 5, 6, 8, 10, 5, 7, 9, 12, 14, 16, 11, 13, 15, 2, 1, 4, 3, 18, 21]]) / 2
|
|
186
|
+
kps_lhand = kp2ds[91:112]
|
|
187
|
+
kps_rhand = kp2ds[112:133]
|
|
188
|
+
kps_face = np.concatenate([kp2ds[23:23+68], kp2ds[1:3]], axis=0)
|
|
189
|
+
pose_meta.kps_body = kps_body[:, :2]
|
|
190
|
+
pose_meta.kps_body_p = kps_body[:, 2]
|
|
191
|
+
pose_meta.kps_lhand = kps_lhand[:, :2]
|
|
192
|
+
pose_meta.kps_lhand_p = kps_lhand[:, 2]
|
|
193
|
+
pose_meta.kps_rhand = kps_rhand[:, :2]
|
|
194
|
+
pose_meta.kps_rhand_p = kps_rhand[:, 2]
|
|
195
|
+
pose_meta.kps_face = kps_face[:, :2]
|
|
196
|
+
pose_meta.kps_face_p = kps_face[:, 2]
|
|
197
|
+
return pose_meta
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def from_dwpose(dwpose_det_res, height, width):
|
|
201
|
+
pose_meta = AAPoseMeta()
|
|
202
|
+
pose_meta.kps_body = dwpose_det_res["bodies"]["candidate"]
|
|
203
|
+
pose_meta.kps_body_p = dwpose_det_res["bodies"]["score"]
|
|
204
|
+
pose_meta.kps_body[:, 0] *= width
|
|
205
|
+
pose_meta.kps_body[:, 1] *= height
|
|
206
|
+
|
|
207
|
+
pose_meta.kps_lhand, pose_meta.kps_rhand = dwpose_det_res["hands"]
|
|
208
|
+
pose_meta.kps_lhand[:, 0] *= width
|
|
209
|
+
pose_meta.kps_lhand[:, 1] *= height
|
|
210
|
+
pose_meta.kps_rhand[:, 0] *= width
|
|
211
|
+
pose_meta.kps_rhand[:, 1] *= height
|
|
212
|
+
pose_meta.kps_lhand_p, pose_meta.kps_rhand_p = dwpose_det_res["hands_score"]
|
|
213
|
+
|
|
214
|
+
pose_meta.kps_face = dwpose_det_res["faces"][0]
|
|
215
|
+
pose_meta.kps_face[:, 0] *= width
|
|
216
|
+
pose_meta.kps_face[:, 1] *= height
|
|
217
|
+
pose_meta.kps_face_p = dwpose_det_res["faces_score"][0]
|
|
218
|
+
return pose_meta
|
|
219
|
+
|
|
220
|
+
def save_json(self):
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
def draw_aapose(self, img, threshold=0.5, stick_width_norm=200, draw_hand=True, draw_head=True):
|
|
224
|
+
from .human_visualization import draw_aapose_by_meta
|
|
225
|
+
return draw_aapose_by_meta(img, self, threshold, stick_width_norm, draw_hand, draw_head)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def translate(self, x0, y0):
|
|
229
|
+
all_kps = [self.kps_body, self.kps_lhand, self.kps_rhand, self.kps_face]
|
|
230
|
+
for kps in all_kps:
|
|
231
|
+
if kps is not None:
|
|
232
|
+
kps[:, 0] -= x0
|
|
233
|
+
kps[:, 1] -= y0
|
|
234
|
+
|
|
235
|
+
def scale(self, sx, sy):
|
|
236
|
+
all_kps = [self.kps_body, self.kps_lhand, self.kps_rhand, self.kps_face]
|
|
237
|
+
for kps in all_kps:
|
|
238
|
+
if kps is not None:
|
|
239
|
+
kps[:, 0] *= sx
|
|
240
|
+
kps[:, 1] *= sy
|
|
241
|
+
|
|
242
|
+
def padding_resize2(self, height=512, width=512):
|
|
243
|
+
"""kps will be changed inplace
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
all_kps = [self.kps_body, self.kps_lhand, self.kps_rhand, self.kps_face]
|
|
248
|
+
|
|
249
|
+
ori_height, ori_width = self.height, self.width
|
|
250
|
+
|
|
251
|
+
if (ori_height / ori_width) > (height / width):
|
|
252
|
+
new_width = int(height / ori_height * ori_width)
|
|
253
|
+
padding = int((width - new_width) / 2)
|
|
254
|
+
padding_width = padding
|
|
255
|
+
padding_height = 0
|
|
256
|
+
scale = height / ori_height
|
|
257
|
+
|
|
258
|
+
for kps in all_kps:
|
|
259
|
+
if kps is not None:
|
|
260
|
+
kps[:, 0] = kps[:, 0] * scale + padding
|
|
261
|
+
kps[:, 1] = kps[:, 1] * scale
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
new_height = int(width / ori_width * ori_height)
|
|
265
|
+
padding = int((height - new_height) / 2)
|
|
266
|
+
padding_width = 0
|
|
267
|
+
padding_height = padding
|
|
268
|
+
scale = width / ori_width
|
|
269
|
+
for kps in all_kps:
|
|
270
|
+
if kps is not None:
|
|
271
|
+
kps[:, 1] = kps[:, 1] * scale + padding
|
|
272
|
+
kps[:, 0] = kps[:, 0] * scale
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
self.width = width
|
|
276
|
+
self.height = height
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def transform_preds(coords, center, scale, output_size, use_udp=False):
|
|
281
|
+
"""Get final keypoint predictions from heatmaps and apply scaling and
|
|
282
|
+
translation to map them back to the image.
|
|
283
|
+
|
|
284
|
+
Note:
|
|
285
|
+
num_keypoints: K
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
coords (np.ndarray[K, ndims]):
|
|
289
|
+
|
|
290
|
+
* If ndims=2, corrds are predicted keypoint location.
|
|
291
|
+
* If ndims=4, corrds are composed of (x, y, scores, tags)
|
|
292
|
+
* If ndims=5, corrds are composed of (x, y, scores, tags,
|
|
293
|
+
flipped_tags)
|
|
294
|
+
|
|
295
|
+
center (np.ndarray[2, ]): Center of the bounding box (x, y).
|
|
296
|
+
scale (np.ndarray[2, ]): Scale of the bounding box
|
|
297
|
+
wrt [width, height].
|
|
298
|
+
output_size (np.ndarray[2, ] | list(2,)): Size of the
|
|
299
|
+
destination heatmaps.
|
|
300
|
+
use_udp (bool): Use unbiased data processing
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
np.ndarray: Predicted coordinates in the images.
|
|
304
|
+
"""
|
|
305
|
+
assert coords.shape[1] in (2, 4, 5)
|
|
306
|
+
assert len(center) == 2
|
|
307
|
+
assert len(scale) == 2
|
|
308
|
+
assert len(output_size) == 2
|
|
309
|
+
|
|
310
|
+
# Recover the scale which is normalized by a factor of 200.
|
|
311
|
+
# scale = scale * 200.0
|
|
312
|
+
|
|
313
|
+
if use_udp:
|
|
314
|
+
scale_x = scale[0] / (output_size[0] - 1.0)
|
|
315
|
+
scale_y = scale[1] / (output_size[1] - 1.0)
|
|
316
|
+
else:
|
|
317
|
+
scale_x = scale[0] / output_size[0]
|
|
318
|
+
scale_y = scale[1] / output_size[1]
|
|
319
|
+
|
|
320
|
+
target_coords = np.ones_like(coords)
|
|
321
|
+
target_coords[:, 0] = coords[:, 0] * scale_x + center[0] - scale[0] * 0.5
|
|
322
|
+
target_coords[:, 1] = coords[:, 1] * scale_y + center[1] - scale[1] * 0.5
|
|
323
|
+
|
|
324
|
+
return target_coords
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _calc_distances(preds, targets, mask, normalize):
|
|
328
|
+
"""Calculate the normalized distances between preds and target.
|
|
329
|
+
|
|
330
|
+
Note:
|
|
331
|
+
batch_size: N
|
|
332
|
+
num_keypoints: K
|
|
333
|
+
dimension of keypoints: D (normally, D=2 or D=3)
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
preds (np.ndarray[N, K, D]): Predicted keypoint location.
|
|
337
|
+
targets (np.ndarray[N, K, D]): Groundtruth keypoint location.
|
|
338
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
339
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
340
|
+
accuracy calculation.
|
|
341
|
+
normalize (np.ndarray[N, D]): Typical value is heatmap_size
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
np.ndarray[K, N]: The normalized distances. \
|
|
345
|
+
If target keypoints are missing, the distance is -1.
|
|
346
|
+
"""
|
|
347
|
+
N, K, _ = preds.shape
|
|
348
|
+
# set mask=0 when normalize==0
|
|
349
|
+
_mask = mask.copy()
|
|
350
|
+
_mask[np.where((normalize == 0).sum(1))[0], :] = False
|
|
351
|
+
distances = np.full((N, K), -1, dtype=np.float32)
|
|
352
|
+
# handle invalid values
|
|
353
|
+
normalize[np.where(normalize <= 0)] = 1e6
|
|
354
|
+
distances[_mask] = np.linalg.norm(
|
|
355
|
+
((preds - targets) / normalize[:, None, :])[_mask], axis=-1)
|
|
356
|
+
return distances.T
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _distance_acc(distances, thr=0.5):
|
|
360
|
+
"""Return the percentage below the distance threshold, while ignoring
|
|
361
|
+
distances values with -1.
|
|
362
|
+
|
|
363
|
+
Note:
|
|
364
|
+
batch_size: N
|
|
365
|
+
Args:
|
|
366
|
+
distances (np.ndarray[N, ]): The normalized distances.
|
|
367
|
+
thr (float): Threshold of the distances.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
float: Percentage of distances below the threshold. \
|
|
371
|
+
If all target keypoints are missing, return -1.
|
|
372
|
+
"""
|
|
373
|
+
distance_valid = distances != -1
|
|
374
|
+
num_distance_valid = distance_valid.sum()
|
|
375
|
+
if num_distance_valid > 0:
|
|
376
|
+
return (distances[distance_valid] < thr).sum() / num_distance_valid
|
|
377
|
+
return -1
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _get_max_preds(heatmaps):
|
|
381
|
+
"""Get keypoint predictions from score maps.
|
|
382
|
+
|
|
383
|
+
Note:
|
|
384
|
+
batch_size: N
|
|
385
|
+
num_keypoints: K
|
|
386
|
+
heatmap height: H
|
|
387
|
+
heatmap width: W
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
heatmaps (np.ndarray[N, K, H, W]): model predicted heatmaps.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
tuple: A tuple containing aggregated results.
|
|
394
|
+
|
|
395
|
+
- preds (np.ndarray[N, K, 2]): Predicted keypoint location.
|
|
396
|
+
- maxvals (np.ndarray[N, K, 1]): Scores (confidence) of the keypoints.
|
|
397
|
+
"""
|
|
398
|
+
assert isinstance(heatmaps,
|
|
399
|
+
np.ndarray), ('heatmaps should be numpy.ndarray')
|
|
400
|
+
assert heatmaps.ndim == 4, 'batch_images should be 4-ndim'
|
|
401
|
+
|
|
402
|
+
N, K, _, W = heatmaps.shape
|
|
403
|
+
heatmaps_reshaped = heatmaps.reshape((N, K, -1))
|
|
404
|
+
idx = np.argmax(heatmaps_reshaped, 2).reshape((N, K, 1))
|
|
405
|
+
maxvals = np.amax(heatmaps_reshaped, 2).reshape((N, K, 1))
|
|
406
|
+
|
|
407
|
+
preds = np.tile(idx, (1, 1, 2)).astype(np.float32)
|
|
408
|
+
preds[:, :, 0] = preds[:, :, 0] % W
|
|
409
|
+
preds[:, :, 1] = preds[:, :, 1] // W
|
|
410
|
+
|
|
411
|
+
preds = np.where(np.tile(maxvals, (1, 1, 2)) > 0.0, preds, -1)
|
|
412
|
+
return preds, maxvals
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _get_max_preds_3d(heatmaps):
|
|
416
|
+
"""Get keypoint predictions from 3D score maps.
|
|
417
|
+
|
|
418
|
+
Note:
|
|
419
|
+
batch size: N
|
|
420
|
+
num keypoints: K
|
|
421
|
+
heatmap depth size: D
|
|
422
|
+
heatmap height: H
|
|
423
|
+
heatmap width: W
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
heatmaps (np.ndarray[N, K, D, H, W]): model predicted heatmaps.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
tuple: A tuple containing aggregated results.
|
|
430
|
+
|
|
431
|
+
- preds (np.ndarray[N, K, 3]): Predicted keypoint location.
|
|
432
|
+
- maxvals (np.ndarray[N, K, 1]): Scores (confidence) of the keypoints.
|
|
433
|
+
"""
|
|
434
|
+
assert isinstance(heatmaps, np.ndarray), \
|
|
435
|
+
('heatmaps should be numpy.ndarray')
|
|
436
|
+
assert heatmaps.ndim == 5, 'heatmaps should be 5-ndim'
|
|
437
|
+
|
|
438
|
+
N, K, D, H, W = heatmaps.shape
|
|
439
|
+
heatmaps_reshaped = heatmaps.reshape((N, K, -1))
|
|
440
|
+
idx = np.argmax(heatmaps_reshaped, 2).reshape((N, K, 1))
|
|
441
|
+
maxvals = np.amax(heatmaps_reshaped, 2).reshape((N, K, 1))
|
|
442
|
+
|
|
443
|
+
preds = np.zeros((N, K, 3), dtype=np.float32)
|
|
444
|
+
_idx = idx[..., 0]
|
|
445
|
+
preds[..., 2] = _idx // (H * W)
|
|
446
|
+
preds[..., 1] = (_idx // W) % H
|
|
447
|
+
preds[..., 0] = _idx % W
|
|
448
|
+
|
|
449
|
+
preds = np.where(maxvals > 0.0, preds, -1)
|
|
450
|
+
return preds, maxvals
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def pose_pck_accuracy(output, target, mask, thr=0.05, normalize=None):
|
|
454
|
+
"""Calculate the pose accuracy of PCK for each individual keypoint and the
|
|
455
|
+
averaged accuracy across all keypoints from heatmaps.
|
|
456
|
+
|
|
457
|
+
Note:
|
|
458
|
+
PCK metric measures accuracy of the localization of the body joints.
|
|
459
|
+
The distances between predicted positions and the ground-truth ones
|
|
460
|
+
are typically normalized by the bounding box size.
|
|
461
|
+
The threshold (thr) of the normalized distance is commonly set
|
|
462
|
+
as 0.05, 0.1 or 0.2 etc.
|
|
463
|
+
|
|
464
|
+
- batch_size: N
|
|
465
|
+
- num_keypoints: K
|
|
466
|
+
- heatmap height: H
|
|
467
|
+
- heatmap width: W
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
output (np.ndarray[N, K, H, W]): Model output heatmaps.
|
|
471
|
+
target (np.ndarray[N, K, H, W]): Groundtruth heatmaps.
|
|
472
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
473
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
474
|
+
accuracy calculation.
|
|
475
|
+
thr (float): Threshold of PCK calculation. Default 0.05.
|
|
476
|
+
normalize (np.ndarray[N, 2]): Normalization factor for H&W.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
tuple: A tuple containing keypoint accuracy.
|
|
480
|
+
|
|
481
|
+
- np.ndarray[K]: Accuracy of each keypoint.
|
|
482
|
+
- float: Averaged accuracy across all keypoints.
|
|
483
|
+
- int: Number of valid keypoints.
|
|
484
|
+
"""
|
|
485
|
+
N, K, H, W = output.shape
|
|
486
|
+
if K == 0:
|
|
487
|
+
return None, 0, 0
|
|
488
|
+
if normalize is None:
|
|
489
|
+
normalize = np.tile(np.array([[H, W]]), (N, 1))
|
|
490
|
+
|
|
491
|
+
pred, _ = _get_max_preds(output)
|
|
492
|
+
gt, _ = _get_max_preds(target)
|
|
493
|
+
return keypoint_pck_accuracy(pred, gt, mask, thr, normalize)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def keypoint_pck_accuracy(pred, gt, mask, thr, normalize):
|
|
497
|
+
"""Calculate the pose accuracy of PCK for each individual keypoint and the
|
|
498
|
+
averaged accuracy across all keypoints for coordinates.
|
|
499
|
+
|
|
500
|
+
Note:
|
|
501
|
+
PCK metric measures accuracy of the localization of the body joints.
|
|
502
|
+
The distances between predicted positions and the ground-truth ones
|
|
503
|
+
are typically normalized by the bounding box size.
|
|
504
|
+
The threshold (thr) of the normalized distance is commonly set
|
|
505
|
+
as 0.05, 0.1 or 0.2 etc.
|
|
506
|
+
|
|
507
|
+
- batch_size: N
|
|
508
|
+
- num_keypoints: K
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
pred (np.ndarray[N, K, 2]): Predicted keypoint location.
|
|
512
|
+
gt (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
|
513
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
514
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
515
|
+
accuracy calculation.
|
|
516
|
+
thr (float): Threshold of PCK calculation.
|
|
517
|
+
normalize (np.ndarray[N, 2]): Normalization factor for H&W.
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
tuple: A tuple containing keypoint accuracy.
|
|
521
|
+
|
|
522
|
+
- acc (np.ndarray[K]): Accuracy of each keypoint.
|
|
523
|
+
- avg_acc (float): Averaged accuracy across all keypoints.
|
|
524
|
+
- cnt (int): Number of valid keypoints.
|
|
525
|
+
"""
|
|
526
|
+
distances = _calc_distances(pred, gt, mask, normalize)
|
|
527
|
+
|
|
528
|
+
acc = np.array([_distance_acc(d, thr) for d in distances])
|
|
529
|
+
valid_acc = acc[acc >= 0]
|
|
530
|
+
cnt = len(valid_acc)
|
|
531
|
+
avg_acc = valid_acc.mean() if cnt > 0 else 0
|
|
532
|
+
return acc, avg_acc, cnt
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def keypoint_auc(pred, gt, mask, normalize, num_step=20):
|
|
536
|
+
"""Calculate the pose accuracy of PCK for each individual keypoint and the
|
|
537
|
+
averaged accuracy across all keypoints for coordinates.
|
|
538
|
+
|
|
539
|
+
Note:
|
|
540
|
+
- batch_size: N
|
|
541
|
+
- num_keypoints: K
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
pred (np.ndarray[N, K, 2]): Predicted keypoint location.
|
|
545
|
+
gt (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
|
546
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
547
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
548
|
+
accuracy calculation.
|
|
549
|
+
normalize (float): Normalization factor.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
float: Area under curve.
|
|
553
|
+
"""
|
|
554
|
+
nor = np.tile(np.array([[normalize, normalize]]), (pred.shape[0], 1))
|
|
555
|
+
x = [1.0 * i / num_step for i in range(num_step)]
|
|
556
|
+
y = []
|
|
557
|
+
for thr in x:
|
|
558
|
+
_, avg_acc, _ = keypoint_pck_accuracy(pred, gt, mask, thr, nor)
|
|
559
|
+
y.append(avg_acc)
|
|
560
|
+
|
|
561
|
+
auc = 0
|
|
562
|
+
for i in range(num_step):
|
|
563
|
+
auc += 1.0 / num_step * y[i]
|
|
564
|
+
return auc
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def keypoint_nme(pred, gt, mask, normalize_factor):
|
|
568
|
+
"""Calculate the normalized mean error (NME).
|
|
569
|
+
|
|
570
|
+
Note:
|
|
571
|
+
- batch_size: N
|
|
572
|
+
- num_keypoints: K
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
pred (np.ndarray[N, K, 2]): Predicted keypoint location.
|
|
576
|
+
gt (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
|
577
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
578
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
579
|
+
accuracy calculation.
|
|
580
|
+
normalize_factor (np.ndarray[N, 2]): Normalization factor.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
float: normalized mean error
|
|
584
|
+
"""
|
|
585
|
+
distances = _calc_distances(pred, gt, mask, normalize_factor)
|
|
586
|
+
distance_valid = distances[distances != -1]
|
|
587
|
+
return distance_valid.sum() / max(1, len(distance_valid))
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def keypoint_epe(pred, gt, mask):
|
|
591
|
+
"""Calculate the end-point error.
|
|
592
|
+
|
|
593
|
+
Note:
|
|
594
|
+
- batch_size: N
|
|
595
|
+
- num_keypoints: K
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
pred (np.ndarray[N, K, 2]): Predicted keypoint location.
|
|
599
|
+
gt (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
|
600
|
+
mask (np.ndarray[N, K]): Visibility of the target. False for invisible
|
|
601
|
+
joints, and True for visible. Invisible joints will be ignored for
|
|
602
|
+
accuracy calculation.
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
float: Average end-point error.
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
distances = _calc_distances(
|
|
609
|
+
pred, gt, mask,
|
|
610
|
+
np.ones((pred.shape[0], pred.shape[2]), dtype=np.float32))
|
|
611
|
+
distance_valid = distances[distances != -1]
|
|
612
|
+
return distance_valid.sum() / max(1, len(distance_valid))
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _taylor(heatmap, coord):
|
|
616
|
+
"""Distribution aware coordinate decoding method.
|
|
617
|
+
|
|
618
|
+
Note:
|
|
619
|
+
- heatmap height: H
|
|
620
|
+
- heatmap width: W
|
|
621
|
+
|
|
622
|
+
Args:
|
|
623
|
+
heatmap (np.ndarray[H, W]): Heatmap of a particular joint type.
|
|
624
|
+
coord (np.ndarray[2,]): Coordinates of the predicted keypoints.
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
np.ndarray[2,]: Updated coordinates.
|
|
628
|
+
"""
|
|
629
|
+
H, W = heatmap.shape[:2]
|
|
630
|
+
px, py = int(coord[0]), int(coord[1])
|
|
631
|
+
if 1 < px < W - 2 and 1 < py < H - 2:
|
|
632
|
+
dx = 0.5 * (heatmap[py][px + 1] - heatmap[py][px - 1])
|
|
633
|
+
dy = 0.5 * (heatmap[py + 1][px] - heatmap[py - 1][px])
|
|
634
|
+
dxx = 0.25 * (
|
|
635
|
+
heatmap[py][px + 2] - 2 * heatmap[py][px] + heatmap[py][px - 2])
|
|
636
|
+
dxy = 0.25 * (
|
|
637
|
+
heatmap[py + 1][px + 1] - heatmap[py - 1][px + 1] -
|
|
638
|
+
heatmap[py + 1][px - 1] + heatmap[py - 1][px - 1])
|
|
639
|
+
dyy = 0.25 * (
|
|
640
|
+
heatmap[py + 2 * 1][px] - 2 * heatmap[py][px] +
|
|
641
|
+
heatmap[py - 2 * 1][px])
|
|
642
|
+
derivative = np.array([[dx], [dy]])
|
|
643
|
+
hessian = np.array([[dxx, dxy], [dxy, dyy]])
|
|
644
|
+
if dxx * dyy - dxy**2 != 0:
|
|
645
|
+
hessianinv = np.linalg.inv(hessian)
|
|
646
|
+
offset = -hessianinv @ derivative
|
|
647
|
+
offset = np.squeeze(np.array(offset.T), axis=0)
|
|
648
|
+
coord += offset
|
|
649
|
+
return coord
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def post_dark_udp(coords, batch_heatmaps, kernel=3):
|
|
653
|
+
"""DARK post-pocessing. Implemented by udp. Paper ref: Huang et al. The
|
|
654
|
+
Devil is in the Details: Delving into Unbiased Data Processing for Human
|
|
655
|
+
Pose Estimation (CVPR 2020). Zhang et al. Distribution-Aware Coordinate
|
|
656
|
+
Representation for Human Pose Estimation (CVPR 2020).
|
|
657
|
+
|
|
658
|
+
Note:
|
|
659
|
+
- batch size: B
|
|
660
|
+
- num keypoints: K
|
|
661
|
+
- num persons: N
|
|
662
|
+
- height of heatmaps: H
|
|
663
|
+
- width of heatmaps: W
|
|
664
|
+
|
|
665
|
+
B=1 for bottom_up paradigm where all persons share the same heatmap.
|
|
666
|
+
B=N for top_down paradigm where each person has its own heatmaps.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
coords (np.ndarray[N, K, 2]): Initial coordinates of human pose.
|
|
670
|
+
batch_heatmaps (np.ndarray[B, K, H, W]): batch_heatmaps
|
|
671
|
+
kernel (int): Gaussian kernel size (K) for modulation.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
np.ndarray([N, K, 2]): Refined coordinates.
|
|
675
|
+
"""
|
|
676
|
+
if not isinstance(batch_heatmaps, np.ndarray):
|
|
677
|
+
batch_heatmaps = batch_heatmaps.cpu().numpy()
|
|
678
|
+
B, K, H, W = batch_heatmaps.shape
|
|
679
|
+
N = coords.shape[0]
|
|
680
|
+
assert (B == 1 or B == N)
|
|
681
|
+
for heatmaps in batch_heatmaps:
|
|
682
|
+
for heatmap in heatmaps:
|
|
683
|
+
cv2.GaussianBlur(heatmap, (kernel, kernel), 0, heatmap)
|
|
684
|
+
np.clip(batch_heatmaps, 0.001, 50, batch_heatmaps)
|
|
685
|
+
np.log(batch_heatmaps, batch_heatmaps)
|
|
686
|
+
|
|
687
|
+
batch_heatmaps_pad = np.pad(
|
|
688
|
+
batch_heatmaps, ((0, 0), (0, 0), (1, 1), (1, 1)),
|
|
689
|
+
mode='edge').flatten()
|
|
690
|
+
|
|
691
|
+
index = coords[..., 0] + 1 + (coords[..., 1] + 1) * (W + 2)
|
|
692
|
+
index += (W + 2) * (H + 2) * np.arange(0, B * K).reshape(-1, K)
|
|
693
|
+
index = index.astype(int).reshape(-1, 1)
|
|
694
|
+
i_ = batch_heatmaps_pad[index]
|
|
695
|
+
ix1 = batch_heatmaps_pad[index + 1]
|
|
696
|
+
iy1 = batch_heatmaps_pad[index + W + 2]
|
|
697
|
+
ix1y1 = batch_heatmaps_pad[index + W + 3]
|
|
698
|
+
ix1_y1_ = batch_heatmaps_pad[index - W - 3]
|
|
699
|
+
ix1_ = batch_heatmaps_pad[index - 1]
|
|
700
|
+
iy1_ = batch_heatmaps_pad[index - 2 - W]
|
|
701
|
+
|
|
702
|
+
dx = 0.5 * (ix1 - ix1_)
|
|
703
|
+
dy = 0.5 * (iy1 - iy1_)
|
|
704
|
+
derivative = np.concatenate([dx, dy], axis=1)
|
|
705
|
+
derivative = derivative.reshape(N, K, 2, 1)
|
|
706
|
+
dxx = ix1 - 2 * i_ + ix1_
|
|
707
|
+
dyy = iy1 - 2 * i_ + iy1_
|
|
708
|
+
dxy = 0.5 * (ix1y1 - ix1 - iy1 + i_ + i_ - ix1_ - iy1_ + ix1_y1_)
|
|
709
|
+
hessian = np.concatenate([dxx, dxy, dxy, dyy], axis=1)
|
|
710
|
+
hessian = hessian.reshape(N, K, 2, 2)
|
|
711
|
+
hessian = np.linalg.inv(hessian + np.finfo(np.float32).eps * np.eye(2))
|
|
712
|
+
coords -= np.einsum('ijmn,ijnk->ijmk', hessian, derivative).squeeze()
|
|
713
|
+
return coords
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _gaussian_blur(heatmaps, kernel=11):
|
|
717
|
+
"""Modulate heatmap distribution with Gaussian.
|
|
718
|
+
sigma = 0.3*((kernel_size-1)*0.5-1)+0.8
|
|
719
|
+
sigma~=3 if k=17
|
|
720
|
+
sigma=2 if k=11;
|
|
721
|
+
sigma~=1.5 if k=7;
|
|
722
|
+
sigma~=1 if k=3;
|
|
723
|
+
|
|
724
|
+
Note:
|
|
725
|
+
- batch_size: N
|
|
726
|
+
- num_keypoints: K
|
|
727
|
+
- heatmap height: H
|
|
728
|
+
- heatmap width: W
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
heatmaps (np.ndarray[N, K, H, W]): model predicted heatmaps.
|
|
732
|
+
kernel (int): Gaussian kernel size (K) for modulation, which should
|
|
733
|
+
match the heatmap gaussian sigma when training.
|
|
734
|
+
K=17 for sigma=3 and k=11 for sigma=2.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
np.ndarray ([N, K, H, W]): Modulated heatmap distribution.
|
|
738
|
+
"""
|
|
739
|
+
assert kernel % 2 == 1
|
|
740
|
+
|
|
741
|
+
border = (kernel - 1) // 2
|
|
742
|
+
batch_size = heatmaps.shape[0]
|
|
743
|
+
num_joints = heatmaps.shape[1]
|
|
744
|
+
height = heatmaps.shape[2]
|
|
745
|
+
width = heatmaps.shape[3]
|
|
746
|
+
for i in range(batch_size):
|
|
747
|
+
for j in range(num_joints):
|
|
748
|
+
origin_max = np.max(heatmaps[i, j])
|
|
749
|
+
dr = np.zeros((height + 2 * border, width + 2 * border),
|
|
750
|
+
dtype=np.float32)
|
|
751
|
+
dr[border:-border, border:-border] = heatmaps[i, j].copy()
|
|
752
|
+
dr = cv2.GaussianBlur(dr, (kernel, kernel), 0)
|
|
753
|
+
heatmaps[i, j] = dr[border:-border, border:-border].copy()
|
|
754
|
+
heatmaps[i, j] *= origin_max / np.max(heatmaps[i, j])
|
|
755
|
+
return heatmaps
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def keypoints_from_regression(regression_preds, center, scale, img_size):
|
|
759
|
+
"""Get final keypoint predictions from regression vectors and transform
|
|
760
|
+
them back to the image.
|
|
761
|
+
|
|
762
|
+
Note:
|
|
763
|
+
- batch_size: N
|
|
764
|
+
- num_keypoints: K
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
regression_preds (np.ndarray[N, K, 2]): model prediction.
|
|
768
|
+
center (np.ndarray[N, 2]): Center of the bounding box (x, y).
|
|
769
|
+
scale (np.ndarray[N, 2]): Scale of the bounding box
|
|
770
|
+
wrt height/width.
|
|
771
|
+
img_size (list(img_width, img_height)): model input image size.
|
|
772
|
+
|
|
773
|
+
Returns:
|
|
774
|
+
tuple:
|
|
775
|
+
|
|
776
|
+
- preds (np.ndarray[N, K, 2]): Predicted keypoint location in images.
|
|
777
|
+
- maxvals (np.ndarray[N, K, 1]): Scores (confidence) of the keypoints.
|
|
778
|
+
"""
|
|
779
|
+
N, K, _ = regression_preds.shape
|
|
780
|
+
preds, maxvals = regression_preds, np.ones((N, K, 1), dtype=np.float32)
|
|
781
|
+
|
|
782
|
+
preds = preds * img_size
|
|
783
|
+
|
|
784
|
+
# Transform back to the image
|
|
785
|
+
for i in range(N):
|
|
786
|
+
preds[i] = transform_preds(preds[i], center[i], scale[i], img_size)
|
|
787
|
+
|
|
788
|
+
return preds, maxvals
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def keypoints_from_heatmaps(heatmaps,
|
|
792
|
+
center,
|
|
793
|
+
scale,
|
|
794
|
+
unbiased=False,
|
|
795
|
+
post_process='default',
|
|
796
|
+
kernel=11,
|
|
797
|
+
valid_radius_factor=0.0546875,
|
|
798
|
+
use_udp=False,
|
|
799
|
+
target_type='GaussianHeatmap'):
|
|
800
|
+
"""Get final keypoint predictions from heatmaps and transform them back to
|
|
801
|
+
the image.
|
|
802
|
+
|
|
803
|
+
Note:
|
|
804
|
+
- batch size: N
|
|
805
|
+
- num keypoints: K
|
|
806
|
+
- heatmap height: H
|
|
807
|
+
- heatmap width: W
|
|
808
|
+
|
|
809
|
+
Args:
|
|
810
|
+
heatmaps (np.ndarray[N, K, H, W]): model predicted heatmaps.
|
|
811
|
+
center (np.ndarray[N, 2]): Center of the bounding box (x, y).
|
|
812
|
+
scale (np.ndarray[N, 2]): Scale of the bounding box
|
|
813
|
+
wrt height/width.
|
|
814
|
+
post_process (str/None): Choice of methods to post-process
|
|
815
|
+
heatmaps. Currently supported: None, 'default', 'unbiased',
|
|
816
|
+
'megvii'.
|
|
817
|
+
unbiased (bool): Option to use unbiased decoding. Mutually
|
|
818
|
+
exclusive with megvii.
|
|
819
|
+
Note: this arg is deprecated and unbiased=True can be replaced
|
|
820
|
+
by post_process='unbiased'
|
|
821
|
+
Paper ref: Zhang et al. Distribution-Aware Coordinate
|
|
822
|
+
Representation for Human Pose Estimation (CVPR 2020).
|
|
823
|
+
kernel (int): Gaussian kernel size (K) for modulation, which should
|
|
824
|
+
match the heatmap gaussian sigma when training.
|
|
825
|
+
K=17 for sigma=3 and k=11 for sigma=2.
|
|
826
|
+
valid_radius_factor (float): The radius factor of the positive area
|
|
827
|
+
in classification heatmap for UDP.
|
|
828
|
+
use_udp (bool): Use unbiased data processing.
|
|
829
|
+
target_type (str): 'GaussianHeatmap' or 'CombinedTarget'.
|
|
830
|
+
GaussianHeatmap: Classification target with gaussian distribution.
|
|
831
|
+
CombinedTarget: The combination of classification target
|
|
832
|
+
(response map) and regression target (offset map).
|
|
833
|
+
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
|
834
|
+
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
tuple: A tuple containing keypoint predictions and scores.
|
|
838
|
+
|
|
839
|
+
- preds (np.ndarray[N, K, 2]): Predicted keypoint location in images.
|
|
840
|
+
- maxvals (np.ndarray[N, K, 1]): Scores (confidence) of the keypoints.
|
|
841
|
+
"""
|
|
842
|
+
# Avoid being affected
|
|
843
|
+
heatmaps = heatmaps.copy()
|
|
844
|
+
|
|
845
|
+
# detect conflicts
|
|
846
|
+
if unbiased:
|
|
847
|
+
assert post_process not in [False, None, 'megvii']
|
|
848
|
+
if post_process in ['megvii', 'unbiased']:
|
|
849
|
+
assert kernel > 0
|
|
850
|
+
if use_udp:
|
|
851
|
+
assert not post_process == 'megvii'
|
|
852
|
+
|
|
853
|
+
# normalize configs
|
|
854
|
+
if post_process is False:
|
|
855
|
+
warnings.warn(
|
|
856
|
+
'post_process=False is deprecated, '
|
|
857
|
+
'please use post_process=None instead', DeprecationWarning)
|
|
858
|
+
post_process = None
|
|
859
|
+
elif post_process is True:
|
|
860
|
+
if unbiased is True:
|
|
861
|
+
warnings.warn(
|
|
862
|
+
'post_process=True, unbiased=True is deprecated,'
|
|
863
|
+
" please use post_process='unbiased' instead",
|
|
864
|
+
DeprecationWarning)
|
|
865
|
+
post_process = 'unbiased'
|
|
866
|
+
else:
|
|
867
|
+
warnings.warn(
|
|
868
|
+
'post_process=True, unbiased=False is deprecated, '
|
|
869
|
+
"please use post_process='default' instead",
|
|
870
|
+
DeprecationWarning)
|
|
871
|
+
post_process = 'default'
|
|
872
|
+
elif post_process == 'default':
|
|
873
|
+
if unbiased is True:
|
|
874
|
+
warnings.warn(
|
|
875
|
+
'unbiased=True is deprecated, please use '
|
|
876
|
+
"post_process='unbiased' instead", DeprecationWarning)
|
|
877
|
+
post_process = 'unbiased'
|
|
878
|
+
|
|
879
|
+
# start processing
|
|
880
|
+
if post_process == 'megvii':
|
|
881
|
+
heatmaps = _gaussian_blur(heatmaps, kernel=kernel)
|
|
882
|
+
|
|
883
|
+
N, K, H, W = heatmaps.shape
|
|
884
|
+
if use_udp:
|
|
885
|
+
if target_type.lower() == 'GaussianHeatMap'.lower():
|
|
886
|
+
preds, maxvals = _get_max_preds(heatmaps)
|
|
887
|
+
preds = post_dark_udp(preds, heatmaps, kernel=kernel)
|
|
888
|
+
elif target_type.lower() == 'CombinedTarget'.lower():
|
|
889
|
+
for person_heatmaps in heatmaps:
|
|
890
|
+
for i, heatmap in enumerate(person_heatmaps):
|
|
891
|
+
kt = 2 * kernel + 1 if i % 3 == 0 else kernel
|
|
892
|
+
cv2.GaussianBlur(heatmap, (kt, kt), 0, heatmap)
|
|
893
|
+
# valid radius is in direct proportion to the height of heatmap.
|
|
894
|
+
valid_radius = valid_radius_factor * H
|
|
895
|
+
offset_x = heatmaps[:, 1::3, :].flatten() * valid_radius
|
|
896
|
+
offset_y = heatmaps[:, 2::3, :].flatten() * valid_radius
|
|
897
|
+
heatmaps = heatmaps[:, ::3, :]
|
|
898
|
+
preds, maxvals = _get_max_preds(heatmaps)
|
|
899
|
+
index = preds[..., 0] + preds[..., 1] * W
|
|
900
|
+
index += W * H * np.arange(0, N * K / 3)
|
|
901
|
+
index = index.astype(int).reshape(N, K // 3, 1)
|
|
902
|
+
preds += np.concatenate((offset_x[index], offset_y[index]), axis=2)
|
|
903
|
+
else:
|
|
904
|
+
raise ValueError('target_type should be either '
|
|
905
|
+
"'GaussianHeatmap' or 'CombinedTarget'")
|
|
906
|
+
else:
|
|
907
|
+
preds, maxvals = _get_max_preds(heatmaps)
|
|
908
|
+
if post_process == 'unbiased': # alleviate biased coordinate
|
|
909
|
+
# apply Gaussian distribution modulation.
|
|
910
|
+
heatmaps = np.log(
|
|
911
|
+
np.maximum(_gaussian_blur(heatmaps, kernel), 1e-10))
|
|
912
|
+
for n in range(N):
|
|
913
|
+
for k in range(K):
|
|
914
|
+
preds[n][k] = _taylor(heatmaps[n][k], preds[n][k])
|
|
915
|
+
elif post_process is not None:
|
|
916
|
+
# add +/-0.25 shift to the predicted locations for higher acc.
|
|
917
|
+
for n in range(N):
|
|
918
|
+
for k in range(K):
|
|
919
|
+
heatmap = heatmaps[n][k]
|
|
920
|
+
px = int(preds[n][k][0])
|
|
921
|
+
py = int(preds[n][k][1])
|
|
922
|
+
if 1 < px < W - 1 and 1 < py < H - 1:
|
|
923
|
+
diff = np.array([
|
|
924
|
+
heatmap[py][px + 1] - heatmap[py][px - 1],
|
|
925
|
+
heatmap[py + 1][px] - heatmap[py - 1][px]
|
|
926
|
+
])
|
|
927
|
+
preds[n][k] += np.sign(diff) * .25
|
|
928
|
+
if post_process == 'megvii':
|
|
929
|
+
preds[n][k] += 0.5
|
|
930
|
+
|
|
931
|
+
# Transform back to the image
|
|
932
|
+
for i in range(N):
|
|
933
|
+
preds[i] = transform_preds(
|
|
934
|
+
preds[i], center[i], scale[i], [W, H], use_udp=use_udp)
|
|
935
|
+
|
|
936
|
+
if post_process == 'megvii':
|
|
937
|
+
maxvals = maxvals / 255.0 + 0.5
|
|
938
|
+
|
|
939
|
+
return preds, maxvals
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def keypoints_from_heatmaps3d(heatmaps, center, scale):
|
|
943
|
+
"""Get final keypoint predictions from 3d heatmaps and transform them back
|
|
944
|
+
to the image.
|
|
945
|
+
|
|
946
|
+
Note:
|
|
947
|
+
- batch size: N
|
|
948
|
+
- num keypoints: K
|
|
949
|
+
- heatmap depth size: D
|
|
950
|
+
- heatmap height: H
|
|
951
|
+
- heatmap width: W
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
heatmaps (np.ndarray[N, K, D, H, W]): model predicted heatmaps.
|
|
955
|
+
center (np.ndarray[N, 2]): Center of the bounding box (x, y).
|
|
956
|
+
scale (np.ndarray[N, 2]): Scale of the bounding box
|
|
957
|
+
wrt height/width.
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
tuple: A tuple containing keypoint predictions and scores.
|
|
961
|
+
|
|
962
|
+
- preds (np.ndarray[N, K, 3]): Predicted 3d keypoint location \
|
|
963
|
+
in images.
|
|
964
|
+
- maxvals (np.ndarray[N, K, 1]): Scores (confidence) of the keypoints.
|
|
965
|
+
"""
|
|
966
|
+
N, K, D, H, W = heatmaps.shape
|
|
967
|
+
preds, maxvals = _get_max_preds_3d(heatmaps)
|
|
968
|
+
# Transform back to the image
|
|
969
|
+
for i in range(N):
|
|
970
|
+
preds[i, :, :2] = transform_preds(preds[i, :, :2], center[i], scale[i],
|
|
971
|
+
[W, H])
|
|
972
|
+
return preds, maxvals
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def multilabel_classification_accuracy(pred, gt, mask, thr=0.5):
|
|
976
|
+
"""Get multi-label classification accuracy.
|
|
977
|
+
|
|
978
|
+
Note:
|
|
979
|
+
- batch size: N
|
|
980
|
+
- label number: L
|
|
981
|
+
|
|
982
|
+
Args:
|
|
983
|
+
pred (np.ndarray[N, L, 2]): model predicted labels.
|
|
984
|
+
gt (np.ndarray[N, L, 2]): ground-truth labels.
|
|
985
|
+
mask (np.ndarray[N, 1] or np.ndarray[N, L] ): reliability of
|
|
986
|
+
ground-truth labels.
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
float: multi-label classification accuracy.
|
|
990
|
+
"""
|
|
991
|
+
# we only compute accuracy on the samples with ground-truth of all labels.
|
|
992
|
+
valid = (mask > 0).min(axis=1) if mask.ndim == 2 else (mask > 0)
|
|
993
|
+
pred, gt = pred[valid], gt[valid]
|
|
994
|
+
|
|
995
|
+
if pred.shape[0] == 0:
|
|
996
|
+
acc = 0.0 # when no sample is with gt labels, set acc to 0.
|
|
997
|
+
else:
|
|
998
|
+
# The classification of a sample is regarded as correct
|
|
999
|
+
# only if it's correct for all labels.
|
|
1000
|
+
acc = (((pred - thr) * (gt - thr)) > 0).all(axis=1).mean()
|
|
1001
|
+
return acc
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def get_transform(center, scale, res, rot=0):
|
|
1006
|
+
"""Generate transformation matrix."""
|
|
1007
|
+
# res: (height, width), (rows, cols)
|
|
1008
|
+
crop_aspect_ratio = res[0] / float(res[1])
|
|
1009
|
+
h = 200 * scale
|
|
1010
|
+
w = h / crop_aspect_ratio
|
|
1011
|
+
t = np.zeros((3, 3))
|
|
1012
|
+
t[0, 0] = float(res[1]) / w
|
|
1013
|
+
t[1, 1] = float(res[0]) / h
|
|
1014
|
+
t[0, 2] = res[1] * (-float(center[0]) / w + .5)
|
|
1015
|
+
t[1, 2] = res[0] * (-float(center[1]) / h + .5)
|
|
1016
|
+
t[2, 2] = 1
|
|
1017
|
+
if not rot == 0:
|
|
1018
|
+
rot = -rot # To match direction of rotation from cropping
|
|
1019
|
+
rot_mat = np.zeros((3, 3))
|
|
1020
|
+
rot_rad = rot * np.pi / 180
|
|
1021
|
+
sn, cs = np.sin(rot_rad), np.cos(rot_rad)
|
|
1022
|
+
rot_mat[0, :2] = [cs, -sn]
|
|
1023
|
+
rot_mat[1, :2] = [sn, cs]
|
|
1024
|
+
rot_mat[2, 2] = 1
|
|
1025
|
+
# Need to rotate around center
|
|
1026
|
+
t_mat = np.eye(3)
|
|
1027
|
+
t_mat[0, 2] = -res[1] / 2
|
|
1028
|
+
t_mat[1, 2] = -res[0] / 2
|
|
1029
|
+
t_inv = t_mat.copy()
|
|
1030
|
+
t_inv[:2, 2] *= -1
|
|
1031
|
+
t = np.dot(t_inv, np.dot(rot_mat, np.dot(t_mat, t)))
|
|
1032
|
+
return t
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def transform(pt, center, scale, res, invert=0, rot=0):
|
|
1036
|
+
"""Transform pixel location to different reference."""
|
|
1037
|
+
t = get_transform(center, scale, res, rot=rot)
|
|
1038
|
+
if invert:
|
|
1039
|
+
t = np.linalg.inv(t)
|
|
1040
|
+
new_pt = np.array([pt[0] - 1, pt[1] - 1, 1.]).T
|
|
1041
|
+
new_pt = np.dot(t, new_pt)
|
|
1042
|
+
return np.array([round(new_pt[0]), round(new_pt[1])], dtype=int) + 1
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def bbox_from_detector(bbox, input_resolution=(224, 224), rescale=1.25):
|
|
1046
|
+
"""
|
|
1047
|
+
Get center and scale of bounding box from bounding box.
|
|
1048
|
+
The expected format is [min_x, min_y, max_x, max_y].
|
|
1049
|
+
"""
|
|
1050
|
+
CROP_IMG_HEIGHT, CROP_IMG_WIDTH = input_resolution
|
|
1051
|
+
CROP_ASPECT_RATIO = CROP_IMG_HEIGHT / float(CROP_IMG_WIDTH)
|
|
1052
|
+
|
|
1053
|
+
# center
|
|
1054
|
+
center_x = (bbox[0] + bbox[2]) / 2.0
|
|
1055
|
+
center_y = (bbox[1] + bbox[3]) / 2.0
|
|
1056
|
+
center = np.array([center_x, center_y])
|
|
1057
|
+
|
|
1058
|
+
# scale
|
|
1059
|
+
bbox_w = bbox[2] - bbox[0]
|
|
1060
|
+
bbox_h = bbox[3] - bbox[1]
|
|
1061
|
+
bbox_size = max(bbox_w * CROP_ASPECT_RATIO, bbox_h)
|
|
1062
|
+
|
|
1063
|
+
scale = np.array([bbox_size / CROP_ASPECT_RATIO, bbox_size]) / 200.0
|
|
1064
|
+
# scale = bbox_size / 200.0
|
|
1065
|
+
# adjust bounding box tightness
|
|
1066
|
+
scale *= rescale
|
|
1067
|
+
return center, scale
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def crop(img, center, scale, res):
|
|
1071
|
+
"""
|
|
1072
|
+
Crop image according to the supplied bounding box.
|
|
1073
|
+
res: [rows, cols]
|
|
1074
|
+
"""
|
|
1075
|
+
# Upper left point
|
|
1076
|
+
ul = np.array(transform([1, 1], center, max(scale), res, invert=1)) - 1
|
|
1077
|
+
# Bottom right point
|
|
1078
|
+
br = np.array(transform([res[1] + 1, res[0] + 1], center, max(scale), res, invert=1)) - 1
|
|
1079
|
+
|
|
1080
|
+
# Padding so that when rotated proper amount of context is included
|
|
1081
|
+
pad = int(np.linalg.norm(br - ul) / 2 - float(br[1] - ul[1]) / 2)
|
|
1082
|
+
|
|
1083
|
+
new_shape = [br[1] - ul[1], br[0] - ul[0]]
|
|
1084
|
+
if len(img.shape) > 2:
|
|
1085
|
+
new_shape += [img.shape[2]]
|
|
1086
|
+
new_img = np.zeros(new_shape, dtype=np.float32)
|
|
1087
|
+
|
|
1088
|
+
# Range to fill new array
|
|
1089
|
+
new_x = max(0, -ul[0]), min(br[0], len(img[0])) - ul[0]
|
|
1090
|
+
new_y = max(0, -ul[1]), min(br[1], len(img)) - ul[1]
|
|
1091
|
+
# Range to sample from original image
|
|
1092
|
+
old_x = max(0, ul[0]), min(len(img[0]), br[0])
|
|
1093
|
+
old_y = max(0, ul[1]), min(len(img), br[1])
|
|
1094
|
+
try:
|
|
1095
|
+
new_img[new_y[0]:new_y[1], new_x[0]:new_x[1]] = img[old_y[0]:old_y[1], old_x[0]:old_x[1]]
|
|
1096
|
+
except Exception as e:
|
|
1097
|
+
print(e)
|
|
1098
|
+
|
|
1099
|
+
new_img = cv2.resize(new_img, (res[1], res[0])) # (cols, rows)
|
|
1100
|
+
return new_img, new_shape, (old_x, old_y), (new_x, new_y) # , ul, br
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def split_kp2ds_for_aa(kp2ds, ret_face=False):
|
|
1104
|
+
kp2ds_body = (kp2ds[[0, 6, 6, 8, 10, 5, 7, 9, 12, 14, 16, 11, 13, 15, 2, 1, 4, 3, 17, 20]] + kp2ds[[0, 5, 6, 8, 10, 5, 7, 9, 12, 14, 16, 11, 13, 15, 2, 1, 4, 3, 18, 21]]) / 2
|
|
1105
|
+
kp2ds_lhand = kp2ds[91:112]
|
|
1106
|
+
kp2ds_rhand = kp2ds[112:133]
|
|
1107
|
+
kp2ds_face = kp2ds[22:91]
|
|
1108
|
+
if ret_face:
|
|
1109
|
+
return kp2ds_body.copy(), kp2ds_lhand.copy(), kp2ds_rhand.copy(), kp2ds_face.copy()
|
|
1110
|
+
return kp2ds_body.copy(), kp2ds_lhand.copy(), kp2ds_rhand.copy()
|
|
1111
|
+
|
|
1112
|
+
def load_pose_metas_from_kp2ds_seq_list(kp2ds_seq, width, height):
|
|
1113
|
+
metas = []
|
|
1114
|
+
for kps in kp2ds_seq:
|
|
1115
|
+
if len(kps) != 1:
|
|
1116
|
+
return None
|
|
1117
|
+
kps = kps[0].copy()
|
|
1118
|
+
kps[:, 0] /= width
|
|
1119
|
+
kps[:, 1] /= height
|
|
1120
|
+
kp2ds_body, kp2ds_lhand, kp2ds_rhand, kp2ds_face = split_kp2ds_for_aa(kps, ret_face=True)
|
|
1121
|
+
|
|
1122
|
+
if kp2ds_body[:, :2].min(axis=1).max() < 0:
|
|
1123
|
+
kp2ds_body = last_kp2ds_body
|
|
1124
|
+
last_kp2ds_body = kp2ds_body
|
|
1125
|
+
|
|
1126
|
+
meta = {
|
|
1127
|
+
"width": width,
|
|
1128
|
+
"height": height,
|
|
1129
|
+
"keypoints_body": kp2ds_body.tolist(),
|
|
1130
|
+
"keypoints_left_hand": kp2ds_lhand.tolist(),
|
|
1131
|
+
"keypoints_right_hand": kp2ds_rhand.tolist(),
|
|
1132
|
+
"keypoints_face": kp2ds_face.tolist(),
|
|
1133
|
+
}
|
|
1134
|
+
metas.append(meta)
|
|
1135
|
+
return metas
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def load_pose_metas_from_kp2ds_seq(kp2ds_seq, width, height):
|
|
1139
|
+
metas = []
|
|
1140
|
+
for kps in kp2ds_seq:
|
|
1141
|
+
kps = kps.copy()
|
|
1142
|
+
kps[:, 0] /= width
|
|
1143
|
+
kps[:, 1] /= height
|
|
1144
|
+
kp2ds_body, kp2ds_lhand, kp2ds_rhand, kp2ds_face = split_kp2ds_for_aa(kps, ret_face=True)
|
|
1145
|
+
|
|
1146
|
+
# 排除全部小于0的情况
|
|
1147
|
+
if kp2ds_body[:, :2].min(axis=1).max() < 0:
|
|
1148
|
+
kp2ds_body = last_kp2ds_body
|
|
1149
|
+
last_kp2ds_body = kp2ds_body
|
|
1150
|
+
|
|
1151
|
+
meta = {
|
|
1152
|
+
"width": width,
|
|
1153
|
+
"height": height,
|
|
1154
|
+
"keypoints_body": kp2ds_body,
|
|
1155
|
+
"keypoints_left_hand": kp2ds_lhand,
|
|
1156
|
+
"keypoints_right_hand": kp2ds_rhand,
|
|
1157
|
+
"keypoints_face": kp2ds_face,
|
|
1158
|
+
}
|
|
1159
|
+
metas.append(meta)
|
|
1160
|
+
return metas
|