@ignix-social/creator 0.0.0-stage → 0.1.0

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.
Files changed (92) hide show
  1. package/README.md +57 -2
  2. package/dist/box-kit/comfyui/locks/linux-x64.txt +2409 -0
  3. package/dist/box-kit/comfyui/locks/macos-arm64.txt +2243 -0
  4. package/dist/box-kit/comfyui/locks/windows-x64.txt +2322 -0
  5. package/dist/box-kit/comfyui/recast/LICENSE-WAN.txt +201 -0
  6. package/dist/box-kit/comfyui/recast/people.py +115 -0
  7. package/dist/box-kit/comfyui/recast/persons.py +90 -0
  8. package/dist/box-kit/comfyui/recast/prep.py +204 -0
  9. package/dist/box-kit/comfyui/recast/wan_pose_draw.py +1368 -0
  10. package/dist/box-kit/comfyui/recast/wan_pose_utils.py +1160 -0
  11. package/dist/box-kit/comfyui/recast/wan_utils.py +227 -0
  12. package/dist/box-kit/comfyui/workflows/infinitetalk.v1.json +94 -0
  13. package/dist/box-kit/comfyui/workflows/wan22-i2v-14b.v1.json +112 -0
  14. package/dist/box-kit/comfyui/workflows/wan22-i2v.v1.json +62 -0
  15. package/dist/cli.cjs +23801 -0
  16. package/dist/core.cjs +63459 -0
  17. package/dist/sfx-kit/achievement.wav +0 -0
  18. package/dist/sfx-kit/applause.wav +0 -0
  19. package/dist/sfx-kit/aww.wav +0 -0
  20. package/dist/sfx-kit/boing.wav +0 -0
  21. package/dist/sfx-kit/bonk.wav +0 -0
  22. package/dist/sfx-kit/boo.wav +0 -0
  23. package/dist/sfx-kit/boom.wav +0 -0
  24. package/dist/sfx-kit/camera-shutter.wav +0 -0
  25. package/dist/sfx-kit/cash-register.wav +0 -0
  26. package/dist/sfx-kit/cheer.wav +0 -0
  27. package/dist/sfx-kit/clap-hit.wav +0 -0
  28. package/dist/sfx-kit/click.wav +0 -0
  29. package/dist/sfx-kit/clock-tick.wav +0 -0
  30. package/dist/sfx-kit/coffee-pour.wav +0 -0
  31. package/dist/sfx-kit/coin.wav +0 -0
  32. package/dist/sfx-kit/coins.wav +0 -0
  33. package/dist/sfx-kit/correct.wav +0 -0
  34. package/dist/sfx-kit/countdown-beep.wav +0 -0
  35. package/dist/sfx-kit/crickets.wav +0 -0
  36. package/dist/sfx-kit/ding.wav +0 -0
  37. package/dist/sfx-kit/door-bell.wav +0 -0
  38. package/dist/sfx-kit/door-knock.wav +0 -0
  39. package/dist/sfx-kit/error.wav +0 -0
  40. package/dist/sfx-kit/flash.wav +0 -0
  41. package/dist/sfx-kit/gasp.wav +0 -0
  42. package/dist/sfx-kit/glitch.wav +0 -0
  43. package/dist/sfx-kit/heartbeat.wav +0 -0
  44. package/dist/sfx-kit/honk.wav +0 -0
  45. package/dist/sfx-kit/impact.wav +0 -0
  46. package/dist/sfx-kit/laugh.wav +0 -0
  47. package/dist/sfx-kit/level-up.wav +0 -0
  48. package/dist/sfx-kit/message-received.wav +0 -0
  49. package/dist/sfx-kit/message-sent.wav +0 -0
  50. package/dist/sfx-kit/notification.wav +0 -0
  51. package/dist/sfx-kit/ooh.wav +0 -0
  52. package/dist/sfx-kit/page-turn.wav +0 -0
  53. package/dist/sfx-kit/paper-crumple.wav +0 -0
  54. package/dist/sfx-kit/pen-write.wav +0 -0
  55. package/dist/sfx-kit/phone-vibrate.wav +0 -0
  56. package/dist/sfx-kit/pop-clean.wav +0 -0
  57. package/dist/sfx-kit/pop.wav +0 -0
  58. package/dist/sfx-kit/punch.wav +0 -0
  59. package/dist/sfx-kit/record-scratch.wav +0 -0
  60. package/dist/sfx-kit/reverse-cymbal.wav +0 -0
  61. package/dist/sfx-kit/rimshot.wav +0 -0
  62. package/dist/sfx-kit/riser-short.wav +0 -0
  63. package/dist/sfx-kit/sad-trombone.wav +0 -0
  64. package/dist/sfx-kit/scroll.wav +0 -0
  65. package/dist/sfx-kit/slide-whistle.wav +0 -0
  66. package/dist/sfx-kit/snap.wav +0 -0
  67. package/dist/sfx-kit/sparkle.wav +0 -0
  68. package/dist/sfx-kit/splat.wav +0 -0
  69. package/dist/sfx-kit/squeak.wav +0 -0
  70. package/dist/sfx-kit/stamp.wav +0 -0
  71. package/dist/sfx-kit/stomp.wav +0 -0
  72. package/dist/sfx-kit/sub-drop.wav +0 -0
  73. package/dist/sfx-kit/success.wav +0 -0
  74. package/dist/sfx-kit/swipe.wav +0 -0
  75. package/dist/sfx-kit/swoosh-down.wav +0 -0
  76. package/dist/sfx-kit/swoosh-up.wav +0 -0
  77. package/dist/sfx-kit/tap.wav +0 -0
  78. package/dist/sfx-kit/tape-stop.wav +0 -0
  79. package/dist/sfx-kit/thud.wav +0 -0
  80. package/dist/sfx-kit/timer-end.wav +0 -0
  81. package/dist/sfx-kit/toggle.wav +0 -0
  82. package/dist/sfx-kit/typing-mobile.wav +0 -0
  83. package/dist/sfx-kit/typing-slow.wav +0 -0
  84. package/dist/sfx-kit/typing.wav +0 -0
  85. package/dist/sfx-kit/whoosh-fast.wav +0 -0
  86. package/dist/sfx-kit/whoosh-heavy.wav +0 -0
  87. package/dist/sfx-kit/whoosh-soft.wav +0 -0
  88. package/dist/sfx-kit/wow.wav +0 -0
  89. package/dist/sfx-kit/wrong.wav +0 -0
  90. package/dist/sfx-kit/zipper.wav +0 -0
  91. package/dist/sfx-kit/zoom-in.wav +0 -0
  92. 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