Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 29 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,69 +54,50 @@ To get an overview of usage and available options, run:
The output may vary depending on your installed version, but it should look similar to this:

```
usage: deface [--output O] [--thresh T] [--scale WxH] [--preview] [--boxes]
[--draw-scores] [--mask-scale M]
[--replacewith {blur,solid,none,img,mosaic}]
[--replaceimg REPLACEIMG] [--mosaicsize width] [--keep-audio]
[--ffmpeg-config FFMPEG_CONFIG] [--backend {auto,onnxrt,opencv}]
[--execution-provider EP] [--version] [--help]
usage: deface [--output O] [--thresh T] [--scale WxH] [--preview] [--boxes] [--draw-scores] [--disable-progress-output]
[--mask-scale M] [--replacewith {blur,solid,none,img,mosaic}] [--replaceimg REPLACEIMG] [--mosaicsize width]
[--keep-replace-aspect] [--keep-audio] [--ffmpeg-config FFMPEG_CONFIG] [--backend {auto,onnxrt,opencv}]
[--execution-provider EP] [--version] [--keep-metadata] [--help]
[input ...]

Video anonymization by face detection
Image and Video anonymization with automatic face detection

positional arguments:
input File path(s) or camera device name. It is possible to
pass multiple paths by separating them by spaces or by
using shell expansion (e.g. `$ deface vids/*.mp4`).
Alternatively, you can pass a directory as an input,
in which case all files in the directory will be used
as inputs. If a camera is installed, a live webcam
demo can be started by running `$ deface cam` (which
is a shortcut for `$ deface -p '<video0>'`.

optional arguments:
--output O, -o O Output file name. Defaults to input path + postfix
"_anonymized".
--thresh T, -t T Detection threshold (tune this to trade off between
false positive and false negative rate). Default: 0.2.
--scale WxH, -s WxH Downscale images for network inference to this size
(format: WxH, example: --scale 640x360).
input File path(s) or camera device name. It is possible to pass multiple paths by separating them by spaces or by
using shell expansion (e.g. `$ deface vids/*.mp4`). Alternatively, you can pass a directory as an input, in
which case all files in the directory will be used as inputs. If a camera is installed, a live webcam demo
can be started by running `$ deface cam` (which is a shortcut for `$ deface -p '<video0>'`.

options:
--output O, -o O Output file name. Defaults to input path + postfix "_anonymized".
--thresh T, -t T Detection threshold (tune this to trade off between false positive and false negative rate). Default: 0.2.
--scale WxH, -s WxH Downscale images for network inference to this size (format: WxH, example: --scale 640x360).
--preview, -p Enable live preview GUI (can decrease performance).
--boxes Use boxes instead of ellipse masks.
--draw-scores Draw detection scores onto outputs.
--disable-progress-output
Disable video progress output to console.
--mask-scale M Scale factor for face masks, to make sure that masks
cover the complete face. Default: 1.3.
--mask-scale M Scale factor for face masks, to make sure that masks cover the complete face. Default: 1.3.
--replacewith {blur,solid,none,img,mosaic}
Anonymization filter mode for face regions. "blur"
applies a strong gaussian blurring, "solid" draws a
solid black box, "none" does leaves the input
unchanged, "img" replaces the face with a custom image
and "mosaic" replaces the face with mosaic. Default:
"blur".
Anonymization filter mode for face regions. "blur" applies a strong gaussian blurring, "solid" draws a solid
black box, "none" does leaves the input unchanged, "img" replaces the face with a custom image and "mosaic"
replaces the face with mosaic. Default: "blur".
--replaceimg REPLACEIMG
Anonymization image for face regions. Requires
--replacewith img option.
--mosaicsize width Setting the mosaic size. Requires --replacewith mosaic
option. Default: 20.
--keep-audio, -k Keep audio from video source file and copy it over to
the output (only applies to videos).
Anonymization image for face regions. Requires --replacewith img option.
--mosaicsize width Setting the mosaic size. Requires --replacewith mosaic option. Default: 20.
--keep-replace-aspect
Preserve the aspect ratio of the replacement image. Requires --replacewith img option. Default: False.
--keep-audio, -k Keep audio from video source file and copy it over to the output (only applies to videos).
--ffmpeg-config FFMPEG_CONFIG
FFMPEG config arguments for encoding output videos.
This argument is expected in JSON notation. For a list
of possible options, refer to the ffmpeg-imageio docs.
Default: '{"codec": "libx264"}'.
FFMPEG config arguments for encoding output videos. This argument is expected in JSON notation. For a list of
possible options, refer to the ffmpeg-imageio docs. Default: '{"codec": "libx264"}'.
--backend {auto,onnxrt,opencv}
Backend for ONNX model execution. Default: "auto"
(prefer onnxrt if available).
Backend for ONNX model execution. Default: "auto" (prefer onnxrt if available).
--execution-provider EP, --ep EP
Override onnxrt execution provider (see
https://onnxruntime.ai/docs/execution-providers/). If
not specified, the presumably fastest available one
will be automatically selected. Only used if backend is
onnxrt.
Override onnxrt execution provider (see https://onnxruntime.ai/docs/execution-providers/). If not specified,
the presumably fastest available one will be automatically selected. Only used if backend is onnxrt.
--version Print version number and exit.
--keep-metadata, -m Keep metadata of the original image. Default : False.
--help, -h Show this help message and exit.
```

Expand Down
67 changes: 52 additions & 15 deletions deface/deface.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def draw_det(
draw_scores: bool = False,
ovcolor: Tuple[int] = (0, 0, 0),
replaceimg = None,
mosaicsize: int = 20
mosaicsize: int = 20,
keep_replace_aspect: bool = False,
):
if replacewith == 'solid':
cv2.rectangle(frame, (x1, y1), (x2, y2), ovcolor, -1)
Expand All @@ -54,12 +55,35 @@ def draw_det(
else:
frame[y1:y2, x1:x2] = blurred_box
elif replacewith == 'img':
target_size = (x2 - x1, y2 - y1)
resized_replaceimg = cv2.resize(replaceimg, target_size)
if replaceimg.shape[2] == 3: # RGB
frame[y1:y2, x1:x2] = resized_replaceimg
elif replaceimg.shape[2] == 4: # RGBA
frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - resized_replaceimg[:, :, 3:] / 255) + resized_replaceimg[:, :, :3] * (resized_replaceimg[:, :, 3:] / 255)
target_w, target_h = x2 - x1, y2 - y1
if keep_replace_aspect:
replace_img_h, replace_img_w = replaceimg.shape[:2]
scale = max(target_w / replace_img_w, target_h / replace_img_h)
new_w, new_h = int(replace_img_w * scale), int(replace_img_h * scale)
resized_replaceimg = cv2.resize(replaceimg, (new_w, new_h))
# Center on the face box, may extend beyond detected boundaries
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
px1, py1 = cx - new_w // 2, cy - new_h // 2
px2, py2 = px1 + new_w, py1 + new_h
# Clamp to frame boundaries
fx1, fy1 = max(0, px1), max(0, py1)
fx2, fy2 = min(frame.shape[1], px2), min(frame.shape[0], py2)
ix1, iy1 = fx1 - px1, fy1 - py1
ix2, iy2 = ix1 + (fx2 - fx1), iy1 + (fy2 - fy1)
if replaceimg.shape[2] == 3: # RGB
frame[fy1:fy2, fx1:fx2] = resized_replaceimg[iy1:iy2, ix1:ix2]
elif replaceimg.shape[2] == 4: # RGBA
alpha = resized_replaceimg[iy1:iy2, ix1:ix2, 3:] / 255
frame[fy1:fy2, fx1:fx2] = (
frame[fy1:fy2, fx1:fx2] * (1 - alpha)
+ resized_replaceimg[iy1:iy2, ix1:ix2, :3] * alpha
)
else:
resized_replaceimg = cv2.resize(replaceimg, (target_w, target_h))
if replaceimg.shape[2] == 3: # RGB
frame[y1:y2, x1:x2] = resized_replaceimg
elif replaceimg.shape[2] == 4: # RGBA
frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - resized_replaceimg[:, :, 3:] / 255) + resized_replaceimg[:, :, :3] * (resized_replaceimg[:, :, 3:] / 255)
elif replacewith == 'mosaic':
for y in range(y1, y2, mosaicsize):
for x in range(x1, x2, mosaicsize):
Expand All @@ -78,7 +102,8 @@ def draw_det(

def anonymize_frame(
dets, frame, mask_scale,
replacewith, ellipse, draw_scores, replaceimg, mosaicsize
replacewith, ellipse, draw_scores, replaceimg, mosaicsize,
keep_replace_aspect: bool = False,
):
for i, det in enumerate(dets):
boxes, score = det[:4], det[4]
Expand All @@ -93,7 +118,8 @@ def anonymize_frame(
ellipse=ellipse,
draw_scores=draw_scores,
replaceimg=replaceimg,
mosaicsize=mosaicsize
mosaicsize=mosaicsize,
keep_replace_aspect=keep_replace_aspect,
)


Expand All @@ -118,6 +144,7 @@ def video_detect(
replaceimg = None,
keep_audio: bool = False,
mosaicsize: int = 20,
keep_replace_aspect: bool = False,
disable_progress_output = False
):
try:
Expand Down Expand Up @@ -165,7 +192,8 @@ def video_detect(
anonymize_frame(
dets, frame, mask_scale=mask_scale,
replacewith=replacewith, ellipse=ellipse, draw_scores=draw_scores,
replaceimg=replaceimg, mosaicsize=mosaicsize
replaceimg=replaceimg, mosaicsize=mosaicsize,
keep_replace_aspect=keep_replace_aspect,
)

if opath is not None:
Expand Down Expand Up @@ -196,6 +224,7 @@ def image_detect(
keep_metadata: bool,
replaceimg = None,
mosaicsize: int = 20,
keep_replace_aspect: bool = False,
):
frame = iio.imread(ipath)

Expand All @@ -210,7 +239,8 @@ def image_detect(
anonymize_frame(
dets, frame, mask_scale=mask_scale,
replacewith=replacewith, ellipse=ellipse, draw_scores=draw_scores,
replaceimg=replaceimg, mosaicsize=mosaicsize
replaceimg=replaceimg, mosaicsize=mosaicsize,
keep_replace_aspect=keep_replace_aspect,
)

if enable_preview:
Expand Down Expand Up @@ -248,7 +278,8 @@ def get_anonymized_image(frame,
mask_scale: float,
ellipse: bool,
draw_scores: bool,
replaceimg = None
replaceimg = None,
keep_replace_aspect: bool = False,
):
"""
Method for getting an anonymized image without CLI
Expand All @@ -261,14 +292,14 @@ def get_anonymized_image(frame,
anonymize_frame(
dets, frame, mask_scale=mask_scale,
replacewith=replacewith, ellipse=ellipse, draw_scores=draw_scores,
replaceimg=replaceimg
replaceimg=replaceimg, keep_replace_aspect=keep_replace_aspect,
)

return frame


def parse_cli_args():
parser = argparse.ArgumentParser(description='Video anonymization by face detection', add_help=False)
parser = argparse.ArgumentParser(description='Image and Video anonymization with automatic face detection', add_help=False)
parser.add_argument(
'input', nargs='*',
help=f'File path(s) or camera device name. It is possible to pass multiple paths by separating them by spaces or by using shell expansion (e.g. `$ deface vids/*.mp4`). Alternatively, you can pass a directory as an input, in which case all files in the directory will be used as inputs. If a camera is installed, a live webcam demo can be started by running `$ deface cam` (which is a shortcut for `$ deface -p \'<video0>\'`.')
Expand Down Expand Up @@ -305,6 +336,9 @@ def parse_cli_args():
parser.add_argument(
'--mosaicsize', default=20, type=int, metavar='width',
help='Setting the mosaic size. Requires --replacewith mosaic option. Default: 20.')
parser.add_argument(
'--keep-replace-aspect', default=False, action='store_true',
help='Preserve the aspect ratio of the replacement image. Requires --replacewith img option. Default: False.')
parser.add_argument(
'--keep-audio', '-k', default=False, action='store_true',
help='Keep audio from video source file and copy it over to the output (only applies to videos).')
Expand Down Expand Up @@ -369,6 +403,7 @@ def main():
execution_provider = args.execution_provider
mosaicsize = args.mosaicsize
keep_metadata = args.keep_metadata
keep_replace_aspect = args.keep_replace_aspect
replaceimg = None
disable_progress_output = args.disable_progress_output

Expand Down Expand Up @@ -417,6 +452,7 @@ def main():
ffmpeg_config=ffmpeg_config,
replaceimg=replaceimg,
mosaicsize=mosaicsize,
keep_replace_aspect=keep_replace_aspect,
disable_progress_output=disable_progress_output
)
elif filetype == 'image':
Expand All @@ -432,7 +468,8 @@ def main():
enable_preview=enable_preview,
keep_metadata=keep_metadata,
replaceimg=replaceimg,
mosaicsize=mosaicsize
mosaicsize=mosaicsize,
keep_replace_aspect=keep_replace_aspect,
)
elif filetype is None:
print(f'Can\'t determine file type of file {ipath}. Skipping...')
Expand Down
Binary file added examples/city_anonymized_replaceimg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.