update model weights and scripts, delete mp4
Browse files- .gitattributes +1 -0
- CODE_OF_CONDUCT.md +80 -0
- CONTRIBUTING.md +31 -0
- demo_assets/test_image.jpg +0 -0
- ego_blur_face.zip +3 -0
- ego_blur_lp.zip +3 -0
- environment.yaml +16 -0
- script/demo_ego_blur.py +511 -0
- tools/README.md +311 -0
- tools/vrs_mutation/CMakeLists.txt +52 -0
- tools/vrs_mutation/EgoBlurImageMutator.h +521 -0
- tools/vrs_mutation/main.cpp +145 -0
.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 < 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 |
+
}
|