Skip to content

Commit dedfde8

Browse files
committed
fix(appearance): allow any subset of holistic points
1 parent 3424394 commit dedfde8

File tree

11 files changed

+51
-20
lines changed

11 files changed

+51
-20
lines changed

README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,15 @@ Pipeline approach:
7373
can be used to synthesize fluent utterances from the anonymized poses.
7474

7575
This pipeline guarantees anonymization from an information theoretic perspective, as the only information that is
76-
preserved is the choice of signs.
76+
preserved is the choice of signs.
77+
78+
## Citing this work
79+
80+
```bibtex
81+
@misc{moryossef2024anonymization,
82+
title={pose-anonymization: Remove identifying information from sign language poses},
83+
author={Moryossef, Amit},
84+
howpublished={\url{https://github.com/sign-language-processing/pose-anonymization}},
85+
year={2024}
86+
}
87+
```

assets/example/anonymized/essen.gif

-5.48 KB
Loading

assets/example/anonymized/essen.pose

0 Bytes
Binary file not shown.

assets/example/anonymized/kinder.gif

8.09 KB
Loading

assets/example/anonymized/kinder.pose

0 Bytes
Binary file not shown.

assets/example/anonymized/kleine.gif

7.78 KB
Loading

assets/example/anonymized/kleine.pose

0 Bytes
Binary file not shown.

assets/example/anonymized/pizza.gif

3.15 KB
Loading

assets/example/anonymized/pizza.pose

0 Bytes
Binary file not shown.

pose_anonymization/appearance.py

+35-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from functools import lru_cache
2+
13
import numpy as np
24
from pose_format import Pose
3-
from pose_format.utils.generic import reduce_holistic, correct_wrists, pose_normalization_info
5+
from pose_format.numpy import NumPyPoseBody
6+
from pose_format.utils.generic import pose_normalization_info
7+
from sign_vq.data.normalize import load_pose_header
48

59

610
def normalize_pose_size(pose: Pose):
@@ -11,17 +15,8 @@ def normalize_pose_size(pose: Pose):
1115
pose.header.dimensions.height = pose.header.dimensions.width = int(new_width * shift * 2)
1216

1317

14-
def reduce_pose(pose: Pose):
15-
# Remove legs, simplify face
16-
pose = reduce_holistic(pose)
17-
# Align hand wrists with body wrists
18-
correct_wrists(pose)
19-
# Adjust pose based on shoulder positions
20-
return pose.normalize(pose_normalization_info(pose.header))
21-
22-
23-
def get_pose_apperance(pose: Pose, include_end_frame=False):
24-
pose = reduce_pose(pose)
18+
def get_pose_appearance(pose: Pose, include_end_frame=False):
19+
pose = pose.normalize(pose_normalization_info(pose.header))
2520

2621
if include_end_frame:
2722
# Assuming the first and last frames are indicative of the signer's appearance
@@ -47,9 +42,12 @@ def change_appearace(pose: Pose, appearance: np.ndarray):
4742
new_pose_data[:, :, start:end] = pose.body.data[:, :, start:end]
4843

4944
# Bring back the wrists
50-
for hand in ['LEFT', 'RIGHT']:
45+
body_component = next(c for c in pose.header.components if c.name == 'POSE_LANDMARKS')
46+
points = [f'{hand}_{point}' for hand in ['LEFT', 'RIGHT'] for point in ['WRIST', 'PINKY', 'INDEX', 'THUMB']]
47+
existing_points = [p for p in points if p in body_component.points]
48+
for point in existing_points:
5149
# pylint: disable=protected-access
52-
wrist_index = pose.header._get_point_index('POSE_LANDMARKS', f'{hand}_WRIST')
50+
wrist_index = pose.header._get_point_index('POSE_LANDMARKS', point)
5351
new_pose_data[:, :, wrist_index] = pose.body.data[:, :, wrist_index]
5452

5553
pose.body.data = new_pose_data
@@ -59,18 +57,36 @@ def change_appearace(pose: Pose, appearance: np.ndarray):
5957
return pose
6058

6159

62-
def remove_appearance(pose: Pose, include_end_frame=False):
63-
pose, appearance = get_pose_apperance(pose, include_end_frame)
60+
@lru_cache(maxsize=1)
61+
def get_mean_appearance():
6462
# pylint: disable=import-outside-toplevel
6563
from sign_vq.data.normalize import load_mean_and_std
6664
mean, _ = load_mean_and_std()
6765

68-
return change_appearace(pose, appearance - mean)
66+
data = mean.reshape((1, 1, -1, 3)) * 1000
67+
confidence = np.ones((1, 1, len(mean)))
68+
body = NumPyPoseBody(fps=1, data=data, confidence=confidence)
69+
pose = Pose(header=load_pose_header(), body=body)
70+
71+
return pose
6972

7073

7174
def transfer_appearance(pose: Pose, appearance_pose: Pose, include_end_frame=False):
72-
pose, appearance = get_pose_apperance(pose, include_end_frame)
73-
_, new_appearance = get_pose_apperance(appearance_pose, include_end_frame)
75+
# Making sure the appearance pose has the same components as the pose, in the same order of points
76+
pose_components = [c.name for c in pose.header.components]
77+
pose_components_points = {c.name: c.points for c in pose.header.components}
78+
appearance_pose = appearance_pose.get_components(pose_components, pose_components_points)
79+
80+
assert pose.header.total_points() == appearance_pose.header.total_points(), \
81+
"Appearance pose missing points"
82+
83+
pose, appearance = get_pose_appearance(pose, include_end_frame)
84+
_, new_appearance = get_pose_appearance(appearance_pose, include_end_frame)
7485

7586
# Switching the pose appearance
7687
return change_appearace(pose, appearance - new_appearance)
88+
89+
90+
def remove_appearance(pose: Pose, include_end_frame=False):
91+
mean_pose = get_mean_appearance()
92+
return transfer_appearance(pose, mean_pose, include_end_frame=include_end_frame)

pose_anonymization/bin.py

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import argparse
44

55
from pose_format import Pose
6+
from pose_format.utils.generic import reduce_holistic, correct_wrists
67

78
from pose_anonymization.appearance import remove_appearance, transfer_appearance
89

@@ -21,6 +22,9 @@ def main():
2122
with open(args.input, "rb") as f:
2223
pose = Pose.read(f.read())
2324

25+
pose = reduce_holistic(pose)
26+
correct_wrists(pose)
27+
2428
if args.appearance:
2529
with open(args.appearance, "rb") as f:
2630
appearance_pose = Pose.read(f.read())

0 commit comments

Comments
 (0)