ariakang commited on
Commit
9c0b319
·
1 Parent(s): e93c3dd

update model weights and scripts, delete mp4

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to make participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
9
+ level of experience, education, socio-economic status, nationality, personal
10
+ appearance, race, religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies within all project spaces, and it also applies when
49
+ an individual is representing the project or its community in public spaces.
50
+ Examples of representing a project or community include using an official
51
+ project e-mail address, posting via an official social media account, or acting
52
+ as an appointed representative at an online or offline event. Representation of
53
+ a project may be further defined and clarified by project maintainers.
54
+
55
+ This Code of Conduct also applies outside the project spaces when there is a
56
+ reasonable belief that an individual's behavior may have a negative impact on
57
+ the project or its community.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported by contacting the project team at <[email protected]>. All
63
+ complaints will be reviewed and investigated and will result in a response that
64
+ is deemed necessary and appropriate to the circumstances. The project team is
65
+ obligated to maintain confidentiality with regard to the reporter of an incident.
66
+ Further details of specific enforcement policies may be posted separately.
67
+
68
+ Project maintainers who do not follow or enforce the Code of Conduct in good
69
+ faith may face temporary or permanent repercussions as determined by other
70
+ members of the project's leadership.
71
+
72
+ ## Attribution
73
+
74
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
75
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
76
+
77
+ [homepage]: https://www.contributor-covenant.org
78
+
79
+ For answers to common questions about this code of conduct, see
80
+ https://www.contributor-covenant.org/faq
CONTRIBUTING.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to EgoBlur
2
+ We want to make contributing to this project as easy and transparent as
3
+ possible.
4
+
5
+ ## Pull Requests
6
+ We actively welcome your pull requests.
7
+
8
+ 1. Fork the repo and create your branch from `main`.
9
+ 2. If you've added code that should be tested, add tests.
10
+ 3. If you've changed APIs, update the documentation.
11
+ 4. Ensure the test suite passes.
12
+ 5. Make sure your code lints.
13
+ 6. If you haven't already, complete the Contributor License Agreement ("CLA").
14
+
15
+ ## Contributor License Agreement ("CLA")
16
+ In order to accept your pull request, we need you to submit a CLA. You only need
17
+ to do this once to work on any of Meta's open source projects.
18
+
19
+ Complete your CLA here: <https://code.facebook.com/cla>
20
+
21
+ ## Issues
22
+ We use GitHub issues to track public bugs. Please ensure your description is
23
+ clear and has sufficient instructions to be able to reproduce the issue.
24
+
25
+ Meta has a [bounty program](https://www.facebook.com/whitehat/) for the safe
26
+ disclosure of security bugs. In those cases, please go through the process
27
+ outlined on that page and do not file a public issue.
28
+
29
+ ## License
30
+ By contributing to EgoBlur, you agree that your contributions will be licensed
31
+ under the LICENSE file in the root directory of this source tree.
demo_assets/test_image.jpg ADDED
ego_blur_face.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2240fd030194fb8b0021856c51d5e28abc864503beb81f22ee7646c866205889
3
+ size 389722007
ego_blur_lp.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4811e08e11bcc7f35207a4412a27e6db0fe9fed6a42c78a565f8fabb45d1963
3
+ size 389684490
environment.yaml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: ego_blur
2
+ channels:
3
+ - pytorch
4
+ - nvidia
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - python=3.10
9
+ - pytorch=1.12.1
10
+ - torchvision=0.13.1
11
+ - moviepy=1.0.3
12
+ - numpy=1.24.3
13
+ - pip=23.1.1
14
+ - ffmpeg=4.4.1
15
+ - pip:
16
+ - opencv-python-headless==4.7.0.72
script/demo_ego_blur.py ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import argparse
8
+ import os
9
+ from functools import lru_cache
10
+ from typing import List
11
+
12
+ import cv2
13
+ import numpy as np
14
+ import torch
15
+ import torchvision
16
+ from moviepy.editor import ImageSequenceClip
17
+ from moviepy.video.io.VideoFileClip import VideoFileClip
18
+
19
+
20
+ def parse_args():
21
+ parser = argparse.ArgumentParser()
22
+ parser.add_argument(
23
+ "--face_model_path",
24
+ required=False,
25
+ type=str,
26
+ default=None,
27
+ help="Absolute EgoBlur face model file path",
28
+ )
29
+
30
+ parser.add_argument(
31
+ "--face_model_score_threshold",
32
+ required=False,
33
+ type=float,
34
+ default=0.9,
35
+ help="Face model score threshold to filter out low confidence detections",
36
+ )
37
+
38
+ parser.add_argument(
39
+ "--lp_model_path",
40
+ required=False,
41
+ type=str,
42
+ default=None,
43
+ help="Absolute EgoBlur license plate model file path",
44
+ )
45
+
46
+ parser.add_argument(
47
+ "--lp_model_score_threshold",
48
+ required=False,
49
+ type=float,
50
+ default=0.9,
51
+ help="License plate model score threshold to filter out low confidence detections",
52
+ )
53
+
54
+ parser.add_argument(
55
+ "--nms_iou_threshold",
56
+ required=False,
57
+ type=float,
58
+ default=0.3,
59
+ help="NMS iou threshold to filter out low confidence overlapping boxes",
60
+ )
61
+
62
+ parser.add_argument(
63
+ "--scale_factor_detections",
64
+ required=False,
65
+ type=float,
66
+ default=1,
67
+ help="Scale detections by the given factor to allow blurring more area, 1.15 would mean 15% scaling",
68
+ )
69
+
70
+ parser.add_argument(
71
+ "--input_image_path",
72
+ required=False,
73
+ type=str,
74
+ default=None,
75
+ help="Absolute path for the given image on which we want to make detections",
76
+ )
77
+
78
+ parser.add_argument(
79
+ "--output_image_path",
80
+ required=False,
81
+ type=str,
82
+ default=None,
83
+ help="Absolute path where we want to store the visualized image",
84
+ )
85
+
86
+ parser.add_argument(
87
+ "--input_video_path",
88
+ required=False,
89
+ type=str,
90
+ default=None,
91
+ help="Absolute path for the given video on which we want to make detections",
92
+ )
93
+
94
+ parser.add_argument(
95
+ "--output_video_path",
96
+ required=False,
97
+ type=str,
98
+ default=None,
99
+ help="Absolute path where we want to store the visualized video",
100
+ )
101
+
102
+ parser.add_argument(
103
+ "--output_video_fps",
104
+ required=False,
105
+ type=int,
106
+ default=30,
107
+ help="FPS for the output video",
108
+ )
109
+
110
+ return parser.parse_args()
111
+
112
+
113
+ def create_output_directory(file_path: str) -> None:
114
+ """
115
+ parameter file_path: absolute path to the directory where we want to create the output files
116
+ Simple logic to create output directories if they don't exist.
117
+ """
118
+ print(
119
+ f"Directory {os.path.dirname(file_path)} does not exist. Attempting to create it..."
120
+ )
121
+ os.makedirs(os.path.dirname(file_path))
122
+ if not os.path.exists(os.path.dirname(file_path)):
123
+ raise ValueError(
124
+ f"Directory {os.path.dirname(file_path)} didn't exist. Attempt to create also failed. Please provide another path."
125
+ )
126
+
127
+
128
+ def validate_inputs(args: argparse.Namespace) -> argparse.Namespace:
129
+ """
130
+ parameter args: parsed arguments
131
+ Run some basic checks on the input arguments
132
+ """
133
+ # input args value checks
134
+ if not 0.0 <= args.face_model_score_threshold <= 1.0:
135
+ raise ValueError(
136
+ f"Invalid face_model_score_threshold {args.face_model_score_threshold}"
137
+ )
138
+ if not 0.0 <= args.lp_model_score_threshold <= 1.0:
139
+ raise ValueError(
140
+ f"Invalid lp_model_score_threshold {args.lp_model_score_threshold}"
141
+ )
142
+ if not 0.0 <= args.nms_iou_threshold <= 1.0:
143
+ raise ValueError(f"Invalid nms_iou_threshold {args.nms_iou_threshold}")
144
+ if not 0 <= args.scale_factor_detections:
145
+ raise ValueError(
146
+ f"Invalid scale_factor_detections {args.scale_factor_detections}"
147
+ )
148
+ if not 1 <= args.output_video_fps or not (
149
+ isinstance(args.output_video_fps, int) and args.output_video_fps % 1 == 0
150
+ ):
151
+ raise ValueError(
152
+ f"Invalid output_video_fps {args.output_video_fps}, should be a positive integer"
153
+ )
154
+
155
+ # input/output paths checks
156
+ if args.face_model_path is None and args.lp_model_path is None:
157
+ raise ValueError(
158
+ "Please provide either face_model_path or lp_model_path or both"
159
+ )
160
+ if args.input_image_path is None and args.input_video_path is None:
161
+ raise ValueError("Please provide either input_image_path or input_video_path")
162
+ if args.input_image_path is not None and args.output_image_path is None:
163
+ raise ValueError(
164
+ "Please provide output_image_path for the visualized image to save."
165
+ )
166
+ if args.input_video_path is not None and args.output_video_path is None:
167
+ raise ValueError(
168
+ "Please provide output_video_path for the visualized video to save."
169
+ )
170
+ if args.input_image_path is not None and not os.path.exists(args.input_image_path):
171
+ raise ValueError(f"{args.input_image_path} does not exist.")
172
+ if args.input_video_path is not None and not os.path.exists(args.input_video_path):
173
+ raise ValueError(f"{args.input_video_path} does not exist.")
174
+ if args.face_model_path is not None and not os.path.exists(args.face_model_path):
175
+ raise ValueError(f"{args.face_model_path} does not exist.")
176
+ if args.lp_model_path is not None and not os.path.exists(args.lp_model_path):
177
+ raise ValueError(f"{args.lp_model_path} does not exist.")
178
+ if args.output_image_path is not None and not os.path.exists(
179
+ os.path.dirname(args.output_image_path)
180
+ ):
181
+ create_output_directory(args.output_image_path)
182
+ if args.output_video_path is not None and not os.path.exists(
183
+ os.path.dirname(args.output_video_path)
184
+ ):
185
+ create_output_directory(args.output_video_path)
186
+
187
+ # check we have write permissions on output paths
188
+ if args.output_image_path is not None and not os.access(
189
+ os.path.dirname(args.output_image_path), os.W_OK
190
+ ):
191
+ raise ValueError(
192
+ f"You don't have permissions to write to {args.output_image_path}. Please grant adequate permissions, or provide a different output path."
193
+ )
194
+ if args.output_video_path is not None and not os.access(
195
+ os.path.dirname(args.output_video_path), os.W_OK
196
+ ):
197
+ raise ValueError(
198
+ f"You don't have permissions to write to {args.output_video_path}. Please grant adequate permissions, or provide a different output path."
199
+ )
200
+
201
+ return args
202
+
203
+
204
+ @lru_cache
205
+ def get_device() -> str:
206
+ """
207
+ Return the device type
208
+ """
209
+ return (
210
+ "cpu"
211
+ if not torch.cuda.is_available()
212
+ else f"cuda:{torch.cuda.current_device()}"
213
+ )
214
+
215
+
216
+ def read_image(image_path: str) -> np.ndarray:
217
+ """
218
+ parameter image_path: absolute path to an image
219
+ Return an image in BGR format
220
+ """
221
+ bgr_image = cv2.imread(image_path)
222
+ if len(bgr_image.shape) == 2:
223
+ bgr_image = cv2.cvtColor(bgr_image, cv2.COLOR_GRAY2BGR)
224
+ return bgr_image
225
+
226
+
227
+ def write_image(image: np.ndarray, image_path: str) -> None:
228
+ """
229
+ parameter image: np.ndarray in BGR format
230
+ parameter image_path: absolute path where we want to save the visualized image
231
+ """
232
+ cv2.imwrite(image_path, image)
233
+
234
+
235
+ def get_image_tensor(bgr_image: np.ndarray) -> torch.Tensor:
236
+ """
237
+ parameter bgr_image: image on which we want to make detections
238
+
239
+ Return the image tensor
240
+ """
241
+ bgr_image_transposed = np.transpose(bgr_image, (2, 0, 1))
242
+ image_tensor = torch.from_numpy(bgr_image_transposed).to(get_device())
243
+
244
+ return image_tensor
245
+
246
+
247
+ def get_detections(
248
+ detector: torch.jit._script.RecursiveScriptModule,
249
+ image_tensor: torch.Tensor,
250
+ model_score_threshold: float,
251
+ nms_iou_threshold: float,
252
+ ) -> List[List[float]]:
253
+ """
254
+ parameter detector: Torchscript module to perform detections
255
+ parameter image_tensor: image tensor on which we want to make detections
256
+ parameter model_score_threshold: model score threshold to filter out low confidence detection
257
+ parameter nms_iou_threshold: NMS iou threshold to filter out low confidence overlapping boxes
258
+
259
+ Returns the list of detections
260
+ """
261
+ with torch.no_grad():
262
+ detections = detector(image_tensor)
263
+
264
+ boxes, _, scores, _ = detections # returns boxes, labels, scores, dims
265
+
266
+ nms_keep_idx = torchvision.ops.nms(boxes, scores, nms_iou_threshold)
267
+ boxes = boxes[nms_keep_idx]
268
+ scores = scores[nms_keep_idx]
269
+
270
+ boxes = boxes.cpu().numpy()
271
+ scores = scores.cpu().numpy()
272
+
273
+ score_keep_idx = np.where(scores > model_score_threshold)[0]
274
+ boxes = boxes[score_keep_idx]
275
+ return boxes.tolist()
276
+
277
+
278
+ def scale_box(
279
+ box: List[List[float]], max_width: int, max_height: int, scale: float
280
+ ) -> List[List[float]]:
281
+ """
282
+ parameter box: detection box in format (x1, y1, x2, y2)
283
+ parameter scale: scaling factor
284
+
285
+ Returns a scaled bbox as (x1, y1, x2, y2)
286
+ """
287
+ x1, y1, x2, y2 = box[0], box[1], box[2], box[3]
288
+ w = x2 - x1
289
+ h = y2 - y1
290
+
291
+ xc = x1 + w / 2
292
+ yc = y1 + h / 2
293
+
294
+ w = scale * w
295
+ h = scale * h
296
+
297
+ x1 = max(xc - w / 2, 0)
298
+ y1 = max(yc - h / 2, 0)
299
+
300
+ x2 = min(xc + w / 2, max_width)
301
+ y2 = min(yc + h / 2, max_height)
302
+
303
+ return [x1, y1, x2, y2]
304
+
305
+
306
+ def visualize(
307
+ image: np.ndarray,
308
+ detections: List[List[float]],
309
+ scale_factor_detections: float,
310
+ ) -> np.ndarray:
311
+ """
312
+ parameter image: image on which we want to make detections
313
+ parameter detections: list of bounding boxes in format [x1, y1, x2, y2]
314
+ parameter scale_factor_detections: scale detections by the given factor to allow blurring more area, 1.15 would mean 15% scaling
315
+
316
+ Visualize the input image with the detections and save the output image at the given path
317
+ """
318
+ image_fg = image.copy()
319
+ mask_shape = (image.shape[0], image.shape[1], 1)
320
+ mask = np.full(mask_shape, 0, dtype=np.uint8)
321
+
322
+ for box in detections:
323
+ if scale_factor_detections != 1.0:
324
+ box = scale_box(
325
+ box, image.shape[1], image.shape[0], scale_factor_detections
326
+ )
327
+ x1, y1, x2, y2 = int(box[0]), int(box[1]), int(box[2]), int(box[3])
328
+ w = x2 - x1
329
+ h = y2 - y1
330
+
331
+ ksize = (image.shape[0] // 2, image.shape[1] // 2)
332
+ image_fg[y1:y2, x1:x2] = cv2.blur(image_fg[y1:y2, x1:x2], ksize)
333
+ cv2.ellipse(mask, (((x1 + x2) // 2, (y1 + y2) // 2), (w, h), 0), 255, -1)
334
+
335
+ inverse_mask = cv2.bitwise_not(mask)
336
+ image_bg = cv2.bitwise_and(image, image, mask=inverse_mask)
337
+ image_fg = cv2.bitwise_and(image_fg, image_fg, mask=mask)
338
+ image = cv2.add(image_bg, image_fg)
339
+
340
+ return image
341
+
342
+
343
+ def visualize_image(
344
+ input_image_path: str,
345
+ face_detector: torch.jit._script.RecursiveScriptModule,
346
+ lp_detector: torch.jit._script.RecursiveScriptModule,
347
+ face_model_score_threshold: float,
348
+ lp_model_score_threshold: float,
349
+ nms_iou_threshold: float,
350
+ output_image_path: str,
351
+ scale_factor_detections: float,
352
+ ):
353
+ """
354
+ parameter input_image_path: absolute path to the input image
355
+ parameter face_detector: face detector model to perform face detections
356
+ parameter lp_detector: face detector model to perform face detections
357
+ parameter face_model_score_threshold: face model score threshold to filter out low confidence detection
358
+ parameter lp_model_score_threshold: license plate model score threshold to filter out low confidence detection
359
+ parameter nms_iou_threshold: NMS iou threshold
360
+ parameter output_image_path: absolute path where the visualized image will be saved
361
+ parameter scale_factor_detections: scale detections by the given factor to allow blurring more area
362
+
363
+ Perform detections on the input image and save the output image at the given path.
364
+ """
365
+ bgr_image = read_image(input_image_path)
366
+ image = bgr_image.copy()
367
+
368
+ image_tensor = get_image_tensor(bgr_image)
369
+ image_tensor_copy = image_tensor.clone()
370
+ detections = []
371
+ # get face detections
372
+ if face_detector is not None:
373
+ detections.extend(
374
+ get_detections(
375
+ face_detector,
376
+ image_tensor,
377
+ face_model_score_threshold,
378
+ nms_iou_threshold,
379
+ )
380
+ )
381
+
382
+ # get license plate detections
383
+ if lp_detector is not None:
384
+ detections.extend(
385
+ get_detections(
386
+ lp_detector,
387
+ image_tensor_copy,
388
+ lp_model_score_threshold,
389
+ nms_iou_threshold,
390
+ )
391
+ )
392
+ image = visualize(
393
+ image,
394
+ detections,
395
+ scale_factor_detections,
396
+ )
397
+ write_image(image, output_image_path)
398
+
399
+
400
+ def visualize_video(
401
+ input_video_path: str,
402
+ face_detector: torch.jit._script.RecursiveScriptModule,
403
+ lp_detector: torch.jit._script.RecursiveScriptModule,
404
+ face_model_score_threshold: float,
405
+ lp_model_score_threshold: float,
406
+ nms_iou_threshold: float,
407
+ output_video_path: str,
408
+ scale_factor_detections: float,
409
+ output_video_fps: int,
410
+ ):
411
+ """
412
+ parameter input_video_path: absolute path to the input video
413
+ parameter face_detector: face detector model to perform face detections
414
+ parameter lp_detector: face detector model to perform face detections
415
+ parameter face_model_score_threshold: face model score threshold to filter out low confidence detection
416
+ parameter lp_model_score_threshold: license plate model score threshold to filter out low confidence detection
417
+ parameter nms_iou_threshold: NMS iou threshold
418
+ parameter output_video_path: absolute path where the visualized video will be saved
419
+ parameter scale_factor_detections: scale detections by the given factor to allow blurring more area
420
+ parameter output_video_fps: fps of the visualized video
421
+
422
+ Perform detections on the input video and save the output video at the given path.
423
+ """
424
+ visualized_images = []
425
+ video_reader_clip = VideoFileClip(input_video_path)
426
+ for frame in video_reader_clip.iter_frames():
427
+ if len(frame.shape) == 2:
428
+ frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB)
429
+ image = frame.copy()
430
+ bgr_image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
431
+ image_tensor = get_image_tensor(bgr_image)
432
+ image_tensor_copy = image_tensor.clone()
433
+ detections = []
434
+ # get face detections
435
+ if face_detector is not None:
436
+ detections.extend(
437
+ get_detections(
438
+ face_detector,
439
+ image_tensor,
440
+ face_model_score_threshold,
441
+ nms_iou_threshold,
442
+ )
443
+ )
444
+ # get license plate detections
445
+ if lp_detector is not None:
446
+ detections.extend(
447
+ get_detections(
448
+ lp_detector,
449
+ image_tensor_copy,
450
+ lp_model_score_threshold,
451
+ nms_iou_threshold,
452
+ )
453
+ )
454
+ visualized_images.append(
455
+ visualize(
456
+ image,
457
+ detections,
458
+ scale_factor_detections,
459
+ )
460
+ )
461
+
462
+ video_reader_clip.close()
463
+
464
+ if visualized_images:
465
+ video_writer_clip = ImageSequenceClip(visualized_images, fps=output_video_fps)
466
+ video_writer_clip.write_videofile(output_video_path)
467
+ video_writer_clip.close()
468
+
469
+
470
+ if __name__ == "__main__":
471
+ args = validate_inputs(parse_args())
472
+ if args.face_model_path is not None:
473
+ face_detector = torch.jit.load(args.face_model_path, map_location="cpu").to(
474
+ get_device()
475
+ )
476
+ face_detector.eval()
477
+ else:
478
+ face_detector = None
479
+
480
+ if args.lp_model_path is not None:
481
+ lp_detector = torch.jit.load(args.lp_model_path, map_location="cpu").to(
482
+ get_device()
483
+ )
484
+ lp_detector.eval()
485
+ else:
486
+ lp_detector = None
487
+
488
+ if args.input_image_path is not None:
489
+ image = visualize_image(
490
+ args.input_image_path,
491
+ face_detector,
492
+ lp_detector,
493
+ args.face_model_score_threshold,
494
+ args.lp_model_score_threshold,
495
+ args.nms_iou_threshold,
496
+ args.output_image_path,
497
+ args.scale_factor_detections,
498
+ )
499
+
500
+ if args.input_video_path is not None:
501
+ visualize_video(
502
+ args.input_video_path,
503
+ face_detector,
504
+ lp_detector,
505
+ args.face_model_score_threshold,
506
+ args.lp_model_score_threshold,
507
+ args.nms_iou_threshold,
508
+ args.output_video_path,
509
+ args.scale_factor_detections,
510
+ args.output_video_fps,
511
+ )
tools/README.md ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EgoBlur VRS Utilities
2
+
3
+ Meta has developed [EgoBlur](https://www.projectaria.com/tools/egoblur/), a sophisticated face and license plate anonymization system, as part of its ongoing commitment to responsible innovation. Since 2020, EgoBlur has been employed internally within the [Project Aria](https://www.projectaria.com/) program. The VRS file format is designed to record and play back streams of sensor data, including images, audio samples, and other discrete sensors (e.g., IMU, temperature), stored as time-stamped records within per-device streams. This library enables users to process VRS formatted videos using anonymization techniques and generate anonymized output videos in the same efficient VRS format.
4
+
5
+
6
+ # Getting Started
7
+
8
+ To use anonymization models with the VRS files, you need to download model files from our website and install necessary software as described below.
9
+
10
+
11
+ # Instruction to retrieve the ML Models
12
+
13
+ Models can be retrieved from [egoblur download](https://www.projectaria.com/tools/egoblur/) section. We will use the downloadable link and will fetch the models using “wget”.
14
+
15
+ We begin by creating a directory to store models
16
+
17
+ ```
18
+ mkdir ~/models && cd ~/models
19
+ ```
20
+
21
+ Run command to fetch models
22
+ ```
23
+ wget -O face.zip "<downloadable_link_fetched_from_website>"
24
+ unzip face.zip
25
+ ```
26
+
27
+ Repeat the same process for the license plate model.
28
+
29
+
30
+ # Installation (Ubuntu 22.04 with libtorch 2.1 with CUDA toolkit 12.1)
31
+
32
+ This installation guide assumes that the nvidia-driver, Cuda, OpenCV, make and gcc are already installed. If not please follow the instructions at the end of this document to install these utilities/drivers.
33
+
34
+
35
+ ## Download CMake
36
+
37
+ Download CMake binary
38
+
39
+ ```
40
+ mkdir ~/cmake && cd ~/cmake
41
+ wget https://github.com/Kitware/CMake/releases/download/v3.28.0-rc4/cmake-3.28.0-rc4-linux-x86_64.sh
42
+ ```
43
+
44
+ Unpack CMake
45
+
46
+ ```
47
+ chmod 555 cmake-3.28.0-rc4-linux-x86_64.sh && ./cmake-3.28.0-rc4-linux-x86_64.sh --skip-license
48
+ ```
49
+
50
+
51
+ ## Download libtorch
52
+
53
+ We are working with libtorch 2.1 with CUDA toolkit 12.1. This can be downloaded using:
54
+
55
+ ```
56
+ cd ~/ && \
57
+ wget https://download.pytorch.org/libtorch/cu121/libtorch-cxx11-abi-shared-with-deps-2.1.0%2Bcu121.zip && \
58
+ unzip libtorch-cxx11-abi-shared-with-deps-2.1.0+cu121.zip
59
+ ```
60
+
61
+ ## Install VRS dependencies
62
+
63
+ ```
64
+ sudo apt install libfmt-dev libturbojpeg-dev libpng-dev && \
65
+ sudo apt install liblz4-dev libzstd-dev libxxhash-dev && \
66
+ sudo apt install libboost-system-dev libboost-iostreams-dev libboost-filesystem-dev libboost-thread-dev libboost-chrono-dev libboost-date-time-dev
67
+ ```
68
+
69
+ ## Install ninja build(required by projectaria_tools)
70
+
71
+ ```
72
+ sudo apt install ninja-build
73
+ ```
74
+
75
+ ## Download github repositories
76
+
77
+ Make directory to hold repos
78
+
79
+ ```
80
+ mkdir ~/repos && cd ~/repos
81
+ ```
82
+
83
+
84
+ ### Torchvision
85
+
86
+ #### Download torchvision
87
+
88
+ ```
89
+ cd ~/repos && \
90
+ git clone --branch v0.16.0 https://github.com/pytorch/vision/
91
+ ```
92
+
93
+ #### Build torchvision
94
+
95
+ ```
96
+ cd ~/repos && \
97
+ rm -rf vision/build && \
98
+ mkdir vision/build && \
99
+ cd vision/build && \
100
+ ~/cmake/bin/cmake .. -DCMAKE_BUILD_TYPE=Release -DTORCH_CUDA_ARCH_LIST=$TORCH_CUDA_ARCH_LIST -DWITH_CUDA=on -DTorch_DIR=~/libtorch/share/cmake/Torch && \
101
+ make -j && \
102
+ sudo make install
103
+ ```
104
+
105
+ ### EgoBlur
106
+
107
+
108
+ #### Download EgoBlur repo
109
+
110
+ ```
111
+ cd ~/repos && \
112
+ git clone https://github.com/facebookresearch/EgoBlur.git
113
+ ```
114
+
115
+
116
+ #### Build ego_blur_vrs_mutation
117
+
118
+ ```
119
+ cd ~/repos/EgoBlur/tools/vrs_mutation && \
120
+ rm -rf build && \
121
+ mkdir build && \
122
+ cd build && \
123
+ ~/cmake/bin/cmake .. -DTorch_DIR=/home/$USER/libtorch/share/cmake/Torch -DTorchVision_DIR=~/repos/vision/cmake && \
124
+ make -j ego_blur_vrs_mutation
125
+ ```
126
+
127
+ # Usage:
128
+
129
+ ## CLI Arguments
130
+
131
+ ```
132
+ -i,--in
133
+ ```
134
+ use this argument to provide an absolute path for the given input VRS file on which we want to make detections and perform blurring. You MUST provide this value.
135
+
136
+ ```
137
+ -o,--out
138
+ ```
139
+ use this argument to provide an absolute path where we want to store the blurred VRS file. You MUST provide this value.
140
+
141
+ ```
142
+ -f, --faceModelPath
143
+ ```
144
+ use this argument to provide an absolute EgoBlur face model file path. You SHOULD provide either --faceModelPath or --licensePlateModelPath or both. If none is provided code will not blur any data and the same input VRS will be written out without any operation.
145
+
146
+ ```
147
+ --face-model-confidence-threshold
148
+ ```
149
+ use this argument to provide a face model score threshold to filter out low confidence face detections. The values must be between 0.0 and 1.0, if not provided this defaults to 0.1.
150
+
151
+
152
+ ```
153
+ -l, --licensePlateModelPath
154
+ ```
155
+ use this argument to provide an absolute EgoBlur license plate model file path. You SHOULD provide either --faceModelPath or --licensePlateModelPath or both. If none is provided code will not blur any data and the same input VRS will be written out without any operation.
156
+
157
+ ```
158
+ --license-plate-model-confidence-threshold
159
+ ```
160
+ use this argument to provide license plate model score threshold to filter out low confidence license plate detections. The values must be between 0.0 and 1.0, if not provided this defaults to 0.1.
161
+
162
+ ```
163
+ --scale-factor-detections
164
+ ```
165
+ use this argument to provide scale detections by the given factor to allow blurring more area. The values can only be positive real numbers eg: 0.9(values &lt; 1) would mean scaling DOWN the predicted blurred region by 10%, whereas as 1.1(values > 1) would mean scaling UP the predicted blurred region by 10%. If not provided this defaults to 1.15.
166
+
167
+ ```
168
+ --nms-threshold
169
+ ```
170
+ use this argument to provide NMS iou threshold to filter out low confidence overlapping boxes. The values must be between 0.0 and 1.0, if not provided this defaults to 0.3.
171
+
172
+ ```
173
+ --use-gpu
174
+ ```
175
+ flag to indicate whether you want to use GPU. It's highly recommended that you use GPU
176
+
177
+ A sample command using mandatory args only:
178
+ ```
179
+ cd ~/repos/EgoBlur/tools/vrs_mutation/build && \
180
+ ./ego_blur_vrs_mutation --in your_vrs_file --out your_output_vrs_file -f ~/models/ego_blur_face.jit -l ~/models/ ego_blur_lp.jit --use-gpu
181
+ ```
182
+
183
+ A sample command using all args:
184
+ ```
185
+ cd ~/repos/EgoBlur/tools/vrs_mutation/build && \
186
+ ./ego_blur_vrs_mutation --in your_vrs_file --out your_output_vrs_file -f ~/models/ego_blur_face.jit --face-model-confidence-threshold 0.75 -l ~/models/ego_blur_lp.jit --license-plate-model-confidence-threshold 0.99 --scale-factor-detections 1.15 --nms-threshold 0.3 --use-gpu
187
+ ```
188
+
189
+ # Additional Installation Instructions
190
+
191
+ In this section we will cover additional installation instructions.
192
+
193
+
194
+ ## Check OS version
195
+
196
+ ```
197
+ hostnamectl
198
+ ```
199
+
200
+ This should give you the OS version which will be helpful in selecting the drivers and CUDA toolkit in the steps below.
201
+
202
+
203
+ ## Install make
204
+ ```
205
+ sudo apt install make
206
+ ```
207
+
208
+ ## Install gcc
209
+ ```
210
+ sudo apt install gcc
211
+ ```
212
+
213
+ ## Install OpenCV
214
+ ```
215
+ sudo apt install libopencv-dev
216
+ ```
217
+
218
+ ## Install utility unzip
219
+ ```
220
+ sudo apt install unzip
221
+ ```
222
+
223
+ ## Check if you have GPU
224
+
225
+ This should provide the type of the GPU on the machine.
226
+
227
+ ```
228
+ lspci | grep nvidia -i
229
+ ```
230
+
231
+ ## Decide GPU Driver
232
+
233
+ Based on the gpu type and your OS type obtained previously, go to the website to search for an appropriate driver: [https://www.nvidia.com/Download/index.aspx?lang=en-us](https://www.nvidia.com/Download/index.aspx?lang=en-us)
234
+
235
+
236
+ ## Update package manager
237
+ ```
238
+ sudo apt update
239
+ ```
240
+
241
+ ## Install GPU drivers
242
+ ```
243
+ sudo apt install ubuntu-drivers-common
244
+ ```
245
+
246
+ Confirm that package manager identifies your device(GPU) and recommends appropriate drivers
247
+ ```
248
+ sudo ubuntu-drivers devices
249
+ ```
250
+
251
+ Finally install the driver
252
+ ```
253
+ sudo apt install nvidia-driver-535
254
+ ```
255
+
256
+ Reboot the system
257
+ ```
258
+ sudo reboot
259
+ ```
260
+
261
+ Check if driver installation went correctly by running nvidia-smi
262
+ ```
263
+ nvidia-smi
264
+ ```
265
+
266
+ ## Install CUDA Toolkit
267
+
268
+ Go to pytorch website and find the specific cuda toolkit version you want to install(this should match with your libtorch supported version). Since we are using libtorch version v2.1.0: [Previous PyTorch Versions | PyTorch](https://fburl.com/himtgbgc) we will install libtorch v2.1.0: [Previous PyTorch Versions | PyTorch](https://fburl.com/z2m3p81z) with cuda toolkit 12.1.
269
+
270
+
271
+ ### CUDA
272
+
273
+ Since we will be using libtorch v2.1.0 with cuda toolkit 12.1, visit website [https://developer.nvidia.com/cuda-toolkit-archive](https://developer.nvidia.com/cuda-toolkit-archive) to get the installation instructions
274
+
275
+ Get cuda run file
276
+
277
+ ```
278
+ wget https://developer.download.nvidia.com/compute/cuda/12.1.1/local_installers/cuda_12.1.1_530.30.02_linux.run
279
+ ```
280
+
281
+ Execute CUDA runfile
282
+
283
+ ```
284
+ sudo sh cuda_12.1.1_530.30.02_linux.run
285
+ ```
286
+
287
+ Since we have already installed driver we don't need to reinstall it, we can simply continue with cuda toolkit installation
288
+
289
+ Export paths
290
+
291
+ ```
292
+ vi ~/.bashrc
293
+ ```
294
+
295
+ And add these lines:
296
+
297
+ ```
298
+ export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}
299
+ export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64\${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
300
+ ```
301
+
302
+ Run
303
+ ```
304
+ source ~/.bashrc
305
+ ```
306
+
307
+
308
+ To verify cuda toolkit installation run:
309
+ ```
310
+ nvcc --version
311
+ ```
tools/vrs_mutation/CMakeLists.txt ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ cmake_minimum_required(VERSION 3.12)
16
+ project(ego_blur
17
+ LANGUAGES CXX)
18
+
19
+ set(CMAKE_CXX_STANDARD 17)
20
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
21
+
22
+ set(PROJECTARIA_TOOLS_BUILD_TOOLS ON CACHE BOOL "")
23
+
24
+
25
+ include(FetchContent)
26
+ FetchContent_Declare(
27
+ projectaria_tools
28
+ GIT_REPOSITORY https://github.com/facebookresearch/projectaria_tools.git
29
+ GIT_TAG origin/main
30
+ SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/projectaria_tools-src/projectaria_tools"
31
+ )
32
+ FetchContent_MakeAvailable(projectaria_tools)
33
+ include_directories("${CMAKE_BINARY_DIR}/_deps/projectaria_tools-src")
34
+
35
+
36
+
37
+ find_package(TorchVision REQUIRED)
38
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
39
+
40
+ find_package(OpenCV REQUIRED)
41
+
42
+
43
+ add_executable( ego_blur_vrs_mutation
44
+ EgoBlurImageMutator.h
45
+ main.cpp
46
+ )
47
+ target_link_libraries(ego_blur_vrs_mutation
48
+ vrs_image_mutation_interface
49
+ TorchVision::TorchVision
50
+ CLI11::CLI11
51
+ "${TORCH_LIBRARIES}"
52
+ ${OpenCV_LIBS})
tools/vrs_mutation/EgoBlurImageMutator.h ADDED
@@ -0,0 +1,521 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #pragma once
18
+
19
+ #include <c10/core/ScalarType.h>
20
+ #include <c10/util/Exception.h>
21
+ #include <torch/types.h>
22
+ #include <vrs/RecordFormat.h> // @manual
23
+
24
+ #include <projectaria_tools/tools/samples/vrs_mutation/ImageMutationFilterCopier.h> // @manual
25
+
26
+ #include <torch/script.h>
27
+ #include <torch/serialize.h> // @manual
28
+ #include <torch/torch.h>
29
+ #include <cstdint>
30
+ #include <iostream>
31
+ #include <memory>
32
+ #include <string>
33
+
34
+ #include <c10/cuda/CUDACachingAllocator.h>
35
+ #include <opencv2/core.hpp>
36
+ #include <opencv2/imgproc.hpp>
37
+
38
+ namespace EgoBlur {
39
+
40
+ struct EgoBlurImageMutator : public vrs::utils::UserDefinedImageMutator {
41
+ // Inherit from UserDefinedImageMutator as defined in projectaria_tools to
42
+ // blur detected faces/license plates. This class implements the logic to run
43
+ // model inference and performs blurring on VRS frame by frame and saves the
44
+ // output as a VRS file.
45
+ std::shared_ptr<torch::jit::script::Module> faceModel_;
46
+ std::shared_ptr<torch::jit::script::Module> licensePlateModel_;
47
+ float faceModelConfidenceThreshold_;
48
+ float licensePlateModelConfidenceThreshold_;
49
+ float scaleFactorDetections_;
50
+ float nmsThreshold_;
51
+ bool useGPU_;
52
+ bool clockwise90Rotation_;
53
+ std::unordered_map<std::string, std::unordered_map<std::string, int>> stats_;
54
+ torch::Device device_ = torch::kCPU;
55
+
56
+ explicit EgoBlurImageMutator(
57
+ const std::string& faceModelPath = "",
58
+ const float faceModelConfidenceThreshold = 0.1,
59
+ const std::string& licensePlateModelPath = "",
60
+ const float licensePlateModelConfidenceThreshold = 0.1,
61
+ const float scaleFactorDetections = 1.15,
62
+ const float nmsThreshold = 0.3,
63
+ const bool useGPU = true,
64
+ const bool clockwise90Rotation = true)
65
+ : faceModelConfidenceThreshold_(faceModelConfidenceThreshold),
66
+ licensePlateModelConfidenceThreshold_(
67
+ licensePlateModelConfidenceThreshold),
68
+ scaleFactorDetections_(scaleFactorDetections),
69
+ nmsThreshold_(nmsThreshold),
70
+ useGPU_(useGPU),
71
+ clockwise90Rotation_(clockwise90Rotation) {
72
+ device_ = getDevice();
73
+ std::cout << "attempting to load ego blur face model: " << faceModelPath
74
+ << std::endl;
75
+
76
+ if (!faceModelPath.empty()) {
77
+ faceModel_ = loadModel(faceModelPath);
78
+ }
79
+
80
+ std::cout << "attempting to load ego blur license plate model: "
81
+ << licensePlateModelPath << std::endl;
82
+
83
+ if (!licensePlateModelPath.empty()) {
84
+ licensePlateModel_ = loadModel(licensePlateModelPath);
85
+ }
86
+ }
87
+
88
+ std::shared_ptr<torch::jit::script::Module> loadModel(
89
+ const std::string& path) {
90
+ std::shared_ptr<torch::jit::script::Module> model;
91
+ try {
92
+ model = std::make_shared<torch::jit::script::Module>();
93
+ // patternlint-disable-next-line no-torch-low-level-api
94
+ *model = torch::jit::load(path);
95
+ std::cout << "Loaded model: " << path << std::endl;
96
+ model->to(device_);
97
+ model->eval();
98
+ } catch (const c10::Error&) {
99
+ std::cout << "Failed to load model: " << path << std::endl;
100
+ throw;
101
+ }
102
+ return model;
103
+ }
104
+
105
+ at::DeviceType getDevice() const {
106
+ if (useGPU_ && torch::cuda::is_available()) {
107
+ // using GPU
108
+ return torch::kCUDA;
109
+ } else {
110
+ // using CPU
111
+ return torch::kCPU;
112
+ }
113
+ }
114
+
115
+ torch::Tensor filterDetections(
116
+ c10::intrusive_ptr<c10::ivalue::Tuple> detections,
117
+ float scoreThreshold) const {
118
+ // filter prediction based of confidence scores, we have scores at index 2
119
+ torch::Tensor scoreThresholdMask =
120
+ torch::gt(
121
+ detections->elements().at(2).toTensor(),
122
+ torch::tensor(scoreThreshold))
123
+ .detach();
124
+ // we have boxes at index 0
125
+ torch::Tensor filteredBoundingBoxes = detections->elements()
126
+ .at(0)
127
+ .toTensor()
128
+ .index({scoreThresholdMask})
129
+ .detach();
130
+ torch::Tensor filteredBoundingBoxesScores = detections->elements()
131
+ .at(2)
132
+ .toTensor()
133
+ .index({scoreThresholdMask})
134
+ .detach();
135
+
136
+ // filter out overlapping detections by performing NMS
137
+ torch::Tensor filteredBoundingBoxesPostNMS =
138
+ performNMS(
139
+ filteredBoundingBoxes, filteredBoundingBoxesScores, nmsThreshold_)
140
+ .detach();
141
+ scoreThresholdMask.reset();
142
+ filteredBoundingBoxes.reset();
143
+ filteredBoundingBoxesScores.reset();
144
+ return filteredBoundingBoxesPostNMS;
145
+ }
146
+
147
+ // Define a custom NMS function
148
+ torch::Tensor performNMS(
149
+ const torch::Tensor& boxes,
150
+ const torch::Tensor& scores,
151
+ float overlapThreshold) const {
152
+ // Convert tensors to CPU
153
+ torch::Tensor boxesCPU = boxes.to(torch::kCPU).detach();
154
+ torch::Tensor scoresCPU = scores.to(torch::kCPU).detach();
155
+
156
+ // Get the number of bounding boxes
157
+ int numBoxes = boxesCPU.size(0);
158
+
159
+ // Extract bounding box coordinates
160
+ auto boxesAccessor = boxesCPU.accessor<float, 2>();
161
+ auto scoresAccessor = scoresCPU.accessor<float, 1>();
162
+
163
+ std::vector<bool> picked(numBoxes, false);
164
+
165
+ for (int i = 0; i < numBoxes; ++i) {
166
+ if (!picked[i]) {
167
+ for (int j = i + 1; j < numBoxes; ++j) {
168
+ if (!picked[j]) {
169
+ float x1 = std::max(boxesAccessor[i][0], boxesAccessor[j][0]);
170
+ float y1 = std::max(boxesAccessor[i][1], boxesAccessor[j][1]);
171
+ float x2 = std::min(boxesAccessor[i][2], boxesAccessor[j][2]);
172
+ float y2 = std::min(boxesAccessor[i][3], boxesAccessor[j][3]);
173
+
174
+ float intersection =
175
+ std::max(0.0f, x2 - x1) * std::max(0.0f, y2 - y1);
176
+ float iou = intersection /
177
+ ((boxesAccessor[i][2] - boxesAccessor[i][0]) *
178
+ (boxesAccessor[i][3] - boxesAccessor[i][1]) +
179
+ (boxesAccessor[j][2] - boxesAccessor[j][0]) *
180
+ (boxesAccessor[j][3] - boxesAccessor[j][1]) -
181
+ intersection);
182
+
183
+ if (iou > overlapThreshold) {
184
+ if (scoresAccessor[i] > scoresAccessor[j]) {
185
+ picked[j] = true;
186
+ } else {
187
+ picked[i] = true;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ std::vector<int> selectedIndices;
196
+ for (int i = 0; i < numBoxes; ++i) {
197
+ if (!picked[i]) {
198
+ selectedIndices.push_back(i);
199
+ }
200
+ }
201
+
202
+ torch::Tensor filteredBoundingBoxes =
203
+ torch::index_select(
204
+ boxes.to(torch::kCPU),
205
+ 0,
206
+ torch::from_blob(
207
+ selectedIndices.data(),
208
+ {static_cast<long>(selectedIndices.size())},
209
+ torch::kInt))
210
+ .detach();
211
+
212
+ boxesCPU.reset();
213
+ scoresCPU.reset();
214
+ return filteredBoundingBoxes;
215
+ }
216
+
217
+ static std::vector<float> scaleBox(
218
+ const std::vector<float>& box,
219
+ int maxWidth,
220
+ int maxHeight,
221
+ float scale) {
222
+ // Extract x1, y1, x2, and y2 from the input box.
223
+ float x1 = box[0];
224
+ float y1 = box[1];
225
+ float x2 = box[2];
226
+ float y2 = box[3];
227
+ float w = x2 - x1;
228
+ float h = y2 - y1;
229
+
230
+ // Calculate the center point of the box.
231
+ float xc = x1 + (w / 2);
232
+ float yc = y1 + (h / 2);
233
+ // Scale the width and height of the box.
234
+ w = scale * w;
235
+ h = scale * h;
236
+ // Update the coordinates of the box to fit within the maximum dimensions.
237
+ x1 = std::max(xc - (w / 2), 0.0f);
238
+ y1 = std::max(yc - (h / 2), 0.0f);
239
+ x2 = std::min(xc + (w / 2), static_cast<float>(maxWidth));
240
+ y2 = std::min(yc + (h / 2), static_cast<float>(maxHeight));
241
+ // Return the scaled box as a vector of vectors.
242
+ return {x1, y1, x2, y2};
243
+ }
244
+
245
+ cv::Mat blurImage(
246
+ const cv::Mat& image,
247
+ const std::vector<torch::Tensor>& detections,
248
+ float scale) {
249
+ // Use the mask to combine the original and blurred images
250
+ cv::Mat response = image.clone();
251
+ cv::Mat mask;
252
+ if (image.channels() == 3) {
253
+ mask = cv::Mat::zeros(image.size(), CV_8UC3);
254
+ } else {
255
+ mask = cv::Mat::zeros(image.size(), CV_8UC1);
256
+ }
257
+ for (const auto& detection : detections) {
258
+ for (auto& box : detection.unbind()) {
259
+ std::vector<float> boxVector(
260
+ box.data_ptr<float>(), box.data_ptr<float>() + box.numel());
261
+ if (scale != 1.0f) {
262
+ boxVector = scaleBox(boxVector, image.cols, image.rows, scale);
263
+ }
264
+ int x1 = static_cast<int>(boxVector[0]);
265
+ int y1 = static_cast<int>(boxVector[1]);
266
+ int x2 = static_cast<int>(boxVector[2]);
267
+ int y2 = static_cast<int>(boxVector[3]);
268
+ int w = x2 - x1;
269
+ int h = y2 - y1;
270
+
271
+ // Blur region inside ellipse
272
+ cv::Scalar color;
273
+ if (image.channels() == 3) {
274
+ color = cv::Scalar(255, 255, 255);
275
+ } else {
276
+ color = cv::Scalar(255);
277
+ }
278
+
279
+ cv::ellipse(
280
+ mask,
281
+ cv::Point((x1 + x2) / 2, (y1 + y2) / 2),
282
+ cv::Size(w / 2, h / 2),
283
+ 0,
284
+ 0,
285
+ 360,
286
+ color,
287
+ -1);
288
+ // Apply blur effect to the whole image
289
+ cv::Size ksize = cv::Size(image.rows / 8, image.cols / 8);
290
+ cv::Mat blurredImage;
291
+ cv::blur(image(cv::Rect({x1, y1, w, h})), blurredImage, ksize);
292
+ blurredImage.copyTo(
293
+ response(cv::Rect({x1, y1, w, h})), mask(cv::Rect({x1, y1, w, h})));
294
+ blurredImage.release();
295
+ }
296
+ }
297
+ mask.release();
298
+ return response;
299
+ }
300
+
301
+ cv::Mat detectAndBlur(
302
+ vrs::utils::PixelFrame* frame,
303
+ const std::string& frameId) {
304
+ // Convert PixelFrame to cv::Mat
305
+ const int width = frame->getWidth();
306
+ const int height = frame->getHeight();
307
+ // Deduce type of the Array (can be either GRAY or RGB)
308
+ const int channels =
309
+ frame->getPixelFormat() == vrs::PixelFormat::RGB8 ? 3 : 1;
310
+
311
+ cv::Mat img = cv::Mat(
312
+ height,
313
+ width,
314
+ CV_8UC(channels),
315
+ static_cast<void*>(frame->getBuffer().data()))
316
+ .clone();
317
+
318
+ // Rotate image if needed
319
+ if (clockwise90Rotation_) {
320
+ cv::rotate(img, img, cv::ROTATE_90_CLOCKWISE);
321
+ }
322
+
323
+ torch::NoGradGuard no_grad;
324
+
325
+ // Convert image to tensor
326
+ torch::Tensor imgTensor = torch::from_blob(
327
+ (void*)frame->rdata(), {height, width, channels}, torch::kUInt8);
328
+ // torch::Tensor imgTensor = getImageTensor(frame);
329
+ torch::Tensor imgTensorFloat = imgTensor.to(torch::kFloat);
330
+
331
+ // If you need to move to GPU
332
+ torch::Tensor imgTensorFloatOnDevice = imgTensorFloat.to(device_);
333
+
334
+ torch::Tensor imgTensorFloatOnDevicePostRotation;
335
+ // rotate the image clockwise
336
+ if (clockwise90Rotation_) {
337
+ imgTensorFloatOnDevicePostRotation =
338
+ torch::rot90(imgTensorFloatOnDevice, -1);
339
+ } else {
340
+ imgTensorFloatOnDevicePostRotation = imgTensorFloatOnDevice;
341
+ }
342
+ // convert from HWC to CHW
343
+ torch::Tensor imgTensorFloatOnDevicePostRotationCHW =
344
+ imgTensorFloatOnDevicePostRotation.permute({2, 0, 1});
345
+
346
+ // Create input tensor for model inference
347
+ std::vector<torch::jit::IValue> inputs = {
348
+ imgTensorFloatOnDevicePostRotationCHW};
349
+
350
+ // Create output vector to store results
351
+ std::vector<torch::Tensor> boundingBoxes;
352
+
353
+ cv::Mat finalImage;
354
+
355
+ torch::Tensor faceBoundingBoxes;
356
+ torch::Tensor licensePlateBoundingBoxes;
357
+
358
+ // Begin making detections
359
+ // use face model to find faces
360
+ if (faceModel_) {
361
+ c10::intrusive_ptr<c10::ivalue::Tuple> faceDetections =
362
+ faceModel_->forward(inputs)
363
+ .toTuple(); // returns boxes, labels, scores, dims
364
+ faceBoundingBoxes =
365
+ filterDetections(faceDetections, faceModelConfidenceThreshold_);
366
+ int totalFaceDetectionsForCurrentFrame = faceBoundingBoxes.sizes()[0];
367
+ stats_[frameId]["faces"] += totalFaceDetectionsForCurrentFrame;
368
+ if (faceBoundingBoxes.sizes()[0] > 0) {
369
+ boundingBoxes.push_back(faceBoundingBoxes);
370
+ }
371
+ faceDetections.reset();
372
+ }
373
+
374
+ // use LP model to find LP
375
+ if (licensePlateModel_) {
376
+ c10::intrusive_ptr<c10::ivalue::Tuple> licensePlateDetections =
377
+ licensePlateModel_->forward(inputs)
378
+ .toTuple(); // returns boxes, labels, scores, dims
379
+ licensePlateBoundingBoxes = filterDetections(
380
+ licensePlateDetections, licensePlateModelConfidenceThreshold_);
381
+ int totaLlicensePlateDetectionsForCurrentFrame =
382
+ licensePlateBoundingBoxes.sizes()[0];
383
+ stats_[frameId]["licensePlate"] +=
384
+ totaLlicensePlateDetectionsForCurrentFrame;
385
+ if (licensePlateBoundingBoxes.sizes()[0] > 0) {
386
+ boundingBoxes.push_back(licensePlateBoundingBoxes);
387
+ }
388
+ licensePlateDetections.reset();
389
+ }
390
+
391
+ if (!boundingBoxes.empty()) {
392
+ // Blur the image
393
+ finalImage = blurImage(img, boundingBoxes, scaleFactorDetections_);
394
+
395
+ // Rotate image back if needed
396
+ if (clockwise90Rotation_) {
397
+ cv::rotate(finalImage, finalImage, cv::ROTATE_90_COUNTERCLOCKWISE);
398
+ }
399
+ // Force Cleanup
400
+ boundingBoxes.clear();
401
+ }
402
+ // Force Cleanup
403
+ inputs.clear();
404
+ imgTensor.reset();
405
+ imgTensorFloat.reset();
406
+ imgTensorFloatOnDevice.reset();
407
+ imgTensorFloatOnDevicePostRotation.reset();
408
+ imgTensorFloatOnDevicePostRotationCHW.reset();
409
+ faceBoundingBoxes.reset();
410
+ licensePlateBoundingBoxes.reset();
411
+ img.release();
412
+ return finalImage;
413
+ }
414
+
415
+ bool operator()(
416
+ double timestamp,
417
+ const vrs::StreamId& streamId,
418
+ vrs::utils::PixelFrame* frame) override {
419
+ // Handle the case where we have no image data
420
+ if (!frame) {
421
+ return false;
422
+ }
423
+
424
+ cv::Mat blurredImage;
425
+ // If not Eye Tracking image
426
+ if (streamId.getNumericName().find("214") != std::string::npos ||
427
+ streamId.getNumericName().find("1201") != std::string::npos) {
428
+ // Get predictions and blur
429
+ std::string frameId =
430
+ streamId.getNumericName() + "_" + std::to_string(timestamp);
431
+ stats_[frameId]["faces"] = 0;
432
+ stats_[frameId]["licensePlate"] = 0;
433
+ blurredImage = detectAndBlur(frame, frameId);
434
+ }
435
+ // Copy back results into the frame
436
+ if (!blurredImage.empty()) {
437
+ // RGB
438
+ if (streamId.getNumericName().find("214") != std::string::npos) {
439
+ std::memcpy(
440
+ frame->wdata(),
441
+ blurredImage.data,
442
+ frame->getWidth() * frame->getStride());
443
+ }
444
+ // Gray
445
+ else if (streamId.getNumericName().find("1201") != std::string::npos) {
446
+ std::memcpy(
447
+ frame->wdata(),
448
+ blurredImage.data,
449
+ frame->getWidth() * frame->getHeight());
450
+ }
451
+ }
452
+ blurredImage.release();
453
+ c10::cuda::CUDACachingAllocator::emptyCache();
454
+ return true;
455
+ }
456
+
457
+ std::string logStatistics() const {
458
+ std::string statsString;
459
+ int totalFrames = 0;
460
+ int totalRGBFramesWithFaces = 0;
461
+ int totalRGBFaces = 0;
462
+ int totalSLAMFramesWithFaces = 0;
463
+ int totalSLAMFaces = 0;
464
+ int totalRGBFramesWithLicensePlate = 0;
465
+ int totalRGBLicensePlate = 0;
466
+ int totalSLAMFramesWithLicensePlate = 0;
467
+ int totalSLAMLicensePlate = 0;
468
+
469
+ for (const auto& outer : stats_) {
470
+ const std::string& frameId = outer.first;
471
+ const std::unordered_map<std::string, int>& categoryBoxCountMapping =
472
+ outer.second;
473
+
474
+ // Do something with the outer key and inner map
475
+ for (const auto& innerPair : categoryBoxCountMapping) {
476
+ const std::string& category = innerPair.first;
477
+ int boxCount = innerPair.second;
478
+
479
+ if (boxCount > 0) {
480
+ if (category == "faces") {
481
+ if (frameId.find("214") != std::string::npos) {
482
+ totalRGBFramesWithFaces++;
483
+ totalRGBFaces += boxCount;
484
+ } else if (frameId.find("1201") != std::string::npos) {
485
+ totalSLAMFramesWithFaces++;
486
+ totalSLAMFaces += boxCount;
487
+ }
488
+ }
489
+ if (category == "licensePlate") {
490
+ if (frameId.find("214") != std::string::npos) {
491
+ totalRGBFramesWithLicensePlate++;
492
+ totalRGBLicensePlate += boxCount;
493
+ } else if (frameId.find("1201") != std::string::npos) {
494
+ totalSLAMFramesWithLicensePlate++;
495
+ totalSLAMLicensePlate += boxCount;
496
+ }
497
+ }
498
+ }
499
+ }
500
+ totalFrames++;
501
+ }
502
+
503
+ std::ostringstream summary;
504
+ summary << " ----------------" << "\n| Summary |"
505
+ << "\n ----------------" << "\nTotal frames: " << totalFrames
506
+ << "\n Faces:" << "\n RGB - Total detected frame: "
507
+ << totalRGBFramesWithFaces
508
+ << "\n RGB - Total detections: " << totalRGBFaces
509
+ << "\n SLAM - Total detected frame: " << totalSLAMFramesWithFaces
510
+ << "\n SLAM - Total detections: " << totalSLAMFaces
511
+ << "\n License Plates:" << "\n RGB - Total detected frame: "
512
+ << totalRGBFramesWithLicensePlate
513
+ << "\n RGB - Total detections: " << totalRGBLicensePlate
514
+ << "\n SLAM - Total detected frame: "
515
+ << totalSLAMFramesWithLicensePlate
516
+ << "\n SLAM - Total detections: " << totalSLAMLicensePlate;
517
+ return summary.str();
518
+ }
519
+ };
520
+
521
+ } // namespace EgoBlur
tools/vrs_mutation/main.cpp ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include <cstdlib>
18
+ #include <memory>
19
+ #include <string>
20
+
21
+ #include <fmt/core.h>
22
+ #include <vrs/utils/FilterCopy.h> // @manual
23
+ #include <vrs/utils/RecordFileInfo.h> // @manual
24
+
25
+ #include <projectaria_tools/tools/samples/vrs_mutation/ImageMutationFilterCopier.h> // @manual
26
+ #include "EgoBlurImageMutator.h"
27
+
28
+ #include <CLI/CLI.hpp>
29
+
30
+ int main(int argc, const char* argv[]) {
31
+ // std::string mutationType;
32
+ std::string vrsPathIn;
33
+ std::string vrsPathOut;
34
+ std::string vrsExportPath;
35
+ std::string faceModelPath;
36
+ std::string licensePlateModelPath;
37
+ float faceModelConfidenceThreshold;
38
+ float licensePlateModelConfidenceThreshold;
39
+ float scaleFactorDetections;
40
+ float nmsThreshold;
41
+ bool useGPU = false;
42
+
43
+ CLI::App app{
44
+ "VRS file Mutation example by using VRS Copy + Filter mechanism"};
45
+
46
+ app.add_option("-i,--in", vrsPathIn, "VRS input")->required();
47
+ app.add_option("-o,--out", vrsPathOut, "VRS output")->required();
48
+ app.add_option("-f, --faceModelPath", faceModelPath, "Face model path");
49
+ app.add_option(
50
+ "--face-model-confidence-threshold",
51
+ faceModelConfidenceThreshold,
52
+ "Face model confidence threshold")
53
+ ->default_val(0.1);
54
+ app.add_option(
55
+ "-l, --licensePlateModelPath",
56
+ licensePlateModelPath,
57
+ "License Plate model path");
58
+ app.add_option(
59
+ "--license-plate-model-confidence-threshold",
60
+ licensePlateModelConfidenceThreshold,
61
+ "License plate model confidence threshold")
62
+ ->default_val(0.1);
63
+ app.add_option(
64
+ "--scale-factor-detections",
65
+ scaleFactorDetections,
66
+ "scale factor for scaling detections in dimensions")
67
+ ->default_val(1.15);
68
+ app.add_option(
69
+ "--nms-threshold",
70
+ nmsThreshold,
71
+ "NMS threshold for filtering overlapping detections")
72
+ ->default_val(0.3);
73
+ app.add_flag("--use-gpu", useGPU, "Use GPU for inference");
74
+ app.add_option("-e,--exportPath", vrsExportPath, "VRS export output path");
75
+
76
+ CLI11_PARSE(app, argc, argv);
77
+
78
+ if (vrsPathIn == vrsPathOut) {
79
+ std::cerr << " <VRS_IN> <VRS_OUT> paths must be different." << std::endl;
80
+ return EXIT_FAILURE;
81
+ }
82
+
83
+ vrs::utils::FilteredFileReader filteredReader;
84
+ // Initialize VRS Reader and filters
85
+ filteredReader.setSource(vrsPathIn);
86
+ filteredReader.openFile();
87
+ filteredReader.applyFilters({});
88
+
89
+ // Configure Copy Filter and initialize the copy
90
+ const std::string targetPath = vrsPathOut;
91
+ vrs::utils::CopyOptions copyOptions;
92
+ copyOptions.setCompressionPreset(vrs::CompressionPreset::Default);
93
+
94
+ // Functor to perform image processing(blurring PII faces/license plates)
95
+ try {
96
+ std::shared_ptr<vrs::utils::UserDefinedImageMutator> imageMutator;
97
+ if (setenv("ONEDNN_PRIMITIVE_CACHE_CAPACITY", "1", 1) == 0) {
98
+ // See github issue https://github.com/pytorch/pytorch/issues/29893 for
99
+ // details
100
+ std::cout << "Successfully Set ONEDNN_PRIMITIVE_CACHE_CAPACITY to 1"
101
+ << std::endl;
102
+ }
103
+ if (setenv("TORCH_CUDNN_V8_API_DISABLED", "1", 1) == 0) {
104
+ std::cout << "Successfully Set TORCH_CUDNN_V8_API_DISABLED to 1"
105
+ << std::endl;
106
+ }
107
+ imageMutator = std::make_shared<EgoBlur::EgoBlurImageMutator>(
108
+ faceModelPath,
109
+ faceModelConfidenceThreshold,
110
+ licensePlateModelPath,
111
+ licensePlateModelConfidenceThreshold,
112
+ scaleFactorDetections,
113
+ nmsThreshold,
114
+ useGPU);
115
+
116
+ auto copyMakeStreamFilterFunction =
117
+ [&imageMutator](
118
+ vrs::RecordFileReader& fileReader,
119
+ vrs::RecordFileWriter& fileWriter,
120
+ vrs::StreamId streamId,
121
+ const vrs::utils::CopyOptions& copyOptions)
122
+ -> std::unique_ptr<vrs::utils::RecordFilterCopier> {
123
+ auto imageMutatorFilter =
124
+ std::make_unique<vrs::utils::ImageMutationFilter>(
125
+ fileReader,
126
+ fileWriter,
127
+ streamId,
128
+ copyOptions,
129
+ imageMutator.get());
130
+ return imageMutatorFilter;
131
+ };
132
+
133
+ const int statusCode = filterCopy(
134
+ filteredReader, targetPath, copyOptions, copyMakeStreamFilterFunction);
135
+ auto* const egoBlurMutator =
136
+ dynamic_cast<EgoBlur::EgoBlurImageMutator*>(imageMutator.get());
137
+ std::cout << egoBlurMutator->logStatistics() << std::endl;
138
+ return statusCode;
139
+ } catch (const std::exception& ex) {
140
+ std::cerr << "Error while applying EGOBLUR mutation : " << " to : "
141
+ << vrsPathIn << "\nError :\n"
142
+ << ex.what() << std::endl;
143
+ return EXIT_FAILURE;
144
+ }
145
+ }