Spaces:
NSOUP
/
No application file

seitoku commited on
Commit
f6c9b39
·
verified ·
1 Parent(s): 8b5f3a6

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .editorconfig +8 -0
  2. .flake8 +3 -0
  3. .gitattributes +1 -0
  4. .github/FUNDING.yml +2 -0
  5. .github/preview.png +3 -0
  6. .github/workflows/ci.yml +35 -0
  7. .gitignore +3 -0
  8. LICENSE.md +3 -0
  9. README.md +110 -8
  10. facefusion.ini +68 -0
  11. facefusion/__init__.py +0 -0
  12. facefusion/audio.py +81 -0
  13. facefusion/choices.py +37 -0
  14. facefusion/common_helper.py +27 -0
  15. facefusion/config.py +92 -0
  16. facefusion/content_analyser.py +110 -0
  17. facefusion/core.py +377 -0
  18. facefusion/download.py +52 -0
  19. facefusion/execution.py +97 -0
  20. facefusion/face_analyser.py +550 -0
  21. facefusion/face_helper.py +164 -0
  22. facefusion/face_masker.py +146 -0
  23. facefusion/face_store.py +48 -0
  24. facefusion/ffmpeg.py +138 -0
  25. facefusion/filesystem.py +109 -0
  26. facefusion/globals.py +56 -0
  27. facefusion/installer.py +75 -0
  28. facefusion/logger.py +47 -0
  29. facefusion/memory.py +21 -0
  30. facefusion/metadata.py +13 -0
  31. facefusion/normalizer.py +43 -0
  32. facefusion/process_manager.py +53 -0
  33. facefusion/processors/__init__.py +0 -0
  34. facefusion/processors/frame/__init__.py +0 -0
  35. facefusion/processors/frame/choices.py +13 -0
  36. facefusion/processors/frame/core.py +116 -0
  37. facefusion/processors/frame/globals.py +11 -0
  38. facefusion/processors/frame/modules/__init__.py +0 -0
  39. facefusion/processors/frame/modules/face_debugger.py +187 -0
  40. facefusion/processors/frame/modules/face_enhancer.py +286 -0
  41. facefusion/processors/frame/modules/face_swapper.py +368 -0
  42. facefusion/processors/frame/modules/frame_enhancer.py +225 -0
  43. facefusion/processors/frame/modules/lip_syncer.py +254 -0
  44. facefusion/processors/frame/typings.py +36 -0
  45. facefusion/statistics.py +51 -0
  46. facefusion/typing.py +120 -0
  47. facefusion/uis/__init__.py +0 -0
  48. facefusion/uis/assets/fixes.css +7 -0
  49. facefusion/uis/assets/overrides.css +58 -0
  50. facefusion/uis/choices.py +7 -0
.editorconfig ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ indent_size = 4
7
+ indent_style = tab
8
+ trim_trailing_whitespace = true
.flake8 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [flake8]
2
+ select = E3, E4, F
3
+ per-file-ignores = facefusion/core.py:E402
.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
+ .github/preview.png filter=lfs diff=lfs merge=lfs -text
.github/FUNDING.yml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ github: henryruhs
2
+ custom: https://paypal.me/henryruhs
.github/preview.png ADDED

Git LFS Details

  • SHA256: 16f8c000253be5980b4c087f81b45010c6ff8f4ac957dc120e72a13f273f6c99
  • Pointer size: 132 Bytes
  • Size of remote file: 1.23 MB
.github/workflows/ci.yml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: ci
2
+
3
+ on: [ push, pull_request ]
4
+
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout
10
+ uses: actions/checkout@v2
11
+ - name: Set up Python 3.10
12
+ uses: actions/setup-python@v2
13
+ with:
14
+ python-version: '3.10'
15
+ - run: pip install flake8
16
+ - run: pip install mypy
17
+ - run: flake8 run.py facefusion tests
18
+ - run: mypy run.py facefusion tests
19
+ test:
20
+ strategy:
21
+ matrix:
22
+ os: [ macos-latest, ubuntu-latest, windows-latest ]
23
+ runs-on: ${{ matrix.os }}
24
+ steps:
25
+ - name: Checkout
26
+ uses: actions/checkout@v2
27
+ - name: Set up ffmpeg
28
+ uses: FedericoCarboni/setup-ffmpeg@v2
29
+ - name: Set up Python 3.10
30
+ uses: actions/setup-python@v2
31
+ with:
32
+ python-version: '3.10'
33
+ - run: python install.py --onnxruntime default --skip-venv
34
+ - run: pip install pytest
35
+ - run: pytest
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .assets
2
+ .idea
3
+ .vscode
LICENSE.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ MIT license
2
+
3
+ Copyright (c) 2023 Henry Ruhs
README.md CHANGED
@@ -1,12 +1,114 @@
1
  ---
2
- title: Facefusion
3
- emoji:
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 4.25.0
8
- app_file: app.py
9
- pinned: false
10
  ---
 
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: facefusion
3
+ app_file: facefusion
 
 
4
  sdk: gradio
5
+ sdk_version: 3.41.2
 
 
6
  ---
7
+ FaceFusion
8
+ ==========
9
 
10
+ > Next generation face swapper and enhancer.
11
+
12
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/facefusion/facefusion/ci.yml.svg?branch=master)](https://github.com/facefusion/facefusion/actions?query=workflow:ci)
13
+ ![License](https://img.shields.io/badge/license-MIT-green)
14
+
15
+
16
+ Preview
17
+ -------
18
+
19
+ ![Preview](https://raw.githubusercontent.com/facefusion/facefusion/master/.github/preview.png?sanitize=true)
20
+
21
+
22
+ Installation
23
+ ------------
24
+
25
+ Be aware, the installation needs technical skills and is not for beginners. Please do not open platform and installation related issues on GitHub. We have a very helpful [Discord](https://join.facefusion.io) community that will guide you to complete the installation.
26
+
27
+ Get started with the [installation](https://docs.facefusion.io/installation) guide.
28
+
29
+
30
+ Usage
31
+ -----
32
+
33
+ Run the command:
34
+
35
+ ```
36
+ python run.py [options]
37
+
38
+ options:
39
+ -h, --help show this help message and exit
40
+ -s SOURCE_PATHS, --source SOURCE_PATHS choose single or multiple source images or audios
41
+ -t TARGET_PATH, --target TARGET_PATH choose single target image or video
42
+ -o OUTPUT_PATH, --output OUTPUT_PATH specify the output file or directory
43
+ -v, --version show program's version number and exit
44
+
45
+ misc:
46
+ --skip-download omit automate downloads and remote lookups
47
+ --headless run the program without a user interface
48
+ --log-level {error,warn,info,debug} adjust the message severity displayed in the terminal
49
+
50
+ execution:
51
+ --execution-providers EXECUTION_PROVIDERS [EXECUTION_PROVIDERS ...] accelerate the model inference using different providers (choices: cpu, ...)
52
+ --execution-thread-count [1-128] specify the amount of parallel threads while processing
53
+ --execution-queue-count [1-32] specify the amount of frames each thread is processing
54
+
55
+ memory:
56
+ --video-memory-strategy {strict,moderate,tolerant} balance fast frame processing and low vram usage
57
+ --system-memory-limit [0-128] limit the available ram that can be used while processing
58
+
59
+ face analyser:
60
+ --face-analyser-order {left-right,right-left,top-bottom,bottom-top,small-large,large-small,best-worst,worst-best} specify the order in which the face analyser detects faces.
61
+ --face-analyser-age {child,teen,adult,senior} filter the detected faces based on their age
62
+ --face-analyser-gender {female,male} filter the detected faces based on their gender
63
+ --face-detector-model {many,retinaface,scrfd,yoloface,yunet} choose the model responsible for detecting the face
64
+ --face-detector-size FACE_DETECTOR_SIZE specify the size of the frame provided to the face detector
65
+ --face-detector-score [0.0-1.0] filter the detected faces base on the confidence score
66
+ --face-landmarker-score [0.0-1.0] filter the detected landmarks base on the confidence score
67
+
68
+ face selector:
69
+ --face-selector-mode {many,one,reference} use reference based tracking or simple matching
70
+ --reference-face-position REFERENCE_FACE_POSITION specify the position used to create the reference face
71
+ --reference-face-distance [0.0-1.5] specify the desired similarity between the reference face and target face
72
+ --reference-frame-number REFERENCE_FRAME_NUMBER specify the frame used to create the reference face
73
+
74
+ face mask:
75
+ --face-mask-types FACE_MASK_TYPES [FACE_MASK_TYPES ...] mix and match different face mask types (choices: box, occlusion, region)
76
+ --face-mask-blur [0.0-1.0] specify the degree of blur applied the box mask
77
+ --face-mask-padding FACE_MASK_PADDING [FACE_MASK_PADDING ...] apply top, right, bottom and left padding to the box mask
78
+ --face-mask-regions FACE_MASK_REGIONS [FACE_MASK_REGIONS ...] choose the facial features used for the region mask (choices: skin, left-eyebrow, right-eyebrow, left-eye, right-eye, eye-glasses, nose, mouth, upper-lip, lower-lip)
79
+
80
+ frame extraction:
81
+ --trim-frame-start TRIM_FRAME_START specify the the start frame of the target video
82
+ --trim-frame-end TRIM_FRAME_END specify the the end frame of the target video
83
+ --temp-frame-format {bmp,jpg,png} specify the temporary resources format
84
+ --keep-temp keep the temporary resources after processing
85
+
86
+ output creation:
87
+ --output-image-quality [0-100] specify the image quality which translates to the compression factor
88
+ --output-image-resolution OUTPUT_IMAGE_RESOLUTION specify the image output resolution based on the target image
89
+ --output-video-encoder {libx264,libx265,libvpx-vp9,h264_nvenc,hevc_nvenc,h264_amf,hevc_amf} specify the encoder use for the video compression
90
+ --output-video-preset {ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow} balance fast video processing and video file size
91
+ --output-video-quality [0-100] specify the video quality which translates to the compression factor
92
+ --output-video-resolution OUTPUT_VIDEO_RESOLUTION specify the video output resolution based on the target video
93
+ --output-video-fps OUTPUT_VIDEO_FPS specify the video output fps based on the target video
94
+ --skip-audio omit the audio from the target video
95
+
96
+ frame processors:
97
+ --frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] load a single or multiple frame processors. (choices: face_debugger, face_enhancer, face_swapper, frame_enhancer, lip_syncer, ...)
98
+ --face-debugger-items FACE_DEBUGGER_ITEMS [FACE_DEBUGGER_ITEMS ...] load a single or multiple frame processors (choices: bounding-box, face-landmark-5, face-landmark-5/68, face-landmark-68, face-mask, face-detector-score, face-landmarker-score, age, gender)
99
+ --face-enhancer-model {codeformer,gfpgan_1.2,gfpgan_1.3,gfpgan_1.4,gpen_bfr_256,gpen_bfr_512,restoreformer_plus_plus} choose the model responsible for enhancing the face
100
+ --face-enhancer-blend [0-100] blend the enhanced into the previous face
101
+ --face-swapper-model {blendswap_256,inswapper_128,inswapper_128_fp16,simswap_256,simswap_512_unofficial,uniface_256} choose the model responsible for swapping the face
102
+ --frame-enhancer-model {lsdir_x4,nomos8k_sc_x4,real_esrgan_x4,real_esrgan_x4_fp16,span_kendata_x4} choose the model responsible for enhancing the frame
103
+ --frame-enhancer-blend [0-100] blend the enhanced into the previous frame
104
+ --lip-syncer-model {wav2lip_gan} choose the model responsible for syncing the lips
105
+
106
+ uis:
107
+ --ui-layouts UI_LAYOUTS [UI_LAYOUTS ...] launch a single or multiple UI layouts (choices: benchmark, default, webcam, ...)
108
+ ```
109
+
110
+
111
+ Documentation
112
+ -------------
113
+
114
+ Read the [documentation](https://docs.facefusion.io) for a deep dive.
facefusion.ini ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [general]
2
+ source_paths =
3
+ target_path =
4
+ output_path =
5
+
6
+ [misc]
7
+ skip_download =
8
+ headless =
9
+ log_level =
10
+
11
+ [execution]
12
+ execution_providers =
13
+ execution_thread_count =
14
+ execution_queue_count =
15
+
16
+ [memory]
17
+ video_memory_strategy =
18
+ system_memory_limit =
19
+
20
+ [face_analyser]
21
+ face_analyser_order =
22
+ face_analyser_age =
23
+ face_analyser_gender =
24
+ face_detector_model =
25
+ face_detector_size =
26
+ face_detector_score =
27
+ face_landmarker_score =
28
+
29
+ [face_selector]
30
+ face_selector_mode =
31
+ reference_face_position =
32
+ reference_face_distance =
33
+ reference_frame_number =
34
+
35
+ [face_mask]
36
+ face_mask_types =
37
+ face_mask_blur =
38
+ face_mask_padding =
39
+ face_mask_regions =
40
+
41
+ [frame_extraction]
42
+ trim_frame_start =
43
+ trim_frame_end =
44
+ temp_frame_format =
45
+ keep_temp =
46
+
47
+ [output_creation]
48
+ output_image_quality =
49
+ output_image_resolution =
50
+ output_video_encoder =
51
+ output_video_preset =
52
+ output_video_quality =
53
+ output_video_resolution =
54
+ output_video_fps =
55
+ skip_audio =
56
+
57
+ [frame_processors]
58
+ frame_processors =
59
+ face_debugger_items =
60
+ face_enhancer_model =
61
+ face_enhancer_blend =
62
+ face_swapper_model =
63
+ frame_enhancer_model =
64
+ frame_enhancer_blend =
65
+ lip_syncer_model =
66
+
67
+ [uis]
68
+ ui_layouts =
facefusion/__init__.py ADDED
File without changes
facefusion/audio.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Any, List
2
+ from functools import lru_cache
3
+ import numpy
4
+ import scipy
5
+
6
+ from facefusion.filesystem import is_audio
7
+ from facefusion.ffmpeg import read_audio_buffer
8
+ from facefusion.typing import Fps, Audio, Spectrogram, AudioFrame
9
+
10
+
11
+ def get_audio_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]:
12
+ if is_audio(audio_path):
13
+ audio_frames = read_static_audio(audio_path, fps)
14
+ if frame_number in range(len(audio_frames)):
15
+ return audio_frames[frame_number]
16
+ return None
17
+
18
+
19
+ def create_empty_audio_frame() -> AudioFrame:
20
+ audio_frame = numpy.zeros((80, 16), dtype = numpy.int16)
21
+ return audio_frame
22
+
23
+
24
+ @lru_cache(maxsize = None)
25
+ def read_static_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
26
+ if is_audio(audio_path):
27
+ audio_buffer = read_audio_buffer(audio_path, 16000, 2)
28
+ audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2)
29
+ audio = normalize_audio(audio)
30
+ audio = filter_audio(audio, -0.97)
31
+ spectrogram = create_spectrogram(audio, 16000, 80, 800, 55.0, 7600.0)
32
+ audio_frames = extract_audio_frames(spectrogram, 80, 16, fps)
33
+ return audio_frames
34
+ return None
35
+
36
+
37
+ def normalize_audio(audio : numpy.ndarray[Any, Any]) -> Audio:
38
+ if audio.ndim > 1:
39
+ audio = numpy.mean(audio, axis = 1)
40
+ audio = audio / numpy.max(numpy.abs(audio), axis = 0)
41
+ return audio
42
+
43
+
44
+ def filter_audio(audio : Audio, filter_coefficient : float) -> Audio:
45
+ audio = scipy.signal.lfilter([ 1.0, filter_coefficient ], [1.0], audio)
46
+ return audio
47
+
48
+
49
+ def convert_hertz_to_mel(hertz : float) -> float:
50
+ return 2595 * numpy.log10(1 + hertz / 700)
51
+
52
+
53
+ def convert_mel_to_hertz(mel : numpy.ndarray[Any, Any]) -> numpy.ndarray[Any, Any]:
54
+ return 700 * (10 ** (mel / 2595) - 1)
55
+
56
+
57
+ @lru_cache(maxsize = None)
58
+ def create_static_mel_filter(sample_rate : int, filter_total : int, filter_size : int, frequency_minimum : float, frequency_maximum : float) -> numpy.ndarray[Any, Any]:
59
+ frequency_maximum = min(sample_rate / 2, frequency_maximum)
60
+ mel_filter = numpy.zeros((filter_total, filter_size // 2 + 1))
61
+ mel_bins = numpy.linspace(convert_hertz_to_mel(frequency_minimum), convert_hertz_to_mel(frequency_maximum), filter_total + 2)
62
+ indices = numpy.floor((filter_size + 1) * convert_mel_to_hertz(mel_bins) / sample_rate).astype(numpy.int16)
63
+ for index in range(filter_total):
64
+ mel_filter[index, indices[index]: indices[index + 1]] = scipy.signal.windows.triang(indices[index + 1] - indices[index])
65
+ return mel_filter
66
+
67
+
68
+ def create_spectrogram(audio : Audio, sample_rate : int, filter_total : int, filter_size : int, frequency_minimum : float, frequency_maximum : float) -> Spectrogram:
69
+ mel_filter = create_static_mel_filter(sample_rate, filter_total, filter_size, frequency_minimum, frequency_maximum)
70
+ spectrogram = scipy.signal.stft(audio, nperseg = filter_size, noverlap = 600, nfft = filter_size)[2]
71
+ spectrogram = numpy.dot(mel_filter, numpy.abs(spectrogram))
72
+ return spectrogram
73
+
74
+
75
+ def extract_audio_frames(spectrogram : Spectrogram, filter_total : int, audio_frame_step : int, fps : Fps) -> List[AudioFrame]:
76
+ indices = numpy.arange(0, spectrogram.shape[1], filter_total / fps).astype(numpy.int16)
77
+ indices = indices[indices >= audio_frame_step]
78
+ audio_frames = []
79
+ for index in indices:
80
+ audio_frames.append(spectrogram[:, max(0, index - audio_frame_step) : index])
81
+ return audio_frames
facefusion/choices.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict
2
+
3
+ from facefusion.typing import VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceDetectorModel, FaceMaskType, FaceMaskRegion, TempFrameFormat, OutputVideoEncoder, OutputVideoPreset
4
+ from facefusion.common_helper import create_int_range, create_float_range
5
+
6
+ video_memory_strategies : List[VideoMemoryStrategy] = [ 'strict', 'moderate', 'tolerant' ]
7
+ face_analyser_orders : List[FaceAnalyserOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ]
8
+ face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ]
9
+ face_analyser_genders : List[FaceAnalyserGender] = [ 'female', 'male' ]
10
+ face_detector_set : Dict[FaceDetectorModel, List[str]] =\
11
+ {
12
+ 'many': [ '640x640' ],
13
+ 'retinaface': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
14
+ 'scrfd': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
15
+ 'yoloface': [ '640x640' ],
16
+ 'yunet': [ '160x160', '320x320', '480x480', '512x512', '640x640', '768x768', '960x960', '1024x1024' ]
17
+ }
18
+ face_selector_modes : List[FaceSelectorMode] = [ 'many', 'one', 'reference' ]
19
+ face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'region' ]
20
+ face_mask_regions : List[FaceMaskRegion] = [ 'skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'eye-glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip' ]
21
+ temp_frame_formats : List[TempFrameFormat] = [ 'bmp', 'jpg', 'png' ]
22
+ output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf' ]
23
+ output_video_presets : List[OutputVideoPreset] = [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ]
24
+
25
+ image_template_sizes : List[float] = [ 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4 ]
26
+ video_template_sizes : List[int] = [ 240, 360, 480, 540, 720, 1080, 1440, 2160, 4320 ]
27
+
28
+ execution_thread_count_range : List[int] = create_int_range(1, 128, 1)
29
+ execution_queue_count_range : List[int] = create_int_range(1, 32, 1)
30
+ system_memory_limit_range : List[int] = create_int_range(0, 128, 1)
31
+ face_detector_score_range : List[float] = create_float_range(0.0, 1.0, 0.05)
32
+ face_landmarker_score_range : List[float] = create_float_range(0.0, 1.0, 0.05)
33
+ face_mask_blur_range : List[float] = create_float_range(0.0, 1.0, 0.05)
34
+ face_mask_padding_range : List[int] = create_int_range(0, 100, 1)
35
+ reference_face_distance_range : List[float] = create_float_range(0.0, 1.5, 0.05)
36
+ output_image_quality_range : List[int] = create_int_range(0, 100, 1)
37
+ output_video_quality_range : List[int] = create_int_range(0, 100, 1)
facefusion/common_helper.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Any, Tuple
2
+ import numpy
3
+
4
+
5
+ def create_metavar(ranges : List[Any]) -> str:
6
+ return '[' + str(ranges[0]) + '-' + str(ranges[-1]) + ']'
7
+
8
+
9
+ def create_int_range(start : int, stop : int, step : int) -> List[int]:
10
+ return (numpy.arange(start, stop + step, step)).tolist()
11
+
12
+
13
+ def create_float_range(start : float, stop : float, step : float) -> List[float]:
14
+ return (numpy.around(numpy.arange(start, stop + step, step), decimals = 2)).tolist()
15
+
16
+
17
+ def get_first(__list__ : Any) -> Any:
18
+ return next(iter(__list__), None)
19
+
20
+
21
+ def extract_major_version(version : str) -> Tuple[int, int]:
22
+ versions = version.split('.')
23
+ if len(versions) > 1:
24
+ return int(versions[0]), int(versions[1])
25
+ if len(versions) == 1:
26
+ return int(versions[0]), 0
27
+ return 0, 0
facefusion/config.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from configparser import ConfigParser
2
+ from typing import Any, Optional, List
3
+
4
+ from facefusion.filesystem import resolve_relative_path
5
+
6
+ CONFIG = None
7
+
8
+
9
+ def get_config() -> ConfigParser:
10
+ global CONFIG
11
+
12
+ if CONFIG is None:
13
+ config_path = resolve_relative_path('../facefusion.ini')
14
+ CONFIG = ConfigParser()
15
+ CONFIG.read(config_path, encoding = 'utf-8')
16
+ return CONFIG
17
+
18
+
19
+ def clear_config() -> None:
20
+ global CONFIG
21
+
22
+ CONFIG = None
23
+
24
+
25
+ def get_str_value(key : str, fallback : Optional[str] = None) -> Optional[str]:
26
+ value = get_value_by_notation(key)
27
+
28
+ if value or fallback:
29
+ return str(value or fallback)
30
+ return None
31
+
32
+
33
+ def get_int_value(key : str, fallback : Optional[str] = None) -> Optional[int]:
34
+ value = get_value_by_notation(key)
35
+
36
+ if value or fallback:
37
+ return int(value or fallback)
38
+ return None
39
+
40
+
41
+ def get_float_value(key : str, fallback : Optional[str] = None) -> Optional[float]:
42
+ value = get_value_by_notation(key)
43
+
44
+ if value or fallback:
45
+ return float(value or fallback)
46
+ return None
47
+
48
+
49
+ def get_bool_value(key : str, fallback : Optional[str] = None) -> Optional[bool]:
50
+ value = get_value_by_notation(key)
51
+
52
+ if value == 'True' or fallback == 'True':
53
+ return True
54
+ if value == 'False' or fallback == 'False':
55
+ return False
56
+ return None
57
+
58
+
59
+ def get_str_list(key : str, fallback : Optional[str] = None) -> Optional[List[str]]:
60
+ value = get_value_by_notation(key)
61
+
62
+ if value or fallback:
63
+ return [ str(value) for value in (value or fallback).split(' ') ]
64
+ return None
65
+
66
+
67
+ def get_int_list(key : str, fallback : Optional[str] = None) -> Optional[List[int]]:
68
+ value = get_value_by_notation(key)
69
+
70
+ if value or fallback:
71
+ return [ int(value) for value in (value or fallback).split(' ') ]
72
+ return None
73
+
74
+
75
+ def get_float_list(key : str, fallback : Optional[str] = None) -> Optional[List[float]]:
76
+ value = get_value_by_notation(key)
77
+
78
+ if value or fallback:
79
+ return [ float(value) for value in (value or fallback).split(' ') ]
80
+ return None
81
+
82
+
83
+ def get_value_by_notation(key : str) -> Optional[Any]:
84
+ config = get_config()
85
+
86
+ if '.' in key:
87
+ section, name = key.split('.')
88
+ if section in config and name in config[section]:
89
+ return config[section][name]
90
+ if key in config:
91
+ return config[key]
92
+ return None
facefusion/content_analyser.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+ from functools import lru_cache
3
+ from time import sleep
4
+ import threading
5
+ import cv2
6
+ import numpy
7
+ import onnxruntime
8
+ from tqdm import tqdm
9
+
10
+ import facefusion.globals
11
+ from facefusion import process_manager, wording
12
+ from facefusion.typing import VisionFrame, ModelValue, Fps
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.vision import get_video_frame, count_video_frame_total, read_image, detect_video_fps
15
+ from facefusion.filesystem import resolve_relative_path
16
+ from facefusion.download import conditional_download
17
+
18
+ CONTENT_ANALYSER = None
19
+ THREAD_LOCK : threading.Lock = threading.Lock()
20
+ MODELS : Dict[str, ModelValue] =\
21
+ {
22
+ 'open_nsfw':
23
+ {
24
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/open_nsfw.onnx',
25
+ 'path': resolve_relative_path('../.assets/models/open_nsfw.onnx')
26
+ }
27
+ }
28
+ PROBABILITY_LIMIT = 0.80
29
+ RATE_LIMIT = 5
30
+ STREAM_COUNTER = 0
31
+
32
+
33
+ def get_content_analyser() -> Any:
34
+ global CONTENT_ANALYSER
35
+
36
+ with THREAD_LOCK:
37
+ while process_manager.is_checking():
38
+ sleep(0.5)
39
+ if CONTENT_ANALYSER is None:
40
+ model_path = MODELS.get('open_nsfw').get('path')
41
+ CONTENT_ANALYSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
42
+ return CONTENT_ANALYSER
43
+
44
+
45
+ def clear_content_analyser() -> None:
46
+ global CONTENT_ANALYSER
47
+
48
+ CONTENT_ANALYSER = None
49
+
50
+
51
+ def pre_check() -> bool:
52
+ if not facefusion.globals.skip_download:
53
+ download_directory_path = resolve_relative_path('../.assets/models')
54
+ model_url = MODELS.get('open_nsfw').get('url')
55
+ process_manager.check()
56
+ conditional_download(download_directory_path, [ model_url ])
57
+ process_manager.end()
58
+ return True
59
+
60
+
61
+ def analyse_stream(vision_frame : VisionFrame, video_fps : Fps) -> bool:
62
+ global STREAM_COUNTER
63
+
64
+ STREAM_COUNTER = STREAM_COUNTER + 1
65
+ if STREAM_COUNTER % int(video_fps) == 0:
66
+ return analyse_frame(vision_frame)
67
+ return False
68
+
69
+
70
+ def analyse_frame(vision_frame : VisionFrame) -> bool:
71
+ content_analyser = get_content_analyser()
72
+ vision_frame = prepare_frame(vision_frame)
73
+ probability = content_analyser.run(None,
74
+ {
75
+ content_analyser.get_inputs()[0].name: vision_frame
76
+ })[0][0][1]
77
+ return probability > PROBABILITY_LIMIT
78
+
79
+
80
+ def prepare_frame(vision_frame : VisionFrame) -> VisionFrame:
81
+ vision_frame = cv2.resize(vision_frame, (224, 224)).astype(numpy.float32)
82
+ vision_frame -= numpy.array([ 104, 117, 123 ]).astype(numpy.float32)
83
+ vision_frame = numpy.expand_dims(vision_frame, axis = 0)
84
+ return vision_frame
85
+
86
+
87
+ @lru_cache(maxsize = None)
88
+ def analyse_image(image_path : str) -> bool:
89
+ frame = read_image(image_path)
90
+ return analyse_frame(frame)
91
+
92
+
93
+ @lru_cache(maxsize = None)
94
+ def analyse_video(video_path : str, start_frame : int, end_frame : int) -> bool:
95
+ video_frame_total = count_video_frame_total(video_path)
96
+ video_fps = detect_video_fps(video_path)
97
+ frame_range = range(start_frame or 0, end_frame or video_frame_total)
98
+ rate = 0.0
99
+ counter = 0
100
+
101
+ with tqdm(total = len(frame_range), desc = wording.get('analysing'), unit = 'frame', ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
102
+ for frame_number in frame_range:
103
+ if frame_number % int(video_fps) == 0:
104
+ frame = get_video_frame(video_path, frame_number)
105
+ if analyse_frame(frame):
106
+ counter += 1
107
+ rate = counter * int(video_fps) / len(frame_range) * 100
108
+ progress.update()
109
+ progress.set_postfix(rate = rate)
110
+ return rate > RATE_LIMIT
facefusion/core.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ['OMP_NUM_THREADS'] = '1'
4
+
5
+ import signal
6
+ import sys
7
+ import warnings
8
+ import shutil
9
+ import numpy
10
+ import onnxruntime
11
+ from time import sleep, time
12
+ from argparse import ArgumentParser, HelpFormatter
13
+
14
+ import facefusion.choices
15
+ import facefusion.globals
16
+ from facefusion.face_analyser import get_one_face, get_average_face
17
+ from facefusion.face_store import get_reference_faces, append_reference_face
18
+ from facefusion import face_analyser, face_masker, content_analyser, config, process_manager, metadata, logger, wording
19
+ from facefusion.content_analyser import analyse_image, analyse_video
20
+ from facefusion.processors.frame.core import get_frame_processors_modules, load_frame_processor_module
21
+ from facefusion.common_helper import create_metavar, get_first
22
+ from facefusion.execution import encode_execution_providers, decode_execution_providers
23
+ from facefusion.normalizer import normalize_output_path, normalize_padding, normalize_fps
24
+ from facefusion.memory import limit_system_memory
25
+ from facefusion.statistics import conditional_log_statistics
26
+ from facefusion.filesystem import list_directory, get_temp_frame_paths, create_temp, move_temp, clear_temp, is_image, is_video, filter_audio_paths
27
+ from facefusion.ffmpeg import extract_frames, merge_video, copy_image, finalize_image, restore_audio, replace_audio
28
+ from facefusion.vision import read_image, read_static_images, detect_image_resolution, restrict_video_fps, create_image_resolutions, get_video_frame, detect_video_resolution, detect_video_fps, restrict_video_resolution, restrict_image_resolution, create_video_resolutions, pack_resolution, unpack_resolution
29
+
30
+ onnxruntime.set_default_logger_severity(3)
31
+ warnings.filterwarnings('ignore', category = UserWarning, module = 'gradio')
32
+
33
+
34
+ def cli() -> None:
35
+ signal.signal(signal.SIGINT, lambda signal_number, frame: destroy())
36
+ program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 130), add_help = False)
37
+ # general
38
+ program.add_argument('-s', '--source', help = wording.get('help.source'), action = 'append', dest = 'source_paths', default = config.get_str_list('general.source_paths'))
39
+ program.add_argument('-t', '--target', help = wording.get('help.target'), dest = 'target_path', default = config.get_str_value('general.target_path'))
40
+ program.add_argument('-o', '--output', help = wording.get('help.output'), dest = 'output_path', default = config.get_str_value('general.output_path'))
41
+ program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
42
+ # misc
43
+ group_misc = program.add_argument_group('misc')
44
+ group_misc.add_argument('--skip-download', help = wording.get('help.skip_download'), action = 'store_true', default = config.get_bool_value('misc.skip_download'))
45
+ group_misc.add_argument('--headless', help = wording.get('help.headless'), action = 'store_true', default = config.get_bool_value('misc.headless'))
46
+ group_misc.add_argument('--log-level', help = wording.get('help.log_level'), default = config.get_str_value('misc.log_level', 'info'), choices = logger.get_log_levels())
47
+ # execution
48
+ execution_providers = encode_execution_providers(onnxruntime.get_available_providers())
49
+ group_execution = program.add_argument_group('execution')
50
+ group_execution.add_argument('--execution-providers', help = wording.get('help.execution_providers').format(choices = ', '.join(execution_providers)), default = config.get_str_list('execution.execution_providers', 'cpu'), choices = execution_providers, nargs = '+', metavar = 'EXECUTION_PROVIDERS')
51
+ group_execution.add_argument('--execution-thread-count', help = wording.get('help.execution_thread_count'), type = int, default = config.get_int_value('execution.execution_thread_count', '4'), choices = facefusion.choices.execution_thread_count_range, metavar = create_metavar(facefusion.choices.execution_thread_count_range))
52
+ group_execution.add_argument('--execution-queue-count', help = wording.get('help.execution_queue_count'), type = int, default = config.get_int_value('execution.execution_queue_count', '1'), choices = facefusion.choices.execution_queue_count_range, metavar = create_metavar(facefusion.choices.execution_queue_count_range))
53
+ # memory
54
+ group_memory = program.add_argument_group('memory')
55
+ group_memory.add_argument('--video-memory-strategy', help = wording.get('help.video_memory_strategy'), default = config.get_str_value('memory.video_memory_strategy', 'strict'), choices = facefusion.choices.video_memory_strategies)
56
+ group_memory.add_argument('--system-memory-limit', help = wording.get('help.system_memory_limit'), type = int, default = config.get_int_value('memory.system_memory_limit', '0'), choices = facefusion.choices.system_memory_limit_range, metavar = create_metavar(facefusion.choices.system_memory_limit_range))
57
+ # face analyser
58
+ group_face_analyser = program.add_argument_group('face analyser')
59
+ group_face_analyser.add_argument('--face-analyser-order', help = wording.get('help.face_analyser_order'), default = config.get_str_value('face_analyser.face_analyser_order', 'left-right'), choices = facefusion.choices.face_analyser_orders)
60
+ group_face_analyser.add_argument('--face-analyser-age', help = wording.get('help.face_analyser_age'), default = config.get_str_value('face_analyser.face_analyser_age'), choices = facefusion.choices.face_analyser_ages)
61
+ group_face_analyser.add_argument('--face-analyser-gender', help = wording.get('help.face_analyser_gender'), default = config.get_str_value('face_analyser.face_analyser_gender'), choices = facefusion.choices.face_analyser_genders)
62
+ group_face_analyser.add_argument('--face-detector-model', help = wording.get('help.face_detector_model'), default = config.get_str_value('face_analyser.face_detector_model', 'yoloface'), choices = facefusion.choices.face_detector_set.keys())
63
+ group_face_analyser.add_argument('--face-detector-size', help = wording.get('help.face_detector_size'), default = config.get_str_value('face_analyser.face_detector_size', '640x640'))
64
+ group_face_analyser.add_argument('--face-detector-score', help = wording.get('help.face_detector_score'), type = float, default = config.get_float_value('face_analyser.face_detector_score', '0.5'), choices = facefusion.choices.face_detector_score_range, metavar = create_metavar(facefusion.choices.face_detector_score_range))
65
+ group_face_analyser.add_argument('--face-landmarker-score', help = wording.get('help.face_landmarker_score'), type = float, default = config.get_float_value('face_analyser.face_landmarker_score', '0.5'), choices = facefusion.choices.face_landmarker_score_range, metavar = create_metavar(facefusion.choices.face_landmarker_score_range))
66
+ # face selector
67
+ group_face_selector = program.add_argument_group('face selector')
68
+ group_face_selector.add_argument('--face-selector-mode', help = wording.get('help.face_selector_mode'), default = config.get_str_value('face_selector.face_selector_mode', 'reference'), choices = facefusion.choices.face_selector_modes)
69
+ group_face_selector.add_argument('--reference-face-position', help = wording.get('help.reference_face_position'), type = int, default = config.get_int_value('face_selector.reference_face_position', '0'))
70
+ group_face_selector.add_argument('--reference-face-distance', help = wording.get('help.reference_face_distance'), type = float, default = config.get_float_value('face_selector.reference_face_distance', '0.6'), choices = facefusion.choices.reference_face_distance_range, metavar = create_metavar(facefusion.choices.reference_face_distance_range))
71
+ group_face_selector.add_argument('--reference-frame-number', help = wording.get('help.reference_frame_number'), type = int, default = config.get_int_value('face_selector.reference_frame_number', '0'))
72
+ # face mask
73
+ group_face_mask = program.add_argument_group('face mask')
74
+ group_face_mask.add_argument('--face-mask-types', help = wording.get('help.face_mask_types').format(choices = ', '.join(facefusion.choices.face_mask_types)), default = config.get_str_list('face_mask.face_mask_types', 'box'), choices = facefusion.choices.face_mask_types, nargs = '+', metavar = 'FACE_MASK_TYPES')
75
+ group_face_mask.add_argument('--face-mask-blur', help = wording.get('help.face_mask_blur'), type = float, default = config.get_float_value('face_mask.face_mask_blur', '0.3'), choices = facefusion.choices.face_mask_blur_range, metavar = create_metavar(facefusion.choices.face_mask_blur_range))
76
+ group_face_mask.add_argument('--face-mask-padding', help = wording.get('help.face_mask_padding'), type = int, default = config.get_int_list('face_mask.face_mask_padding', '0 0 0 0'), nargs = '+')
77
+ group_face_mask.add_argument('--face-mask-regions', help = wording.get('help.face_mask_regions').format(choices = ', '.join(facefusion.choices.face_mask_regions)), default = config.get_str_list('face_mask.face_mask_regions', ' '.join(facefusion.choices.face_mask_regions)), choices = facefusion.choices.face_mask_regions, nargs = '+', metavar = 'FACE_MASK_REGIONS')
78
+ # frame extraction
79
+ group_frame_extraction = program.add_argument_group('frame extraction')
80
+ group_frame_extraction.add_argument('--trim-frame-start', help = wording.get('help.trim_frame_start'), type = int, default = facefusion.config.get_int_value('frame_extraction.trim_frame_start'))
81
+ group_frame_extraction.add_argument('--trim-frame-end', help = wording.get('help.trim_frame_end'), type = int, default = facefusion.config.get_int_value('frame_extraction.trim_frame_end'))
82
+ group_frame_extraction.add_argument('--temp-frame-format', help = wording.get('help.temp_frame_format'), default = config.get_str_value('frame_extraction.temp_frame_format', 'png'), choices = facefusion.choices.temp_frame_formats)
83
+ group_frame_extraction.add_argument('--keep-temp', help = wording.get('help.keep_temp'), action = 'store_true', default = config.get_bool_value('frame_extraction.keep_temp'))
84
+ # output creation
85
+ group_output_creation = program.add_argument_group('output creation')
86
+ group_output_creation.add_argument('--output-image-quality', help = wording.get('help.output_image_quality'), type = int, default = config.get_int_value('output_creation.output_image_quality', '80'), choices = facefusion.choices.output_image_quality_range, metavar = create_metavar(facefusion.choices.output_image_quality_range))
87
+ group_output_creation.add_argument('--output-image-resolution', help = wording.get('help.output_image_resolution'), default = config.get_str_value('output_creation.output_image_resolution'))
88
+ group_output_creation.add_argument('--output-video-encoder', help = wording.get('help.output_video_encoder'), default = config.get_str_value('output_creation.output_video_encoder', 'libx264'), choices = facefusion.choices.output_video_encoders)
89
+ group_output_creation.add_argument('--output-video-preset', help = wording.get('help.output_video_preset'), default = config.get_str_value('output_creation.output_video_preset', 'veryfast'), choices = facefusion.choices.output_video_presets)
90
+ group_output_creation.add_argument('--output-video-quality', help = wording.get('help.output_video_quality'), type = int, default = config.get_int_value('output_creation.output_video_quality', '80'), choices = facefusion.choices.output_video_quality_range, metavar = create_metavar(facefusion.choices.output_video_quality_range))
91
+ group_output_creation.add_argument('--output-video-resolution', help = wording.get('help.output_video_resolution'), default = config.get_str_value('output_creation.output_video_resolution'))
92
+ group_output_creation.add_argument('--output-video-fps', help = wording.get('help.output_video_fps'), type = float)
93
+ group_output_creation.add_argument('--skip-audio', help = wording.get('help.skip_audio'), action = 'store_true', default = config.get_bool_value('output_creation.skip_audio'))
94
+ # frame processors
95
+ available_frame_processors = list_directory('facefusion/processors/frame/modules')
96
+ program = ArgumentParser(parents = [ program ], formatter_class = program.formatter_class, add_help = True)
97
+ group_frame_processors = program.add_argument_group('frame processors')
98
+ group_frame_processors.add_argument('--frame-processors', help = wording.get('help.frame_processors').format(choices = ', '.join(available_frame_processors)), default = config.get_str_list('frame_processors.frame_processors', 'face_swapper'), nargs = '+')
99
+ for frame_processor in available_frame_processors:
100
+ frame_processor_module = load_frame_processor_module(frame_processor)
101
+ frame_processor_module.register_args(group_frame_processors)
102
+ # uis
103
+ available_ui_layouts = list_directory('facefusion/uis/layouts')
104
+ group_uis = program.add_argument_group('uis')
105
+ group_uis.add_argument('--ui-layouts', help = wording.get('help.ui_layouts').format(choices = ', '.join(available_ui_layouts)), default = config.get_str_list('uis.ui_layouts', 'default'), nargs = '+')
106
+ run(program)
107
+
108
+
109
+ def apply_args(program : ArgumentParser) -> None:
110
+ args = program.parse_args()
111
+ # general
112
+ facefusion.globals.source_paths = args.source_paths
113
+ facefusion.globals.target_path = args.target_path
114
+ facefusion.globals.output_path = args.output_path
115
+ # misc
116
+ facefusion.globals.skip_download = args.skip_download
117
+ facefusion.globals.headless = args.headless
118
+ facefusion.globals.log_level = args.log_level
119
+ # execution
120
+ facefusion.globals.execution_providers = decode_execution_providers(args.execution_providers)
121
+ facefusion.globals.execution_thread_count = args.execution_thread_count
122
+ facefusion.globals.execution_queue_count = args.execution_queue_count
123
+ # memory
124
+ facefusion.globals.video_memory_strategy = args.video_memory_strategy
125
+ facefusion.globals.system_memory_limit = args.system_memory_limit
126
+ # face analyser
127
+ facefusion.globals.face_analyser_order = args.face_analyser_order
128
+ facefusion.globals.face_analyser_age = args.face_analyser_age
129
+ facefusion.globals.face_analyser_gender = args.face_analyser_gender
130
+ facefusion.globals.face_detector_model = args.face_detector_model
131
+ if args.face_detector_size in facefusion.choices.face_detector_set[args.face_detector_model]:
132
+ facefusion.globals.face_detector_size = args.face_detector_size
133
+ else:
134
+ facefusion.globals.face_detector_size = '640x640'
135
+ facefusion.globals.face_detector_score = args.face_detector_score
136
+ facefusion.globals.face_landmarker_score = args.face_landmarker_score
137
+ # face selector
138
+ facefusion.globals.face_selector_mode = args.face_selector_mode
139
+ facefusion.globals.reference_face_position = args.reference_face_position
140
+ facefusion.globals.reference_face_distance = args.reference_face_distance
141
+ facefusion.globals.reference_frame_number = args.reference_frame_number
142
+ # face mask
143
+ facefusion.globals.face_mask_types = args.face_mask_types
144
+ facefusion.globals.face_mask_blur = args.face_mask_blur
145
+ facefusion.globals.face_mask_padding = normalize_padding(args.face_mask_padding)
146
+ facefusion.globals.face_mask_regions = args.face_mask_regions
147
+ # frame extraction
148
+ facefusion.globals.trim_frame_start = args.trim_frame_start
149
+ facefusion.globals.trim_frame_end = args.trim_frame_end
150
+ facefusion.globals.temp_frame_format = args.temp_frame_format
151
+ facefusion.globals.keep_temp = args.keep_temp
152
+ # output creation
153
+ facefusion.globals.output_image_quality = args.output_image_quality
154
+ if is_image(args.target_path):
155
+ output_image_resolution = detect_image_resolution(args.target_path)
156
+ output_image_resolutions = create_image_resolutions(output_image_resolution)
157
+ if args.output_image_resolution in output_image_resolutions:
158
+ facefusion.globals.output_image_resolution = args.output_image_resolution
159
+ else:
160
+ facefusion.globals.output_image_resolution = pack_resolution(output_image_resolution)
161
+ facefusion.globals.output_video_encoder = args.output_video_encoder
162
+ facefusion.globals.output_video_preset = args.output_video_preset
163
+ facefusion.globals.output_video_quality = args.output_video_quality
164
+ if is_video(args.target_path):
165
+ output_video_resolution = detect_video_resolution(args.target_path)
166
+ output_video_resolutions = create_video_resolutions(output_video_resolution)
167
+ if args.output_video_resolution in output_video_resolutions:
168
+ facefusion.globals.output_video_resolution = args.output_video_resolution
169
+ else:
170
+ facefusion.globals.output_video_resolution = pack_resolution(output_video_resolution)
171
+ if args.output_video_fps or is_video(args.target_path):
172
+ facefusion.globals.output_video_fps = normalize_fps(args.output_video_fps) or detect_video_fps(args.target_path)
173
+ facefusion.globals.skip_audio = args.skip_audio
174
+ # frame processors
175
+ available_frame_processors = list_directory('facefusion/processors/frame/modules')
176
+ facefusion.globals.frame_processors = args.frame_processors
177
+ for frame_processor in available_frame_processors:
178
+ frame_processor_module = load_frame_processor_module(frame_processor)
179
+ frame_processor_module.apply_args(program)
180
+ # uis
181
+ facefusion.globals.ui_layouts = args.ui_layouts
182
+
183
+
184
+ def run(program : ArgumentParser) -> None:
185
+ apply_args(program)
186
+ logger.init(facefusion.globals.log_level)
187
+ if facefusion.globals.system_memory_limit > 0:
188
+ limit_system_memory(facefusion.globals.system_memory_limit)
189
+ if not pre_check() or not content_analyser.pre_check() or not face_analyser.pre_check() or not face_masker.pre_check():
190
+ return
191
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
192
+ if not frame_processor_module.pre_check():
193
+ return
194
+ if facefusion.globals.headless:
195
+ conditional_process()
196
+ else:
197
+ import facefusion.uis.core as ui
198
+
199
+ for ui_layout in ui.get_ui_layouts_modules(facefusion.globals.ui_layouts):
200
+ if not ui_layout.pre_check():
201
+ return
202
+ ui.launch()
203
+
204
+
205
+ def destroy() -> None:
206
+ process_manager.stop()
207
+ while process_manager.is_processing():
208
+ sleep(0.5)
209
+ if facefusion.globals.target_path:
210
+ clear_temp(facefusion.globals.target_path)
211
+ sys.exit(0)
212
+
213
+
214
+ def pre_check() -> bool:
215
+ if sys.version_info < (3, 9):
216
+ logger.error(wording.get('python_not_supported').format(version = '3.9'), __name__.upper())
217
+ return False
218
+ if not shutil.which('ffmpeg'):
219
+ logger.error(wording.get('ffmpeg_not_installed'), __name__.upper())
220
+ return False
221
+ return True
222
+
223
+
224
+ def conditional_process() -> None:
225
+ start_time = time()
226
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
227
+ while not frame_processor_module.post_check():
228
+ logger.disable()
229
+ sleep(0.5)
230
+ logger.enable()
231
+ if not frame_processor_module.pre_process('output'):
232
+ return
233
+ conditional_append_reference_faces()
234
+ if is_image(facefusion.globals.target_path):
235
+ process_image(start_time)
236
+ if is_video(facefusion.globals.target_path):
237
+ process_video(start_time)
238
+
239
+
240
+ def conditional_append_reference_faces() -> None:
241
+ if 'reference' in facefusion.globals.face_selector_mode and not get_reference_faces():
242
+ source_frames = read_static_images(facefusion.globals.source_paths)
243
+ source_face = get_average_face(source_frames)
244
+ if is_video(facefusion.globals.target_path):
245
+ reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
246
+ else:
247
+ reference_frame = read_image(facefusion.globals.target_path)
248
+ reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
249
+ append_reference_face('origin', reference_face)
250
+ if source_face and reference_face:
251
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
252
+ abstract_reference_frame = frame_processor_module.get_reference_frame(source_face, reference_face, reference_frame)
253
+ if numpy.any(abstract_reference_frame):
254
+ reference_frame = abstract_reference_frame
255
+ reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
256
+ append_reference_face(frame_processor_module.__name__, reference_face)
257
+
258
+
259
+ def process_image(start_time : float) -> None:
260
+ normed_output_path = normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path)
261
+ if analyse_image(facefusion.globals.target_path):
262
+ return
263
+ # copy image
264
+ process_manager.start()
265
+ temp_image_resolution = pack_resolution(restrict_image_resolution(facefusion.globals.target_path, unpack_resolution(facefusion.globals.output_image_resolution)))
266
+ logger.info(wording.get('copying_image').format(resolution = temp_image_resolution), __name__.upper())
267
+ if copy_image(facefusion.globals.target_path, normed_output_path, temp_image_resolution):
268
+ logger.debug(wording.get('copying_image_succeed'), __name__.upper())
269
+ else:
270
+ logger.error(wording.get('copying_image_failed'), __name__.upper())
271
+ return
272
+ # process image
273
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
274
+ logger.info(wording.get('processing'), frame_processor_module.NAME)
275
+ frame_processor_module.process_image(facefusion.globals.source_paths, normed_output_path, normed_output_path)
276
+ frame_processor_module.post_process()
277
+ if is_process_stopping():
278
+ return
279
+ # finalize image
280
+ logger.info(wording.get('finalizing_image').format(resolution = facefusion.globals.output_image_resolution), __name__.upper())
281
+ if finalize_image(normed_output_path, facefusion.globals.output_image_resolution):
282
+ logger.debug(wording.get('finalizing_image_succeed'), __name__.upper())
283
+ else:
284
+ logger.warn(wording.get('finalizing_image_skipped'), __name__.upper())
285
+ # validate image
286
+ if is_image(normed_output_path):
287
+ seconds = '{:.2f}'.format((time() - start_time) % 60)
288
+ logger.info(wording.get('processing_image_succeed').format(seconds = seconds), __name__.upper())
289
+ conditional_log_statistics()
290
+ else:
291
+ logger.error(wording.get('processing_image_failed'), __name__.upper())
292
+ process_manager.end()
293
+
294
+
295
+ def process_video(start_time : float) -> None:
296
+ normed_output_path = normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path)
297
+ if analyse_video(facefusion.globals.target_path, facefusion.globals.trim_frame_start, facefusion.globals.trim_frame_end):
298
+ return
299
+ # clear temp
300
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
301
+ clear_temp(facefusion.globals.target_path)
302
+ # create temp
303
+ logger.debug(wording.get('creating_temp'), __name__.upper())
304
+ create_temp(facefusion.globals.target_path)
305
+ # extract frames
306
+ process_manager.start()
307
+ temp_video_resolution = pack_resolution(restrict_video_resolution(facefusion.globals.target_path, unpack_resolution(facefusion.globals.output_video_resolution)))
308
+ temp_video_fps = restrict_video_fps(facefusion.globals.target_path, facefusion.globals.output_video_fps)
309
+ logger.info(wording.get('extracting_frames').format(resolution = temp_video_resolution, fps = temp_video_fps), __name__.upper())
310
+ if extract_frames(facefusion.globals.target_path, temp_video_resolution, temp_video_fps):
311
+ logger.debug(wording.get('extracting_frames_succeed'), __name__.upper())
312
+ else:
313
+ if is_process_stopping():
314
+ return
315
+ logger.error(wording.get('extracting_frames_failed'), __name__.upper())
316
+ return
317
+ # process frames
318
+ temp_frame_paths = get_temp_frame_paths(facefusion.globals.target_path)
319
+ if temp_frame_paths:
320
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
321
+ logger.info(wording.get('processing'), frame_processor_module.NAME)
322
+ frame_processor_module.process_video(facefusion.globals.source_paths, temp_frame_paths)
323
+ frame_processor_module.post_process()
324
+ if is_process_stopping():
325
+ return
326
+ else:
327
+ logger.error(wording.get('temp_frames_not_found'), __name__.upper())
328
+ return
329
+ # merge video
330
+ logger.info(wording.get('merging_video').format(resolution = facefusion.globals.output_video_resolution, fps = facefusion.globals.output_video_fps), __name__.upper())
331
+ if merge_video(facefusion.globals.target_path, facefusion.globals.output_video_resolution, facefusion.globals.output_video_fps):
332
+ logger.debug(wording.get('merging_video_succeed'), __name__.upper())
333
+ else:
334
+ if is_process_stopping():
335
+ return
336
+ logger.error(wording.get('merging_video_failed'), __name__.upper())
337
+ return
338
+ # handle audio
339
+ if facefusion.globals.skip_audio:
340
+ logger.info(wording.get('skipping_audio'), __name__.upper())
341
+ move_temp(facefusion.globals.target_path, normed_output_path)
342
+ else:
343
+ if 'lip_syncer' in facefusion.globals.frame_processors:
344
+ source_audio_path = get_first(filter_audio_paths(facefusion.globals.source_paths))
345
+ if source_audio_path and replace_audio(facefusion.globals.target_path, source_audio_path, normed_output_path):
346
+ logger.debug(wording.get('restoring_audio_succeed'), __name__.upper())
347
+ else:
348
+ if is_process_stopping():
349
+ return
350
+ logger.warn(wording.get('restoring_audio_skipped'), __name__.upper())
351
+ move_temp(facefusion.globals.target_path, normed_output_path)
352
+ else:
353
+ if restore_audio(facefusion.globals.target_path, normed_output_path, facefusion.globals.output_video_fps):
354
+ logger.debug(wording.get('restoring_audio_succeed'), __name__.upper())
355
+ else:
356
+ if is_process_stopping():
357
+ return
358
+ logger.warn(wording.get('restoring_audio_skipped'), __name__.upper())
359
+ move_temp(facefusion.globals.target_path, normed_output_path)
360
+ # clear temp
361
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
362
+ clear_temp(facefusion.globals.target_path)
363
+ # validate video
364
+ if is_video(normed_output_path):
365
+ seconds = '{:.2f}'.format((time() - start_time))
366
+ logger.info(wording.get('processing_video_succeed').format(seconds = seconds), __name__.upper())
367
+ conditional_log_statistics()
368
+ else:
369
+ logger.error(wording.get('processing_video_failed'), __name__.upper())
370
+ process_manager.end()
371
+
372
+
373
+ def is_process_stopping() -> bool:
374
+ if process_manager.is_stopping():
375
+ process_manager.end()
376
+ logger.info(wording.get('processing_stopped'), __name__.upper())
377
+ return process_manager.is_pending()
facefusion/download.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import platform
4
+ import ssl
5
+ import urllib.request
6
+ from typing import List
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from functools import lru_cache
9
+ from tqdm import tqdm
10
+
11
+ import facefusion.globals
12
+ from facefusion import wording
13
+ from facefusion.filesystem import is_file
14
+
15
+ if platform.system().lower() == 'darwin':
16
+ ssl._create_default_https_context = ssl._create_unverified_context
17
+
18
+
19
+ def conditional_download(download_directory_path : str, urls : List[str]) -> None:
20
+ with ThreadPoolExecutor() as executor:
21
+ for url in urls:
22
+ executor.submit(get_download_size, url)
23
+ for url in urls:
24
+ download_file_path = os.path.join(download_directory_path, os.path.basename(url))
25
+ initial_size = os.path.getsize(download_file_path) if is_file(download_file_path) else 0
26
+ download_size = get_download_size(url)
27
+ if initial_size < download_size:
28
+ with tqdm(total = download_size, initial = initial_size, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
29
+ subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ])
30
+ current_size = initial_size
31
+ while current_size < download_size:
32
+ if is_file(download_file_path):
33
+ current_size = os.path.getsize(download_file_path)
34
+ progress.update(current_size - progress.n)
35
+ if download_size and not is_download_done(url, download_file_path):
36
+ os.remove(download_file_path)
37
+ conditional_download(download_directory_path, [ url ])
38
+
39
+
40
+ @lru_cache(maxsize = None)
41
+ def get_download_size(url : str) -> int:
42
+ try:
43
+ response = urllib.request.urlopen(url, timeout = 10)
44
+ return int(response.getheader('Content-Length'))
45
+ except (OSError, ValueError):
46
+ return 0
47
+
48
+
49
+ def is_download_done(url : str, file_path : str) -> bool:
50
+ if is_file(file_path):
51
+ return get_download_size(url) == os.path.getsize(file_path)
52
+ return False
facefusion/execution.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Any
2
+ from functools import lru_cache
3
+ import subprocess
4
+ import xml.etree.ElementTree as ElementTree
5
+ import onnxruntime
6
+
7
+ from facefusion.typing import ExecutionDevice, ValueAndUnit
8
+
9
+
10
+ def encode_execution_providers(execution_providers : List[str]) -> List[str]:
11
+ return [ execution_provider.replace('ExecutionProvider', '').lower() for execution_provider in execution_providers ]
12
+
13
+
14
+ def decode_execution_providers(execution_providers: List[str]) -> List[str]:
15
+ available_execution_providers = onnxruntime.get_available_providers()
16
+ encoded_execution_providers = encode_execution_providers(available_execution_providers)
17
+
18
+ return [ execution_provider for execution_provider, encoded_execution_provider in zip(available_execution_providers, encoded_execution_providers) if any(execution_provider in encoded_execution_provider for execution_provider in execution_providers) ]
19
+
20
+
21
+ def apply_execution_provider_options(execution_providers: List[str]) -> List[Any]:
22
+ execution_providers_with_options : List[Any] = []
23
+
24
+ for execution_provider in execution_providers:
25
+ if execution_provider == 'CUDAExecutionProvider':
26
+ execution_providers_with_options.append((execution_provider,
27
+ {
28
+ 'cudnn_conv_algo_search': 'EXHAUSTIVE' if use_exhaustive() else 'DEFAULT'
29
+ }))
30
+ else:
31
+ execution_providers_with_options.append(execution_provider)
32
+ return execution_providers_with_options
33
+
34
+
35
+ def use_exhaustive() -> bool:
36
+ execution_devices = detect_static_execution_devices()
37
+ product_names = ('GeForce GTX 1630', 'GeForce GTX 1650', 'GeForce GTX 1660')
38
+
39
+ return any(execution_device.get('product').get('name').startswith(product_names) for execution_device in execution_devices)
40
+
41
+
42
+ def run_nvidia_smi() -> subprocess.Popen[bytes]:
43
+ commands = [ 'nvidia-smi', '--query', '--xml-format' ]
44
+ return subprocess.Popen(commands, stdout = subprocess.PIPE)
45
+
46
+
47
+ @lru_cache(maxsize = None)
48
+ def detect_static_execution_devices() -> List[ExecutionDevice]:
49
+ return detect_execution_devices()
50
+
51
+
52
+ def detect_execution_devices() -> List[ExecutionDevice]:
53
+ execution_devices : List[ExecutionDevice] = []
54
+ try:
55
+ output, _ = run_nvidia_smi().communicate()
56
+ root_element = ElementTree.fromstring(output)
57
+ except Exception:
58
+ root_element = ElementTree.Element('xml')
59
+
60
+ for gpu_element in root_element.findall('gpu'):
61
+ execution_devices.append(
62
+ {
63
+ 'driver_version': root_element.find('driver_version').text,
64
+ 'framework':
65
+ {
66
+ 'name': 'CUDA',
67
+ 'version': root_element.find('cuda_version').text,
68
+ },
69
+ 'product':
70
+ {
71
+ 'vendor': 'NVIDIA',
72
+ 'name': gpu_element.find('product_name').text.replace('NVIDIA ', ''),
73
+ 'architecture': gpu_element.find('product_architecture').text,
74
+ },
75
+ 'video_memory':
76
+ {
77
+ 'total': create_value_and_unit(gpu_element.find('fb_memory_usage/total').text),
78
+ 'free': create_value_and_unit(gpu_element.find('fb_memory_usage/free').text)
79
+ },
80
+ 'utilization':
81
+ {
82
+ 'gpu': create_value_and_unit(gpu_element.find('utilization/gpu_util').text),
83
+ 'memory': create_value_and_unit(gpu_element.find('utilization/memory_util').text)
84
+ }
85
+ })
86
+ return execution_devices
87
+
88
+
89
+ def create_value_and_unit(text : str) -> ValueAndUnit:
90
+ value, unit = text.split()
91
+ value_and_unit : ValueAndUnit =\
92
+ {
93
+ 'value': value,
94
+ 'unit': unit
95
+ }
96
+
97
+ return value_and_unit
facefusion/face_analyser.py ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Optional, List, Tuple
2
+ from time import sleep
3
+ import threading
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+
8
+ import facefusion.globals
9
+ from facefusion import process_manager
10
+ from facefusion.common_helper import get_first
11
+ from facefusion.face_helper import warp_face_by_face_landmark_5, warp_face_by_translation, create_static_anchors, distance_to_face_landmark_5, distance_to_bounding_box, convert_face_landmark_68_to_5, apply_nms, categorize_age, categorize_gender
12
+ from facefusion.face_store import get_static_faces, set_static_faces
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.download import conditional_download
15
+ from facefusion.filesystem import resolve_relative_path
16
+ from facefusion.typing import VisionFrame, Face, FaceSet, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, ModelSet, BoundingBox, FaceLandmarkSet, FaceLandmark5, FaceLandmark68, Score, FaceScoreSet, Embedding
17
+ from facefusion.vision import resize_frame_resolution, unpack_resolution
18
+
19
+ FACE_ANALYSER = None
20
+ THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
21
+ THREAD_LOCK : threading.Lock = threading.Lock()
22
+ MODELS : ModelSet =\
23
+ {
24
+ 'face_detector_retinaface':
25
+ {
26
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/retinaface_10g.onnx',
27
+ 'path': resolve_relative_path('../.assets/models/retinaface_10g.onnx')
28
+ },
29
+ 'face_detector_scrfd':
30
+ {
31
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/scrfd_2.5g.onnx',
32
+ 'path': resolve_relative_path('../.assets/models/scrfd_2.5g.onnx')
33
+ },
34
+ 'face_detector_yoloface':
35
+ {
36
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yoloface_8n.onnx',
37
+ 'path': resolve_relative_path('../.assets/models/yoloface_8n.onnx')
38
+ },
39
+ 'face_detector_yunet':
40
+ {
41
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yunet_2023mar.onnx',
42
+ 'path': resolve_relative_path('../.assets/models/yunet_2023mar.onnx')
43
+ },
44
+ 'face_recognizer_arcface_blendswap':
45
+ {
46
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
47
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
48
+ },
49
+ 'face_recognizer_arcface_inswapper':
50
+ {
51
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
52
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
53
+ },
54
+ 'face_recognizer_arcface_simswap':
55
+ {
56
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_simswap.onnx',
57
+ 'path': resolve_relative_path('../.assets/models/arcface_simswap.onnx')
58
+ },
59
+ 'face_recognizer_arcface_uniface':
60
+ {
61
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
62
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
63
+ },
64
+ 'face_landmarker':
65
+ {
66
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/2dfan4.onnx',
67
+ 'path': resolve_relative_path('../.assets/models/2dfan4.onnx')
68
+ },
69
+ 'gender_age':
70
+ {
71
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gender_age.onnx',
72
+ 'path': resolve_relative_path('../.assets/models/gender_age.onnx')
73
+ }
74
+ }
75
+
76
+
77
+ def get_face_analyser() -> Any:
78
+ global FACE_ANALYSER
79
+
80
+ face_detectors = {}
81
+ with THREAD_LOCK:
82
+ while process_manager.is_checking():
83
+ sleep(0.5)
84
+ if FACE_ANALYSER is None:
85
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface' ]:
86
+ face_detector = onnxruntime.InferenceSession(MODELS.get('face_detector_retinaface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
87
+ face_detectors['retinaface'] = face_detector
88
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
89
+ face_detector = onnxruntime.InferenceSession(MODELS.get('face_detector_scrfd').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
90
+ face_detectors['scrfd'] = face_detector
91
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
92
+ face_detector = onnxruntime.InferenceSession(MODELS.get('face_detector_yoloface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
93
+ face_detectors['yoloface'] = face_detector
94
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
95
+ face_detector = cv2.FaceDetectorYN.create(MODELS.get('face_detector_yunet').get('path'), '', (0, 0))
96
+ face_detectors['yunet'] = face_detector
97
+ if facefusion.globals.face_recognizer_model == 'arcface_blendswap':
98
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_blendswap').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
99
+ if facefusion.globals.face_recognizer_model == 'arcface_inswapper':
100
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_inswapper').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
101
+ if facefusion.globals.face_recognizer_model == 'arcface_simswap':
102
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_simswap').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
103
+ if facefusion.globals.face_recognizer_model == 'arcface_uniface':
104
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_uniface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
105
+ face_landmarker = onnxruntime.InferenceSession(MODELS.get('face_landmarker').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
106
+ gender_age = onnxruntime.InferenceSession(MODELS.get('gender_age').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_providers))
107
+ FACE_ANALYSER =\
108
+ {
109
+ 'face_detectors': face_detectors,
110
+ 'face_recognizer': face_recognizer,
111
+ 'face_landmarker': face_landmarker,
112
+ 'gender_age': gender_age
113
+ }
114
+ return FACE_ANALYSER
115
+
116
+
117
+ def clear_face_analyser() -> Any:
118
+ global FACE_ANALYSER
119
+
120
+ FACE_ANALYSER = None
121
+
122
+
123
+ def pre_check() -> bool:
124
+ if not facefusion.globals.skip_download:
125
+ download_directory_path = resolve_relative_path('../.assets/models')
126
+ model_urls =\
127
+ [
128
+ MODELS.get('face_landmarker').get('url'),
129
+ MODELS.get('gender_age').get('url')
130
+ ]
131
+
132
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface' ]:
133
+ model_urls.append(MODELS.get('face_detector_retinaface').get('url'))
134
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
135
+ model_urls.append(MODELS.get('face_detector_scrfd').get('url'))
136
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
137
+ model_urls.append(MODELS.get('face_detector_yoloface').get('url'))
138
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
139
+ model_urls.append(MODELS.get('face_detector_yunet').get('url'))
140
+ if facefusion.globals.face_recognizer_model == 'arcface_blendswap':
141
+ model_urls.append(MODELS.get('face_recognizer_arcface_blendswap').get('url'))
142
+ if facefusion.globals.face_recognizer_model == 'arcface_inswapper':
143
+ model_urls.append(MODELS.get('face_recognizer_arcface_inswapper').get('url'))
144
+ if facefusion.globals.face_recognizer_model == 'arcface_simswap':
145
+ model_urls.append(MODELS.get('face_recognizer_arcface_simswap').get('url'))
146
+ if facefusion.globals.face_recognizer_model == 'arcface_uniface':
147
+ model_urls.append(MODELS.get('face_recognizer_arcface_uniface').get('url'))
148
+ process_manager.check()
149
+ conditional_download(download_directory_path, model_urls)
150
+ process_manager.end()
151
+ return True
152
+
153
+
154
+ def detect_with_retinaface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
155
+ face_detector = get_face_analyser().get('face_detectors').get('retinaface')
156
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
157
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
158
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
159
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
160
+ feature_strides = [ 8, 16, 32 ]
161
+ feature_map_channel = 3
162
+ anchor_total = 2
163
+ bounding_box_list = []
164
+ face_landmark_5_list = []
165
+ score_list = []
166
+
167
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
168
+ with THREAD_SEMAPHORE:
169
+ detections = face_detector.run(None,
170
+ {
171
+ face_detector.get_inputs()[0].name: detect_vision_frame
172
+ })
173
+ for index, feature_stride in enumerate(feature_strides):
174
+ keep_indices = numpy.where(detections[index] >= facefusion.globals.face_detector_score)[0]
175
+ if keep_indices.any():
176
+ stride_height = face_detector_height // feature_stride
177
+ stride_width = face_detector_width // feature_stride
178
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
179
+ bounding_box_raw = detections[index + feature_map_channel] * feature_stride
180
+ face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride
181
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
182
+ bounding_box_list.append(numpy.array(
183
+ [
184
+ bounding_box[0] * ratio_width,
185
+ bounding_box[1] * ratio_height,
186
+ bounding_box[2] * ratio_width,
187
+ bounding_box[3] * ratio_height
188
+ ]))
189
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
190
+ face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ])
191
+ for score in detections[index][keep_indices]:
192
+ score_list.append(score[0])
193
+ return bounding_box_list, face_landmark_5_list, score_list
194
+
195
+
196
+ def detect_with_scrfd(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
197
+ face_detector = get_face_analyser().get('face_detectors').get('scrfd')
198
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
199
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
200
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
201
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
202
+ feature_strides = [ 8, 16, 32 ]
203
+ feature_map_channel = 3
204
+ anchor_total = 2
205
+ bounding_box_list = []
206
+ face_landmark_5_list = []
207
+ score_list = []
208
+
209
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
210
+ with THREAD_SEMAPHORE:
211
+ detections = face_detector.run(None,
212
+ {
213
+ face_detector.get_inputs()[0].name: detect_vision_frame
214
+ })
215
+ for index, feature_stride in enumerate(feature_strides):
216
+ keep_indices = numpy.where(detections[index] >= facefusion.globals.face_detector_score)[0]
217
+ if keep_indices.any():
218
+ stride_height = face_detector_height // feature_stride
219
+ stride_width = face_detector_width // feature_stride
220
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
221
+ bounding_box_raw = detections[index + feature_map_channel] * feature_stride
222
+ face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride
223
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
224
+ bounding_box_list.append(numpy.array(
225
+ [
226
+ bounding_box[0] * ratio_width,
227
+ bounding_box[1] * ratio_height,
228
+ bounding_box[2] * ratio_width,
229
+ bounding_box[3] * ratio_height
230
+ ]))
231
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
232
+ face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ])
233
+ for score in detections[index][keep_indices]:
234
+ score_list.append(score[0])
235
+ return bounding_box_list, face_landmark_5_list, score_list
236
+
237
+
238
+ def detect_with_yoloface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
239
+ face_detector = get_face_analyser().get('face_detectors').get('yoloface')
240
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
241
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
242
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
243
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
244
+ bounding_box_list = []
245
+ face_landmark_5_list = []
246
+ score_list = []
247
+
248
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
249
+ with THREAD_SEMAPHORE:
250
+ detections = face_detector.run(None,
251
+ {
252
+ face_detector.get_inputs()[0].name: detect_vision_frame
253
+ })
254
+ detections = numpy.squeeze(detections).T
255
+ bounding_box_raw, score_raw, face_landmark_5_raw = numpy.split(detections, [ 4, 5 ], axis = 1)
256
+ keep_indices = numpy.where(score_raw > facefusion.globals.face_detector_score)[0]
257
+ if keep_indices.any():
258
+ bounding_box_raw, face_landmark_5_raw, score_raw = bounding_box_raw[keep_indices], face_landmark_5_raw[keep_indices], score_raw[keep_indices]
259
+ for bounding_box in bounding_box_raw:
260
+ bounding_box_list.append(numpy.array(
261
+ [
262
+ (bounding_box[0] - bounding_box[2] / 2) * ratio_width,
263
+ (bounding_box[1] - bounding_box[3] / 2) * ratio_height,
264
+ (bounding_box[0] + bounding_box[2] / 2) * ratio_width,
265
+ (bounding_box[1] + bounding_box[3] / 2) * ratio_height
266
+ ]))
267
+ face_landmark_5_raw[:, 0::3] = (face_landmark_5_raw[:, 0::3]) * ratio_width
268
+ face_landmark_5_raw[:, 1::3] = (face_landmark_5_raw[:, 1::3]) * ratio_height
269
+ for face_landmark_5 in face_landmark_5_raw:
270
+ face_landmark_5_list.append(numpy.array(face_landmark_5.reshape(-1, 3)[:, :2]))
271
+ score_list = score_raw.ravel().tolist()
272
+ return bounding_box_list, face_landmark_5_list, score_list
273
+
274
+
275
+ def detect_with_yunet(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
276
+ face_detector = get_face_analyser().get('face_detectors').get('yunet')
277
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
278
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
279
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
280
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
281
+ bounding_box_list = []
282
+ face_landmark_5_list = []
283
+ score_list = []
284
+
285
+ face_detector.setInputSize((temp_vision_frame.shape[1], temp_vision_frame.shape[0]))
286
+ face_detector.setScoreThreshold(facefusion.globals.face_detector_score)
287
+ with THREAD_SEMAPHORE:
288
+ _, detections = face_detector.detect(temp_vision_frame)
289
+ if numpy.any(detections):
290
+ for detection in detections:
291
+ bounding_box_list.append(numpy.array(
292
+ [
293
+ detection[0] * ratio_width,
294
+ detection[1] * ratio_height,
295
+ (detection[0] + detection[2]) * ratio_width,
296
+ (detection[1] + detection[3]) * ratio_height
297
+ ]))
298
+ face_landmark_5_list.append(detection[4:14].reshape((5, 2)) * [ ratio_width, ratio_height ])
299
+ score_list.append(detection[14])
300
+ return bounding_box_list, face_landmark_5_list, score_list
301
+
302
+
303
+ def prepare_detect_frame(temp_vision_frame : VisionFrame, face_detector_size : str) -> VisionFrame:
304
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
305
+ detect_vision_frame = numpy.zeros((face_detector_height, face_detector_width, 3))
306
+ detect_vision_frame[:temp_vision_frame.shape[0], :temp_vision_frame.shape[1], :] = temp_vision_frame
307
+ detect_vision_frame = (detect_vision_frame - 127.5) / 128.0
308
+ detect_vision_frame = numpy.expand_dims(detect_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
309
+ return detect_vision_frame
310
+
311
+
312
+ def create_faces(vision_frame : VisionFrame, bounding_box_list : List[BoundingBox], face_landmark_5_list : List[FaceLandmark5], score_list : List[Score]) -> List[Face]:
313
+ faces = []
314
+ if facefusion.globals.face_detector_score > 0:
315
+ sort_indices = numpy.argsort(-numpy.array(score_list))
316
+ bounding_box_list = [ bounding_box_list[index] for index in sort_indices ]
317
+ face_landmark_5_list = [face_landmark_5_list[index] for index in sort_indices]
318
+ score_list = [ score_list[index] for index in sort_indices ]
319
+ iou_threshold = 0.1 if facefusion.globals.face_detector_model == 'many' else 0.4
320
+ keep_indices = apply_nms(bounding_box_list, iou_threshold)
321
+ for index in keep_indices:
322
+ bounding_box = bounding_box_list[index]
323
+ face_landmark_5_68 = face_landmark_5_list[index]
324
+ face_landmark_68 = None
325
+ face_landmark_68_score = 0.0
326
+ if facefusion.globals.face_landmarker_score > 0:
327
+ face_landmark_68, face_landmark_68_score = detect_face_landmark_68(vision_frame, bounding_box)
328
+ if face_landmark_68_score > facefusion.globals.face_landmarker_score:
329
+ face_landmark_5_68 = convert_face_landmark_68_to_5(face_landmark_68)
330
+ landmarks : FaceLandmarkSet =\
331
+ {
332
+ '5': face_landmark_5_list[index],
333
+ '5/68': face_landmark_5_68,
334
+ '68': face_landmark_68
335
+ }
336
+ scores : FaceScoreSet = \
337
+ {
338
+ 'detector': score_list[index],
339
+ 'landmarker': face_landmark_68_score
340
+ }
341
+ embedding, normed_embedding = calc_embedding(vision_frame, landmarks.get('5/68'))
342
+ gender, age = detect_gender_age(vision_frame, bounding_box)
343
+ faces.append(Face(
344
+ bounding_box = bounding_box,
345
+ landmarks = landmarks,
346
+ scores = scores,
347
+ embedding = embedding,
348
+ normed_embedding = normed_embedding,
349
+ gender = gender,
350
+ age = age
351
+ ))
352
+ return faces
353
+
354
+
355
+ def calc_embedding(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5) -> Tuple[Embedding, Embedding]:
356
+ face_recognizer = get_face_analyser().get('face_recognizer')
357
+ crop_vision_frame, matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, 'arcface_112_v2', (112, 112))
358
+ crop_vision_frame = crop_vision_frame / 127.5 - 1
359
+ crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32)
360
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
361
+ embedding = face_recognizer.run(None,
362
+ {
363
+ face_recognizer.get_inputs()[0].name: crop_vision_frame
364
+ })[0]
365
+ embedding = embedding.ravel()
366
+ normed_embedding = embedding / numpy.linalg.norm(embedding)
367
+ return embedding, normed_embedding
368
+
369
+
370
+ def detect_face_landmark_68(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[FaceLandmark68, Score]:
371
+ face_landmarker = get_face_analyser().get('face_landmarker')
372
+ scale = 195 / numpy.subtract(bounding_box[2:], bounding_box[:2]).max()
373
+ translation = (256 - numpy.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5
374
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (256, 256))
375
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_RGB2Lab)
376
+ if numpy.mean(crop_vision_frame[:, :, 0]) < 30:
377
+ crop_vision_frame[:, :, 0] = cv2.createCLAHE(clipLimit = 2).apply(crop_vision_frame[:, :, 0])
378
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_Lab2RGB)
379
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1).astype(numpy.float32) / 255.0
380
+ face_landmark_68, face_heatmap = face_landmarker.run(None,
381
+ {
382
+ face_landmarker.get_inputs()[0].name: [ crop_vision_frame ]
383
+ })
384
+ face_landmark_68 = face_landmark_68[:, :, :2][0] / 64
385
+ face_landmark_68 = face_landmark_68.reshape(1, -1, 2) * 256
386
+ face_landmark_68 = cv2.transform(face_landmark_68, cv2.invertAffineTransform(affine_matrix))
387
+ face_landmark_68 = face_landmark_68.reshape(-1, 2)
388
+ face_landmark_68_score = numpy.amax(face_heatmap, axis = (2, 3))
389
+ face_landmark_68_score = numpy.mean(face_landmark_68_score)
390
+ return face_landmark_68, face_landmark_68_score
391
+
392
+
393
+ def detect_gender_age(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[int, int]:
394
+ gender_age = get_face_analyser().get('gender_age')
395
+ bounding_box = bounding_box.reshape(2, -1)
396
+ scale = 64 / numpy.subtract(*bounding_box[::-1]).max()
397
+ translation = 48 - bounding_box.sum(axis = 0) * scale * 0.5
398
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (96, 96))
399
+ crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32)
400
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
401
+ prediction = gender_age.run(None,
402
+ {
403
+ gender_age.get_inputs()[0].name: crop_vision_frame
404
+ })[0][0]
405
+ gender = int(numpy.argmax(prediction[:2]))
406
+ age = int(numpy.round(prediction[2] * 100))
407
+ return gender, age
408
+
409
+
410
+ def get_one_face(vision_frame : VisionFrame, position : int = 0) -> Optional[Face]:
411
+ many_faces = get_many_faces(vision_frame)
412
+ if many_faces:
413
+ try:
414
+ return many_faces[position]
415
+ except IndexError:
416
+ return many_faces[-1]
417
+ return None
418
+
419
+
420
+ def get_average_face(vision_frames : List[VisionFrame], position : int = 0) -> Optional[Face]:
421
+ average_face = None
422
+ faces = []
423
+ embedding_list = []
424
+ normed_embedding_list = []
425
+
426
+ for vision_frame in vision_frames:
427
+ face = get_one_face(vision_frame, position)
428
+ if face:
429
+ faces.append(face)
430
+ embedding_list.append(face.embedding)
431
+ normed_embedding_list.append(face.normed_embedding)
432
+ if faces:
433
+ first_face = get_first(faces)
434
+ average_face = Face(
435
+ bounding_box = first_face.bounding_box,
436
+ landmarks = first_face.landmarks,
437
+ scores = first_face.scores,
438
+ embedding = numpy.mean(embedding_list, axis = 0),
439
+ normed_embedding = numpy.mean(normed_embedding_list, axis = 0),
440
+ gender = first_face.gender,
441
+ age = first_face.age
442
+ )
443
+ return average_face
444
+
445
+
446
+ def get_many_faces(vision_frame : VisionFrame) -> List[Face]:
447
+ faces = []
448
+ try:
449
+ faces_cache = get_static_faces(vision_frame)
450
+ if faces_cache:
451
+ faces = faces_cache
452
+ else:
453
+ bounding_box_list = []
454
+ face_landmark_5_list = []
455
+ score_list = []
456
+
457
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface']:
458
+ bounding_box_list_retinaface, face_landmark_5_list_retinaface, score_list_retinaface = detect_with_retinaface(vision_frame, facefusion.globals.face_detector_size)
459
+ bounding_box_list.extend(bounding_box_list_retinaface)
460
+ face_landmark_5_list.extend(face_landmark_5_list_retinaface)
461
+ score_list.extend(score_list_retinaface)
462
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
463
+ bounding_box_list_scrfd, face_landmark_5_list_scrfd, score_list_scrfd = detect_with_scrfd(vision_frame, facefusion.globals.face_detector_size)
464
+ bounding_box_list.extend(bounding_box_list_scrfd)
465
+ face_landmark_5_list.extend(face_landmark_5_list_scrfd)
466
+ score_list.extend(score_list_scrfd)
467
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
468
+ bounding_box_list_yoloface, face_landmark_5_list_yoloface, score_list_yoloface = detect_with_yoloface(vision_frame, facefusion.globals.face_detector_size)
469
+ bounding_box_list.extend(bounding_box_list_yoloface)
470
+ face_landmark_5_list.extend(face_landmark_5_list_yoloface)
471
+ score_list.extend(score_list_yoloface)
472
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
473
+ bounding_box_list_yunet, face_landmark_5_list_yunet, score_list_yunet = detect_with_yunet(vision_frame, facefusion.globals.face_detector_size)
474
+ bounding_box_list.extend(bounding_box_list_yunet)
475
+ face_landmark_5_list.extend(face_landmark_5_list_yunet)
476
+ score_list.extend(score_list_yunet)
477
+ if bounding_box_list and face_landmark_5_list and score_list:
478
+ faces = create_faces(vision_frame, bounding_box_list, face_landmark_5_list, score_list)
479
+ if faces:
480
+ set_static_faces(vision_frame, faces)
481
+ if facefusion.globals.face_analyser_order:
482
+ faces = sort_by_order(faces, facefusion.globals.face_analyser_order)
483
+ if facefusion.globals.face_analyser_age:
484
+ faces = filter_by_age(faces, facefusion.globals.face_analyser_age)
485
+ if facefusion.globals.face_analyser_gender:
486
+ faces = filter_by_gender(faces, facefusion.globals.face_analyser_gender)
487
+ except (AttributeError, ValueError):
488
+ pass
489
+ return faces
490
+
491
+
492
+ def find_similar_faces(reference_faces : FaceSet, vision_frame : VisionFrame, face_distance : float) -> List[Face]:
493
+ similar_faces : List[Face] = []
494
+ many_faces = get_many_faces(vision_frame)
495
+
496
+ if reference_faces:
497
+ for reference_set in reference_faces:
498
+ if not similar_faces:
499
+ for reference_face in reference_faces[reference_set]:
500
+ for face in many_faces:
501
+ if compare_faces(face, reference_face, face_distance):
502
+ similar_faces.append(face)
503
+ return similar_faces
504
+
505
+
506
+ def compare_faces(face : Face, reference_face : Face, face_distance : float) -> bool:
507
+ current_face_distance = calc_face_distance(face, reference_face)
508
+ return current_face_distance < face_distance
509
+
510
+
511
+ def calc_face_distance(face : Face, reference_face : Face) -> float:
512
+ if hasattr(face, 'normed_embedding') and hasattr(reference_face, 'normed_embedding'):
513
+ return 1 - numpy.dot(face.normed_embedding, reference_face.normed_embedding)
514
+ return 0
515
+
516
+
517
+ def sort_by_order(faces : List[Face], order : FaceAnalyserOrder) -> List[Face]:
518
+ if order == 'left-right':
519
+ return sorted(faces, key = lambda face: face.bounding_box[0])
520
+ if order == 'right-left':
521
+ return sorted(faces, key = lambda face: face.bounding_box[0], reverse = True)
522
+ if order == 'top-bottom':
523
+ return sorted(faces, key = lambda face: face.bounding_box[1])
524
+ if order == 'bottom-top':
525
+ return sorted(faces, key = lambda face: face.bounding_box[1], reverse = True)
526
+ if order == 'small-large':
527
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]))
528
+ if order == 'large-small':
529
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]), reverse = True)
530
+ if order == 'best-worst':
531
+ return sorted(faces, key = lambda face: face.scores.get('detector'), reverse = True)
532
+ if order == 'worst-best':
533
+ return sorted(faces, key = lambda face: face.scores.get('detector'))
534
+ return faces
535
+
536
+
537
+ def filter_by_age(faces : List[Face], age : FaceAnalyserAge) -> List[Face]:
538
+ filter_faces = []
539
+ for face in faces:
540
+ if categorize_age(face.age) == age:
541
+ filter_faces.append(face)
542
+ return filter_faces
543
+
544
+
545
+ def filter_by_gender(faces : List[Face], gender : FaceAnalyserGender) -> List[Face]:
546
+ filter_faces = []
547
+ for face in faces:
548
+ if categorize_gender(face.gender) == gender:
549
+ filter_faces.append(face)
550
+ return filter_faces
facefusion/face_helper.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Tuple, List
2
+ from cv2.typing import Size
3
+ from functools import lru_cache
4
+ import cv2
5
+ import numpy
6
+
7
+ from facefusion.typing import BoundingBox, FaceLandmark5, FaceLandmark68, VisionFrame, Mask, Matrix, Translation, WarpTemplate, WarpTemplateSet, FaceAnalyserAge, FaceAnalyserGender
8
+
9
+ WARP_TEMPLATES : WarpTemplateSet =\
10
+ {
11
+ 'arcface_112_v1': numpy.array(
12
+ [
13
+ [ 0.35473214, 0.45658929 ],
14
+ [ 0.64526786, 0.45658929 ],
15
+ [ 0.50000000, 0.61154464 ],
16
+ [ 0.37913393, 0.77687500 ],
17
+ [ 0.62086607, 0.77687500 ]
18
+ ]),
19
+ 'arcface_112_v2': numpy.array(
20
+ [
21
+ [ 0.34191607, 0.46157411 ],
22
+ [ 0.65653393, 0.45983393 ],
23
+ [ 0.50022500, 0.64050536 ],
24
+ [ 0.37097589, 0.82469196 ],
25
+ [ 0.63151696, 0.82325089 ]
26
+ ]),
27
+ 'arcface_128_v2': numpy.array(
28
+ [
29
+ [ 0.36167656, 0.40387734 ],
30
+ [ 0.63696719, 0.40235469 ],
31
+ [ 0.50019687, 0.56044219 ],
32
+ [ 0.38710391, 0.72160547 ],
33
+ [ 0.61507734, 0.72034453 ]
34
+ ]),
35
+ 'ffhq_512': numpy.array(
36
+ [
37
+ [ 0.37691676, 0.46864664 ],
38
+ [ 0.62285697, 0.46912813 ],
39
+ [ 0.50123859, 0.61331904 ],
40
+ [ 0.39308822, 0.72541100 ],
41
+ [ 0.61150205, 0.72490465 ]
42
+ ])
43
+ }
44
+
45
+
46
+ def warp_face_by_face_landmark_5(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
47
+ normed_warp_template = WARP_TEMPLATES.get(warp_template) * crop_size
48
+ affine_matrix = cv2.estimateAffinePartial2D(face_landmark_5, normed_warp_template, method = cv2.RANSAC, ransacReprojThreshold = 100)[0]
49
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, borderMode = cv2.BORDER_REPLICATE, flags = cv2.INTER_AREA)
50
+ return crop_vision_frame, affine_matrix
51
+
52
+
53
+ def warp_face_by_bounding_box(temp_vision_frame : VisionFrame, bounding_box : BoundingBox, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
54
+ source_points = numpy.array([ [ bounding_box[0], bounding_box[1] ], [bounding_box[2], bounding_box[1] ], [ bounding_box[0], bounding_box[3] ] ], dtype = numpy.float32)
55
+ target_points = numpy.array([ [ 0, 0 ], [ crop_size[0], 0 ], [ 0, crop_size[1] ] ], dtype = numpy.float32)
56
+ affine_matrix = cv2.getAffineTransform(source_points, target_points)
57
+ if bounding_box[2] - bounding_box[0] > crop_size[0] or bounding_box[3] - bounding_box[1] > crop_size[1]:
58
+ interpolation_method = cv2.INTER_AREA
59
+ else:
60
+ interpolation_method = cv2.INTER_LINEAR
61
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, flags = interpolation_method)
62
+ return crop_vision_frame, affine_matrix
63
+
64
+
65
+ def warp_face_by_translation(temp_vision_frame : VisionFrame, translation : Translation, scale : float, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
66
+ affine_matrix = numpy.array([ [ scale, 0, translation[0] ], [ 0, scale, translation[1] ] ])
67
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size)
68
+ return crop_vision_frame, affine_matrix
69
+
70
+
71
+ def paste_back(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, crop_mask : Mask, affine_matrix : Matrix) -> VisionFrame:
72
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
73
+ temp_size = temp_vision_frame.shape[:2][::-1]
74
+ inverse_mask = cv2.warpAffine(crop_mask, inverse_matrix, temp_size).clip(0, 1)
75
+ inverse_vision_frame = cv2.warpAffine(crop_vision_frame, inverse_matrix, temp_size, borderMode = cv2.BORDER_REPLICATE)
76
+ paste_vision_frame = temp_vision_frame.copy()
77
+ paste_vision_frame[:, :, 0] = inverse_mask * inverse_vision_frame[:, :, 0] + (1 - inverse_mask) * temp_vision_frame[:, :, 0]
78
+ paste_vision_frame[:, :, 1] = inverse_mask * inverse_vision_frame[:, :, 1] + (1 - inverse_mask) * temp_vision_frame[:, :, 1]
79
+ paste_vision_frame[:, :, 2] = inverse_mask * inverse_vision_frame[:, :, 2] + (1 - inverse_mask) * temp_vision_frame[:, :, 2]
80
+ return paste_vision_frame
81
+
82
+
83
+ @lru_cache(maxsize = None)
84
+ def create_static_anchors(feature_stride : int, anchor_total : int, stride_height : int, stride_width : int) -> numpy.ndarray[Any, Any]:
85
+ y, x = numpy.mgrid[:stride_height, :stride_width][::-1]
86
+ anchors = numpy.stack((y, x), axis = -1)
87
+ anchors = (anchors * feature_stride).reshape((-1, 2))
88
+ anchors = numpy.stack([ anchors ] * anchor_total, axis = 1).reshape((-1, 2))
89
+ return anchors
90
+
91
+
92
+ def create_bounding_box_from_face_landmark_68(face_landmark_68 : FaceLandmark68) -> BoundingBox:
93
+ min_x, min_y = numpy.min(face_landmark_68, axis = 0)
94
+ max_x, max_y = numpy.max(face_landmark_68, axis = 0)
95
+ bounding_box = numpy.array([ min_x, min_y, max_x, max_y ]).astype(numpy.int16)
96
+ return bounding_box
97
+
98
+
99
+ def distance_to_bounding_box(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> BoundingBox:
100
+ x1 = points[:, 0] - distance[:, 0]
101
+ y1 = points[:, 1] - distance[:, 1]
102
+ x2 = points[:, 0] + distance[:, 2]
103
+ y2 = points[:, 1] + distance[:, 3]
104
+ bounding_box = numpy.column_stack([ x1, y1, x2, y2 ])
105
+ return bounding_box
106
+
107
+
108
+ def distance_to_face_landmark_5(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> FaceLandmark5:
109
+ x = points[:, 0::2] + distance[:, 0::2]
110
+ y = points[:, 1::2] + distance[:, 1::2]
111
+ face_landmark_5 = numpy.stack((x, y), axis = -1)
112
+ return face_landmark_5
113
+
114
+
115
+ def convert_face_landmark_68_to_5(landmark_68 : FaceLandmark68) -> FaceLandmark5:
116
+ face_landmark_5 = numpy.array(
117
+ [
118
+ numpy.mean(landmark_68[36:42], axis = 0),
119
+ numpy.mean(landmark_68[42:48], axis = 0),
120
+ landmark_68[30],
121
+ landmark_68[48],
122
+ landmark_68[54]
123
+ ])
124
+ return face_landmark_5
125
+
126
+
127
+ def apply_nms(bounding_box_list : List[BoundingBox], iou_threshold : float) -> List[int]:
128
+ keep_indices = []
129
+ dimension_list = numpy.reshape(bounding_box_list, (-1, 4))
130
+ x1 = dimension_list[:, 0]
131
+ y1 = dimension_list[:, 1]
132
+ x2 = dimension_list[:, 2]
133
+ y2 = dimension_list[:, 3]
134
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
135
+ indices = numpy.arange(len(bounding_box_list))
136
+ while indices.size > 0:
137
+ index = indices[0]
138
+ remain_indices = indices[1:]
139
+ keep_indices.append(index)
140
+ xx1 = numpy.maximum(x1[index], x1[remain_indices])
141
+ yy1 = numpy.maximum(y1[index], y1[remain_indices])
142
+ xx2 = numpy.minimum(x2[index], x2[remain_indices])
143
+ yy2 = numpy.minimum(y2[index], y2[remain_indices])
144
+ width = numpy.maximum(0, xx2 - xx1 + 1)
145
+ height = numpy.maximum(0, yy2 - yy1 + 1)
146
+ iou = width * height / (areas[index] + areas[remain_indices] - width * height)
147
+ indices = indices[numpy.where(iou <= iou_threshold)[0] + 1]
148
+ return keep_indices
149
+
150
+
151
+ def categorize_age(age : int) -> FaceAnalyserAge:
152
+ if age < 13:
153
+ return 'child'
154
+ elif age < 19:
155
+ return 'teen'
156
+ elif age < 60:
157
+ return 'adult'
158
+ return 'senior'
159
+
160
+
161
+ def categorize_gender(gender : int) -> FaceAnalyserGender:
162
+ if gender == 0:
163
+ return 'female'
164
+ return 'male'
facefusion/face_masker.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List
2
+ from cv2.typing import Size
3
+ from functools import lru_cache
4
+ from time import sleep
5
+ import threading
6
+ import cv2
7
+ import numpy
8
+ import onnxruntime
9
+
10
+ import facefusion.globals
11
+ from facefusion import process_manager
12
+ from facefusion.typing import FaceLandmark68, VisionFrame, Mask, Padding, FaceMaskRegion, ModelSet
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.filesystem import resolve_relative_path
15
+ from facefusion.download import conditional_download
16
+
17
+ FACE_OCCLUDER = None
18
+ FACE_PARSER = None
19
+ THREAD_LOCK : threading.Lock = threading.Lock()
20
+ MODELS : ModelSet =\
21
+ {
22
+ 'face_occluder':
23
+ {
24
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_occluder.onnx',
25
+ 'path': resolve_relative_path('../.assets/models/face_occluder.onnx')
26
+ },
27
+ 'face_parser':
28
+ {
29
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_parser.onnx',
30
+ 'path': resolve_relative_path('../.assets/models/face_parser.onnx')
31
+ }
32
+ }
33
+ FACE_MASK_REGIONS : Dict[FaceMaskRegion, int] =\
34
+ {
35
+ 'skin': 1,
36
+ 'left-eyebrow': 2,
37
+ 'right-eyebrow': 3,
38
+ 'left-eye': 4,
39
+ 'right-eye': 5,
40
+ 'eye-glasses': 6,
41
+ 'nose': 10,
42
+ 'mouth': 11,
43
+ 'upper-lip': 12,
44
+ 'lower-lip': 13
45
+ }
46
+
47
+
48
+ def get_face_occluder() -> Any:
49
+ global FACE_OCCLUDER
50
+
51
+ with THREAD_LOCK:
52
+ if FACE_OCCLUDER is None:
53
+ model_path = MODELS.get('face_occluder').get('path')
54
+ FACE_OCCLUDER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
55
+ return FACE_OCCLUDER
56
+
57
+
58
+ def get_face_parser() -> Any:
59
+ global FACE_PARSER
60
+
61
+ with THREAD_LOCK:
62
+ while process_manager.is_checking():
63
+ sleep(0.5)
64
+ if FACE_PARSER is None:
65
+ model_path = MODELS.get('face_parser').get('path')
66
+ FACE_PARSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
67
+ return FACE_PARSER
68
+
69
+
70
+ def clear_face_occluder() -> None:
71
+ global FACE_OCCLUDER
72
+
73
+ FACE_OCCLUDER = None
74
+
75
+
76
+ def clear_face_parser() -> None:
77
+ global FACE_PARSER
78
+
79
+ FACE_PARSER = None
80
+
81
+
82
+ def pre_check() -> bool:
83
+ if not facefusion.globals.skip_download:
84
+ download_directory_path = resolve_relative_path('../.assets/models')
85
+ model_urls =\
86
+ [
87
+ MODELS.get('face_occluder').get('url'),
88
+ MODELS.get('face_parser').get('url'),
89
+ ]
90
+ process_manager.check()
91
+ conditional_download(download_directory_path, model_urls)
92
+ process_manager.end()
93
+ return True
94
+
95
+
96
+ @lru_cache(maxsize = None)
97
+ def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Mask:
98
+ blur_amount = int(crop_size[0] * 0.5 * face_mask_blur)
99
+ blur_area = max(blur_amount // 2, 1)
100
+ box_mask : Mask = numpy.ones(crop_size, numpy.float32)
101
+ box_mask[:max(blur_area, int(crop_size[1] * face_mask_padding[0] / 100)), :] = 0
102
+ box_mask[-max(blur_area, int(crop_size[1] * face_mask_padding[2] / 100)):, :] = 0
103
+ box_mask[:, :max(blur_area, int(crop_size[0] * face_mask_padding[3] / 100))] = 0
104
+ box_mask[:, -max(blur_area, int(crop_size[0] * face_mask_padding[1] / 100)):] = 0
105
+ if blur_amount > 0:
106
+ box_mask = cv2.GaussianBlur(box_mask, (0, 0), blur_amount * 0.25)
107
+ return box_mask
108
+
109
+
110
+ def create_occlusion_mask(crop_vision_frame : VisionFrame) -> Mask:
111
+ face_occluder = get_face_occluder()
112
+ prepare_vision_frame = cv2.resize(crop_vision_frame, face_occluder.get_inputs()[0].shape[1:3][::-1])
113
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32) / 255
114
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 1, 2, 3)
115
+ occlusion_mask : Mask = face_occluder.run(None,
116
+ {
117
+ face_occluder.get_inputs()[0].name: prepare_vision_frame
118
+ })[0][0]
119
+ occlusion_mask = occlusion_mask.transpose(0, 1, 2).clip(0, 1).astype(numpy.float32)
120
+ occlusion_mask = cv2.resize(occlusion_mask, crop_vision_frame.shape[:2][::-1])
121
+ occlusion_mask = (cv2.GaussianBlur(occlusion_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
122
+ return occlusion_mask
123
+
124
+
125
+ def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List[FaceMaskRegion]) -> Mask:
126
+ face_parser = get_face_parser()
127
+ prepare_vision_frame = cv2.flip(cv2.resize(crop_vision_frame, (512, 512)), 1)
128
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32)[:, :, ::-1] / 127.5 - 1
129
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 3, 1, 2)
130
+ region_mask : Mask = face_parser.run(None,
131
+ {
132
+ face_parser.get_inputs()[0].name: prepare_vision_frame
133
+ })[0][0]
134
+ region_mask = numpy.isin(region_mask.argmax(0), [ FACE_MASK_REGIONS[region] for region in face_mask_regions ])
135
+ region_mask = cv2.resize(region_mask.astype(numpy.float32), crop_vision_frame.shape[:2][::-1])
136
+ region_mask = (cv2.GaussianBlur(region_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
137
+ return region_mask
138
+
139
+
140
+ def create_mouth_mask(face_landmark_68 : FaceLandmark68) -> Mask:
141
+ convex_hull = cv2.convexHull(face_landmark_68[numpy.r_[3:14, 31:36]].astype(numpy.int32))
142
+ mouth_mask : Mask = numpy.zeros((512, 512), dtype = numpy.float32)
143
+ mouth_mask = cv2.fillConvexPoly(mouth_mask, convex_hull, 1.0)
144
+ mouth_mask = cv2.erode(mouth_mask.clip(0, 1), numpy.ones((21, 3)))
145
+ mouth_mask = cv2.GaussianBlur(mouth_mask, (0, 0), sigmaX = 1, sigmaY = 15)
146
+ return mouth_mask
facefusion/face_store.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, List
2
+ import hashlib
3
+ import numpy
4
+
5
+ from facefusion.typing import VisionFrame, Face, FaceStore, FaceSet
6
+
7
+ FACE_STORE: FaceStore =\
8
+ {
9
+ 'static_faces': {},
10
+ 'reference_faces': {}
11
+ }
12
+
13
+
14
+ def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
15
+ frame_hash = create_frame_hash(vision_frame)
16
+ if frame_hash in FACE_STORE['static_faces']:
17
+ return FACE_STORE['static_faces'][frame_hash]
18
+ return None
19
+
20
+
21
+ def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
22
+ frame_hash = create_frame_hash(vision_frame)
23
+ if frame_hash:
24
+ FACE_STORE['static_faces'][frame_hash] = faces
25
+
26
+
27
+ def clear_static_faces() -> None:
28
+ FACE_STORE['static_faces'] = {}
29
+
30
+
31
+ def create_frame_hash(vision_frame : VisionFrame) -> Optional[str]:
32
+ return hashlib.sha1(vision_frame.tobytes()).hexdigest() if numpy.any(vision_frame) else None
33
+
34
+
35
+ def get_reference_faces() -> Optional[FaceSet]:
36
+ if FACE_STORE['reference_faces']:
37
+ return FACE_STORE['reference_faces']
38
+ return None
39
+
40
+
41
+ def append_reference_face(name : str, face : Face) -> None:
42
+ if name not in FACE_STORE['reference_faces']:
43
+ FACE_STORE['reference_faces'][name] = []
44
+ FACE_STORE['reference_faces'][name].append(face)
45
+
46
+
47
+ def clear_reference_faces() -> None:
48
+ FACE_STORE['reference_faces'] = {}
facefusion/ffmpeg.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import subprocess
3
+ import filetype
4
+
5
+ import facefusion.globals
6
+ from facefusion import process_manager
7
+ from facefusion.typing import OutputVideoPreset, Fps, AudioBuffer
8
+ from facefusion.filesystem import get_temp_frames_pattern, get_temp_output_video_path
9
+
10
+
11
+ def run_ffmpeg(args : List[str]) -> bool:
12
+ commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'quiet' ]
13
+ commands.extend(args)
14
+ process = subprocess.Popen(commands, stdout = subprocess.PIPE)
15
+
16
+ while process_manager.is_processing():
17
+ try:
18
+ return process.wait(timeout = 0.5) == 0
19
+ except subprocess.TimeoutExpired:
20
+ continue
21
+ return process.returncode == 0
22
+
23
+
24
+ def open_ffmpeg(args : List[str]) -> subprocess.Popen[bytes]:
25
+ commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'quiet' ]
26
+ commands.extend(args)
27
+ return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
28
+
29
+
30
+ def extract_frames(target_path : str, temp_video_resolution : str, temp_video_fps : Fps) -> bool:
31
+ trim_frame_start = facefusion.globals.trim_frame_start
32
+ trim_frame_end = facefusion.globals.trim_frame_end
33
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d')
34
+ commands = [ '-hwaccel', 'auto', '-i', target_path, '-q:v', '0' ]
35
+
36
+ if trim_frame_start is not None and trim_frame_end is not None:
37
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ':end_frame=' + str(trim_frame_end) + ',scale=' + str(temp_video_resolution) + ',fps=' + str(temp_video_fps) ])
38
+ elif trim_frame_start is not None:
39
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ',scale=' + str(temp_video_resolution) + ',fps=' + str(temp_video_fps) ])
40
+ elif trim_frame_end is not None:
41
+ commands.extend([ '-vf', 'trim=end_frame=' + str(trim_frame_end) + ',scale=' + str(temp_video_resolution) + ',fps=' + str(temp_video_fps) ])
42
+ else:
43
+ commands.extend([ '-vf', 'scale=' + str(temp_video_resolution) + ',fps=' + str(temp_video_fps) ])
44
+ commands.extend([ '-vsync', '0', temp_frames_pattern ])
45
+ return run_ffmpeg(commands)
46
+
47
+
48
+ def merge_video(target_path : str, output_video_resolution : str, output_video_fps : Fps) -> bool:
49
+ temp_output_video_path = get_temp_output_video_path(target_path)
50
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d')
51
+ commands = [ '-hwaccel', 'auto', '-s', str(output_video_resolution), '-r', str(output_video_fps), '-i', temp_frames_pattern, '-c:v', facefusion.globals.output_video_encoder ]
52
+
53
+ if facefusion.globals.output_video_encoder in [ 'libx264', 'libx265' ]:
54
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
55
+ commands.extend([ '-crf', str(output_video_compression), '-preset', facefusion.globals.output_video_preset ])
56
+ if facefusion.globals.output_video_encoder in [ 'libvpx-vp9' ]:
57
+ output_video_compression = round(63 - (facefusion.globals.output_video_quality * 0.63))
58
+ commands.extend([ '-crf', str(output_video_compression) ])
59
+ if facefusion.globals.output_video_encoder in [ 'h264_nvenc', 'hevc_nvenc' ]:
60
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
61
+ commands.extend([ '-cq', str(output_video_compression), '-preset', map_nvenc_preset(facefusion.globals.output_video_preset) ])
62
+ if facefusion.globals.output_video_encoder in [ 'h264_amf', 'hevc_amf' ]:
63
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
64
+ commands.extend([ '-qp_i', str(output_video_compression), '-qp_p', str(output_video_compression), '-quality', map_amf_preset(facefusion.globals.output_video_preset) ])
65
+ commands.extend([ '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_output_video_path ])
66
+ return run_ffmpeg(commands)
67
+
68
+
69
+ def copy_image(target_path : str, output_path : str, temp_image_resolution : str) -> bool:
70
+ is_webp = filetype.guess_mime(target_path) == 'image/webp'
71
+ temp_image_compression = 100 if is_webp else 0
72
+ commands = [ '-i', target_path, '-q:v', str(temp_image_compression), '-vf', 'scale=' + str(temp_image_resolution), '-y', output_path ]
73
+ return run_ffmpeg(commands)
74
+
75
+
76
+ def finalize_image(output_path : str, output_image_resolution : str) -> bool:
77
+ output_image_compression = round(31 - (facefusion.globals.output_image_quality * 0.31))
78
+ commands = [ '-i', output_path, '-q:v', str(output_image_compression), '-vf', 'scale=' + str(output_image_resolution), '-y', output_path ]
79
+ return run_ffmpeg(commands)
80
+
81
+
82
+ def read_audio_buffer(target_path : str, sample_rate : int, total_channel : int) -> Optional[AudioBuffer]:
83
+ commands = [ '-i', target_path, '-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sample_rate), '-ac', str(total_channel), '-' ]
84
+ process = open_ffmpeg(commands)
85
+ audio_buffer, _ = process.communicate()
86
+ if process.returncode == 0:
87
+ return audio_buffer
88
+ return None
89
+
90
+
91
+ def restore_audio(target_path : str, output_path : str, output_video_fps : Fps) -> bool:
92
+ trim_frame_start = facefusion.globals.trim_frame_start
93
+ trim_frame_end = facefusion.globals.trim_frame_end
94
+ temp_output_video_path = get_temp_output_video_path(target_path)
95
+ commands = [ '-hwaccel', 'auto', '-i', temp_output_video_path ]
96
+
97
+ if trim_frame_start is not None:
98
+ start_time = trim_frame_start / output_video_fps
99
+ commands.extend([ '-ss', str(start_time) ])
100
+ if trim_frame_end is not None:
101
+ end_time = trim_frame_end / output_video_fps
102
+ commands.extend([ '-to', str(end_time) ])
103
+ commands.extend([ '-i', target_path, '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest', '-y', output_path ])
104
+ return run_ffmpeg(commands)
105
+
106
+
107
+ def replace_audio(target_path : str, audio_path : str, output_path : str) -> bool:
108
+ temp_output_path = get_temp_output_video_path(target_path)
109
+ commands = [ '-hwaccel', 'auto', '-i', temp_output_path, '-i', audio_path, '-c:v', 'copy', '-af', 'apad', '-shortest', '-map', '0:v:0', '-map', '1:a:0', '-y', output_path ]
110
+ return run_ffmpeg(commands)
111
+
112
+
113
+ def map_nvenc_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
114
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast' ]:
115
+ return 'p1'
116
+ if output_video_preset == 'faster':
117
+ return 'p2'
118
+ if output_video_preset == 'fast':
119
+ return 'p3'
120
+ if output_video_preset == 'medium':
121
+ return 'p4'
122
+ if output_video_preset == 'slow':
123
+ return 'p5'
124
+ if output_video_preset == 'slower':
125
+ return 'p6'
126
+ if output_video_preset == 'veryslow':
127
+ return 'p7'
128
+ return None
129
+
130
+
131
+ def map_amf_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
132
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast' ]:
133
+ return 'speed'
134
+ if output_video_preset in [ 'faster', 'fast', 'medium' ]:
135
+ return 'balanced'
136
+ if output_video_preset in [ 'slow', 'slower', 'veryslow' ]:
137
+ return 'quality'
138
+ return None
facefusion/filesystem.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import glob
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ import filetype
7
+ from pathlib import Path
8
+
9
+ import facefusion.globals
10
+
11
+ TEMP_DIRECTORY_PATH = os.path.join(tempfile.gettempdir(), 'facefusion')
12
+ TEMP_OUTPUT_VIDEO_NAME = 'temp.mp4'
13
+
14
+
15
+ def get_temp_frame_paths(target_path : str) -> List[str]:
16
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '*')
17
+ return sorted(glob.glob(temp_frames_pattern))
18
+
19
+
20
+ def get_temp_frames_pattern(target_path : str, temp_frame_prefix : str) -> str:
21
+ temp_directory_path = get_temp_directory_path(target_path)
22
+ return os.path.join(temp_directory_path, temp_frame_prefix + '.' + facefusion.globals.temp_frame_format)
23
+
24
+
25
+ def get_temp_directory_path(target_path : str) -> str:
26
+ target_name, _ = os.path.splitext(os.path.basename(target_path))
27
+ return os.path.join(TEMP_DIRECTORY_PATH, target_name)
28
+
29
+
30
+ def get_temp_output_video_path(target_path : str) -> str:
31
+ temp_directory_path = get_temp_directory_path(target_path)
32
+ return os.path.join(temp_directory_path, TEMP_OUTPUT_VIDEO_NAME)
33
+
34
+
35
+ def create_temp(target_path : str) -> None:
36
+ temp_directory_path = get_temp_directory_path(target_path)
37
+ Path(temp_directory_path).mkdir(parents = True, exist_ok = True)
38
+
39
+
40
+ def move_temp(target_path : str, output_path : str) -> None:
41
+ temp_output_video_path = get_temp_output_video_path(target_path)
42
+ if is_file(temp_output_video_path):
43
+ if is_file(output_path):
44
+ os.remove(output_path)
45
+ shutil.move(temp_output_video_path, output_path)
46
+
47
+
48
+ def clear_temp(target_path : str) -> None:
49
+ temp_directory_path = get_temp_directory_path(target_path)
50
+ parent_directory_path = os.path.dirname(temp_directory_path)
51
+ if not facefusion.globals.keep_temp and is_directory(temp_directory_path):
52
+ shutil.rmtree(temp_directory_path, ignore_errors = True)
53
+ if os.path.exists(parent_directory_path) and not os.listdir(parent_directory_path):
54
+ os.rmdir(parent_directory_path)
55
+
56
+
57
+ def is_file(file_path : str) -> bool:
58
+ return bool(file_path and os.path.isfile(file_path))
59
+
60
+
61
+ def is_directory(directory_path : str) -> bool:
62
+ return bool(directory_path and os.path.isdir(directory_path))
63
+
64
+
65
+ def is_audio(audio_path : str) -> bool:
66
+ return is_file(audio_path) and filetype.helpers.is_audio(audio_path)
67
+
68
+
69
+ def has_audio(audio_paths : List[str]) -> bool:
70
+ if audio_paths:
71
+ return any(is_audio(audio_path) for audio_path in audio_paths)
72
+ return False
73
+
74
+
75
+ def is_image(image_path : str) -> bool:
76
+ return is_file(image_path) and filetype.helpers.is_image(image_path)
77
+
78
+
79
+ def has_image(image_paths: List[str]) -> bool:
80
+ if image_paths:
81
+ return any(is_image(image_path) for image_path in image_paths)
82
+ return False
83
+
84
+
85
+ def is_video(video_path : str) -> bool:
86
+ return is_file(video_path) and filetype.helpers.is_video(video_path)
87
+
88
+
89
+ def filter_audio_paths(paths : List[str]) -> List[str]:
90
+ if paths:
91
+ return [ path for path in paths if is_audio(path) ]
92
+ return []
93
+
94
+
95
+ def filter_image_paths(paths : List[str]) -> List[str]:
96
+ if paths:
97
+ return [ path for path in paths if is_image(path) ]
98
+ return []
99
+
100
+
101
+ def resolve_relative_path(path : str) -> str:
102
+ return os.path.abspath(os.path.join(os.path.dirname(__file__), path))
103
+
104
+
105
+ def list_directory(directory_path : str) -> Optional[List[str]]:
106
+ if is_directory(directory_path):
107
+ files = os.listdir(directory_path)
108
+ return sorted([ Path(file).stem for file in files if not Path(file).stem.startswith(('.', '__')) ])
109
+ return None
facefusion/globals.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from facefusion.typing import LogLevel, VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceMaskType, FaceMaskRegion, OutputVideoEncoder, OutputVideoPreset, FaceDetectorModel, FaceRecognizerModel, TempFrameFormat, Padding
4
+
5
+ # general
6
+ source_paths : Optional[List[str]] = None
7
+ target_path : Optional[str] = None
8
+ output_path : Optional[str] = None
9
+ # misc
10
+ skip_download : Optional[bool] = None
11
+ headless : Optional[bool] = None
12
+ log_level : Optional[LogLevel] = None
13
+ # execution
14
+ execution_providers : List[str] = []
15
+ execution_thread_count : Optional[int] = None
16
+ execution_queue_count : Optional[int] = None
17
+ # memory
18
+ video_memory_strategy : Optional[VideoMemoryStrategy] = None
19
+ system_memory_limit : Optional[int] = None
20
+ # face analyser
21
+ face_analyser_order : Optional[FaceAnalyserOrder] = None
22
+ face_analyser_age : Optional[FaceAnalyserAge] = None
23
+ face_analyser_gender : Optional[FaceAnalyserGender] = None
24
+ face_detector_model : Optional[FaceDetectorModel] = None
25
+ face_detector_size : Optional[str] = None
26
+ face_detector_score : Optional[float] = None
27
+ face_landmarker_score : Optional[float] = None
28
+ face_recognizer_model : Optional[FaceRecognizerModel] = None
29
+ # face selector
30
+ face_selector_mode : Optional[FaceSelectorMode] = None
31
+ reference_face_position : Optional[int] = None
32
+ reference_face_distance : Optional[float] = None
33
+ reference_frame_number : Optional[int] = None
34
+ # face mask
35
+ face_mask_types : Optional[List[FaceMaskType]] = None
36
+ face_mask_blur : Optional[float] = None
37
+ face_mask_padding : Optional[Padding] = None
38
+ face_mask_regions : Optional[List[FaceMaskRegion]] = None
39
+ # frame extraction
40
+ trim_frame_start : Optional[int] = None
41
+ trim_frame_end : Optional[int] = None
42
+ temp_frame_format : Optional[TempFrameFormat] = None
43
+ keep_temp : Optional[bool] = None
44
+ # output creation
45
+ output_image_quality : Optional[int] = None
46
+ output_image_resolution : Optional[str] = None
47
+ output_video_encoder : Optional[OutputVideoEncoder] = None
48
+ output_video_preset : Optional[OutputVideoPreset] = None
49
+ output_video_quality : Optional[int] = None
50
+ output_video_resolution : Optional[str] = None
51
+ output_video_fps : Optional[float] = None
52
+ skip_audio : Optional[bool] = None
53
+ # frame processors
54
+ frame_processors : List[str] = []
55
+ # uis
56
+ ui_layouts : List[str] = []
facefusion/installer.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Tuple
2
+ import sys
3
+ import os
4
+ import platform
5
+ import tempfile
6
+ import subprocess
7
+ import inquirer
8
+ from argparse import ArgumentParser, HelpFormatter
9
+
10
+ from facefusion import metadata, wording
11
+
12
+ ONNXRUNTIMES : Dict[str, Tuple[str, str]] = {}
13
+
14
+ if platform.system().lower() == 'darwin':
15
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.17.1')
16
+ else:
17
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.16.3')
18
+ ONNXRUNTIMES['cuda-12.2'] = ('onnxruntime-gpu', '1.17.1')
19
+ ONNXRUNTIMES['cuda-11.8'] = ('onnxruntime-gpu', '1.16.3')
20
+ ONNXRUNTIMES['openvino'] = ('onnxruntime-openvino', '1.16.0')
21
+ if platform.system().lower() == 'linux':
22
+ ONNXRUNTIMES['rocm-5.4.2'] = ('onnxruntime-rocm', '1.16.3')
23
+ ONNXRUNTIMES['rocm-5.6'] = ('onnxruntime-rocm', '1.16.3')
24
+ if platform.system().lower() == 'windows':
25
+ ONNXRUNTIMES['directml'] = ('onnxruntime-directml', '1.16.0')
26
+
27
+
28
+ def cli() -> None:
29
+ program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 130))
30
+ program.add_argument('--onnxruntime', help = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIMES.keys())
31
+ program.add_argument('--skip-venv', help = wording.get('help.skip_venv'), action = 'store_true')
32
+ program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
33
+ run(program)
34
+
35
+
36
+ def run(program : ArgumentParser) -> None:
37
+ args = program.parse_args()
38
+ python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor)
39
+
40
+ if platform.system().lower() == 'darwin':
41
+ os.environ['SYSTEM_VERSION_COMPAT'] = '0'
42
+ if not args.skip_venv:
43
+ os.environ['PIP_REQUIRE_VIRTUALENV'] = '1'
44
+ if args.onnxruntime:
45
+ answers =\
46
+ {
47
+ 'onnxruntime': args.onnxruntime
48
+ }
49
+ else:
50
+ answers = inquirer.prompt(
51
+ [
52
+ inquirer.List('onnxruntime', message = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = list(ONNXRUNTIMES.keys()))
53
+ ])
54
+ if answers:
55
+ onnxruntime = answers['onnxruntime']
56
+ onnxruntime_name, onnxruntime_version = ONNXRUNTIMES[onnxruntime]
57
+
58
+ subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--force-reinstall' ])
59
+ if onnxruntime == 'rocm-5.4.2' or onnxruntime == 'rocm-5.6':
60
+ if python_id in [ 'cp39', 'cp310', 'cp311' ]:
61
+ rocm_version = onnxruntime.replace('-', '')
62
+ rocm_version = rocm_version.replace('.', '')
63
+ wheel_name = 'onnxruntime_training-' + onnxruntime_version + '+' + rocm_version + '-' + python_id + '-' + python_id + '-manylinux_2_17_x86_64.manylinux2014_x86_64.whl'
64
+ wheel_path = os.path.join(tempfile.gettempdir(), wheel_name)
65
+ wheel_url = 'https://download.onnxruntime.ai/' + wheel_name
66
+ subprocess.call([ 'curl', '--silent', '--location', '--continue-at', '-', '--output', wheel_path, wheel_url ])
67
+ subprocess.call([ 'pip', 'uninstall', wheel_path, '-y', '-q' ])
68
+ subprocess.call([ 'pip', 'install', wheel_path, '--force-reinstall' ])
69
+ os.remove(wheel_path)
70
+ else:
71
+ subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ])
72
+ if onnxruntime == 'cuda-12.2':
73
+ subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--extra-index-url', 'https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple', '--force-reinstall' ])
74
+ else:
75
+ subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--force-reinstall' ])
facefusion/logger.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ from logging import basicConfig, getLogger, Logger, DEBUG, INFO, WARNING, ERROR
3
+
4
+ from facefusion.typing import LogLevel
5
+
6
+
7
+ def init(log_level : LogLevel) -> None:
8
+ basicConfig(format = None)
9
+ get_package_logger().setLevel(get_log_levels()[log_level])
10
+
11
+
12
+ def get_package_logger() -> Logger:
13
+ return getLogger('facefusion')
14
+
15
+
16
+ def debug(message : str, scope : str) -> None:
17
+ get_package_logger().debug('[' + scope + '] ' + message)
18
+
19
+
20
+ def info(message : str, scope : str) -> None:
21
+ get_package_logger().info('[' + scope + '] ' + message)
22
+
23
+
24
+ def warn(message : str, scope : str) -> None:
25
+ get_package_logger().warning('[' + scope + '] ' + message)
26
+
27
+
28
+ def error(message : str, scope : str) -> None:
29
+ get_package_logger().error('[' + scope + '] ' + message)
30
+
31
+
32
+ def enable() -> None:
33
+ get_package_logger().disabled = False
34
+
35
+
36
+ def disable() -> None:
37
+ get_package_logger().disabled = True
38
+
39
+
40
+ def get_log_levels() -> Dict[LogLevel, int]:
41
+ return\
42
+ {
43
+ 'error': ERROR,
44
+ 'warn': WARNING,
45
+ 'info': INFO,
46
+ 'debug': DEBUG
47
+ }
facefusion/memory.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ if platform.system().lower() == 'windows':
4
+ import ctypes
5
+ else:
6
+ import resource
7
+
8
+
9
+ def limit_system_memory(system_memory_limit : int = 1) -> bool:
10
+ if platform.system().lower() == 'darwin':
11
+ system_memory_limit = system_memory_limit * (1024 ** 6)
12
+ else:
13
+ system_memory_limit = system_memory_limit * (1024 ** 3)
14
+ try:
15
+ if platform.system().lower() == 'windows':
16
+ ctypes.windll.kernel32.SetProcessWorkingSetSize(-1, ctypes.c_size_t(system_memory_limit), ctypes.c_size_t(system_memory_limit)) # type: ignore[attr-defined]
17
+ else:
18
+ resource.setrlimit(resource.RLIMIT_DATA, (system_memory_limit, system_memory_limit))
19
+ return True
20
+ except Exception:
21
+ return False
facefusion/metadata.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ METADATA =\
2
+ {
3
+ 'name': 'FaceFusion',
4
+ 'description': 'Next generation face swapper and enhancer',
5
+ 'version': '2.4.1',
6
+ 'license': 'MIT',
7
+ 'author': 'Henry Ruhs',
8
+ 'url': 'https://facefusion.io'
9
+ }
10
+
11
+
12
+ def get(key : str) -> str:
13
+ return METADATA[key]
facefusion/normalizer.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import hashlib
3
+ import os
4
+
5
+ import facefusion.globals
6
+ from facefusion.filesystem import is_directory
7
+ from facefusion.typing import Padding, Fps
8
+
9
+
10
+ def normalize_output_path(target_path : Optional[str], output_path : Optional[str]) -> Optional[str]:
11
+ if target_path and output_path:
12
+ target_name, target_extension = os.path.splitext(os.path.basename(target_path))
13
+ if is_directory(output_path):
14
+ output_hash = hashlib.sha1(str(facefusion.globals.__dict__).encode('utf-8')).hexdigest()[:8]
15
+ output_name = target_name + '-' + output_hash
16
+ return os.path.join(output_path, output_name + target_extension)
17
+ output_name, output_extension = os.path.splitext(os.path.basename(output_path))
18
+ output_directory_path = os.path.dirname(output_path)
19
+ if is_directory(output_directory_path) and output_extension:
20
+ return os.path.join(output_directory_path, output_name + target_extension)
21
+ return None
22
+
23
+
24
+ def normalize_padding(padding : Optional[List[int]]) -> Optional[Padding]:
25
+ if padding and len(padding) == 1:
26
+ return tuple([ padding[0], padding[0], padding[0], padding[0] ]) # type: ignore[return-value]
27
+ if padding and len(padding) == 2:
28
+ return tuple([ padding[0], padding[1], padding[0], padding[1] ]) # type: ignore[return-value]
29
+ if padding and len(padding) == 3:
30
+ return tuple([ padding[0], padding[1], padding[2], padding[1] ]) # type: ignore[return-value]
31
+ if padding and len(padding) == 4:
32
+ return tuple(padding) # type: ignore[return-value]
33
+ return None
34
+
35
+
36
+ def normalize_fps(fps : Optional[float]) -> Optional[Fps]:
37
+ if fps is not None:
38
+ if fps < 1.0:
39
+ return 1.0
40
+ if fps > 60.0:
41
+ return 60.0
42
+ return fps
43
+ return None
facefusion/process_manager.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Generator, List
2
+
3
+ from facefusion.typing import QueuePayload, ProcessState
4
+
5
+ PROCESS_STATE : ProcessState = 'pending'
6
+
7
+
8
+ def get_process_state() -> ProcessState:
9
+ return PROCESS_STATE
10
+
11
+
12
+ def set_process_state(process_state : ProcessState) -> None:
13
+ global PROCESS_STATE
14
+
15
+ PROCESS_STATE = process_state
16
+
17
+
18
+ def is_checking() -> bool:
19
+ return get_process_state() == 'checking'
20
+
21
+
22
+ def is_processing() -> bool:
23
+ return get_process_state() == 'processing'
24
+
25
+
26
+ def is_stopping() -> bool:
27
+ return get_process_state() == 'stopping'
28
+
29
+
30
+ def is_pending() -> bool:
31
+ return get_process_state() == 'pending'
32
+
33
+
34
+ def check() -> None:
35
+ set_process_state('checking')
36
+
37
+
38
+ def start() -> None:
39
+ set_process_state('processing')
40
+
41
+
42
+ def stop() -> None:
43
+ set_process_state('stopping')
44
+
45
+
46
+ def end() -> None:
47
+ set_process_state('pending')
48
+
49
+
50
+ def manage(queue_payloads : List[QueuePayload]) -> Generator[QueuePayload, None, None]:
51
+ for query_payload in queue_payloads:
52
+ if is_processing():
53
+ yield query_payload
facefusion/processors/__init__.py ADDED
File without changes
facefusion/processors/frame/__init__.py ADDED
File without changes
facefusion/processors/frame/choices.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.common_helper import create_int_range
4
+ from facefusion.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameEnhancerModel, LipSyncerModel
5
+
6
+ face_debugger_items : List[FaceDebuggerItem] = [ 'bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender' ]
7
+ face_enhancer_models : List[FaceEnhancerModel] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'restoreformer_plus_plus' ]
8
+ face_swapper_models : List[FaceSwapperModel] = [ 'blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256' ]
9
+ frame_enhancer_models : List[FrameEnhancerModel] = [ 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'span_kendata_x4' ]
10
+ lip_syncer_models : List[LipSyncerModel] = [ 'wav2lip_gan' ]
11
+
12
+ face_enhancer_blend_range : List[int] = create_int_range(0, 100, 1)
13
+ frame_enhancer_blend_range : List[int] = create_int_range(0, 100, 1)
facefusion/processors/frame/core.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import importlib
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from queue import Queue
6
+ from types import ModuleType
7
+ from typing import Any, List
8
+ from tqdm import tqdm
9
+
10
+ import facefusion.globals
11
+ from facefusion.typing import ProcessFrames, QueuePayload
12
+ from facefusion.execution import encode_execution_providers
13
+ from facefusion import logger, wording
14
+
15
+ FRAME_PROCESSORS_MODULES : List[ModuleType] = []
16
+ FRAME_PROCESSORS_METHODS =\
17
+ [
18
+ 'get_frame_processor',
19
+ 'clear_frame_processor',
20
+ 'get_options',
21
+ 'set_options',
22
+ 'register_args',
23
+ 'apply_args',
24
+ 'pre_check',
25
+ 'post_check',
26
+ 'pre_process',
27
+ 'post_process',
28
+ 'get_reference_frame',
29
+ 'process_frame',
30
+ 'process_frames',
31
+ 'process_image',
32
+ 'process_video'
33
+ ]
34
+
35
+
36
+ def load_frame_processor_module(frame_processor : str) -> Any:
37
+ try:
38
+ frame_processor_module = importlib.import_module('facefusion.processors.frame.modules.' + frame_processor)
39
+ for method_name in FRAME_PROCESSORS_METHODS:
40
+ if not hasattr(frame_processor_module, method_name):
41
+ raise NotImplementedError
42
+ except ModuleNotFoundError as exception:
43
+ logger.error(wording.get('frame_processor_not_loaded').format(frame_processor = frame_processor), __name__.upper())
44
+ logger.debug(exception.msg, __name__.upper())
45
+ sys.exit(1)
46
+ except NotImplementedError:
47
+ logger.error(wording.get('frame_processor_not_implemented').format(frame_processor = frame_processor), __name__.upper())
48
+ sys.exit(1)
49
+ return frame_processor_module
50
+
51
+
52
+ def get_frame_processors_modules(frame_processors : List[str]) -> List[ModuleType]:
53
+ global FRAME_PROCESSORS_MODULES
54
+
55
+ if not FRAME_PROCESSORS_MODULES:
56
+ for frame_processor in frame_processors:
57
+ frame_processor_module = load_frame_processor_module(frame_processor)
58
+ FRAME_PROCESSORS_MODULES.append(frame_processor_module)
59
+ return FRAME_PROCESSORS_MODULES
60
+
61
+
62
+ def clear_frame_processors_modules() -> None:
63
+ global FRAME_PROCESSORS_MODULES
64
+
65
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
66
+ frame_processor_module.clear_frame_processor()
67
+ FRAME_PROCESSORS_MODULES = []
68
+
69
+
70
+ def multi_process_frames(source_paths : List[str], temp_frame_paths : List[str], process_frames : ProcessFrames) -> None:
71
+ queue_payloads = create_queue_payloads(temp_frame_paths)
72
+ with tqdm(total = len(queue_payloads), desc = wording.get('processing'), unit = 'frame', ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
73
+ progress.set_postfix(
74
+ {
75
+ 'execution_providers': encode_execution_providers(facefusion.globals.execution_providers),
76
+ 'execution_thread_count': facefusion.globals.execution_thread_count,
77
+ 'execution_queue_count': facefusion.globals.execution_queue_count
78
+ })
79
+ with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:
80
+ futures = []
81
+ queue : Queue[QueuePayload] = create_queue(queue_payloads)
82
+ queue_per_future = max(len(queue_payloads) // facefusion.globals.execution_thread_count * facefusion.globals.execution_queue_count, 1)
83
+ while not queue.empty():
84
+ future = executor.submit(process_frames, source_paths, pick_queue(queue, queue_per_future), progress.update)
85
+ futures.append(future)
86
+ for future_done in as_completed(futures):
87
+ future_done.result()
88
+
89
+
90
+ def create_queue(queue_payloads : List[QueuePayload]) -> Queue[QueuePayload]:
91
+ queue : Queue[QueuePayload] = Queue()
92
+ for queue_payload in queue_payloads:
93
+ queue.put(queue_payload)
94
+ return queue
95
+
96
+
97
+ def pick_queue(queue : Queue[QueuePayload], queue_per_future : int) -> List[QueuePayload]:
98
+ queues = []
99
+ for _ in range(queue_per_future):
100
+ if not queue.empty():
101
+ queues.append(queue.get())
102
+ return queues
103
+
104
+
105
+ def create_queue_payloads(temp_frame_paths : List[str]) -> List[QueuePayload]:
106
+ queue_payloads = []
107
+ temp_frame_paths = sorted(temp_frame_paths, key = os.path.basename)
108
+
109
+ for frame_number, frame_path in enumerate(temp_frame_paths):
110
+ frame_payload : QueuePayload =\
111
+ {
112
+ 'frame_number': frame_number,
113
+ 'frame_path': frame_path
114
+ }
115
+ queue_payloads.append(frame_payload)
116
+ return queue_payloads
facefusion/processors/frame/globals.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from facefusion.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameEnhancerModel, LipSyncerModel
4
+
5
+ face_debugger_items : Optional[List[FaceDebuggerItem]] = None
6
+ face_enhancer_model : Optional[FaceEnhancerModel] = None
7
+ face_enhancer_blend : Optional[int] = None
8
+ face_swapper_model : Optional[FaceSwapperModel] = None
9
+ frame_enhancer_model : Optional[FrameEnhancerModel] = None
10
+ frame_enhancer_blend : Optional[int] = None
11
+ lip_syncer_model : Optional[LipSyncerModel] = None
facefusion/processors/frame/modules/__init__.py ADDED
File without changes
facefusion/processors/frame/modules/face_debugger.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal
2
+ from argparse import ArgumentParser
3
+ import cv2
4
+ import numpy
5
+
6
+ import facefusion.globals
7
+ import facefusion.processors.frame.core as frame_processors
8
+ from facefusion import config, process_manager, wording
9
+ from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
10
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_region_mask, clear_face_occluder, clear_face_parser
11
+ from facefusion.face_helper import warp_face_by_face_landmark_5, categorize_age, categorize_gender
12
+ from facefusion.face_store import get_reference_faces
13
+ from facefusion.content_analyser import clear_content_analyser
14
+ from facefusion.typing import Face, VisionFrame, UpdateProcess, ProcessMode, QueuePayload
15
+ from facefusion.vision import read_image, read_static_image, write_image
16
+ from facefusion.processors.frame.typings import FaceDebuggerInputs
17
+ from facefusion.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices
18
+
19
+ NAME = __name__.upper()
20
+
21
+
22
+ def get_frame_processor() -> None:
23
+ pass
24
+
25
+
26
+ def clear_frame_processor() -> None:
27
+ pass
28
+
29
+
30
+ def get_options(key : Literal['model']) -> None:
31
+ pass
32
+
33
+
34
+ def set_options(key : Literal['model'], value : Any) -> None:
35
+ pass
36
+
37
+
38
+ def register_args(program : ArgumentParser) -> None:
39
+ program.add_argument('--face-debugger-items', help = wording.get('help.face_debugger_items').format(choices = ', '.join(frame_processors_choices.face_debugger_items)), default = config.get_str_list('frame_processors.face_debugger_items', 'face-landmark-5/68 face-mask'), choices = frame_processors_choices.face_debugger_items, nargs = '+', metavar = 'FACE_DEBUGGER_ITEMS')
40
+
41
+
42
+ def apply_args(program : ArgumentParser) -> None:
43
+ args = program.parse_args()
44
+ frame_processors_globals.face_debugger_items = args.face_debugger_items
45
+
46
+
47
+ def pre_check() -> bool:
48
+ return True
49
+
50
+
51
+ def post_check() -> bool:
52
+ return True
53
+
54
+
55
+ def pre_process(mode : ProcessMode) -> bool:
56
+ return True
57
+
58
+
59
+ def post_process() -> None:
60
+ read_static_image.cache_clear()
61
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
62
+ clear_frame_processor()
63
+ if facefusion.globals.video_memory_strategy == 'strict':
64
+ clear_face_analyser()
65
+ clear_content_analyser()
66
+ clear_face_occluder()
67
+ clear_face_parser()
68
+
69
+
70
+ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
71
+ primary_color = (0, 0, 255)
72
+ secondary_color = (0, 255, 0)
73
+ tertiary_color = (255, 255, 0)
74
+ bounding_box = target_face.bounding_box.astype(numpy.int32)
75
+ temp_vision_frame = temp_vision_frame.copy()
76
+ has_face_landmark_5_fallback = numpy.array_equal(target_face.landmarks.get('5'), target_face.landmarks.get('5/68'))
77
+
78
+ if 'bounding-box' in frame_processors_globals.face_debugger_items:
79
+ cv2.rectangle(temp_vision_frame, (bounding_box[0], bounding_box[1]), (bounding_box[2], bounding_box[3]), primary_color, 2)
80
+ if 'face-mask' in frame_processors_globals.face_debugger_items:
81
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'arcface_128_v2', (512, 512))
82
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
83
+ temp_size = temp_vision_frame.shape[:2][::-1]
84
+ crop_mask_list = []
85
+ if 'box' in facefusion.globals.face_mask_types:
86
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], 0, facefusion.globals.face_mask_padding)
87
+ crop_mask_list.append(box_mask)
88
+ if 'occlusion' in facefusion.globals.face_mask_types:
89
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
90
+ crop_mask_list.append(occlusion_mask)
91
+ if 'region' in facefusion.globals.face_mask_types:
92
+ region_mask = create_region_mask(crop_vision_frame, facefusion.globals.face_mask_regions)
93
+ crop_mask_list.append(region_mask)
94
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
95
+ crop_mask = (crop_mask * 255).astype(numpy.uint8)
96
+ inverse_vision_frame = cv2.warpAffine(crop_mask, inverse_matrix, temp_size)
97
+ inverse_vision_frame = cv2.threshold(inverse_vision_frame, 100, 255, cv2.THRESH_BINARY)[1]
98
+ inverse_vision_frame[inverse_vision_frame > 0] = 255
99
+ inverse_contours = cv2.findContours(inverse_vision_frame, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)[0]
100
+ cv2.drawContours(temp_vision_frame, inverse_contours, -1, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2)
101
+ if 'face-landmark-5' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5')):
102
+ face_landmark_5 = target_face.landmarks.get('5').astype(numpy.int32)
103
+ for index in range(face_landmark_5.shape[0]):
104
+ cv2.circle(temp_vision_frame, (face_landmark_5[index][0], face_landmark_5[index][1]), 3, primary_color, -1)
105
+ if 'face-landmark-5/68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5/68')):
106
+ face_landmark_5_68 = target_face.landmarks.get('5/68').astype(numpy.int32)
107
+ for index in range(face_landmark_5_68.shape[0]):
108
+ cv2.circle(temp_vision_frame, (face_landmark_5_68[index][0], face_landmark_5_68[index][1]), 3, tertiary_color if has_face_landmark_5_fallback else secondary_color, -1)
109
+ if 'face-landmark-68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('68')):
110
+ face_landmark_68 = target_face.landmarks.get('68').astype(numpy.int32)
111
+ for index in range(face_landmark_68.shape[0]):
112
+ cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, secondary_color, -1)
113
+ if bounding_box[3] - bounding_box[1] > 50 and bounding_box[2] - bounding_box[0] > 50:
114
+ top = bounding_box[1]
115
+ left = bounding_box[0] - 20
116
+ if 'face-detector-score' in frame_processors_globals.face_debugger_items:
117
+ face_score_text = str(round(target_face.scores.get('detector'), 2))
118
+ top = top + 20
119
+ cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
120
+ if 'face-landmarker-score' in frame_processors_globals.face_debugger_items:
121
+ face_score_text = str(round(target_face.scores.get('landmarker'), 2))
122
+ top = top + 20
123
+ cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2)
124
+ if 'age' in frame_processors_globals.face_debugger_items:
125
+ face_age_text = categorize_age(target_face.age)
126
+ top = top + 20
127
+ cv2.putText(temp_vision_frame, face_age_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
128
+ if 'gender' in frame_processors_globals.face_debugger_items:
129
+ face_gender_text = categorize_gender(target_face.gender)
130
+ top = top + 20
131
+ cv2.putText(temp_vision_frame, face_gender_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
132
+ return temp_vision_frame
133
+
134
+
135
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
136
+ pass
137
+
138
+
139
+ def process_frame(inputs : FaceDebuggerInputs) -> VisionFrame:
140
+ reference_faces = inputs.get('reference_faces')
141
+ target_vision_frame = inputs.get('target_vision_frame')
142
+
143
+ if facefusion.globals.face_selector_mode == 'many':
144
+ many_faces = get_many_faces(target_vision_frame)
145
+ if many_faces:
146
+ for target_face in many_faces:
147
+ target_vision_frame = debug_face(target_face, target_vision_frame)
148
+ if facefusion.globals.face_selector_mode == 'one':
149
+ target_face = get_one_face(target_vision_frame)
150
+ if target_face:
151
+ target_vision_frame = debug_face(target_face, target_vision_frame)
152
+ if facefusion.globals.face_selector_mode == 'reference':
153
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
154
+ if similar_faces:
155
+ for similar_face in similar_faces:
156
+ target_vision_frame = debug_face(similar_face, target_vision_frame)
157
+ return target_vision_frame
158
+
159
+
160
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProcess) -> None:
161
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
162
+
163
+ for queue_payload in process_manager.manage(queue_payloads):
164
+ target_vision_path = queue_payload['frame_path']
165
+ target_vision_frame = read_image(target_vision_path)
166
+ output_vision_frame = process_frame(
167
+ {
168
+ 'reference_faces': reference_faces,
169
+ 'target_vision_frame': target_vision_frame
170
+ })
171
+ write_image(target_vision_path, output_vision_frame)
172
+ update_progress()
173
+
174
+
175
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
176
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
177
+ target_vision_frame = read_static_image(target_path)
178
+ output_vision_frame = process_frame(
179
+ {
180
+ 'reference_faces': reference_faces,
181
+ 'target_vision_frame': target_vision_frame
182
+ })
183
+ write_image(output_path, output_vision_frame)
184
+
185
+
186
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
187
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/face_enhancer.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import cv2
5
+ import threading
6
+ import numpy
7
+ import onnxruntime
8
+
9
+ import facefusion.globals
10
+ import facefusion.processors.frame.core as frame_processors
11
+ from facefusion import config, process_manager, logger, wording
12
+ from facefusion.face_analyser import get_many_faces, clear_face_analyser, find_similar_faces, get_one_face
13
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, clear_face_occluder
14
+ from facefusion.face_helper import warp_face_by_face_landmark_5, paste_back
15
+ from facefusion.execution import apply_execution_provider_options
16
+ from facefusion.content_analyser import clear_content_analyser
17
+ from facefusion.face_store import get_reference_faces
18
+ from facefusion.normalizer import normalize_output_path
19
+ from facefusion.typing import Face, VisionFrame, UpdateProcess, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
20
+ from facefusion.common_helper import create_metavar
21
+ from facefusion.filesystem import is_file, is_image, is_video, resolve_relative_path
22
+ from facefusion.download import conditional_download, is_download_done
23
+ from facefusion.vision import read_image, read_static_image, write_image
24
+ from facefusion.processors.frame.typings import FaceEnhancerInputs
25
+ from facefusion.processors.frame import globals as frame_processors_globals
26
+ from facefusion.processors.frame import choices as frame_processors_choices
27
+
28
+ FRAME_PROCESSOR = None
29
+ THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
30
+ THREAD_LOCK : threading.Lock = threading.Lock()
31
+ NAME = __name__.upper()
32
+ MODELS : ModelSet =\
33
+ {
34
+ 'codeformer':
35
+ {
36
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx',
37
+ 'path': resolve_relative_path('../.assets/models/codeformer.onnx'),
38
+ 'template': 'ffhq_512',
39
+ 'size': (512, 512)
40
+ },
41
+ 'gfpgan_1.2':
42
+ {
43
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.2.onnx',
44
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.2.onnx'),
45
+ 'template': 'ffhq_512',
46
+ 'size': (512, 512)
47
+ },
48
+ 'gfpgan_1.3':
49
+ {
50
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.3.onnx',
51
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.3.onnx'),
52
+ 'template': 'ffhq_512',
53
+ 'size': (512, 512)
54
+ },
55
+ 'gfpgan_1.4':
56
+ {
57
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.4.onnx',
58
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.4.onnx'),
59
+ 'template': 'ffhq_512',
60
+ 'size': (512, 512)
61
+ },
62
+ 'gpen_bfr_256':
63
+ {
64
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_256.onnx',
65
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_256.onnx'),
66
+ 'template': 'arcface_128_v2',
67
+ 'size': (256, 256)
68
+ },
69
+ 'gpen_bfr_512':
70
+ {
71
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_512.onnx',
72
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_512.onnx'),
73
+ 'template': 'ffhq_512',
74
+ 'size': (512, 512)
75
+ },
76
+ 'restoreformer_plus_plus':
77
+ {
78
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/restoreformer_plus_plus.onnx',
79
+ 'path': resolve_relative_path('../.assets/models/restoreformer_plus_plus.onnx'),
80
+ 'template': 'ffhq_512',
81
+ 'size': (512, 512)
82
+ }
83
+ }
84
+ OPTIONS : Optional[OptionsWithModel] = None
85
+
86
+
87
+ def get_frame_processor() -> Any:
88
+ global FRAME_PROCESSOR
89
+
90
+ with THREAD_LOCK:
91
+ while process_manager.is_checking():
92
+ sleep(0.5)
93
+ if FRAME_PROCESSOR is None:
94
+ model_path = get_options('model').get('path')
95
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
96
+ return FRAME_PROCESSOR
97
+
98
+
99
+ def clear_frame_processor() -> None:
100
+ global FRAME_PROCESSOR
101
+
102
+ FRAME_PROCESSOR = None
103
+
104
+
105
+ def get_options(key : Literal['model']) -> Any:
106
+ global OPTIONS
107
+
108
+ if OPTIONS is None:
109
+ OPTIONS =\
110
+ {
111
+ 'model': MODELS[frame_processors_globals.face_enhancer_model]
112
+ }
113
+ return OPTIONS.get(key)
114
+
115
+
116
+ def set_options(key : Literal['model'], value : Any) -> None:
117
+ global OPTIONS
118
+
119
+ OPTIONS[key] = value
120
+
121
+
122
+ def register_args(program : ArgumentParser) -> None:
123
+ program.add_argument('--face-enhancer-model', help = wording.get('help.face_enhancer_model'), default = config.get_str_value('frame_processors.face_enhancer_model', 'gfpgan_1.4'), choices = frame_processors_choices.face_enhancer_models)
124
+ program.add_argument('--face-enhancer-blend', help = wording.get('help.face_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.face_enhancer_blend', '80'), choices = frame_processors_choices.face_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.face_enhancer_blend_range))
125
+
126
+
127
+ def apply_args(program : ArgumentParser) -> None:
128
+ args = program.parse_args()
129
+ frame_processors_globals.face_enhancer_model = args.face_enhancer_model
130
+ frame_processors_globals.face_enhancer_blend = args.face_enhancer_blend
131
+
132
+
133
+ def pre_check() -> bool:
134
+ if not facefusion.globals.skip_download:
135
+ download_directory_path = resolve_relative_path('../.assets/models')
136
+ model_url = get_options('model').get('url')
137
+ process_manager.check()
138
+ conditional_download(download_directory_path, [ model_url ])
139
+ process_manager.end()
140
+ return True
141
+
142
+
143
+ def post_check() -> bool:
144
+ model_url = get_options('model').get('url')
145
+ model_path = get_options('model').get('path')
146
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
147
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
148
+ return False
149
+ elif not is_file(model_path):
150
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
151
+ return False
152
+ return True
153
+
154
+
155
+ def pre_process(mode : ProcessMode) -> bool:
156
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
157
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
158
+ return False
159
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
160
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
161
+ return False
162
+ return True
163
+
164
+
165
+ def post_process() -> None:
166
+ read_static_image.cache_clear()
167
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
168
+ clear_frame_processor()
169
+ if facefusion.globals.video_memory_strategy == 'strict':
170
+ clear_face_analyser()
171
+ clear_content_analyser()
172
+ clear_face_occluder()
173
+
174
+
175
+ def enhance_face(target_face: Face, temp_vision_frame : VisionFrame) -> VisionFrame:
176
+ model_template = get_options('model').get('template')
177
+ model_size = get_options('model').get('size')
178
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size)
179
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, (0, 0, 0, 0))
180
+ crop_mask_list =\
181
+ [
182
+ box_mask
183
+ ]
184
+
185
+ if 'occlusion' in facefusion.globals.face_mask_types:
186
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
187
+ crop_mask_list.append(occlusion_mask)
188
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
189
+ crop_vision_frame = apply_enhance(crop_vision_frame)
190
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
191
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
192
+ paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
193
+ temp_vision_frame = blend_frame(temp_vision_frame, paste_vision_frame)
194
+ return temp_vision_frame
195
+
196
+
197
+ def apply_enhance(crop_vision_frame : VisionFrame) -> VisionFrame:
198
+ frame_processor = get_frame_processor()
199
+ frame_processor_inputs = {}
200
+
201
+ for frame_processor_input in frame_processor.get_inputs():
202
+ if frame_processor_input.name == 'input':
203
+ frame_processor_inputs[frame_processor_input.name] = crop_vision_frame
204
+ if frame_processor_input.name == 'weight':
205
+ weight = numpy.array([ 1 ], dtype = numpy.double)
206
+ frame_processor_inputs[frame_processor_input.name] = weight
207
+ with THREAD_SEMAPHORE:
208
+ crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
209
+ return crop_vision_frame
210
+
211
+
212
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
213
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
214
+ crop_vision_frame = (crop_vision_frame - 0.5) / 0.5
215
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
216
+ return crop_vision_frame
217
+
218
+
219
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
220
+ crop_vision_frame = numpy.clip(crop_vision_frame, -1, 1)
221
+ crop_vision_frame = (crop_vision_frame + 1) / 2
222
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
223
+ crop_vision_frame = (crop_vision_frame * 255.0).round()
224
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1]
225
+ return crop_vision_frame
226
+
227
+
228
+ def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame:
229
+ face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100)
230
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, face_enhancer_blend, paste_vision_frame, 1 - face_enhancer_blend, 0)
231
+ return temp_vision_frame
232
+
233
+
234
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
235
+ return enhance_face(target_face, temp_vision_frame)
236
+
237
+
238
+ def process_frame(inputs : FaceEnhancerInputs) -> VisionFrame:
239
+ reference_faces = inputs.get('reference_faces')
240
+ target_vision_frame = inputs.get('target_vision_frame')
241
+
242
+ if facefusion.globals.face_selector_mode == 'many':
243
+ many_faces = get_many_faces(target_vision_frame)
244
+ if many_faces:
245
+ for target_face in many_faces:
246
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
247
+ if facefusion.globals.face_selector_mode == 'one':
248
+ target_face = get_one_face(target_vision_frame)
249
+ if target_face:
250
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
251
+ if facefusion.globals.face_selector_mode == 'reference':
252
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
253
+ if similar_faces:
254
+ for similar_face in similar_faces:
255
+ target_vision_frame = enhance_face(similar_face, target_vision_frame)
256
+ return target_vision_frame
257
+
258
+
259
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProcess) -> None:
260
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
261
+
262
+ for queue_payload in process_manager.manage(queue_payloads):
263
+ target_vision_path = queue_payload['frame_path']
264
+ target_vision_frame = read_image(target_vision_path)
265
+ output_vision_frame = process_frame(
266
+ {
267
+ 'reference_faces': reference_faces,
268
+ 'target_vision_frame': target_vision_frame
269
+ })
270
+ write_image(target_vision_path, output_vision_frame)
271
+ update_progress()
272
+
273
+
274
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
275
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
276
+ target_vision_frame = read_static_image(target_path)
277
+ output_vision_frame = process_frame(
278
+ {
279
+ 'reference_faces': reference_faces,
280
+ 'target_vision_frame': target_vision_frame
281
+ })
282
+ write_image(output_path, output_vision_frame)
283
+
284
+
285
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
286
+ frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/face_swapper.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import threading
5
+ import numpy
6
+ import onnx
7
+ import onnxruntime
8
+ from onnx import numpy_helper
9
+
10
+ import facefusion.globals
11
+ import facefusion.processors.frame.core as frame_processors
12
+ from facefusion import config, process_manager, logger, wording
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.face_analyser import get_one_face, get_average_face, get_many_faces, find_similar_faces, clear_face_analyser
15
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_region_mask, clear_face_occluder, clear_face_parser
16
+ from facefusion.face_helper import warp_face_by_face_landmark_5, paste_back
17
+ from facefusion.face_store import get_reference_faces
18
+ from facefusion.common_helper import extract_major_version
19
+ from facefusion.content_analyser import clear_content_analyser
20
+ from facefusion.normalizer import normalize_output_path
21
+ from facefusion.typing import Face, Embedding, VisionFrame, UpdateProcess, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
22
+ from facefusion.filesystem import is_file, is_image, has_image, is_video, filter_image_paths, resolve_relative_path
23
+ from facefusion.download import conditional_download, is_download_done
24
+ from facefusion.vision import read_image, read_static_image, read_static_images, write_image
25
+ from facefusion.processors.frame.typings import FaceSwapperInputs
26
+ from facefusion.processors.frame import globals as frame_processors_globals
27
+ from facefusion.processors.frame import choices as frame_processors_choices
28
+
29
+ FRAME_PROCESSOR = None
30
+ MODEL_MATRIX = None
31
+ THREAD_LOCK : threading.Lock = threading.Lock()
32
+ NAME = __name__.upper()
33
+ MODELS : ModelSet =\
34
+ {
35
+ 'blendswap_256':
36
+ {
37
+ 'type': 'blendswap',
38
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/blendswap_256.onnx',
39
+ 'path': resolve_relative_path('../.assets/models/blendswap_256.onnx'),
40
+ 'template': 'ffhq_512',
41
+ 'size': (256, 256),
42
+ 'mean': [ 0.0, 0.0, 0.0 ],
43
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
44
+ },
45
+ 'inswapper_128':
46
+ {
47
+ 'type': 'inswapper',
48
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx',
49
+ 'path': resolve_relative_path('../.assets/models/inswapper_128.onnx'),
50
+ 'template': 'arcface_128_v2',
51
+ 'size': (128, 128),
52
+ 'mean': [ 0.0, 0.0, 0.0 ],
53
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
54
+ },
55
+ 'inswapper_128_fp16':
56
+ {
57
+ 'type': 'inswapper',
58
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx',
59
+ 'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx'),
60
+ 'template': 'arcface_128_v2',
61
+ 'size': (128, 128),
62
+ 'mean': [ 0.0, 0.0, 0.0 ],
63
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
64
+ },
65
+ 'simswap_256':
66
+ {
67
+ 'type': 'simswap',
68
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_256.onnx',
69
+ 'path': resolve_relative_path('../.assets/models/simswap_256.onnx'),
70
+ 'template': 'arcface_112_v1',
71
+ 'size': (256, 256),
72
+ 'mean': [ 0.485, 0.456, 0.406 ],
73
+ 'standard_deviation': [ 0.229, 0.224, 0.225 ]
74
+ },
75
+ 'simswap_512_unofficial':
76
+ {
77
+ 'type': 'simswap',
78
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_512_unofficial.onnx',
79
+ 'path': resolve_relative_path('../.assets/models/simswap_512_unofficial.onnx'),
80
+ 'template': 'arcface_112_v1',
81
+ 'size': (512, 512),
82
+ 'mean': [ 0.0, 0.0, 0.0 ],
83
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
84
+ },
85
+ 'uniface_256':
86
+ {
87
+ 'type': 'uniface',
88
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/uniface_256.onnx',
89
+ 'path': resolve_relative_path('../.assets/models/uniface_256.onnx'),
90
+ 'template': 'ffhq_512',
91
+ 'size': (256, 256),
92
+ 'mean': [ 0.0, 0.0, 0.0 ],
93
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
94
+ }
95
+ }
96
+ OPTIONS : Optional[OptionsWithModel] = None
97
+
98
+
99
+ def get_frame_processor() -> Any:
100
+ global FRAME_PROCESSOR
101
+
102
+ with THREAD_LOCK:
103
+ while process_manager.is_checking():
104
+ sleep(0.5)
105
+ if FRAME_PROCESSOR is None:
106
+ model_path = get_options('model').get('path')
107
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
108
+ return FRAME_PROCESSOR
109
+
110
+
111
+ def clear_frame_processor() -> None:
112
+ global FRAME_PROCESSOR
113
+
114
+ FRAME_PROCESSOR = None
115
+
116
+
117
+ def get_model_matrix() -> Any:
118
+ global MODEL_MATRIX
119
+
120
+ with THREAD_LOCK:
121
+ while process_manager.is_checking():
122
+ sleep(0.5)
123
+ if MODEL_MATRIX is None:
124
+ model_path = get_options('model').get('path')
125
+ model = onnx.load(model_path)
126
+ MODEL_MATRIX = numpy_helper.to_array(model.graph.initializer[-1])
127
+ return MODEL_MATRIX
128
+
129
+
130
+ def clear_model_matrix() -> None:
131
+ global MODEL_MATRIX
132
+
133
+ MODEL_MATRIX = None
134
+
135
+
136
+ def get_options(key : Literal['model']) -> Any:
137
+ global OPTIONS
138
+
139
+ if OPTIONS is None:
140
+ OPTIONS =\
141
+ {
142
+ 'model': MODELS[frame_processors_globals.face_swapper_model]
143
+ }
144
+ return OPTIONS.get(key)
145
+
146
+
147
+ def set_options(key : Literal['model'], value : Any) -> None:
148
+ global OPTIONS
149
+
150
+ OPTIONS[key] = value
151
+
152
+
153
+ def register_args(program : ArgumentParser) -> None:
154
+ onnxruntime_version = extract_major_version(onnxruntime.__version__)
155
+ if onnxruntime_version > (1, 16):
156
+ face_swapper_model_fallback = 'inswapper_128'
157
+ else:
158
+ face_swapper_model_fallback = 'inswapper_128_fp16'
159
+ program.add_argument('--face-swapper-model', help = wording.get('help.face_swapper_model'), default = config.get_str_value('frame_processors.face_swapper_model', face_swapper_model_fallback), choices = frame_processors_choices.face_swapper_models)
160
+
161
+
162
+ def apply_args(program : ArgumentParser) -> None:
163
+ args = program.parse_args()
164
+ frame_processors_globals.face_swapper_model = args.face_swapper_model
165
+ if args.face_swapper_model == 'blendswap_256':
166
+ facefusion.globals.face_recognizer_model = 'arcface_blendswap'
167
+ if args.face_swapper_model == 'inswapper_128' or args.face_swapper_model == 'inswapper_128_fp16':
168
+ facefusion.globals.face_recognizer_model = 'arcface_inswapper'
169
+ if args.face_swapper_model == 'simswap_256' or args.face_swapper_model == 'simswap_512_unofficial':
170
+ facefusion.globals.face_recognizer_model = 'arcface_simswap'
171
+ if args.face_swapper_model == 'uniface_256':
172
+ facefusion.globals.face_recognizer_model = 'arcface_uniface'
173
+
174
+
175
+ def pre_check() -> bool:
176
+ if not facefusion.globals.skip_download:
177
+ download_directory_path = resolve_relative_path('../.assets/models')
178
+ model_url = get_options('model').get('url')
179
+ process_manager.check()
180
+ conditional_download(download_directory_path, [ model_url ])
181
+ process_manager.end()
182
+ return True
183
+
184
+
185
+ def post_check() -> bool:
186
+ model_url = get_options('model').get('url')
187
+ model_path = get_options('model').get('path')
188
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
189
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
190
+ return False
191
+ elif not is_file(model_path):
192
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
193
+ return False
194
+ return True
195
+
196
+
197
+ def pre_process(mode : ProcessMode) -> bool:
198
+ if not has_image(facefusion.globals.source_paths):
199
+ logger.error(wording.get('select_image_source') + wording.get('exclamation_mark'), NAME)
200
+ return False
201
+ source_image_paths = filter_image_paths(facefusion.globals.source_paths)
202
+ source_frames = read_static_images(source_image_paths)
203
+ for source_frame in source_frames:
204
+ if not get_one_face(source_frame):
205
+ logger.error(wording.get('no_source_face_detected') + wording.get('exclamation_mark'), NAME)
206
+ return False
207
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
208
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
209
+ return False
210
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
211
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
212
+ return False
213
+ return True
214
+
215
+
216
+ def post_process() -> None:
217
+ read_static_image.cache_clear()
218
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
219
+ clear_frame_processor()
220
+ clear_model_matrix()
221
+ if facefusion.globals.video_memory_strategy == 'strict':
222
+ clear_face_analyser()
223
+ clear_content_analyser()
224
+ clear_face_occluder()
225
+ clear_face_parser()
226
+
227
+
228
+ def swap_face(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
229
+ model_template = get_options('model').get('template')
230
+ model_size = get_options('model').get('size')
231
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size)
232
+ crop_mask_list = []
233
+
234
+ if 'box' in facefusion.globals.face_mask_types:
235
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, facefusion.globals.face_mask_padding)
236
+ crop_mask_list.append(box_mask)
237
+ if 'occlusion' in facefusion.globals.face_mask_types:
238
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
239
+ crop_mask_list.append(occlusion_mask)
240
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
241
+ crop_vision_frame = apply_swap(source_face, crop_vision_frame)
242
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
243
+ if 'region' in facefusion.globals.face_mask_types:
244
+ region_mask = create_region_mask(crop_vision_frame, facefusion.globals.face_mask_regions)
245
+ crop_mask_list.append(region_mask)
246
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
247
+ temp_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
248
+ return temp_vision_frame
249
+
250
+
251
+ def apply_swap(source_face : Face, crop_vision_frame : VisionFrame) -> VisionFrame:
252
+ frame_processor = get_frame_processor()
253
+ model_type = get_options('model').get('type')
254
+ frame_processor_inputs = {}
255
+
256
+ for frame_processor_input in frame_processor.get_inputs():
257
+ if frame_processor_input.name == 'source':
258
+ if model_type == 'blendswap' or model_type == 'uniface':
259
+ frame_processor_inputs[frame_processor_input.name] = prepare_source_frame(source_face)
260
+ else:
261
+ frame_processor_inputs[frame_processor_input.name] = prepare_source_embedding(source_face)
262
+ if frame_processor_input.name == 'target':
263
+ frame_processor_inputs[frame_processor_input.name] = crop_vision_frame
264
+ crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
265
+ return crop_vision_frame
266
+
267
+
268
+ def prepare_source_frame(source_face : Face) -> VisionFrame:
269
+ model_type = get_options('model').get('type')
270
+ source_vision_frame = read_static_image(facefusion.globals.source_paths[0])
271
+ if model_type == 'blendswap':
272
+ source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'arcface_112_v2', (112, 112))
273
+ if model_type == 'uniface':
274
+ source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'ffhq_512', (256, 256))
275
+ source_vision_frame = source_vision_frame[:, :, ::-1] / 255.0
276
+ source_vision_frame = source_vision_frame.transpose(2, 0, 1)
277
+ source_vision_frame = numpy.expand_dims(source_vision_frame, axis = 0).astype(numpy.float32)
278
+ return source_vision_frame
279
+
280
+
281
+ def prepare_source_embedding(source_face : Face) -> Embedding:
282
+ model_type = get_options('model').get('type')
283
+ if model_type == 'inswapper':
284
+ model_matrix = get_model_matrix()
285
+ source_embedding = source_face.embedding.reshape((1, -1))
286
+ source_embedding = numpy.dot(source_embedding, model_matrix) / numpy.linalg.norm(source_embedding)
287
+ else:
288
+ source_embedding = source_face.normed_embedding.reshape(1, -1)
289
+ return source_embedding
290
+
291
+
292
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
293
+ model_mean = get_options('model').get('mean')
294
+ model_standard_deviation = get_options('model').get('standard_deviation')
295
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
296
+ crop_vision_frame = (crop_vision_frame - model_mean) / model_standard_deviation
297
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1)
298
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0).astype(numpy.float32)
299
+ return crop_vision_frame
300
+
301
+
302
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
303
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
304
+ crop_vision_frame = (crop_vision_frame * 255.0).round()
305
+ crop_vision_frame = crop_vision_frame[:, :, ::-1]
306
+ return crop_vision_frame
307
+
308
+
309
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
310
+ return swap_face(source_face, target_face, temp_vision_frame)
311
+
312
+
313
+ def process_frame(inputs : FaceSwapperInputs) -> VisionFrame:
314
+ reference_faces = inputs.get('reference_faces')
315
+ source_face = inputs.get('source_face')
316
+ target_vision_frame = inputs.get('target_vision_frame')
317
+
318
+ if facefusion.globals.face_selector_mode == 'many':
319
+ many_faces = get_many_faces(target_vision_frame)
320
+ if many_faces:
321
+ for target_face in many_faces:
322
+ target_vision_frame = swap_face(source_face, target_face, target_vision_frame)
323
+ if facefusion.globals.face_selector_mode == 'one':
324
+ target_face = get_one_face(target_vision_frame)
325
+ if target_face:
326
+ target_vision_frame = swap_face(source_face, target_face, target_vision_frame)
327
+ if facefusion.globals.face_selector_mode == 'reference':
328
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
329
+ if similar_faces:
330
+ for similar_face in similar_faces:
331
+ target_vision_frame = swap_face(source_face, similar_face, target_vision_frame)
332
+ return target_vision_frame
333
+
334
+
335
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProcess) -> None:
336
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
337
+ source_frames = read_static_images(source_paths)
338
+ source_face = get_average_face(source_frames)
339
+
340
+ for queue_payload in process_manager.manage(queue_payloads):
341
+ target_vision_path = queue_payload['frame_path']
342
+ target_vision_frame = read_image(target_vision_path)
343
+ output_vision_frame = process_frame(
344
+ {
345
+ 'reference_faces': reference_faces,
346
+ 'source_face': source_face,
347
+ 'target_vision_frame': target_vision_frame
348
+ })
349
+ write_image(target_vision_path, output_vision_frame)
350
+ update_progress()
351
+
352
+
353
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
354
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
355
+ source_frames = read_static_images(source_paths)
356
+ source_face = get_average_face(source_frames)
357
+ target_vision_frame = read_static_image(target_path)
358
+ output_vision_frame = process_frame(
359
+ {
360
+ 'reference_faces': reference_faces,
361
+ 'source_face': source_face,
362
+ 'target_vision_frame': target_vision_frame
363
+ })
364
+ write_image(output_path, output_vision_frame)
365
+
366
+
367
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
368
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/frame_enhancer.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import threading
5
+ import cv2
6
+ import numpy
7
+ import onnxruntime
8
+
9
+ import facefusion.globals
10
+ import facefusion.processors.frame.core as frame_processors
11
+ from facefusion import config, process_manager, logger, wording
12
+ from facefusion.face_analyser import clear_face_analyser
13
+ from facefusion.content_analyser import clear_content_analyser
14
+ from facefusion.execution import apply_execution_provider_options
15
+ from facefusion.normalizer import normalize_output_path
16
+ from facefusion.typing import Face, VisionFrame, UpdateProcess, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
17
+ from facefusion.common_helper import create_metavar
18
+ from facefusion.filesystem import is_file, resolve_relative_path, is_image, is_video
19
+ from facefusion.download import conditional_download, is_download_done
20
+ from facefusion.vision import read_image, read_static_image, write_image, merge_tile_frames, create_tile_frames
21
+ from facefusion.processors.frame.typings import FrameEnhancerInputs
22
+ from facefusion.processors.frame import globals as frame_processors_globals
23
+ from facefusion.processors.frame import choices as frame_processors_choices
24
+
25
+ FRAME_PROCESSOR = None
26
+ THREAD_LOCK : threading.Lock = threading.Lock()
27
+ NAME = __name__.upper()
28
+ MODELS : ModelSet =\
29
+ {
30
+ 'lsdir_x4':
31
+ {
32
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/lsdir_x4.onnx',
33
+ 'path': resolve_relative_path('../.assets/models/lsdir_x4.onnx'),
34
+ 'size': (128, 8, 2),
35
+ 'scale': 4
36
+ },
37
+ 'nomos8k_sc_x4':
38
+ {
39
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/nomos8k_sc_x4.onnx',
40
+ 'path': resolve_relative_path('../.assets/models/nomos8k_sc_x4.onnx'),
41
+ 'size': (128, 8, 2),
42
+ 'scale': 4
43
+ },
44
+ 'real_esrgan_x4':
45
+ {
46
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4.onnx',
47
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x4.onnx'),
48
+ 'size': (128, 8, 2),
49
+ 'scale': 4
50
+ },
51
+ 'real_esrgan_x4_fp16':
52
+ {
53
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4_fp16.onnx',
54
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x4_fp16.onnx'),
55
+ 'size': (128, 8, 2),
56
+ 'scale': 4
57
+ },
58
+ 'span_kendata_x4':
59
+ {
60
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/span_kendata_x4.onnx',
61
+ 'path': resolve_relative_path('../.assets/models/span_kendata_x4.onnx'),
62
+ 'size': (128, 8, 2),
63
+ 'scale': 4
64
+ }
65
+ }
66
+ OPTIONS : Optional[OptionsWithModel] = None
67
+
68
+
69
+ def get_frame_processor() -> Any:
70
+ global FRAME_PROCESSOR
71
+
72
+ with THREAD_LOCK:
73
+ while process_manager.is_checking():
74
+ sleep(0.5)
75
+ if FRAME_PROCESSOR is None:
76
+ model_path = get_options('model').get('path')
77
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
78
+ return FRAME_PROCESSOR
79
+
80
+
81
+ def clear_frame_processor() -> None:
82
+ global FRAME_PROCESSOR
83
+
84
+ FRAME_PROCESSOR = None
85
+
86
+
87
+ def get_options(key : Literal['model']) -> Any:
88
+ global OPTIONS
89
+
90
+ if OPTIONS is None:
91
+ OPTIONS =\
92
+ {
93
+ 'model': MODELS[frame_processors_globals.frame_enhancer_model]
94
+ }
95
+ return OPTIONS.get(key)
96
+
97
+
98
+ def set_options(key : Literal['model'], value : Any) -> None:
99
+ global OPTIONS
100
+
101
+ OPTIONS[key] = value
102
+
103
+
104
+ def register_args(program : ArgumentParser) -> None:
105
+ program.add_argument('--frame-enhancer-model', help = wording.get('help.frame_enhancer_model'), default = config.get_str_value('frame_processors.frame_enhancer_model', 'span_kendata_x4'), choices = frame_processors_choices.frame_enhancer_models)
106
+ program.add_argument('--frame-enhancer-blend', help = wording.get('help.frame_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.frame_enhancer_blend', '80'), choices = frame_processors_choices.frame_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.frame_enhancer_blend_range))
107
+
108
+
109
+ def apply_args(program : ArgumentParser) -> None:
110
+ args = program.parse_args()
111
+ frame_processors_globals.frame_enhancer_model = args.frame_enhancer_model
112
+ frame_processors_globals.frame_enhancer_blend = args.frame_enhancer_blend
113
+
114
+
115
+ def pre_check() -> bool:
116
+ if not facefusion.globals.skip_download:
117
+ download_directory_path = resolve_relative_path('../.assets/models')
118
+ model_url = get_options('model').get('url')
119
+ process_manager.check()
120
+ conditional_download(download_directory_path, [ model_url ])
121
+ process_manager.end()
122
+ return True
123
+
124
+
125
+ def post_check() -> bool:
126
+ model_url = get_options('model').get('url')
127
+ model_path = get_options('model').get('path')
128
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
129
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
130
+ return False
131
+ elif not is_file(model_path):
132
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
133
+ return False
134
+ return True
135
+
136
+
137
+ def pre_process(mode : ProcessMode) -> bool:
138
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
139
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
140
+ return False
141
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
142
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
143
+ return False
144
+ return True
145
+
146
+
147
+ def post_process() -> None:
148
+ read_static_image.cache_clear()
149
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
150
+ clear_frame_processor()
151
+ if facefusion.globals.video_memory_strategy == 'strict':
152
+ clear_face_analyser()
153
+ clear_content_analyser()
154
+
155
+
156
+ def enhance_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
157
+ frame_processor = get_frame_processor()
158
+ size = get_options('model').get('size')
159
+ scale = get_options('model').get('scale')
160
+ temp_height, temp_width = temp_vision_frame.shape[:2]
161
+ tile_vision_frames, pad_width, pad_height = create_tile_frames(temp_vision_frame, size)
162
+
163
+ for index, tile_vision_frame in enumerate(tile_vision_frames):
164
+ tile_vision_frame = frame_processor.run(None,
165
+ {
166
+ frame_processor.get_inputs()[0].name : prepare_tile_frame(tile_vision_frame)
167
+ })[0]
168
+ tile_vision_frames[index] = normalize_tile_frame(tile_vision_frame)
169
+ merge_vision_frame = merge_tile_frames(tile_vision_frames, temp_width * scale, temp_height * scale, pad_width * scale, pad_height * scale, (size[0] * scale, size[1] * scale, size[2] * scale))
170
+ temp_vision_frame = blend_frame(temp_vision_frame, merge_vision_frame)
171
+ return temp_vision_frame
172
+
173
+
174
+ def prepare_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame:
175
+ vision_tile_frame = numpy.expand_dims(vision_tile_frame[:,:,::-1], axis = 0)
176
+ vision_tile_frame = vision_tile_frame.transpose(0, 3, 1, 2)
177
+ vision_tile_frame = vision_tile_frame.astype(numpy.float32) / 255
178
+ return vision_tile_frame
179
+
180
+
181
+ def normalize_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame:
182
+ vision_tile_frame = vision_tile_frame.transpose(0, 2, 3, 1).squeeze(0) * 255
183
+ vision_tile_frame = vision_tile_frame.clip(0, 255).astype(numpy.uint8)[:,:,::-1]
184
+ return vision_tile_frame
185
+
186
+
187
+ def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame:
188
+ frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100)
189
+ temp_vision_frame = cv2.resize(temp_vision_frame, (paste_vision_frame.shape[1], paste_vision_frame.shape[0]))
190
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, frame_enhancer_blend, paste_vision_frame, 1 - frame_enhancer_blend, 0)
191
+ return temp_vision_frame
192
+
193
+
194
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
195
+ pass
196
+
197
+
198
+ def process_frame(inputs : FrameEnhancerInputs) -> VisionFrame:
199
+ target_vision_frame = inputs.get('target_vision_frame')
200
+ return enhance_frame(target_vision_frame)
201
+
202
+
203
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProcess) -> None:
204
+ for queue_payload in process_manager.manage(queue_payloads):
205
+ target_vision_path = queue_payload['frame_path']
206
+ target_vision_frame = read_image(target_vision_path)
207
+ output_vision_frame = process_frame(
208
+ {
209
+ 'target_vision_frame': target_vision_frame
210
+ })
211
+ write_image(target_vision_path, output_vision_frame)
212
+ update_progress()
213
+
214
+
215
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
216
+ target_vision_frame = read_static_image(target_path)
217
+ output_vision_frame = process_frame(
218
+ {
219
+ 'target_vision_frame': target_vision_frame
220
+ })
221
+ write_image(output_path, output_vision_frame)
222
+
223
+
224
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
225
+ frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/lip_syncer.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import threading
5
+ import cv2
6
+ import numpy
7
+ import onnxruntime
8
+
9
+ import facefusion.globals
10
+ import facefusion.processors.frame.core as frame_processors
11
+ from facefusion import config, process_manager, logger, wording
12
+ from facefusion.execution import apply_execution_provider_options
13
+ from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
14
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_mouth_mask, clear_face_occluder, clear_face_parser
15
+ from facefusion.face_helper import warp_face_by_face_landmark_5, warp_face_by_bounding_box, paste_back, create_bounding_box_from_face_landmark_68
16
+ from facefusion.face_store import get_reference_faces
17
+ from facefusion.content_analyser import clear_content_analyser
18
+ from facefusion.normalizer import normalize_output_path
19
+ from facefusion.typing import Face, VisionFrame, UpdateProcess, ProcessMode, ModelSet, OptionsWithModel, AudioFrame, QueuePayload
20
+ from facefusion.filesystem import is_file, has_audio, resolve_relative_path
21
+ from facefusion.download import conditional_download, is_download_done
22
+ from facefusion.audio import read_static_audio, get_audio_frame, create_empty_audio_frame
23
+ from facefusion.filesystem import is_image, is_video, filter_audio_paths
24
+ from facefusion.common_helper import get_first
25
+ from facefusion.vision import read_image, write_image, read_static_image
26
+ from facefusion.processors.frame.typings import LipSyncerInputs
27
+ from facefusion.processors.frame import globals as frame_processors_globals
28
+ from facefusion.processors.frame import choices as frame_processors_choices
29
+
30
+ FRAME_PROCESSOR = None
31
+ MODEL_MATRIX = None
32
+ THREAD_LOCK : threading.Lock = threading.Lock()
33
+ NAME = __name__.upper()
34
+ MODELS : ModelSet =\
35
+ {
36
+ 'wav2lip_gan':
37
+ {
38
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/wav2lip_gan.onnx',
39
+ 'path': resolve_relative_path('../.assets/models/wav2lip_gan.onnx'),
40
+ }
41
+ }
42
+ OPTIONS : Optional[OptionsWithModel] = None
43
+
44
+
45
+ def get_frame_processor() -> Any:
46
+ global FRAME_PROCESSOR
47
+
48
+ with THREAD_LOCK:
49
+ while process_manager.is_checking():
50
+ sleep(0.5)
51
+ if FRAME_PROCESSOR is None:
52
+ model_path = get_options('model').get('path')
53
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_providers))
54
+ return FRAME_PROCESSOR
55
+
56
+
57
+ def clear_frame_processor() -> None:
58
+ global FRAME_PROCESSOR
59
+
60
+ FRAME_PROCESSOR = None
61
+
62
+
63
+ def get_options(key : Literal['model']) -> Any:
64
+ global OPTIONS
65
+
66
+ if OPTIONS is None:
67
+ OPTIONS =\
68
+ {
69
+ 'model': MODELS[frame_processors_globals.lip_syncer_model]
70
+ }
71
+ return OPTIONS.get(key)
72
+
73
+
74
+ def set_options(key : Literal['model'], value : Any) -> None:
75
+ global OPTIONS
76
+
77
+ OPTIONS[key] = value
78
+
79
+
80
+ def register_args(program : ArgumentParser) -> None:
81
+ program.add_argument('--lip-syncer-model', help = wording.get('help.lip_syncer_model'), default = config.get_str_value('frame_processors.lip_syncer_model', 'wav2lip_gan'), choices = frame_processors_choices.lip_syncer_models)
82
+
83
+
84
+ def apply_args(program : ArgumentParser) -> None:
85
+ args = program.parse_args()
86
+ frame_processors_globals.lip_syncer_model = args.lip_syncer_model
87
+
88
+
89
+ def pre_check() -> bool:
90
+ if not facefusion.globals.skip_download:
91
+ download_directory_path = resolve_relative_path('../.assets/models')
92
+ model_url = get_options('model').get('url')
93
+ process_manager.check()
94
+ conditional_download(download_directory_path, [ model_url ])
95
+ process_manager.end()
96
+ return True
97
+
98
+
99
+ def post_check() -> bool:
100
+ model_url = get_options('model').get('url')
101
+ model_path = get_options('model').get('path')
102
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
103
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
104
+ return False
105
+ elif not is_file(model_path):
106
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
107
+ return False
108
+ return True
109
+
110
+
111
+ def pre_process(mode : ProcessMode) -> bool:
112
+ if not has_audio(facefusion.globals.source_paths):
113
+ logger.error(wording.get('select_audio_source') + wording.get('exclamation_mark'), NAME)
114
+ return False
115
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
116
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
117
+ return False
118
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
119
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
120
+ return False
121
+ return True
122
+
123
+
124
+ def post_process() -> None:
125
+ read_static_image.cache_clear()
126
+ read_static_audio.cache_clear()
127
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
128
+ clear_frame_processor()
129
+ if facefusion.globals.video_memory_strategy == 'strict':
130
+ clear_face_analyser()
131
+ clear_content_analyser()
132
+ clear_face_occluder()
133
+ clear_face_parser()
134
+
135
+
136
+ def sync_lip(target_face : Face, temp_audio_frame : AudioFrame, temp_vision_frame : VisionFrame) -> VisionFrame:
137
+ frame_processor = get_frame_processor()
138
+ crop_mask_list = []
139
+ temp_audio_frame = prepare_audio_frame(temp_audio_frame)
140
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'ffhq_512', (512, 512))
141
+ if numpy.any(target_face.landmarks.get('68')):
142
+ face_landmark_68 = cv2.transform(target_face.landmarks.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
143
+ bounding_box = create_bounding_box_from_face_landmark_68(face_landmark_68)
144
+ bounding_box[1] -= numpy.abs(bounding_box[3] - bounding_box[1]) * 0.125
145
+ mouth_mask = create_mouth_mask(face_landmark_68)
146
+ crop_mask_list.append(mouth_mask)
147
+ else:
148
+ bounding_box = target_face.bounding_box
149
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, facefusion.globals.face_mask_padding)
150
+ crop_mask_list.append(box_mask)
151
+
152
+ if 'occlusion' in facefusion.globals.face_mask_types:
153
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
154
+ crop_mask_list.append(occlusion_mask)
155
+ close_vision_frame, close_matrix = warp_face_by_bounding_box(crop_vision_frame, bounding_box, (96, 96))
156
+ close_vision_frame = prepare_crop_frame(close_vision_frame)
157
+ close_vision_frame = frame_processor.run(None,
158
+ {
159
+ 'source': temp_audio_frame,
160
+ 'target': close_vision_frame
161
+ })[0]
162
+ crop_vision_frame = normalize_crop_frame(close_vision_frame)
163
+ crop_vision_frame = cv2.warpAffine(crop_vision_frame, cv2.invertAffineTransform(close_matrix), (512, 512), borderMode = cv2.BORDER_REPLICATE)
164
+ crop_mask = numpy.minimum.reduce(crop_mask_list)
165
+ paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
166
+ return paste_vision_frame
167
+
168
+
169
+ def prepare_audio_frame(temp_audio_frame : AudioFrame) -> AudioFrame:
170
+ temp_audio_frame = numpy.maximum(numpy.exp(-5 * numpy.log(10)), temp_audio_frame)
171
+ temp_audio_frame = numpy.log10(temp_audio_frame) * 1.6 + 3.2
172
+ temp_audio_frame = temp_audio_frame.clip(-4, 4).astype(numpy.float32)
173
+ temp_audio_frame = numpy.expand_dims(temp_audio_frame, axis = (0, 1))
174
+ return temp_audio_frame
175
+
176
+
177
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
178
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
179
+ prepare_vision_frame = crop_vision_frame.copy()
180
+ prepare_vision_frame[:, 48:] = 0
181
+ crop_vision_frame = numpy.concatenate((prepare_vision_frame, crop_vision_frame), axis = 3)
182
+ crop_vision_frame = crop_vision_frame.transpose(0, 3, 1, 2).astype('float32') / 255.0
183
+ return crop_vision_frame
184
+
185
+
186
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
187
+ crop_vision_frame = crop_vision_frame[0].transpose(1, 2, 0)
188
+ crop_vision_frame = crop_vision_frame.clip(0, 1) * 255
189
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)
190
+ return crop_vision_frame
191
+
192
+
193
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
194
+ pass
195
+
196
+
197
+ def process_frame(inputs : LipSyncerInputs) -> VisionFrame:
198
+ reference_faces = inputs.get('reference_faces')
199
+ source_audio_frame = inputs.get('source_audio_frame')
200
+ target_vision_frame = inputs.get('target_vision_frame')
201
+
202
+ if facefusion.globals.face_selector_mode == 'many':
203
+ many_faces = get_many_faces(target_vision_frame)
204
+ if many_faces:
205
+ for target_face in many_faces:
206
+ target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame)
207
+ if facefusion.globals.face_selector_mode == 'one':
208
+ target_face = get_one_face(target_vision_frame)
209
+ if target_face:
210
+ target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame)
211
+ if facefusion.globals.face_selector_mode == 'reference':
212
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
213
+ if similar_faces:
214
+ for similar_face in similar_faces:
215
+ target_vision_frame = sync_lip(similar_face, source_audio_frame, target_vision_frame)
216
+ return target_vision_frame
217
+
218
+
219
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProcess) -> None:
220
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
221
+ source_audio_path = get_first(filter_audio_paths(source_paths))
222
+
223
+ for queue_payload in process_manager.manage(queue_payloads):
224
+ frame_number = queue_payload['frame_number']
225
+ target_vision_path = queue_payload['frame_path']
226
+ source_audio_frame = get_audio_frame(source_audio_path, facefusion.globals.output_video_fps, frame_number)
227
+ if not numpy.any(source_audio_frame):
228
+ source_audio_frame = create_empty_audio_frame()
229
+ target_vision_frame = read_image(target_vision_path)
230
+ output_vision_frame = process_frame(
231
+ {
232
+ 'reference_faces': reference_faces,
233
+ 'source_audio_frame': source_audio_frame,
234
+ 'target_vision_frame': target_vision_frame
235
+ })
236
+ write_image(target_vision_path, output_vision_frame)
237
+ update_progress()
238
+
239
+
240
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
241
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
242
+ source_audio_frame = create_empty_audio_frame()
243
+ target_vision_frame = read_static_image(target_path)
244
+ output_vision_frame = process_frame(
245
+ {
246
+ 'reference_faces': reference_faces,
247
+ 'source_audio_frame': source_audio_frame,
248
+ 'target_vision_frame': target_vision_frame
249
+ })
250
+ write_image(output_path, output_vision_frame)
251
+
252
+
253
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
254
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/typings.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal, TypedDict
2
+
3
+ from facefusion.typing import Face, FaceSet, AudioFrame, VisionFrame
4
+
5
+ FaceDebuggerItem = Literal['bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender']
6
+ FaceEnhancerModel = Literal['codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'restoreformer_plus_plus']
7
+ FaceSwapperModel = Literal['blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256']
8
+ FrameEnhancerModel = Literal['lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'span_kendata_x4']
9
+ LipSyncerModel = Literal['wav2lip_gan']
10
+
11
+ FaceDebuggerInputs = TypedDict('FaceDebuggerInputs',
12
+ {
13
+ 'reference_faces' : FaceSet,
14
+ 'target_vision_frame' : VisionFrame
15
+ })
16
+ FaceEnhancerInputs = TypedDict('FaceEnhancerInputs',
17
+ {
18
+ 'reference_faces' : FaceSet,
19
+ 'target_vision_frame' : VisionFrame
20
+ })
21
+ FaceSwapperInputs = TypedDict('FaceSwapperInputs',
22
+ {
23
+ 'reference_faces' : FaceSet,
24
+ 'source_face' : Face,
25
+ 'target_vision_frame' : VisionFrame
26
+ })
27
+ FrameEnhancerInputs = TypedDict('FrameEnhancerInputs',
28
+ {
29
+ 'target_vision_frame' : VisionFrame
30
+ })
31
+ LipSyncerInputs = TypedDict('LipSyncerInputs',
32
+ {
33
+ 'reference_faces' : FaceSet,
34
+ 'source_audio_frame' : AudioFrame,
35
+ 'target_vision_frame' : VisionFrame
36
+ })
facefusion/statistics.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+ import numpy
3
+
4
+ import facefusion.globals
5
+ from facefusion.face_store import FACE_STORE
6
+ from facefusion.typing import FaceSet
7
+ from facefusion import logger
8
+
9
+
10
+ def create_statistics(static_faces : FaceSet) -> Dict[str, Any]:
11
+ face_detector_score_list = []
12
+ face_landmarker_score_list = []
13
+ statistics =\
14
+ {
15
+ 'min_face_detector_score': 0,
16
+ 'min_face_landmarker_score': 0,
17
+ 'max_face_detector_score': 0,
18
+ 'max_face_landmarker_score': 0,
19
+ 'average_face_detector_score': 0,
20
+ 'average_face_landmarker_score': 0,
21
+ 'total_face_landmark_5_fallbacks': 0,
22
+ 'total_frames_with_faces': 0,
23
+ 'total_faces': 0
24
+ }
25
+
26
+ for faces in static_faces.values():
27
+ statistics['total_frames_with_faces'] = statistics.get('total_frames_with_faces') + 1
28
+ for face in faces:
29
+ statistics['total_faces'] = statistics.get('total_faces') + 1
30
+ face_detector_score_list.append(face.scores.get('detector'))
31
+ face_landmarker_score_list.append(face.scores.get('landmarker'))
32
+ if numpy.array_equal(face.landmarks.get('5'), face.landmarks.get('5/68')):
33
+ statistics['total_face_landmark_5_fallbacks'] = statistics.get('total_face_landmark_5_fallbacks') + 1
34
+
35
+ if face_detector_score_list:
36
+ statistics['min_face_detector_score'] = round(min(face_detector_score_list), 2)
37
+ statistics['max_face_detector_score'] = round(max(face_detector_score_list), 2)
38
+ statistics['average_face_detector_score'] = round(numpy.mean(face_detector_score_list), 2)
39
+ if face_landmarker_score_list:
40
+ statistics['min_face_landmarker_score'] = round(min(face_landmarker_score_list), 2)
41
+ statistics['max_face_landmarker_score'] = round(max(face_landmarker_score_list), 2)
42
+ statistics['average_face_landmarker_score'] = round(numpy.mean(face_landmarker_score_list), 2)
43
+ return statistics
44
+
45
+
46
+ def conditional_log_statistics() -> None:
47
+ if facefusion.globals.log_level == 'debug':
48
+ statistics = create_statistics(FACE_STORE.get('static_faces'))
49
+
50
+ for name, value in statistics.items():
51
+ logger.debug(str(name) + ': ' + str(value), __name__.upper())
facefusion/typing.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Literal, Callable, List, Tuple, Dict, TypedDict
2
+ from collections import namedtuple
3
+ import numpy
4
+
5
+ BoundingBox = numpy.ndarray[Any, Any]
6
+ FaceLandmark5 = numpy.ndarray[Any, Any]
7
+ FaceLandmark68 = numpy.ndarray[Any, Any]
8
+ FaceLandmarkSet = TypedDict('FaceLandmarkSet',
9
+ {
10
+ '5' : FaceLandmark5, # type: ignore[valid-type]
11
+ '5/68' : FaceLandmark5, # type: ignore[valid-type]
12
+ '68' : FaceLandmark68 # type: ignore[valid-type]
13
+ })
14
+ Score = float
15
+ FaceScoreSet = TypedDict('FaceScoreSet',
16
+ {
17
+ 'detector' : Score,
18
+ 'landmarker' : Score
19
+ })
20
+ Embedding = numpy.ndarray[Any, Any]
21
+ Face = namedtuple('Face',
22
+ [
23
+ 'bounding_box',
24
+ 'landmarks',
25
+ 'scores',
26
+ 'embedding',
27
+ 'normed_embedding',
28
+ 'gender',
29
+ 'age'
30
+ ])
31
+ FaceSet = Dict[str, List[Face]]
32
+ FaceStore = TypedDict('FaceStore',
33
+ {
34
+ 'static_faces' : FaceSet,
35
+ 'reference_faces': FaceSet
36
+ })
37
+
38
+ VisionFrame = numpy.ndarray[Any, Any]
39
+ Mask = numpy.ndarray[Any, Any]
40
+ Matrix = numpy.ndarray[Any, Any]
41
+ Translation = numpy.ndarray[Any, Any]
42
+
43
+ AudioBuffer = bytes
44
+ Audio = numpy.ndarray[Any, Any]
45
+ AudioFrame = numpy.ndarray[Any, Any]
46
+ Spectrogram = numpy.ndarray[Any, Any]
47
+
48
+ Fps = float
49
+ Padding = Tuple[int, int, int, int]
50
+ Resolution = Tuple[int, int]
51
+
52
+ ProcessState = Literal['checking', 'processing', 'stopping', 'pending']
53
+ QueuePayload = TypedDict('QueuePayload',
54
+ {
55
+ 'frame_number' : int,
56
+ 'frame_path' : str
57
+ })
58
+ UpdateProcess = Callable[[], None]
59
+ ProcessFrames = Callable[[List[str], List[QueuePayload], UpdateProcess], None]
60
+
61
+ WarpTemplate = Literal['arcface_112_v1', 'arcface_112_v2', 'arcface_128_v2', 'ffhq_512']
62
+ WarpTemplateSet = Dict[WarpTemplate, numpy.ndarray[Any, Any]]
63
+ ProcessMode = Literal['output', 'preview', 'stream']
64
+
65
+ LogLevel = Literal['error', 'warn', 'info', 'debug']
66
+ VideoMemoryStrategy = Literal['strict', 'moderate', 'tolerant']
67
+ FaceSelectorMode = Literal['many', 'one', 'reference']
68
+ FaceAnalyserOrder = Literal['left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best']
69
+ FaceAnalyserAge = Literal['child', 'teen', 'adult', 'senior']
70
+ FaceAnalyserGender = Literal['female', 'male']
71
+ FaceDetectorModel = Literal['many', 'retinaface', 'scrfd', 'yoloface', 'yunet']
72
+ FaceDetectorTweak = Literal['low-luminance', 'high-luminance']
73
+ FaceRecognizerModel = Literal['arcface_blendswap', 'arcface_inswapper', 'arcface_simswap', 'arcface_uniface']
74
+ FaceMaskType = Literal['box', 'occlusion', 'region']
75
+ FaceMaskRegion = Literal['skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'eye-glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip']
76
+ TempFrameFormat = Literal['jpg', 'png', 'bmp']
77
+ OutputVideoEncoder = Literal['libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf']
78
+ OutputVideoPreset = Literal['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow']
79
+
80
+ ModelValue = Dict[str, Any]
81
+ ModelSet = Dict[str, ModelValue]
82
+ OptionsWithModel = TypedDict('OptionsWithModel',
83
+ {
84
+ 'model' : ModelValue
85
+ })
86
+
87
+ ValueAndUnit = TypedDict('ValueAndUnit',
88
+ {
89
+ 'value' : str,
90
+ 'unit' : str
91
+ })
92
+ ExecutionDeviceFramework = TypedDict('ExecutionDeviceFramework',
93
+ {
94
+ 'name' : str,
95
+ 'version' : str
96
+ })
97
+ ExecutionDeviceProduct = TypedDict('ExecutionDeviceProduct',
98
+ {
99
+ 'vendor' : str,
100
+ 'name' : str,
101
+ 'architecture' : str,
102
+ })
103
+ ExecutionDeviceVideoMemory = TypedDict('ExecutionDeviceVideoMemory',
104
+ {
105
+ 'total' : ValueAndUnit,
106
+ 'free' : ValueAndUnit
107
+ })
108
+ ExecutionDeviceUtilization = TypedDict('ExecutionDeviceUtilization',
109
+ {
110
+ 'gpu' : ValueAndUnit,
111
+ 'memory' : ValueAndUnit
112
+ })
113
+ ExecutionDevice = TypedDict('ExecutionDevice',
114
+ {
115
+ 'driver_version' : str,
116
+ 'framework' : ExecutionDeviceFramework,
117
+ 'product' : ExecutionDeviceProduct,
118
+ 'video_memory' : ExecutionDeviceVideoMemory,
119
+ 'utilization' : ExecutionDeviceUtilization
120
+ })
facefusion/uis/__init__.py ADDED
File without changes
facefusion/uis/assets/fixes.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ :root:root:root button:not([class])
2
+ {
3
+ border-radius: 0.375rem;
4
+ float: left;
5
+ overflow: hidden;
6
+ width: 100%;
7
+ }
facefusion/uis/assets/overrides.css ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root:root:root input[type="number"]
2
+ {
3
+ max-width: 6rem;
4
+ }
5
+
6
+ :root:root:root [type="checkbox"],
7
+ :root:root:root [type="radio"]
8
+ {
9
+ border-radius: 50%;
10
+ height: 1.125rem;
11
+ width: 1.125rem;
12
+ }
13
+
14
+ :root:root:root input[type="range"]
15
+ {
16
+ height: 0.5rem;
17
+ }
18
+
19
+ :root:root:root input[type="range"]::-moz-range-thumb,
20
+ :root:root:root input[type="range"]::-webkit-slider-thumb
21
+ {
22
+ background: var(--neutral-300);
23
+ border: unset;
24
+ border-radius: 50%;
25
+ height: 1.125rem;
26
+ width: 1.125rem;
27
+ }
28
+
29
+ :root:root:root input[type="range"]::-webkit-slider-thumb
30
+ {
31
+ margin-top: 0.375rem;
32
+ }
33
+
34
+ :root:root:root .grid-wrap.fixed-height
35
+ {
36
+ min-height: unset;
37
+ }
38
+
39
+ :root:root:root .grid-container
40
+ {
41
+ grid-auto-rows: minmax(5em, 1fr);
42
+ grid-template-columns: repeat(var(--grid-cols), minmax(5em, 1fr));
43
+ grid-template-rows: repeat(var(--grid-rows), minmax(5em, 1fr));
44
+ }
45
+
46
+ :root:root:root .tab-nav > button
47
+ {
48
+ border: unset;
49
+ border-bottom: 0.125rem solid transparent;
50
+ font-size: 1.125em;
51
+ margin: 0.5rem 1rem;
52
+ padding: 0;
53
+ }
54
+
55
+ :root:root:root .tab-nav > button.selected
56
+ {
57
+ border-bottom: 0.125rem solid;
58
+ }
facefusion/uis/choices.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.uis.typing import WebcamMode
4
+
5
+ common_options : List[str] = [ 'keep-temp', 'skip-audio', 'skip-download' ]
6
+ webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ]
7
+ webcam_resolutions : List[str] = [ '320x240', '640x480', '800x600', '1024x768', '1280x720', '1280x960', '1920x1080', '2560x1440', '3840x2160' ]