plice13 commited on
Commit
d07546e
·
verified ·
1 Parent(s): 81d9b9f

Upload 20 files

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. example_keypoint_adder.py +307 -0
  3. examples.json +38 -0
.gitattributes CHANGED
@@ -44,3 +44,4 @@ examples/displayed/fine_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
44
  examples/displayed/help_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
45
  examples/displayed/no_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
46
  examples/displayed/woman_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
 
 
44
  examples/displayed/help_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
45
  examples/displayed/no_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
46
  examples/displayed/woman_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
47
+ examples/displayed/book_keypoints.mp4 filter=lfs diff=lfs merge=lfs -text
example_keypoint_adder.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ example_keypoint_adder.py
3
+ Manages example videos and their precomputed keypoint JSON files and rendered keypoint videos.
4
+ Ensures that each example entry has:
5
+ 1. video_path: Path to the input ASL video.
6
+ 2. keypoints_path: Path to the extracted keypoints JSON.
7
+ 3. keypoints_video_path: Path to the video with keypoints rendered on top.
8
+
9
+ Can be run as a standalone script to batch-process all examples or add new ones.
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import json
15
+ import argparse
16
+ import numpy as np
17
+
18
+ # Ensure UTF-8 stdout
19
+ if hasattr(sys.stdout, 'reconfigure'):
20
+ try:
21
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
22
+ except Exception:
23
+ pass
24
+
25
+ from predict_pose import create_mediapipe_models, predict_pose, load_video_cv
26
+ from visualize_pose import render_keypoints_video
27
+
28
+
29
+ def to_json_serializable(obj):
30
+ """Recursively converts NumPy arrays, numbers, and tensors to JSON-serializable Python types."""
31
+ if isinstance(obj, np.ndarray):
32
+ return obj.tolist()
33
+ if isinstance(obj, (np.float32, np.float64, np.float16)):
34
+ return float(obj)
35
+ if isinstance(obj, (np.int32, np.int64, np.int16, np.int8, np.uint8)):
36
+ return int(obj)
37
+ if isinstance(obj, dict):
38
+ return {str(k): to_json_serializable(v) for k, v in obj.items()}
39
+ if isinstance(obj, (list, tuple)):
40
+ return [to_json_serializable(v) for v in obj]
41
+ return obj
42
+
43
+
44
+ def load_examples_json(json_path="examples.json"):
45
+ """Loads examples configuration from a JSON file."""
46
+ if not os.path.exists(json_path):
47
+ return []
48
+ try:
49
+ with open(json_path, "r", encoding="utf-8") as f:
50
+ data = json.load(f)
51
+ if isinstance(data, list):
52
+ return data
53
+ elif isinstance(data, dict) and "examples" in data:
54
+ return data["examples"]
55
+ return []
56
+ except Exception as e:
57
+ print(f"Error loading {json_path}: {e}")
58
+ return []
59
+
60
+
61
+ def save_examples_json(examples, json_path="examples.json"):
62
+ """Saves examples configuration to a JSON file."""
63
+ try:
64
+ with open(json_path, "w", encoding="utf-8") as f:
65
+ json.dump(examples, f, indent=2, ensure_ascii=False)
66
+ print(f"Saved {len(examples)} examples to {json_path}")
67
+ except Exception as e:
68
+ print(f"Error saving {json_path}: {e}")
69
+
70
+
71
+ def process_example(
72
+ video_path,
73
+ title=None,
74
+ keypoints_path=None,
75
+ keypoints_video_path=None,
76
+ pose_models=None,
77
+ force=False,
78
+ checkpoint_folder="checkpoints/pose/"
79
+ ):
80
+ """
81
+ Checks if keypoints JSON and rendered keypoints video exist for a video.
82
+ If not (or if force=True), extracts keypoints and renders the visualization video.
83
+ Returns the updated example dictionary.
84
+ """
85
+ video_path = os.path.normpath(video_path)
86
+ if not os.path.exists(video_path):
87
+ print(f"[Warning] Video file not found: {video_path}")
88
+ return None
89
+
90
+ video_dir = os.path.dirname(video_path)
91
+ video_stem = os.path.splitext(os.path.basename(video_path))[0]
92
+
93
+ if not title:
94
+ title = video_stem.replace("_", " ").capitalize()
95
+
96
+ if not keypoints_path:
97
+ keypoints_path = os.path.join(video_dir, f"{video_stem}_keypoints.json")
98
+ keypoints_path = os.path.normpath(keypoints_path)
99
+
100
+ if not keypoints_video_path:
101
+ keypoints_video_path = os.path.join(video_dir, f"{video_stem}_keypoints.mp4")
102
+ keypoints_video_path = os.path.normpath(keypoints_video_path)
103
+
104
+ kp_exists = os.path.exists(keypoints_path) and os.path.getsize(keypoints_path) > 0
105
+ vid_exists = os.path.exists(keypoints_video_path) and os.path.getsize(keypoints_video_path) > 0
106
+
107
+ if kp_exists and vid_exists and not force:
108
+ print(f"[OK] Keypoints and video already exist for '{title}' ({video_path})")
109
+ return {
110
+ "title": title,
111
+ "video_path": video_path.replace("\\", "/"),
112
+ "keypoints_path": keypoints_path.replace("\\", "/"),
113
+ "keypoints_video_path": keypoints_video_path.replace("\\", "/"),
114
+ }
115
+
116
+ print(f"\n[Processing] Generating keypoints for '{title}' ({video_path})...")
117
+
118
+ # Load pose models if not yet loaded
119
+ if pose_models is None:
120
+ print(" Initializing MediaPipe & YOLO models...")
121
+ pose_models = create_mediapipe_models(checkpoint_folder)
122
+
123
+ # Load video frames
124
+ video_frames, fps = load_video_cv(video_path)
125
+ if fps is None or fps <= 0 or np.isnan(fps):
126
+ fps = 25.0
127
+ print(f" Loaded {len(video_frames)} frames @ {fps:.1f} FPS")
128
+
129
+ # Predict pose & keypoints (includes 512x512 scaling for WLASL)
130
+ pose_results = predict_pose(video_frames, pose_models)
131
+
132
+ # Attach bounding boxes to keypoints dict for each frame
133
+ kps_list = pose_results.get("keypoints", [])
134
+ bbox_face_list = pose_results.get("bbox_face", [])
135
+ bbox_lh_list = pose_results.get("bbox_left_hand", [])
136
+ bbox_rh_list = pose_results.get("bbox_right_hand", [])
137
+
138
+ for i, kp in enumerate(kps_list):
139
+ if i < len(bbox_face_list):
140
+ kp['bbox_face'] = bbox_face_list[i]
141
+ if i < len(bbox_lh_list):
142
+ kp['bbox_left_hand'] = bbox_lh_list[i]
143
+ if i < len(bbox_rh_list):
144
+ kp['bbox_right_hand'] = bbox_rh_list[i]
145
+
146
+ # Save keypoints to JSON (strip heavy raw image arrays to keep JSON file lightweight ~50KB and ultra-fast)
147
+ heavy_image_keys = {'images', 'cropped_images', 'cropped_left_hand', 'cropped_right_hand', 'cropped_face'}
148
+ clean_pose_results = {k: v for k, v in pose_results.items() if k not in heavy_image_keys}
149
+
150
+ os.makedirs(os.path.dirname(os.path.abspath(keypoints_path)), exist_ok=True)
151
+ serializable_results = to_json_serializable(clean_pose_results)
152
+ with open(keypoints_path, "w", encoding="utf-8") as f:
153
+ json.dump(serializable_results, f)
154
+ print(f" [Saved] Lightweight Keypoints JSON -> {keypoints_path}")
155
+
156
+ # Render keypoint overlay video
157
+ rendered_path = render_keypoints_video(
158
+ video_frames=video_frames,
159
+ keypoints_list=kps_list,
160
+ fps=fps,
161
+ output_path=keypoints_video_path,
162
+ )
163
+ print(f" [Saved] Rendered Keypoints Video -> {rendered_path}")
164
+
165
+ return {
166
+ "title": title,
167
+ "video_path": video_path.replace("\\", "/"),
168
+ "keypoints_path": keypoints_path.replace("\\", "/"),
169
+ "keypoints_video_path": keypoints_video_path.replace("\\", "/"),
170
+ }
171
+
172
+
173
+ def ensure_all_examples(
174
+ json_path="examples.json",
175
+ default_dir="examples/displayed",
176
+ force=False,
177
+ checkpoint_folder="checkpoints/pose/"
178
+ ):
179
+ """
180
+ Ensures all examples in `examples.json` (or found in default_dir)
181
+ have precomputed keypoints JSON and rendered MP4 video.
182
+ """
183
+ existing_examples = load_examples_json(json_path)
184
+
185
+ # If examples.json doesn't exist or is empty, discover .mp4 files in default_dir
186
+ if not existing_examples and os.path.exists(default_dir):
187
+ print(f"Discovering example videos in {default_dir}...")
188
+ for fname in sorted(os.listdir(default_dir)):
189
+ if fname.lower().endswith(".mp4") and not fname.endswith("_keypoints.mp4"):
190
+ vpath = os.path.join(default_dir, fname)
191
+ stem = os.path.splitext(fname)[0]
192
+ existing_examples.append({
193
+ "title": stem.capitalize(),
194
+ "video_path": vpath.replace("\\", "/"),
195
+ "keypoints_path": os.path.join(default_dir, f"{stem}_keypoints.json").replace("\\", "/"),
196
+ "keypoints_video_path": os.path.join(default_dir, f"{stem}_keypoints.mp4").replace("\\", "/"),
197
+ })
198
+
199
+ if not existing_examples:
200
+ print("No examples found to process.")
201
+ return []
202
+
203
+ pose_models = None
204
+ # Check if any model processing is needed before initializing heavy models
205
+ needs_processing = force or any(
206
+ not (
207
+ os.path.exists(ex.get("keypoints_path", "")) and
208
+ os.path.exists(ex.get("keypoints_video_path", "")) and
209
+ os.path.getsize(ex.get("keypoints_path", "")) > 0 and
210
+ os.path.getsize(ex.get("keypoints_video_path", "")) > 0
211
+ )
212
+ for ex in existing_examples
213
+ )
214
+
215
+ if needs_processing:
216
+ print("Initializing pose estimation models for processing...")
217
+ pose_models = create_mediapipe_models(checkpoint_folder)
218
+
219
+ updated_examples = []
220
+ for ex in existing_examples:
221
+ vpath = ex.get("video_path", "")
222
+ title = ex.get("title", "")
223
+ kpath = ex.get("keypoints_path", "")
224
+ kvpath = ex.get("keypoints_video_path", "")
225
+
226
+ res = process_example(
227
+ video_path=vpath,
228
+ title=title,
229
+ keypoints_path=kpath,
230
+ keypoints_video_path=kvpath,
231
+ pose_models=pose_models,
232
+ force=force,
233
+ checkpoint_folder=checkpoint_folder,
234
+ )
235
+ if res:
236
+ updated_examples.append(res)
237
+ else:
238
+ updated_examples.append(ex)
239
+
240
+ save_examples_json(updated_examples, json_path)
241
+ return updated_examples
242
+
243
+
244
+ def main():
245
+ parser = argparse.ArgumentParser(
246
+ description="Ensure all examples have precomputed keypoints JSON and rendered pose videos."
247
+ )
248
+ parser.add_argument(
249
+ "--json",
250
+ default="examples.json",
251
+ help="Path to examples.json (default: examples.json)"
252
+ )
253
+ parser.add_argument(
254
+ "--dir",
255
+ default="examples/displayed",
256
+ help="Directory to scan for example videos if JSON is empty (default: examples/displayed)"
257
+ )
258
+ parser.add_argument(
259
+ "--add",
260
+ default=None,
261
+ help="Add a specific video path to examples.json and generate its keypoints"
262
+ )
263
+ parser.add_argument(
264
+ "--title",
265
+ default=None,
266
+ help="Optional title/label when adding a single video"
267
+ )
268
+ parser.add_argument(
269
+ "--force",
270
+ action="store_true",
271
+ help="Force re-generation of keypoints and videos even if they exist"
272
+ )
273
+ args = parser.parse_args()
274
+
275
+ if args.add:
276
+ print(f"Adding single video: {args.add}")
277
+ examples = load_examples_json(args.json)
278
+ pose_models = create_mediapipe_models("checkpoints/pose/")
279
+ res = process_example(
280
+ video_path=args.add,
281
+ title=args.title,
282
+ pose_models=pose_models,
283
+ force=args.force
284
+ )
285
+ if res:
286
+ # Check if entry with same video_path already exists
287
+ norm_new = os.path.normpath(res["video_path"])
288
+ exists_idx = -1
289
+ for idx, ex in enumerate(examples):
290
+ if os.path.normpath(ex.get("video_path", "")) == norm_new:
291
+ exists_idx = idx
292
+ break
293
+ if exists_idx >= 0:
294
+ examples[exists_idx] = res
295
+ else:
296
+ examples.append(res)
297
+ save_examples_json(examples, args.json)
298
+ else:
299
+ ensure_all_examples(
300
+ json_path=args.json,
301
+ default_dir=args.dir,
302
+ force=args.force
303
+ )
304
+
305
+
306
+ if __name__ == "__main__":
307
+ main()
examples.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "title": "Book",
4
+ "video_path": "examples/displayed/book.mp4",
5
+ "keypoints_path": "examples/displayed/book_keypoints.json",
6
+ "keypoints_video_path": "examples/displayed/book_keypoints.mp4"
7
+ },
8
+ {
9
+ "title": "Deaf",
10
+ "video_path": "examples/displayed/deaf.mp4",
11
+ "keypoints_path": "examples/displayed/deaf_keypoints.json",
12
+ "keypoints_video_path": "examples/displayed/deaf_keypoints.mp4"
13
+ },
14
+ {
15
+ "title": "Fine",
16
+ "video_path": "examples/displayed/fine.mp4",
17
+ "keypoints_path": "examples/displayed/fine_keypoints.json",
18
+ "keypoints_video_path": "examples/displayed/fine_keypoints.mp4"
19
+ },
20
+ {
21
+ "title": "Help",
22
+ "video_path": "examples/displayed/help.mp4",
23
+ "keypoints_path": "examples/displayed/help_keypoints.json",
24
+ "keypoints_video_path": "examples/displayed/help_keypoints.mp4"
25
+ },
26
+ {
27
+ "title": "No",
28
+ "video_path": "examples/displayed/no.mp4",
29
+ "keypoints_path": "examples/displayed/no_keypoints.json",
30
+ "keypoints_video_path": "examples/displayed/no_keypoints.mp4"
31
+ },
32
+ {
33
+ "title": "Woman",
34
+ "video_path": "examples/displayed/woman.mp4",
35
+ "keypoints_path": "examples/displayed/woman_keypoints.json",
36
+ "keypoints_video_path": "examples/displayed/woman_keypoints.mp4"
37
+ }
38
+ ]