fmpose3d 0.0.7__py3-none-any.whl
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.
- fmpose3d/__init__.py +38 -0
- fmpose3d/aggregation_methods.py +203 -0
- fmpose3d/animals/__init__.py +13 -0
- fmpose3d/animals/common/__init__.py +12 -0
- fmpose3d/animals/common/animal3d_dataset.py +66 -0
- fmpose3d/animals/common/animal_visualization.py +111 -0
- fmpose3d/animals/common/arber_dataset.py +315 -0
- fmpose3d/animals/common/arguments.py +228 -0
- fmpose3d/animals/common/camera.py +74 -0
- fmpose3d/animals/common/graph_utils.py +69 -0
- fmpose3d/animals/common/lifter3d.py +669 -0
- fmpose3d/animals/common/mocap_dataset.py +40 -0
- fmpose3d/animals/common/skeleton.py +88 -0
- fmpose3d/animals/common/utils.py +547 -0
- fmpose3d/animals/models/__init__.py +20 -0
- fmpose3d/animals/models/graph_frames.py +354 -0
- fmpose3d/animals/models/model_animal3d.py +245 -0
- fmpose3d/common/__init__.py +41 -0
- fmpose3d/common/arguments.py +190 -0
- fmpose3d/common/camera.py +74 -0
- fmpose3d/common/generator.py +254 -0
- fmpose3d/common/graph_utils.py +81 -0
- fmpose3d/common/h36m_dataset.py +466 -0
- fmpose3d/common/load_data_hm36.py +244 -0
- fmpose3d/common/mocap_dataset.py +40 -0
- fmpose3d/common/skeleton.py +88 -0
- fmpose3d/common/utils.py +471 -0
- fmpose3d/lib/__init__.py +0 -0
- fmpose3d/lib/checkpoint/__init__.py +0 -0
- fmpose3d/lib/checkpoint/download_checkpoints.py +129 -0
- fmpose3d/lib/hrnet/__init__.py +0 -0
- fmpose3d/lib/hrnet/experiments/w48_384x288_adam_lr1e-3.yaml +127 -0
- fmpose3d/lib/hrnet/gen_kpts.py +195 -0
- fmpose3d/lib/hrnet/lib/__init__.py +0 -0
- fmpose3d/lib/hrnet/lib/config/__init__.py +18 -0
- fmpose3d/lib/hrnet/lib/config/default.py +169 -0
- fmpose3d/lib/hrnet/lib/config/models.py +67 -0
- fmpose3d/lib/hrnet/lib/models/__init__.py +0 -0
- fmpose3d/lib/hrnet/lib/models/pose_hrnet.py +510 -0
- fmpose3d/lib/hrnet/lib/utils/__init__.py +0 -0
- fmpose3d/lib/hrnet/lib/utils/coco_h36m.py +60 -0
- fmpose3d/lib/hrnet/lib/utils/inference.py +91 -0
- fmpose3d/lib/hrnet/lib/utils/transforms.py +131 -0
- fmpose3d/lib/hrnet/lib/utils/utilitys.py +178 -0
- fmpose3d/lib/preprocess.py +113 -0
- fmpose3d/lib/sort/__init__.py +0 -0
- fmpose3d/lib/sort/sort.py +239 -0
- fmpose3d/lib/yolov3/__init__.py +0 -0
- fmpose3d/lib/yolov3/bbox.py +120 -0
- fmpose3d/lib/yolov3/cfg/tiny-yolo-voc.cfg +134 -0
- fmpose3d/lib/yolov3/cfg/yolo-voc.cfg +258 -0
- fmpose3d/lib/yolov3/cfg/yolo.cfg +258 -0
- fmpose3d/lib/yolov3/cfg/yolov3.cfg +789 -0
- fmpose3d/lib/yolov3/darknet.py +442 -0
- fmpose3d/lib/yolov3/data/coco.names +80 -0
- fmpose3d/lib/yolov3/data/voc.names +20 -0
- fmpose3d/lib/yolov3/human_detector.py +168 -0
- fmpose3d/lib/yolov3/preprocess.py +72 -0
- fmpose3d/lib/yolov3/util.py +235 -0
- fmpose3d/models/__init__.py +21 -0
- fmpose3d/models/graph_frames.py +210 -0
- fmpose3d/models/model_GAMLP.py +279 -0
- fmpose3d-0.0.7.dist-info/METADATA +156 -0
- fmpose3d-0.0.7.dist-info/RECORD +67 -0
- fmpose3d-0.0.7.dist-info/WHEEL +5 -0
- fmpose3d-0.0.7.dist-info/licenses/LICENSE.txt +201 -0
- fmpose3d-0.0.7.dist-info/top_level.txt +1 -0
fmpose3d/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.0.7"
|
|
11
|
+
__author__ = "Ti Wang, Xiaohang Yu, Mackenzie Weygandt Mathis"
|
|
12
|
+
__license__ = "Apache 2.0"
|
|
13
|
+
|
|
14
|
+
# Import key components for easy access
|
|
15
|
+
from .aggregation_methods import (
|
|
16
|
+
average_aggregation,
|
|
17
|
+
aggregation_select_single_best_hypothesis_by_2D_error,
|
|
18
|
+
aggregation_RPEA_joint_level,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Import 2D pose detection utilities
|
|
22
|
+
from .lib.hrnet.gen_kpts import gen_video_kpts
|
|
23
|
+
from .lib.preprocess import h36m_coco_format, revise_kpts
|
|
24
|
+
|
|
25
|
+
# Make commonly used classes/functions available at package level
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Aggregation methods
|
|
28
|
+
"average_aggregation",
|
|
29
|
+
"aggregation_select_single_best_hypothesis_by_2D_error",
|
|
30
|
+
"aggregation_RPEA_joint_level",
|
|
31
|
+
# 2D pose detection
|
|
32
|
+
"gen_video_kpts",
|
|
33
|
+
"h36m_coco_format",
|
|
34
|
+
"revise_kpts",
|
|
35
|
+
# Version
|
|
36
|
+
"__version__",
|
|
37
|
+
]
|
|
38
|
+
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
from fmpose3d.common.utils import project_to_2d
|
|
12
|
+
|
|
13
|
+
def average_aggregation(list_hypothesis):
|
|
14
|
+
return torch.mean(torch.stack(list_hypothesis), dim=0)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def aggregation_select_single_best_hypothesis_by_2D_error(args,
|
|
18
|
+
list_hypothesis, batch_cam, input_2D, gt_3D
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Select per-joint 3D from the hypothesis whose 2D projection yields minimal L2 error.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
list_hypothesis: list of (B,1,J,3) tensors
|
|
25
|
+
batch_cam: (B, 9) or (B, 1, 9) intrinsics [f(2), c(2), k(3), p(2)]
|
|
26
|
+
input_2D: (B, F, J, 2) 2D joints in image coordinates
|
|
27
|
+
gt_3D: (B, F, J, 3) used for shape metadata only
|
|
28
|
+
Returns:
|
|
29
|
+
(B,1,J,3) aggregated 3D pose with joint 0 set to 0
|
|
30
|
+
"""
|
|
31
|
+
if len(list_hypothesis) == 0:
|
|
32
|
+
raise ValueError("list_hypothesis is empty")
|
|
33
|
+
|
|
34
|
+
device = list_hypothesis[0].device
|
|
35
|
+
dtype = list_hypothesis[0].dtype
|
|
36
|
+
|
|
37
|
+
# Shapes
|
|
38
|
+
B = gt_3D.size(0)
|
|
39
|
+
J = gt_3D.size(2)
|
|
40
|
+
F = gt_3D.size(1)
|
|
41
|
+
assert F >= 1, "Expected at least one frame"
|
|
42
|
+
|
|
43
|
+
# Stack hypotheses: (H,B,1,J,3) -> (B,H,J,3)
|
|
44
|
+
stack = torch.stack(list_hypothesis, dim=0) # (H,B,1,J,3)
|
|
45
|
+
X_hbj3 = stack[:, :, 0, :, :] # (H,B,J,3)
|
|
46
|
+
X_bhj3 = X_hbj3.transpose(0, 1).contiguous() # (B,H,J,3)
|
|
47
|
+
H = X_bhj3.size(1)
|
|
48
|
+
|
|
49
|
+
# Prepare camera params: (B,9)
|
|
50
|
+
if batch_cam.dim() == 3 and batch_cam.size(1) == 1:
|
|
51
|
+
cam_b9 = batch_cam[:, 0, :].contiguous()
|
|
52
|
+
elif batch_cam.dim() == 2 and batch_cam.size(1) == 9:
|
|
53
|
+
cam_b9 = batch_cam
|
|
54
|
+
else:
|
|
55
|
+
cam_b9 = batch_cam.view(B, -1)
|
|
56
|
+
assert cam_b9.size(1) == 9, f"camera params should be 9-dim, got {cam_b9.size()}"
|
|
57
|
+
|
|
58
|
+
# Target 2D at the same frame index as 3D selection (args.pad)
|
|
59
|
+
# input_2D: (B,F,J,2) -> (B,J,2)
|
|
60
|
+
target_2d = input_2D[:, getattr(args, "pad", 0)].contiguous() # (B,J,2)
|
|
61
|
+
|
|
62
|
+
# Convert hypotheses from root-relative to absolute camera coordinates using GT root
|
|
63
|
+
# Root at frame args.pad: (B,3)
|
|
64
|
+
gt_root = gt_3D[:, getattr(args, "pad", 0), 0, :].contiguous() # (B,3)
|
|
65
|
+
X_abs = X_bhj3.clone()
|
|
66
|
+
X_abs[:, :, 1:, :] = X_abs[:, :, 1:, :] + gt_root.unsqueeze(1).unsqueeze(1)
|
|
67
|
+
X_abs[:, :, 0, :] = gt_root.unsqueeze(1)
|
|
68
|
+
|
|
69
|
+
# Vectorized projection for all hypotheses in absolute coordinates
|
|
70
|
+
# (B,H,J,3) -> (B*H,J,3)
|
|
71
|
+
X_flat = X_abs.view(B * H, J, 3)
|
|
72
|
+
cam_rep = cam_b9.repeat_interleave(H, dim=0) # (B*H,9)
|
|
73
|
+
|
|
74
|
+
# project_to_2d expects last dim=3 and cam (N,9)
|
|
75
|
+
# Returns normalized coordinates (when crop_uv=0) because camera params are normalized
|
|
76
|
+
proj2d_flat = project_to_2d(X_flat, cam_rep) # (B*H,J,2) normalized coordinates
|
|
77
|
+
proj2d_bhj = proj2d_flat.view(B, H, J, 2)
|
|
78
|
+
|
|
79
|
+
# Per-hypothesis per-joint 2D error (both in normalized coordinates)
|
|
80
|
+
diff = proj2d_bhj - target_2d.unsqueeze(1) # (B,H,J,2)
|
|
81
|
+
dist = torch.norm(diff, dim=-1) # (B,H,J)
|
|
82
|
+
|
|
83
|
+
# Exclude root joint (0) due to undefined depth when using root-relative 3D
|
|
84
|
+
dist[:, :, 0] = float("inf")
|
|
85
|
+
|
|
86
|
+
# Argmin across hypotheses per joint
|
|
87
|
+
best_h = torch.argmin(dist, dim=1) # (B,J)
|
|
88
|
+
|
|
89
|
+
# Gather 3D using advanced indexing (return root-relative coordinates)
|
|
90
|
+
b_idx = torch.arange(B, device=device).unsqueeze(1).expand(B, J) # (B,J)
|
|
91
|
+
j_idx = torch.arange(J, device=device).unsqueeze(0).expand(B, J) # (B,J)
|
|
92
|
+
selected_bj3 = X_bhj3[b_idx, best_h, j_idx, :] # (B,J,3)
|
|
93
|
+
|
|
94
|
+
agg = selected_bj3.unsqueeze(1).to(dtype=dtype)
|
|
95
|
+
agg[:, :, 0, :] = 0
|
|
96
|
+
return agg
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def aggregation_RPEA_joint_level(
|
|
100
|
+
args, list_hypothesis, batch_cam, input_2D, gt_3D, topk=3
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
Select per-joint 3D from the hypothesis whose 2D projection yields minimal L2 error.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
list_hypothesis: list of (B,1,J,3) tensors
|
|
107
|
+
batch_cam: (B, 9) or (B, 1, 9) intrinsics [f(2), c(2), k(3), p(2)]
|
|
108
|
+
input_2D: (B, F, J, 2) 2D joints in image coordinates
|
|
109
|
+
gt_3D: (B, F, J, 3) used for shape metadata only
|
|
110
|
+
Returns:
|
|
111
|
+
(B,1,J,3) aggregated 3D pose with joint 0 set to 0
|
|
112
|
+
"""
|
|
113
|
+
if len(list_hypothesis) == 0:
|
|
114
|
+
raise ValueError("list_hypothesis is empty")
|
|
115
|
+
|
|
116
|
+
device = list_hypothesis[0].device
|
|
117
|
+
dtype = list_hypothesis[0].dtype
|
|
118
|
+
|
|
119
|
+
# Shapes
|
|
120
|
+
B = gt_3D.size(0)
|
|
121
|
+
J = gt_3D.size(2)
|
|
122
|
+
F = gt_3D.size(1)
|
|
123
|
+
assert F >= 1, "Expected at least one frame"
|
|
124
|
+
|
|
125
|
+
# Stack hypotheses: (H,B,1,J,3) -> (B,H,J,3)
|
|
126
|
+
stack = torch.stack(list_hypothesis, dim=0) # (H,B,1,J,3)
|
|
127
|
+
X_hbj3 = stack[:, :, 0, :, :] # (H,B,J,3)
|
|
128
|
+
X_bhj3 = X_hbj3.transpose(0, 1).contiguous() # (B,H,J,3)
|
|
129
|
+
H = X_bhj3.size(1)
|
|
130
|
+
|
|
131
|
+
# Prepare camera params: (B,9)
|
|
132
|
+
if batch_cam.dim() == 3 and batch_cam.size(1) == 1:
|
|
133
|
+
cam_b9 = batch_cam[:, 0, :].contiguous()
|
|
134
|
+
elif batch_cam.dim() == 2 and batch_cam.size(1) == 9:
|
|
135
|
+
cam_b9 = batch_cam
|
|
136
|
+
else:
|
|
137
|
+
cam_b9 = batch_cam.view(B, -1)
|
|
138
|
+
assert cam_b9.size(1) == 9, f"camera params should be 9-dim, got {cam_b9.size()}"
|
|
139
|
+
|
|
140
|
+
# Target 2D at the same frame index as 3D selection (args.pad)
|
|
141
|
+
# input_2D: (B,F,J,2) -> (B,J,2)
|
|
142
|
+
target_2d = input_2D[:, getattr(args, "pad", 0)].contiguous() # (B,J,2)
|
|
143
|
+
|
|
144
|
+
# Convert hypotheses from root-relative to absolute camera coordinates using GT root
|
|
145
|
+
# Root at frame args.pad: (B,3)
|
|
146
|
+
gt_root = gt_3D[:, getattr(args, "pad", 0), 0, :].contiguous() # (B,3)
|
|
147
|
+
X_abs = X_bhj3.clone()
|
|
148
|
+
X_abs[:, :, 1:, :] = X_abs[:, :, 1:, :] + gt_root.unsqueeze(1).unsqueeze(1)
|
|
149
|
+
X_abs[:, :, 0, :] = gt_root.unsqueeze(1)
|
|
150
|
+
|
|
151
|
+
# Vectorized projection for all hypotheses in absolute coordinates
|
|
152
|
+
# (B,H,J,3) -> (B*H,J,3)
|
|
153
|
+
X_flat = X_abs.view(B * H, J, 3)
|
|
154
|
+
cam_rep = cam_b9.repeat_interleave(H, dim=0) # (B*H,9)
|
|
155
|
+
|
|
156
|
+
# project_to_2d expects last dim=3 and cam (N,9)
|
|
157
|
+
proj2d_flat = project_to_2d(X_flat, cam_rep) # (B*H,J,2)
|
|
158
|
+
proj2d_bhj = proj2d_flat.view(B, H, J, 2)
|
|
159
|
+
|
|
160
|
+
# Per-hypothesis per-joint 2D error
|
|
161
|
+
diff = proj2d_bhj - target_2d.unsqueeze(1) # (B,H,J,2)
|
|
162
|
+
dist = torch.norm(diff, dim=-1) # (B,H,J)
|
|
163
|
+
|
|
164
|
+
# For root joint (0), avoid NaNs in softmax by setting equal distances
|
|
165
|
+
# This yields uniform weights at the root (we set root to 0 later anyway)
|
|
166
|
+
dist[:, :, 0] = 0.0
|
|
167
|
+
|
|
168
|
+
# Convert 2D losses to weights using softmax over top-k hypotheses per joint
|
|
169
|
+
tau = float(getattr(args, "weight_softmax_tau", 1.0))
|
|
170
|
+
H = dist.size(1)
|
|
171
|
+
k = int(getattr(args, "topk", None))
|
|
172
|
+
# print("k:", k)
|
|
173
|
+
# k = int(H//2)+1
|
|
174
|
+
k = max(1, min(k, H))
|
|
175
|
+
|
|
176
|
+
# top-k smallest distances along hypothesis dim
|
|
177
|
+
topk_vals, topk_idx = torch.topk(dist, k=k, dim=1, largest=False) # (B,k,J)
|
|
178
|
+
|
|
179
|
+
# Weight calculation method ; weight_method = 'exp'
|
|
180
|
+
temp = args.exp_temp
|
|
181
|
+
max_safe_val = temp * 20
|
|
182
|
+
topk_vals_clipped = torch.clamp(topk_vals, max=max_safe_val)
|
|
183
|
+
exp_vals = torch.exp(-topk_vals_clipped / temp)
|
|
184
|
+
exp_sum = exp_vals.sum(dim=1, keepdim=True)
|
|
185
|
+
topk_weights = exp_vals / torch.clamp(exp_sum, min=1e-10)
|
|
186
|
+
nan_mask = torch.isnan(topk_weights).any(dim=1, keepdim=True)
|
|
187
|
+
uniform_weights = torch.ones_like(topk_weights) / k
|
|
188
|
+
topk_weights = torch.where(
|
|
189
|
+
nan_mask.expand_as(topk_weights), uniform_weights, topk_weights
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# scatter back to full H with zeros elsewhere
|
|
193
|
+
weights = torch.zeros_like(dist) # (B,H,J)
|
|
194
|
+
weights.scatter_(1, topk_idx, topk_weights)
|
|
195
|
+
|
|
196
|
+
# Weighted sum of root-relative 3D hypotheses per joint
|
|
197
|
+
weights_exp = weights.unsqueeze(-1) # (B,H,J,1)
|
|
198
|
+
weighted_bj3 = torch.sum(X_bhj3 * weights_exp, dim=1) # (B,J,3)
|
|
199
|
+
|
|
200
|
+
# Assemble output (B,1,J,3) and enforce root at origin
|
|
201
|
+
agg = weighted_bj3.unsqueeze(1).to(dtype=dtype)
|
|
202
|
+
agg[:, :, 0, :] = 0
|
|
203
|
+
return agg
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"common",
|
|
12
|
+
]
|
|
13
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Shared utilities for animal datasets and models.
|
|
12
|
+
"""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from .camera import normalize_screen_coordinates
|
|
14
|
+
from torch.utils.data import Dataset
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TrainDataset(Dataset):
|
|
18
|
+
def __init__(self, is_train: bool, json_file: str, root_joint: int = 12):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.focal_length = 1000
|
|
21
|
+
self.root_joint = root_joint # Root joint index for making coordinates relative
|
|
22
|
+
|
|
23
|
+
json_file = json_file
|
|
24
|
+
with open(json_file, "r") as f:
|
|
25
|
+
self.data = json.load(f)
|
|
26
|
+
|
|
27
|
+
self.is_train = is_train
|
|
28
|
+
|
|
29
|
+
def __len__(self):
|
|
30
|
+
return len(self.data["data"])
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, item):
|
|
33
|
+
data = self.data["data"][item]
|
|
34
|
+
# safely check for reproj_kp_2d
|
|
35
|
+
reproj = data.get("reproj_kp_2d", None)
|
|
36
|
+
if reproj is not None:
|
|
37
|
+
keypoint_2d = np.array(reproj, dtype=np.float32)
|
|
38
|
+
else:
|
|
39
|
+
keypoint_2d = np.array(data.get("keypoint_2d", []), dtype=np.float32)
|
|
40
|
+
# normalize 2D keypoints
|
|
41
|
+
hight = np.array(data["height"])
|
|
42
|
+
width = np.array(data["width"])
|
|
43
|
+
keypoint_2d = normalize_screen_coordinates(keypoint_2d[..., :2], width, hight)
|
|
44
|
+
|
|
45
|
+
# build 3D keypoints; append ones; fallback to zeros if missing
|
|
46
|
+
if "keypoint_3d" in data and data["keypoint_3d"] is not None:
|
|
47
|
+
kp3d = np.array(data["keypoint_3d"], dtype=np.float32)
|
|
48
|
+
keypoint_3d = np.concatenate((kp3d, np.ones((len(kp3d), 1), dtype=np.float32)), axis=-1)
|
|
49
|
+
else:
|
|
50
|
+
keypoint_3d = np.zeros((len(keypoint_2d), 4), dtype=np.float32)
|
|
51
|
+
|
|
52
|
+
# Make 3D keypoints root-relative
|
|
53
|
+
if keypoint_3d.shape[0] > self.root_joint:
|
|
54
|
+
root_pos = keypoint_3d[self.root_joint : self.root_joint + 1, :].copy() # (1, 4)
|
|
55
|
+
keypoint_3d = keypoint_3d - root_pos # All joints relative to root
|
|
56
|
+
# Now root joint should be exactly [0,0,0,0]
|
|
57
|
+
|
|
58
|
+
bbox = data["bbox"] # [x, y, w, h]
|
|
59
|
+
ori_keypoint_2d = keypoint_2d.copy()
|
|
60
|
+
|
|
61
|
+
item = {
|
|
62
|
+
"keypoints_2d": keypoint_2d, #
|
|
63
|
+
"keypoints_3d": keypoint_3d,
|
|
64
|
+
"img_path": str(data["img_path"]),
|
|
65
|
+
}
|
|
66
|
+
return item
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FMPose3D: monocular 3D Pose Estimation via Flow Matching
|
|
3
|
+
|
|
4
|
+
Official implementation of the paper:
|
|
5
|
+
"FMPose3D: monocular 3D Pose Estimation via Flow Matching"
|
|
6
|
+
by Ti Wang, Xiaohang Yu, and Mackenzie Weygandt Mathis
|
|
7
|
+
Licensed under Apache 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import cv2
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def save_3Dpose_colored(pre_pose, gt_pose, figure_name):
|
|
19
|
+
fig = plt.figure()
|
|
20
|
+
ax1 = fig.add_subplot(211, projection="3d")
|
|
21
|
+
ax1.scatter(
|
|
22
|
+
pre_pose[:, 0], pre_pose[:, 1], pre_pose[:, 2], c=list(range(pre_pose.shape[0])), cmap="jet"
|
|
23
|
+
)
|
|
24
|
+
# plt.axis('off')
|
|
25
|
+
ax2 = fig.add_subplot(212, projection="3d")
|
|
26
|
+
ax2.scatter(
|
|
27
|
+
gt_pose[:, 0], gt_pose[:, 1], gt_pose[:, 2], c=list(range(gt_pose.shape[0])), cmap="jet"
|
|
28
|
+
)
|
|
29
|
+
# plt.axis('off')
|
|
30
|
+
plt.show()
|
|
31
|
+
plt.savefig(figure_name, dpi=400.0)
|
|
32
|
+
plt.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def save_absolute_3Dpose_image(image, pre_pose, gt_pose, vid_3D, skeleton, figure_name):
|
|
36
|
+
fig = plt.figure(figsize=(20, 9))
|
|
37
|
+
ax1 = fig.add_subplot(131, projection="3d")
|
|
38
|
+
ax1.scatter(
|
|
39
|
+
pre_pose[:, 0],
|
|
40
|
+
pre_pose[:, 2],
|
|
41
|
+
-pre_pose[:, 1],
|
|
42
|
+
c=list(range(pre_pose.shape[0])),
|
|
43
|
+
cmap="jet",
|
|
44
|
+
)
|
|
45
|
+
for i in range(skeleton.shape[0]):
|
|
46
|
+
ax1.plot(
|
|
47
|
+
[pre_pose[skeleton[i, 0], 0], pre_pose[skeleton[i, 1], 0]],
|
|
48
|
+
[pre_pose[skeleton[i, 0], 2], pre_pose[skeleton[i, 1], 2]],
|
|
49
|
+
[-pre_pose[skeleton[i, 0], 1], -pre_pose[skeleton[i, 1], 1]],
|
|
50
|
+
c="black",
|
|
51
|
+
)
|
|
52
|
+
ax1.set_xlim([-3, 3])
|
|
53
|
+
ax1.set_zlim([-1.5, 3])
|
|
54
|
+
ax1.set_ylim([12, 20])
|
|
55
|
+
ax1.title.set_text("Prediction")
|
|
56
|
+
|
|
57
|
+
# plt.axis('off')
|
|
58
|
+
ax2 = fig.add_subplot(132, projection="3d")
|
|
59
|
+
visiable_gt = gt_pose[np.where(vid_3D)[0], :]
|
|
60
|
+
ax2.scatter(
|
|
61
|
+
visiable_gt[:, 0],
|
|
62
|
+
visiable_gt[:, 2],
|
|
63
|
+
-visiable_gt[:, 1],
|
|
64
|
+
c=list(np.array(range(gt_pose.shape[0]))[np.where(vid_3D)]),
|
|
65
|
+
cmap="jet",
|
|
66
|
+
)
|
|
67
|
+
for i in range(skeleton.shape[0]):
|
|
68
|
+
if vid_3D[skeleton[i, 0]] > 0 and vid_3D[skeleton[i, 1]] > 0:
|
|
69
|
+
ax2.plot(
|
|
70
|
+
[gt_pose[skeleton[i, 0], 0], gt_pose[skeleton[i, 1], 0]],
|
|
71
|
+
[gt_pose[skeleton[i, 0], 2], gt_pose[skeleton[i, 1], 2]],
|
|
72
|
+
[-gt_pose[skeleton[i, 0], 1], -gt_pose[skeleton[i, 1], 1]],
|
|
73
|
+
c="black",
|
|
74
|
+
)
|
|
75
|
+
ax2.set_xlim([-3, 3])
|
|
76
|
+
ax2.set_zlim([-1.5, 3])
|
|
77
|
+
ax2.set_ylim([12, 20])
|
|
78
|
+
ax2.title.set_text("GT")
|
|
79
|
+
# plt.axis('off')
|
|
80
|
+
ax3 = fig.add_subplot(133)
|
|
81
|
+
ax3.imshow(image)
|
|
82
|
+
ax3.title.set_text("Camera1 view")
|
|
83
|
+
plt.show()
|
|
84
|
+
plt.savefig(figure_name, dpi=200.0)
|
|
85
|
+
plt.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def save_absolute_3Dpose(pre_pose, skeleton, figure_name):
|
|
89
|
+
fig = plt.figure(figsize=(20, 9))
|
|
90
|
+
ax1 = fig.add_subplot(111, projection="3d")
|
|
91
|
+
ax1.scatter(
|
|
92
|
+
pre_pose[:, 0],
|
|
93
|
+
pre_pose[:, 2],
|
|
94
|
+
-pre_pose[:, 1],
|
|
95
|
+
c=list(range(pre_pose.shape[0])),
|
|
96
|
+
cmap="jet",
|
|
97
|
+
)
|
|
98
|
+
for i in range(skeleton.shape[0]):
|
|
99
|
+
ax1.plot(
|
|
100
|
+
[pre_pose[skeleton[i, 0], 0], pre_pose[skeleton[i, 1], 0]],
|
|
101
|
+
[pre_pose[skeleton[i, 0], 2], pre_pose[skeleton[i, 1], 2]],
|
|
102
|
+
[-pre_pose[skeleton[i, 0], 1], -pre_pose[skeleton[i, 1], 1]],
|
|
103
|
+
c="black",
|
|
104
|
+
)
|
|
105
|
+
ax1.set_xlim([-1, 1])
|
|
106
|
+
ax1.set_zlim([-1, 1])
|
|
107
|
+
ax1.set_ylim([-1, 1])
|
|
108
|
+
ax1.title.set_text("gt")
|
|
109
|
+
plt.show()
|
|
110
|
+
plt.savefig(figure_name, dpi=200.0)
|
|
111
|
+
plt.close()
|