Next (#216)
* Simplify bbox access
* Code cleanup
* Simplify bbox access
* Move code to face helper
* Swap and paste back without insightface
* Swap and paste back without insightface
* Remove semaphore where possible
* Improve paste back performance
* Cosmetic changes
* Move the predictor to ONNX to avoid tensorflow, Use video ranges for prediction
* Make CI happy
* Move template and size to the options
* Fix different color on box
* Uniform model handling for predictor
* Uniform frame handling for predictor
* Pass kps direct to warp_face
* Fix urllib
* Analyse based on matches
* Analyse based on rate
* Fix CI
* ROCM and OpenVINO mapping for torch backends
* Fix the paste back speed
* Fix import
* Replace retinaface with yunet (#168)
* Remove insightface dependency
* Fix urllib
* Some fixes
* Analyse based on matches
* Analyse based on rate
* Fix CI
* Migrate to Yunet
* Something is off here
* We indeed need semaphore for yunet
* Normalize the normed_embedding
* Fix download of models
* Fix download of models
* Fix download of models
* Add score and improve affine_matrix
* Temp fix for bbox out of frame
* Temp fix for bbox out of frame
* ROCM and OpenVINO mapping for torch backends
* Normalize bbox
* Implement gender age
* Cosmetics on cli args
* Prevent face jumping
* Fix the paste back speed
* FIx import
* Introduce detection size
* Cosmetics on face analyser ARGS and globals
* Temp fix for shaking face
* Accurate event handling
* Accurate event handling
* Accurate event handling
* Set the reference_frame_number in face_selector component
* Simswap model (#171)
* Add simswap models
* Add ghost models
* Introduce normed template
* Conditional prepare and normalize for ghost
* Conditional prepare and normalize for ghost
* Get simswap working
* Get simswap working
* Fix refresh of swapper model
* Refine face selection and detection (#174)
* Refine face selection and detection
* Update README.md
* Fix some face analyser UI
* Fix some face analyser UI
* Introduce range handling for CLI arguments
* Introduce range handling for CLI arguments
* Fix some spacings
* Disable onnxruntime warnings
* Use cv2.blur over cv2.GaussianBlur for better performance
* Revert "Use cv2.blur over cv2.GaussianBlur for better performance"
This reverts commit bab666d6f9
.
* Prepare universal face detection
* Prepare universal face detection part2
* Reimplement retinaface
* Introduce cached anchors creation
* Restore filtering to enhance performance
* Minor changes
* Minor changes
* More code but easier to understand
* Minor changes
* Rename predictor to content analyser
* Change detection/recognition to detector/recognizer
* Fix crop frame borders
* Fix spacing
* Allow normalize output without a source
* Improve conditional set face reference
* Update dependencies
* Add timeout for get_download_size
* Fix performance due disorder
* Move models to assets repository, Adjust namings
* Refactor face analyser
* Rename models once again
* Fix spacing
* Highres simswap (#192)
* Introduce highres simswap
* Fix simswap 256 color issue (#191)
* Fix simswap 256 color issue
* Update face_swapper.py
* Normalize models and host in our repo
* Normalize models and host in our repo
---------
Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>
* Rename face analyser direction to face analyser order
* Improve the UI for face selector
* Add best-worst, worst-best detector ordering
* Clear as needed and fix zero score bug
* Fix linter
* Improve startup time by multi thread remote download size
* Just some cosmetics
* Normalize swagger source input, Add blendface_256 (unfinished)
* New paste back (#195)
* add new paste_back (#194)
* add new paste_back
* Update face_helper.py
* Update face_helper.py
* add commandline arguments and gui
* fix conflict
* Update face_mask.py
* type fix
* Clean some wording and typing
---------
Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>
* Clean more names, use blur range approach
* Add blur padding range
* Change the padding order
* Fix yunet filename
* Introduce face debugger
* Use percent for mask padding
* Ignore this
* Ignore this
* Simplify debugger output
* implement blendface (#198)
* Clean up after the genius
* Add gpen_bfr_256
* Cosmetics
* Ignore face_mask_padding on face enhancer
* Update face_debugger.py (#202)
* Shrink debug_face() to a minimum
* Mark as 2.0.0 release
* remove unused (#204)
* Apply NMS (#205)
* Apply NMS
* Apply NMS part2
* Fix restoreformer url
* Add debugger cli and gui components (#206)
* Add debugger cli and gui components
* update
* Polishing the types
* Fix usage in README.md
* Update onnxruntime
* Support for webp
* Rename paste-back to face-mask
* Add license to README
* Add license to README
* Extend face selector mode by one
* Update utilities.py (#212)
* Stop inline camera on stream
* Minor webcam updates
* Gracefully start and stop webcam
* Rename capture to video_capture
* Make get webcam capture pure
* Check webcam to not be None
* Remove some is not None
* Use index 0 for webcam
* Remove memory lookup within progress bar
* Less progress bar updates
* Uniform progress bar
* Use classic progress bar
* Fix image and video validation
* Use different hash for cache
* Use best-worse order for webcam
* Normalize padding like CSS
* Update preview
* Fix max memory
* Move disclaimer and license to the docs
* Update wording in README
* Add LICENSE.md
* Fix argument in README
---------
Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>
Co-authored-by: alex00ds <31631959+alex00ds@users.noreply.github.com>
This commit is contained in:
parent
ea8ecf7db0
commit
6587d2def1
BIN
.github/preview.png
vendored
BIN
.github/preview.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
3
LICENSE.md
Normal file
3
LICENSE.md
Normal file
@ -0,0 +1,3 @@
|
||||
MIT license
|
||||
|
||||
Copyright (c) 2023 Henry Ruhs
|
52
README.md
52
README.md
@ -16,9 +16,9 @@ Preview
|
||||
Installation
|
||||
------------
|
||||
|
||||
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 install FaceFusion.
|
||||
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.
|
||||
|
||||
Read the [installation](https://docs.facefusion.io/installation) now.
|
||||
Get started with the [installation](https://docs.facefusion.io/installation) guide.
|
||||
|
||||
|
||||
Usage
|
||||
@ -41,20 +41,29 @@ misc:
|
||||
--headless run the program in headless mode
|
||||
|
||||
execution:
|
||||
--execution-providers {cpu} [{cpu} ...] choose from the available execution providers (choices: cpu, ...)
|
||||
--execution-thread-count EXECUTION_THREAD_COUNT specify the number of execution threads
|
||||
--execution-queue-count EXECUTION_QUEUE_COUNT specify the number of execution queries
|
||||
--max-memory MAX_MEMORY specify the maximum amount of ram to be used (in gb)
|
||||
--execution-providers {cpu} [{cpu} ...] choose from the available execution providers
|
||||
--execution-thread-count [1-128] specify the number of execution threads
|
||||
--execution-queue-count [1-32] specify the number of execution queries
|
||||
--max-memory [0-128] specify the maximum amount of ram to be used (in gb)
|
||||
|
||||
face recognition:
|
||||
--face-recognition {reference,many} specify the method for face recognition
|
||||
--face-analyser-direction {left-right,right-left,top-bottom,bottom-top,small-large,large-small} specify the direction used for face analysis
|
||||
--face-analyser-age {child,teen,adult,senior} specify the age used for face analysis
|
||||
--face-analyser-gender {male,female} specify the gender used for face analysis
|
||||
face analyser:
|
||||
--face-analyser-order {left-right,right-left,top-bottom,bottom-top,small-large,large-small,best-worst,worst-best} specify the order used for the face analyser
|
||||
--face-analyser-age {child,teen,adult,senior} specify the age used for the face analyser
|
||||
--face-analyser-gender {male,female} specify the gender used for the face analyser
|
||||
--face-detector-model {retinaface,yunet} specify the model used for the face detector
|
||||
--face-detector-size {160x160,320x320,480x480,512x512,640x640,768x768,960x960,1024x1024} specify the size threshold used for the face detector
|
||||
--face-detector-score [0.0-1.0] specify the score threshold used for the face detector
|
||||
|
||||
face selector:
|
||||
--face-selector-mode {reference,one,many} specify the mode for the face selector
|
||||
--reference-face-position REFERENCE_FACE_POSITION specify the position of the reference face
|
||||
--reference-face-distance REFERENCE_FACE_DISTANCE specify the distance between the reference face and the target face
|
||||
--reference-face-distance [0.0-1.5] specify the distance between the reference face and the target face
|
||||
--reference-frame-number REFERENCE_FRAME_NUMBER specify the number of the reference frame
|
||||
|
||||
face mask:
|
||||
--face-mask-blur [0.0-1.0] specify the blur amount for face mask
|
||||
--face-mask-padding FACE_MASK_PADDING [FACE_MASK_PADDING ...] specify the face mask padding (top, right, bottom, left) in percent
|
||||
|
||||
frame extraction:
|
||||
--trim-frame-start TRIM_FRAME_START specify the start frame for extraction
|
||||
--trim-frame-end TRIM_FRAME_END specify the end frame for extraction
|
||||
@ -70,11 +79,12 @@ output creation:
|
||||
--skip-audio omit audio from the target
|
||||
|
||||
frame processors:
|
||||
--frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] choose from the available frame processors (choices: face_enhancer, face_swapper, frame_enhancer, ...)
|
||||
--face-enhancer-model {codeformer,gfpgan_1.2,gfpgan_1.3,gfpgan_1.4,gpen_bfr_512} choose from the mode for the frame processor
|
||||
--frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] choose from the available frame processors (choices: face_debugger, face_enhancer, face_swapper, frame_enhancer, ...)
|
||||
--face-debugger-items {bbox,kps,face-mask,score} [{bbox,kps,face-mask,score} ...] specify the face debugger items
|
||||
--face-enhancer-model {codeformer,gfpgan_1.2,gfpgan_1.3,gfpgan_1.4,gpen_bfr_256,gpen_bfr_512,restoreformer} choose the model for the frame processor
|
||||
--face-enhancer-blend [0-100] specify the blend factor for the frame processor
|
||||
--face-swapper-model {inswapper_128,inswapper_128_fp16} choose from the mode for the frame processor
|
||||
--frame-enhancer-model {realesrgan_x2plus,realesrgan_x4plus,realesrnet_x4plus} choose from the mode for the frame processor
|
||||
--face-swapper-model {blendface_256,inswapper_128,inswapper_128_fp16,simswap_256,simswap_512_unofficial} choose the model for the frame processor
|
||||
--frame-enhancer-model {real_esrgan_x2plus,real_esrgan_x4plus,real_esrnet_x4plus} choose the model for the frame processor
|
||||
--frame-enhancer-blend [0-100] specify the blend factor for the frame processor
|
||||
|
||||
uis:
|
||||
@ -82,16 +92,6 @@ uis:
|
||||
```
|
||||
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
|
||||
We acknowledge the unethical potential of FaceFusion and are resolutely dedicated to establishing safeguards against such misuse. This program has been engineered to abstain from processing inappropriate content such as nudity, graphic content and sensitive material.
|
||||
|
||||
It is important to note that we maintain a strong stance against any type of pornographic nature and do not collaborate with any websites promoting the unauthorized use of our software.
|
||||
|
||||
Users who seek to engage in such activities will face consequences, including being banned from our community. We reserve the right to report developers on GitHub who distribute unlocked forks of our software at any time.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
|
@ -1,10 +1,26 @@
|
||||
from typing import List
|
||||
|
||||
from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder
|
||||
import numpy
|
||||
|
||||
face_recognitions : List[FaceRecognition] = [ 'reference', 'many' ]
|
||||
face_analyser_directions : List[FaceAnalyserDirection] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small' ]
|
||||
from facefusion.typing import FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder
|
||||
|
||||
|
||||
face_analyser_orders : List[FaceAnalyserOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ]
|
||||
face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ]
|
||||
face_analyser_genders : List[FaceAnalyserGender] = [ 'male', 'female' ]
|
||||
face_detector_models : List[str] = [ 'retinaface', 'yunet' ]
|
||||
face_detector_sizes : List[str] = [ '160x160', '320x320', '480x480', '512x512', '640x640', '768x768', '960x960', '1024x1024' ]
|
||||
face_selector_modes : List[FaceSelectorMode] = [ 'reference', 'one', 'many' ]
|
||||
temp_frame_formats : List[TempFrameFormat] = [ 'jpg', 'png' ]
|
||||
output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ]
|
||||
|
||||
execution_thread_count_range : List[int] = numpy.arange(1, 129, 1).tolist()
|
||||
execution_queue_count_range : List[int] = numpy.arange(1, 33, 1).tolist()
|
||||
max_memory_range : List[int] = numpy.arange(0, 129, 1).tolist()
|
||||
face_detector_score_range : List[float] = numpy.arange(0.0, 1.05, 0.05).tolist()
|
||||
face_mask_blur_range : List[float] = numpy.arange(0.0, 1.05, 0.05).tolist()
|
||||
face_mask_padding_range : List[float] = numpy.arange(0, 101, 1).tolist()
|
||||
reference_face_distance_range : List[float] = numpy.arange(0.0, 1.55, 0.05).tolist()
|
||||
temp_frame_quality_range : List[int] = numpy.arange(0, 101, 1).tolist()
|
||||
output_image_quality_range : List[int] = numpy.arange(0, 101, 1).tolist()
|
||||
output_video_quality_range : List[int] = numpy.arange(0, 101, 1).tolist()
|
||||
|
102
facefusion/content_analyser.py
Normal file
102
facefusion/content_analyser.py
Normal file
@ -0,0 +1,102 @@
|
||||
from typing import Any, Dict
|
||||
from functools import lru_cache
|
||||
import threading
|
||||
import cv2
|
||||
import numpy
|
||||
import onnxruntime
|
||||
from tqdm import tqdm
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.typing import Frame, ModelValue
|
||||
from facefusion.vision import get_video_frame, count_video_frame_total, read_image, detect_fps
|
||||
from facefusion.utilities import resolve_relative_path, conditional_download
|
||||
|
||||
CONTENT_ANALYSER = None
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
MODELS : Dict[str, ModelValue] =\
|
||||
{
|
||||
'open_nsfw':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/open_nsfw.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/open_nsfw.onnx')
|
||||
}
|
||||
}
|
||||
MAX_PROBABILITY = 0.80
|
||||
MAX_RATE = 5
|
||||
STREAM_COUNTER = 0
|
||||
|
||||
|
||||
def get_content_analyser() -> Any:
|
||||
global CONTENT_ANALYSER
|
||||
|
||||
with THREAD_LOCK:
|
||||
if CONTENT_ANALYSER is None:
|
||||
model_path = MODELS.get('open_nsfw').get('path')
|
||||
CONTENT_ANALYSER = onnxruntime.InferenceSession(model_path, providers = facefusion.globals.execution_providers)
|
||||
return CONTENT_ANALYSER
|
||||
|
||||
|
||||
def clear_content_analyser() -> None:
|
||||
global CONTENT_ANALYSER
|
||||
|
||||
CONTENT_ANALYSER = None
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
if not facefusion.globals.skip_download:
|
||||
download_directory_path = resolve_relative_path('../.assets/models')
|
||||
model_url = MODELS.get('open_nsfw').get('url')
|
||||
conditional_download(download_directory_path, [ model_url ])
|
||||
return True
|
||||
|
||||
|
||||
def analyse_stream(frame : Frame, fps : float) -> bool:
|
||||
global STREAM_COUNTER
|
||||
|
||||
STREAM_COUNTER = STREAM_COUNTER + 1
|
||||
if STREAM_COUNTER % int(fps) == 0:
|
||||
return analyse_frame(frame)
|
||||
return False
|
||||
|
||||
|
||||
def prepare_frame(frame : Frame) -> Frame:
|
||||
frame = cv2.resize(frame, (224, 224)).astype(numpy.float32)
|
||||
frame -= numpy.array([ 104, 117, 123 ]).astype(numpy.float32)
|
||||
frame = numpy.expand_dims(frame, axis = 0)
|
||||
return frame
|
||||
|
||||
|
||||
def analyse_frame(frame : Frame) -> bool:
|
||||
content_analyser = get_content_analyser()
|
||||
frame = prepare_frame(frame)
|
||||
probability = content_analyser.run(None,
|
||||
{
|
||||
'input:0': frame
|
||||
})[0][0][1]
|
||||
return probability > MAX_PROBABILITY
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def analyse_image(image_path : str) -> bool:
|
||||
frame = read_image(image_path)
|
||||
return analyse_frame(frame)
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def analyse_video(video_path : str, start_frame : int, end_frame : int) -> bool:
|
||||
video_frame_total = count_video_frame_total(video_path)
|
||||
fps = detect_fps(video_path)
|
||||
frame_range = range(start_frame or 0, end_frame or video_frame_total)
|
||||
rate = 0.0
|
||||
counter = 0
|
||||
with tqdm(total = len(frame_range), desc = wording.get('analysing'), unit = 'frame', ascii = ' =') as progress:
|
||||
for frame_number in frame_range:
|
||||
if frame_number % int(fps) == 0:
|
||||
frame = get_video_frame(video_path, frame_number)
|
||||
if analyse_frame(frame):
|
||||
counter += 1
|
||||
rate = counter * int(fps) / len(frame_range) * 100
|
||||
progress.update()
|
||||
progress.set_postfix(rate = rate)
|
||||
return rate > MAX_RATE
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
os.environ['OMP_NUM_THREADS'] = '1'
|
||||
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
|
||||
|
||||
import signal
|
||||
import sys
|
||||
@ -9,17 +8,20 @@ import warnings
|
||||
import platform
|
||||
import shutil
|
||||
import onnxruntime
|
||||
import tensorflow
|
||||
from argparse import ArgumentParser, HelpFormatter
|
||||
|
||||
import facefusion.choices
|
||||
import facefusion.globals
|
||||
from facefusion import metadata, wording
|
||||
from facefusion.predictor import predict_image, predict_video
|
||||
from facefusion.face_analyser import get_one_face
|
||||
from facefusion.face_reference import get_face_reference, set_face_reference
|
||||
from facefusion.vision import get_video_frame, read_image
|
||||
from facefusion import face_analyser, content_analyser, metadata, wording
|
||||
from facefusion.content_analyser import analyse_image, analyse_video
|
||||
from facefusion.processors.frame.core import get_frame_processors_modules, load_frame_processor_module
|
||||
from facefusion.utilities import is_image, is_video, detect_fps, compress_image, merge_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clear_temp, list_module_names, encode_execution_providers, decode_execution_providers, normalize_output_path
|
||||
from facefusion.utilities import is_image, is_video, detect_fps, compress_image, merge_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clear_temp, list_module_names, encode_execution_providers, decode_execution_providers, normalize_output_path, normalize_padding, create_metavar, update_status
|
||||
|
||||
warnings.filterwarnings('ignore', category = FutureWarning, module = 'insightface')
|
||||
onnxruntime.set_default_logger_severity(3)
|
||||
warnings.filterwarnings('ignore', category = UserWarning, module = 'gradio')
|
||||
warnings.filterwarnings('ignore', category = UserWarning, module = 'torchvision')
|
||||
|
||||
|
||||
@ -37,33 +39,42 @@ def cli() -> None:
|
||||
group_misc.add_argument('--headless', help = wording.get('headless_help'), dest = 'headless', action = 'store_true')
|
||||
# execution
|
||||
group_execution = program.add_argument_group('execution')
|
||||
group_execution.add_argument('--execution-providers', help = wording.get('execution_providers_help').format(choices = 'cpu'), dest = 'execution_providers', default = [ 'cpu' ], choices = encode_execution_providers(onnxruntime.get_available_providers()), nargs = '+')
|
||||
group_execution.add_argument('--execution-thread-count', help = wording.get('execution_thread_count_help'), dest = 'execution_thread_count', type = int, default = 1)
|
||||
group_execution.add_argument('--execution-queue-count', help = wording.get('execution_queue_count_help'), dest = 'execution_queue_count', type = int, default = 1)
|
||||
group_execution.add_argument('--max-memory', help=wording.get('max_memory_help'), dest='max_memory', type = int)
|
||||
# face recognition
|
||||
group_face_recognition = program.add_argument_group('face recognition')
|
||||
group_face_recognition.add_argument('--face-recognition', help = wording.get('face_recognition_help'), dest = 'face_recognition', default = 'reference', choices = facefusion.choices.face_recognitions)
|
||||
group_face_recognition.add_argument('--face-analyser-direction', help = wording.get('face_analyser_direction_help'), dest = 'face_analyser_direction', default = 'left-right', choices = facefusion.choices.face_analyser_directions)
|
||||
group_face_recognition.add_argument('--face-analyser-age', help = wording.get('face_analyser_age_help'), dest = 'face_analyser_age', choices = facefusion.choices.face_analyser_ages)
|
||||
group_face_recognition.add_argument('--face-analyser-gender', help = wording.get('face_analyser_gender_help'), dest = 'face_analyser_gender', choices = facefusion.choices.face_analyser_genders)
|
||||
group_face_recognition.add_argument('--reference-face-position', help = wording.get('reference_face_position_help'), dest = 'reference_face_position', type = int, default = 0)
|
||||
group_face_recognition.add_argument('--reference-face-distance', help = wording.get('reference_face_distance_help'), dest = 'reference_face_distance', type = float, default = 1.5)
|
||||
group_face_recognition.add_argument('--reference-frame-number', help = wording.get('reference_frame_number_help'), dest = 'reference_frame_number', type = int, default = 0)
|
||||
group_execution.add_argument('--execution-providers', help = wording.get('execution_providers_help'), dest = 'execution_providers', default = [ 'cpu' ], choices = encode_execution_providers(onnxruntime.get_available_providers()), nargs = '+')
|
||||
group_execution.add_argument('--execution-thread-count', help = wording.get('execution_thread_count_help'), dest = 'execution_thread_count', type = int, default = 4, choices = facefusion.choices.execution_thread_count_range, metavar = create_metavar(facefusion.choices.execution_thread_count_range))
|
||||
group_execution.add_argument('--execution-queue-count', help = wording.get('execution_queue_count_help'), dest = 'execution_queue_count', type = int, default = 1, choices = facefusion.choices.execution_queue_count_range, metavar = create_metavar(facefusion.choices.execution_queue_count_range))
|
||||
group_execution.add_argument('--max-memory', help = wording.get('max_memory_help'), dest = 'max_memory', type = int, choices = facefusion.choices.max_memory_range, metavar = create_metavar(facefusion.choices.max_memory_range))
|
||||
# face analyser
|
||||
group_face_analyser = program.add_argument_group('face analyser')
|
||||
group_face_analyser.add_argument('--face-analyser-order', help = wording.get('face_analyser_order_help'), dest = 'face_analyser_order', default = 'left-right', choices = facefusion.choices.face_analyser_orders)
|
||||
group_face_analyser.add_argument('--face-analyser-age', help = wording.get('face_analyser_age_help'), dest = 'face_analyser_age', choices = facefusion.choices.face_analyser_ages)
|
||||
group_face_analyser.add_argument('--face-analyser-gender', help = wording.get('face_analyser_gender_help'), dest = 'face_analyser_gender', choices = facefusion.choices.face_analyser_genders)
|
||||
group_face_analyser.add_argument('--face-detector-model', help = wording.get('face_detector_model_help'), dest = 'face_detector_model', default = 'retinaface', choices = facefusion.choices.face_detector_models)
|
||||
group_face_analyser.add_argument('--face-detector-size', help = wording.get('face_detector_size_help'), dest = 'face_detector_size', default = '640x640', choices = facefusion.choices.face_detector_sizes)
|
||||
group_face_analyser.add_argument('--face-detector-score', help = wording.get('face_detector_score_help'), dest = 'face_detector_score', type = float, default = 0.5, choices = facefusion.choices.face_detector_score_range, metavar = create_metavar(facefusion.choices.face_detector_score_range))
|
||||
# face selector
|
||||
group_face_selector = program.add_argument_group('face selector')
|
||||
group_face_selector.add_argument('--face-selector-mode', help = wording.get('face_selector_mode_help'), dest = 'face_selector_mode', default = 'reference', choices = facefusion.choices.face_selector_modes)
|
||||
group_face_selector.add_argument('--reference-face-position', help = wording.get('reference_face_position_help'), dest = 'reference_face_position', type = int, default = 0)
|
||||
group_face_selector.add_argument('--reference-face-distance', help = wording.get('reference_face_distance_help'), dest = 'reference_face_distance', type = float, default = 0.6, choices = facefusion.choices.reference_face_distance_range, metavar = create_metavar(facefusion.choices.reference_face_distance_range))
|
||||
group_face_selector.add_argument('--reference-frame-number', help = wording.get('reference_frame_number_help'), dest = 'reference_frame_number', type = int, default = 0)
|
||||
# face mask
|
||||
group_face_mask = program.add_argument_group('face mask')
|
||||
group_face_mask.add_argument('--face-mask-blur', help = wording.get('face_mask_blur_help'), dest = 'face_mask_blur', type = float, default = 0.3, choices = facefusion.choices.face_mask_blur_range, metavar = create_metavar(facefusion.choices.face_mask_blur_range))
|
||||
group_face_mask.add_argument('--face-mask-padding', help = wording.get('face_mask_padding_help'), dest = 'face_mask_padding', type = int, default = [ 0, 0, 0, 0 ], nargs = '+')
|
||||
# frame extraction
|
||||
group_processing = program.add_argument_group('frame extraction')
|
||||
group_processing.add_argument('--trim-frame-start', help = wording.get('trim_frame_start_help'), dest = 'trim_frame_start', type = int)
|
||||
group_processing.add_argument('--trim-frame-end', help = wording.get('trim_frame_end_help'), dest = 'trim_frame_end', type = int)
|
||||
group_processing.add_argument('--temp-frame-format', help = wording.get('temp_frame_format_help'), dest = 'temp_frame_format', default = 'jpg', choices = facefusion.choices.temp_frame_formats)
|
||||
group_processing.add_argument('--temp-frame-quality', help = wording.get('temp_frame_quality_help'), dest = 'temp_frame_quality', type = int, default = 100, choices = range(101), metavar = '[0-100]')
|
||||
group_processing.add_argument('--keep-temp', help = wording.get('keep_temp_help'), dest = 'keep_temp', action = 'store_true')
|
||||
group_frame_extraction = program.add_argument_group('frame extraction')
|
||||
group_frame_extraction.add_argument('--trim-frame-start', help = wording.get('trim_frame_start_help'), dest = 'trim_frame_start', type = int)
|
||||
group_frame_extraction.add_argument('--trim-frame-end', help = wording.get('trim_frame_end_help'), dest = 'trim_frame_end', type = int)
|
||||
group_frame_extraction.add_argument('--temp-frame-format', help = wording.get('temp_frame_format_help'), dest = 'temp_frame_format', default = 'jpg', choices = facefusion.choices.temp_frame_formats)
|
||||
group_frame_extraction.add_argument('--temp-frame-quality', help = wording.get('temp_frame_quality_help'), dest = 'temp_frame_quality', type = int, default = 100, choices = facefusion.choices.temp_frame_quality_range, metavar = create_metavar(facefusion.choices.temp_frame_quality_range))
|
||||
group_frame_extraction.add_argument('--keep-temp', help = wording.get('keep_temp_help'), dest = 'keep_temp', action = 'store_true')
|
||||
# output creation
|
||||
group_output = program.add_argument_group('output creation')
|
||||
group_output.add_argument('--output-image-quality', help=wording.get('output_image_quality_help'), dest = 'output_image_quality', type = int, default = 80, choices = range(101), metavar = '[0-100]')
|
||||
group_output.add_argument('--output-video-encoder', help = wording.get('output_video_encoder_help'), dest = 'output_video_encoder', default = 'libx264', choices = facefusion.choices.output_video_encoders)
|
||||
group_output.add_argument('--output-video-quality', help = wording.get('output_video_quality_help'), dest = 'output_video_quality', type = int, default = 80, choices = range(101), metavar = '[0-100]')
|
||||
group_output.add_argument('--keep-fps', help = wording.get('keep_fps_help'), dest = 'keep_fps', action = 'store_true')
|
||||
group_output.add_argument('--skip-audio', help = wording.get('skip_audio_help'), dest = 'skip_audio', action = 'store_true')
|
||||
group_output_creation = program.add_argument_group('output creation')
|
||||
group_output_creation.add_argument('--output-image-quality', help = wording.get('output_image_quality_help'), dest = 'output_image_quality', type = int, default = 80, choices = facefusion.choices.output_image_quality_range, metavar = create_metavar(facefusion.choices.output_image_quality_range))
|
||||
group_output_creation.add_argument('--output-video-encoder', help = wording.get('output_video_encoder_help'), dest = 'output_video_encoder', default = 'libx264', choices = facefusion.choices.output_video_encoders)
|
||||
group_output_creation.add_argument('--output-video-quality', help = wording.get('output_video_quality_help'), dest = 'output_video_quality', type = int, default = 80, choices = facefusion.choices.output_video_quality_range, metavar = create_metavar(facefusion.choices.output_video_quality_range))
|
||||
group_output_creation.add_argument('--keep-fps', help = wording.get('keep_fps_help'), dest = 'keep_fps', action = 'store_true')
|
||||
group_output_creation.add_argument('--skip-audio', help = wording.get('skip_audio_help'), dest = 'skip_audio', action = 'store_true')
|
||||
# frame processors
|
||||
available_frame_processors = list_module_names('facefusion/processors/frame/modules')
|
||||
program = ArgumentParser(parents = [ program ], formatter_class = program.formatter_class, add_help = True)
|
||||
@ -92,14 +103,21 @@ def apply_args(program : ArgumentParser) -> None:
|
||||
facefusion.globals.execution_thread_count = args.execution_thread_count
|
||||
facefusion.globals.execution_queue_count = args.execution_queue_count
|
||||
facefusion.globals.max_memory = args.max_memory
|
||||
# face recognition
|
||||
facefusion.globals.face_recognition = args.face_recognition
|
||||
facefusion.globals.face_analyser_direction = args.face_analyser_direction
|
||||
# face analyser
|
||||
facefusion.globals.face_analyser_order = args.face_analyser_order
|
||||
facefusion.globals.face_analyser_age = args.face_analyser_age
|
||||
facefusion.globals.face_analyser_gender = args.face_analyser_gender
|
||||
facefusion.globals.face_detector_model = args.face_detector_model
|
||||
facefusion.globals.face_detector_size = args.face_detector_size
|
||||
facefusion.globals.face_detector_score = args.face_detector_score
|
||||
# face selector
|
||||
facefusion.globals.face_selector_mode = args.face_selector_mode
|
||||
facefusion.globals.reference_face_position = args.reference_face_position
|
||||
facefusion.globals.reference_face_distance = args.reference_face_distance
|
||||
facefusion.globals.reference_frame_number = args.reference_frame_number
|
||||
# face mask
|
||||
facefusion.globals.face_mask_blur = args.face_mask_blur
|
||||
facefusion.globals.face_mask_padding = normalize_padding(args.face_mask_padding)
|
||||
# frame extraction
|
||||
facefusion.globals.trim_frame_start = args.trim_frame_start
|
||||
facefusion.globals.trim_frame_end = args.trim_frame_end
|
||||
@ -125,7 +143,7 @@ def apply_args(program : ArgumentParser) -> None:
|
||||
def run(program : ArgumentParser) -> None:
|
||||
apply_args(program)
|
||||
limit_resources()
|
||||
if not pre_check():
|
||||
if not pre_check() or not content_analyser.pre_check() or not face_analyser.pre_check():
|
||||
return
|
||||
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
|
||||
if not frame_processor_module.pre_check():
|
||||
@ -148,14 +166,6 @@ def destroy() -> None:
|
||||
|
||||
|
||||
def limit_resources() -> None:
|
||||
# prevent tensorflow memory leak
|
||||
gpus = tensorflow.config.experimental.list_physical_devices('GPU')
|
||||
for gpu in gpus:
|
||||
tensorflow.config.experimental.set_virtual_device_configuration(gpu,
|
||||
[
|
||||
tensorflow.config.experimental.VirtualDeviceConfiguration(memory_limit = 512)
|
||||
])
|
||||
# limit memory usage
|
||||
if facefusion.globals.max_memory:
|
||||
memory = facefusion.globals.max_memory * 1024 ** 3
|
||||
if platform.system().lower() == 'darwin':
|
||||
@ -180,6 +190,7 @@ def pre_check() -> bool:
|
||||
|
||||
|
||||
def conditional_process() -> None:
|
||||
conditional_set_face_reference()
|
||||
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
|
||||
if not frame_processor_module.pre_process('output'):
|
||||
return
|
||||
@ -189,8 +200,18 @@ def conditional_process() -> None:
|
||||
process_video()
|
||||
|
||||
|
||||
def conditional_set_face_reference() -> None:
|
||||
if 'reference' in facefusion.globals.face_selector_mode and not get_face_reference():
|
||||
if is_video(facefusion.globals.target_path):
|
||||
reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
|
||||
else:
|
||||
reference_frame = read_image(facefusion.globals.target_path)
|
||||
reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
|
||||
set_face_reference(reference_face)
|
||||
|
||||
|
||||
def process_image() -> None:
|
||||
if predict_image(facefusion.globals.target_path):
|
||||
if analyse_image(facefusion.globals.target_path):
|
||||
return
|
||||
shutil.copy2(facefusion.globals.target_path, facefusion.globals.output_path)
|
||||
# process frame
|
||||
@ -203,14 +224,14 @@ def process_image() -> None:
|
||||
if not compress_image(facefusion.globals.output_path):
|
||||
update_status(wording.get('compressing_image_failed'))
|
||||
# validate image
|
||||
if is_image(facefusion.globals.target_path):
|
||||
if is_image(facefusion.globals.output_path):
|
||||
update_status(wording.get('processing_image_succeed'))
|
||||
else:
|
||||
update_status(wording.get('processing_image_failed'))
|
||||
|
||||
|
||||
def process_video() -> None:
|
||||
if predict_video(facefusion.globals.target_path):
|
||||
if analyse_video(facefusion.globals.target_path, facefusion.globals.trim_frame_start, facefusion.globals.trim_frame_end):
|
||||
return
|
||||
fps = detect_fps(facefusion.globals.target_path) if facefusion.globals.keep_fps else 25.0
|
||||
# create temp
|
||||
@ -247,11 +268,7 @@ def process_video() -> None:
|
||||
update_status(wording.get('clearing_temp'))
|
||||
clear_temp(facefusion.globals.target_path)
|
||||
# validate video
|
||||
if is_video(facefusion.globals.target_path):
|
||||
if is_video(facefusion.globals.output_path):
|
||||
update_status(wording.get('processing_video_succeed'))
|
||||
else:
|
||||
update_status(wording.get('processing_video_failed'))
|
||||
|
||||
|
||||
def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None:
|
||||
print('[' + scope + '] ' + message)
|
||||
|
@ -1,14 +1,52 @@
|
||||
from typing import Any, Optional, List
|
||||
from typing import Any, Optional, List, Dict, Tuple
|
||||
import threading
|
||||
import insightface
|
||||
import cv2
|
||||
import numpy
|
||||
import onnxruntime
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion.face_cache import get_faces_cache, set_faces_cache
|
||||
from facefusion.typing import Frame, Face, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender
|
||||
from facefusion.face_helper import warp_face, create_static_anchors, distance_to_kps, distance_to_bbox, apply_nms
|
||||
from facefusion.typing import Frame, Face, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, ModelValue, Bbox, Kps, Score, Embedding
|
||||
from facefusion.utilities import resolve_relative_path, conditional_download
|
||||
from facefusion.vision import resize_frame_dimension
|
||||
|
||||
FACE_ANALYSER = None
|
||||
THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
MODELS : Dict[str, ModelValue] =\
|
||||
{
|
||||
'face_detector_retinaface':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/retinaface_10g.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/retinaface_10g.onnx')
|
||||
},
|
||||
'face_detector_yunet':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yunet_2023mar.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/yunet_2023mar.onnx')
|
||||
},
|
||||
'face_recognizer_arcface_blendface':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
|
||||
},
|
||||
'face_recognizer_arcface_inswapper':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
|
||||
},
|
||||
'face_recognizer_arcface_simswap':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_simswap.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/arcface_simswap.onnx')
|
||||
},
|
||||
'gender_age':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gender_age.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gender_age.onnx')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_face_analyser() -> Any:
|
||||
@ -16,8 +54,23 @@ def get_face_analyser() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FACE_ANALYSER is None:
|
||||
FACE_ANALYSER = insightface.app.FaceAnalysis(name = 'buffalo_l', providers = facefusion.globals.execution_providers)
|
||||
FACE_ANALYSER.prepare(ctx_id = 0)
|
||||
if facefusion.globals.face_detector_model == 'retinaface':
|
||||
face_detector = onnxruntime.InferenceSession(MODELS.get('face_detector_retinaface').get('path'), providers = facefusion.globals.execution_providers)
|
||||
if facefusion.globals.face_detector_model == 'yunet':
|
||||
face_detector = cv2.FaceDetectorYN.create(MODELS.get('face_detector_yunet').get('path'), '', (0, 0))
|
||||
if facefusion.globals.face_recognizer_model == 'arcface_blendface':
|
||||
face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_blendface').get('path'), providers = facefusion.globals.execution_providers)
|
||||
if facefusion.globals.face_recognizer_model == 'arcface_inswapper':
|
||||
face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_inswapper').get('path'), providers = facefusion.globals.execution_providers)
|
||||
if facefusion.globals.face_recognizer_model == 'arcface_simswap':
|
||||
face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_simswap').get('path'), providers = facefusion.globals.execution_providers)
|
||||
gender_age = onnxruntime.InferenceSession(MODELS.get('gender_age').get('path'), providers = facefusion.globals.execution_providers)
|
||||
FACE_ANALYSER =\
|
||||
{
|
||||
'face_detector': face_detector,
|
||||
'face_recognizer': face_recognizer,
|
||||
'gender_age': gender_age
|
||||
}
|
||||
return FACE_ANALYSER
|
||||
|
||||
|
||||
@ -27,6 +80,150 @@ def clear_face_analyser() -> Any:
|
||||
FACE_ANALYSER = None
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
if not facefusion.globals.skip_download:
|
||||
download_directory_path = resolve_relative_path('../.assets/models')
|
||||
model_urls =\
|
||||
[
|
||||
MODELS.get('face_detector_retinaface').get('url'),
|
||||
MODELS.get('face_detector_yunet').get('url'),
|
||||
MODELS.get('face_recognizer_arcface_inswapper').get('url'),
|
||||
MODELS.get('face_recognizer_arcface_simswap').get('url'),
|
||||
MODELS.get('gender_age').get('url')
|
||||
]
|
||||
conditional_download(download_directory_path, model_urls)
|
||||
return True
|
||||
|
||||
|
||||
def extract_faces(frame: Frame) -> List[Face]:
|
||||
face_detector_width, face_detector_height = map(int, facefusion.globals.face_detector_size.split('x'))
|
||||
frame_height, frame_width, _ = frame.shape
|
||||
temp_frame = resize_frame_dimension(frame, face_detector_width, face_detector_height)
|
||||
temp_frame_height, temp_frame_width, _ = temp_frame.shape
|
||||
ratio_height = frame_height / temp_frame_height
|
||||
ratio_width = frame_width / temp_frame_width
|
||||
if facefusion.globals.face_detector_model == 'retinaface':
|
||||
bbox_list, kps_list, score_list = detect_with_retinaface(temp_frame, temp_frame_height, temp_frame_width, face_detector_height, face_detector_width, ratio_height, ratio_width)
|
||||
return create_faces(frame, bbox_list, kps_list, score_list)
|
||||
elif facefusion.globals.face_detector_model == 'yunet':
|
||||
bbox_list, kps_list, score_list = detect_with_yunet(temp_frame, temp_frame_height, temp_frame_width, ratio_height, ratio_width)
|
||||
return create_faces(frame, bbox_list, kps_list, score_list)
|
||||
return []
|
||||
|
||||
|
||||
def detect_with_retinaface(temp_frame : Frame, temp_frame_height : int, temp_frame_width : int, face_detector_height : int, face_detector_width : int, ratio_height : float, ratio_width : float) -> Tuple[List[Bbox], List[Kps], List[Score]]:
|
||||
face_detector = get_face_analyser().get('face_detector')
|
||||
bbox_list = []
|
||||
kps_list = []
|
||||
score_list = []
|
||||
feature_strides = [ 8, 16, 32 ]
|
||||
feature_map_channel = 3
|
||||
anchor_total = 2
|
||||
prepare_frame = numpy.zeros((face_detector_height, face_detector_width, 3))
|
||||
prepare_frame[:temp_frame_height, :temp_frame_width, :] = temp_frame
|
||||
temp_frame = (prepare_frame - 127.5) / 128.0
|
||||
temp_frame = numpy.expand_dims(temp_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
|
||||
with THREAD_SEMAPHORE:
|
||||
detections = face_detector.run(None,
|
||||
{
|
||||
face_detector.get_inputs()[0].name: temp_frame
|
||||
})
|
||||
for index, feature_stride in enumerate(feature_strides):
|
||||
keep_indices = numpy.where(detections[index] >= facefusion.globals.face_detector_score)[0]
|
||||
if keep_indices.any():
|
||||
stride_height = face_detector_height // feature_stride
|
||||
stride_width = face_detector_width // feature_stride
|
||||
anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
|
||||
bbox_raw = (detections[index + feature_map_channel] * feature_stride)
|
||||
kps_raw = detections[index + feature_map_channel * 2] * feature_stride
|
||||
for bbox in distance_to_bbox(anchors, bbox_raw)[keep_indices]:
|
||||
bbox_list.append(numpy.array(
|
||||
[
|
||||
bbox[0] * ratio_width,
|
||||
bbox[1] * ratio_height,
|
||||
bbox[2] * ratio_width,
|
||||
bbox[3] * ratio_height
|
||||
]))
|
||||
for kps in distance_to_kps(anchors, kps_raw)[keep_indices]:
|
||||
kps_list.append(kps * [ ratio_width, ratio_height ])
|
||||
for score in detections[index][keep_indices]:
|
||||
score_list.append(score[0])
|
||||
return bbox_list, kps_list, score_list
|
||||
|
||||
|
||||
def detect_with_yunet(temp_frame : Frame, temp_frame_height : int, temp_frame_width : int, ratio_height : float, ratio_width : float) -> Tuple[List[Bbox], List[Kps], List[Score]]:
|
||||
face_detector = get_face_analyser().get('face_detector')
|
||||
face_detector.setInputSize((temp_frame_width, temp_frame_height))
|
||||
face_detector.setScoreThreshold(facefusion.globals.face_detector_score)
|
||||
bbox_list = []
|
||||
kps_list = []
|
||||
score_list = []
|
||||
with THREAD_SEMAPHORE:
|
||||
_, detections = face_detector.detect(temp_frame)
|
||||
if detections.any():
|
||||
for detection in detections:
|
||||
bbox_list.append(numpy.array(
|
||||
[
|
||||
detection[0] * ratio_width,
|
||||
detection[1] * ratio_height,
|
||||
(detection[0] + detection[2]) * ratio_width,
|
||||
(detection[1] + detection[3]) * ratio_height
|
||||
]))
|
||||
kps_list.append(detection[4:14].reshape((5, 2)) * [ ratio_width, ratio_height])
|
||||
score_list.append(detection[14])
|
||||
return bbox_list, kps_list, score_list
|
||||
|
||||
|
||||
def create_faces(frame : Frame, bbox_list : List[Bbox], kps_list : List[Kps], score_list : List[Score]) -> List[Face] :
|
||||
faces : List[Face] = []
|
||||
if facefusion.globals.face_detector_score > 0:
|
||||
keep_indices = apply_nms(bbox_list, 0.4)
|
||||
for index in keep_indices:
|
||||
bbox = bbox_list[index]
|
||||
kps = kps_list[index]
|
||||
score = score_list[index]
|
||||
embedding, normed_embedding = calc_embedding(frame, kps)
|
||||
gender, age = detect_gender_age(frame, kps)
|
||||
faces.append(Face(
|
||||
bbox = bbox,
|
||||
kps = kps,
|
||||
score = score,
|
||||
embedding = embedding,
|
||||
normed_embedding = normed_embedding,
|
||||
gender = gender,
|
||||
age = age
|
||||
))
|
||||
return faces
|
||||
|
||||
|
||||
def calc_embedding(temp_frame : Frame, kps : Kps) -> Tuple[Embedding, Embedding]:
|
||||
face_recognizer = get_face_analyser().get('face_recognizer')
|
||||
crop_frame, matrix = warp_face(temp_frame, kps, 'arcface_v2', (112, 112))
|
||||
crop_frame = crop_frame.astype(numpy.float32) / 127.5 - 1
|
||||
crop_frame = crop_frame[:, :, ::-1].transpose(2, 0, 1)
|
||||
crop_frame = numpy.expand_dims(crop_frame, axis = 0)
|
||||
embedding = face_recognizer.run(None,
|
||||
{
|
||||
face_recognizer.get_inputs()[0].name: crop_frame
|
||||
})[0]
|
||||
embedding = embedding.ravel()
|
||||
normed_embedding = embedding / numpy.linalg.norm(embedding)
|
||||
return embedding, normed_embedding
|
||||
|
||||
|
||||
def detect_gender_age(frame : Frame, kps : Kps) -> Tuple[int, int]:
|
||||
gender_age = get_face_analyser().get('gender_age')
|
||||
crop_frame, affine_matrix = warp_face(frame, kps, 'arcface_v2', (96, 96))
|
||||
crop_frame = numpy.expand_dims(crop_frame, axis = 0).transpose(0, 3, 1, 2).astype(numpy.float32)
|
||||
prediction = gender_age.run(None,
|
||||
{
|
||||
gender_age.get_inputs()[0].name: crop_frame
|
||||
})[0][0]
|
||||
gender = int(numpy.argmax(prediction[:2]))
|
||||
age = int(numpy.round(prediction[2] * 100))
|
||||
return gender, age
|
||||
|
||||
|
||||
def get_one_face(frame : Frame, position : int = 0) -> Optional[Face]:
|
||||
many_faces = get_many_faces(frame)
|
||||
if many_faces:
|
||||
@ -43,10 +240,10 @@ def get_many_faces(frame : Frame) -> List[Face]:
|
||||
if faces_cache:
|
||||
faces = faces_cache
|
||||
else:
|
||||
faces = get_face_analyser().get(frame)
|
||||
faces = extract_faces(frame)
|
||||
set_faces_cache(frame, faces)
|
||||
if facefusion.globals.face_analyser_direction:
|
||||
faces = sort_by_direction(faces, facefusion.globals.face_analyser_direction)
|
||||
if facefusion.globals.face_analyser_order:
|
||||
faces = sort_by_order(faces, facefusion.globals.face_analyser_order)
|
||||
if facefusion.globals.face_analyser_age:
|
||||
faces = filter_by_age(faces, facefusion.globals.face_analyser_age)
|
||||
if facefusion.globals.face_analyser_gender:
|
||||
@ -62,38 +259,42 @@ def find_similar_faces(frame : Frame, reference_face : Face, face_distance : flo
|
||||
if many_faces:
|
||||
for face in many_faces:
|
||||
if hasattr(face, 'normed_embedding') and hasattr(reference_face, 'normed_embedding'):
|
||||
current_face_distance = numpy.sum(numpy.square(face.normed_embedding - reference_face.normed_embedding))
|
||||
current_face_distance = 1 - numpy.dot(face.normed_embedding, reference_face.normed_embedding)
|
||||
if current_face_distance < face_distance:
|
||||
similar_faces.append(face)
|
||||
return similar_faces
|
||||
|
||||
|
||||
def sort_by_direction(faces : List[Face], direction : FaceAnalyserDirection) -> List[Face]:
|
||||
if direction == 'left-right':
|
||||
return sorted(faces, key = lambda face: face['bbox'][0])
|
||||
if direction == 'right-left':
|
||||
return sorted(faces, key = lambda face: face['bbox'][0], reverse = True)
|
||||
if direction == 'top-bottom':
|
||||
return sorted(faces, key = lambda face: face['bbox'][1])
|
||||
if direction == 'bottom-top':
|
||||
return sorted(faces, key = lambda face: face['bbox'][1], reverse = True)
|
||||
if direction == 'small-large':
|
||||
return sorted(faces, key = lambda face: (face['bbox'][2] - face['bbox'][0]) * (face['bbox'][3] - face['bbox'][1]))
|
||||
if direction == 'large-small':
|
||||
return sorted(faces, key = lambda face: (face['bbox'][2] - face['bbox'][0]) * (face['bbox'][3] - face['bbox'][1]), reverse = True)
|
||||
def sort_by_order(faces : List[Face], order : FaceAnalyserOrder) -> List[Face]:
|
||||
if order == 'left-right':
|
||||
return sorted(faces, key = lambda face: face.bbox[0])
|
||||
if order == 'right-left':
|
||||
return sorted(faces, key = lambda face: face.bbox[0], reverse = True)
|
||||
if order == 'top-bottom':
|
||||
return sorted(faces, key = lambda face: face.bbox[1])
|
||||
if order == 'bottom-top':
|
||||
return sorted(faces, key = lambda face: face.bbox[1], reverse = True)
|
||||
if order == 'small-large':
|
||||
return sorted(faces, key = lambda face: (face.bbox[2] - face.bbox[0]) * (face.bbox[3] - face.bbox[1]))
|
||||
if order == 'large-small':
|
||||
return sorted(faces, key = lambda face: (face.bbox[2] - face.bbox[0]) * (face.bbox[3] - face.bbox[1]), reverse = True)
|
||||
if order == 'best-worst':
|
||||
return sorted(faces, key = lambda face: face.score, reverse = True)
|
||||
if order == 'worst-best':
|
||||
return sorted(faces, key = lambda face: face.score)
|
||||
return faces
|
||||
|
||||
|
||||
def filter_by_age(faces : List[Face], age : FaceAnalyserAge) -> List[Face]:
|
||||
filter_faces = []
|
||||
for face in faces:
|
||||
if face['age'] < 13 and age == 'child':
|
||||
if face.age < 13 and age == 'child':
|
||||
filter_faces.append(face)
|
||||
elif face['age'] < 19 and age == 'teen':
|
||||
elif face.age < 19 and age == 'teen':
|
||||
filter_faces.append(face)
|
||||
elif face['age'] < 60 and age == 'adult':
|
||||
elif face.age < 60 and age == 'adult':
|
||||
filter_faces.append(face)
|
||||
elif face['age'] > 59 and age == 'senior':
|
||||
elif face.age > 59 and age == 'senior':
|
||||
filter_faces.append(face)
|
||||
return filter_faces
|
||||
|
||||
@ -101,8 +302,8 @@ def filter_by_age(faces : List[Face], age : FaceAnalyserAge) -> List[Face]:
|
||||
def filter_by_gender(faces : List[Face], gender : FaceAnalyserGender) -> List[Face]:
|
||||
filter_faces = []
|
||||
for face in faces:
|
||||
if face['gender'] == 1 and gender == 'male':
|
||||
if face.gender == 0 and gender == 'female':
|
||||
filter_faces.append(face)
|
||||
if face['gender'] == 0 and gender == 'female':
|
||||
if face.gender == 1 and gender == 'male':
|
||||
filter_faces.append(face)
|
||||
return filter_faces
|
||||
|
@ -26,4 +26,4 @@ def clear_faces_cache() -> None:
|
||||
|
||||
|
||||
def create_frame_hash(frame : Frame) -> Optional[str]:
|
||||
return hashlib.sha256(frame.tobytes()).hexdigest() if frame is not None else None
|
||||
return hashlib.sha1(frame.tobytes()).hexdigest() if frame.any() else None
|
||||
|
119
facefusion/face_helper.py
Normal file
119
facefusion/face_helper.py
Normal file
@ -0,0 +1,119 @@
|
||||
from typing import Any, Dict, Tuple, List
|
||||
from functools import lru_cache
|
||||
from cv2.typing import Size
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
from facefusion.typing import Bbox, Kps, Frame, Matrix, Template, Padding
|
||||
|
||||
TEMPLATES : Dict[Template, numpy.ndarray[Any, Any]] =\
|
||||
{
|
||||
'arcface_v1': numpy.array(
|
||||
[
|
||||
[ 39.7300, 51.1380 ],
|
||||
[ 72.2700, 51.1380 ],
|
||||
[ 56.0000, 68.4930 ],
|
||||
[ 42.4630, 87.0100 ],
|
||||
[ 69.5370, 87.0100 ]
|
||||
]),
|
||||
'arcface_v2': numpy.array(
|
||||
[
|
||||
[ 38.2946, 51.6963 ],
|
||||
[ 73.5318, 51.5014 ],
|
||||
[ 56.0252, 71.7366 ],
|
||||
[ 41.5493, 92.3655 ],
|
||||
[ 70.7299, 92.2041 ]
|
||||
]),
|
||||
'ffhq': numpy.array(
|
||||
[
|
||||
[ 192.98138, 239.94708 ],
|
||||
[ 318.90277, 240.1936 ],
|
||||
[ 256.63416, 314.01935 ],
|
||||
[ 201.26117, 371.41043 ],
|
||||
[ 313.08905, 371.15118 ]
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
def warp_face(temp_frame : Frame, kps : Kps, template : Template, size : Size) -> Tuple[Frame, Matrix]:
|
||||
normed_template = TEMPLATES.get(template) * size[1] / size[0]
|
||||
affine_matrix = cv2.estimateAffinePartial2D(kps, normed_template, method = cv2.LMEDS)[0]
|
||||
crop_frame = cv2.warpAffine(temp_frame, affine_matrix, (size[1], size[1]), borderMode = cv2.BORDER_REPLICATE)
|
||||
return crop_frame, affine_matrix
|
||||
|
||||
|
||||
def paste_back(temp_frame : Frame, crop_frame: Frame, affine_matrix : Matrix, face_mask_blur : float, face_mask_padding : Padding) -> Frame:
|
||||
inverse_matrix = cv2.invertAffineTransform(affine_matrix)
|
||||
temp_frame_size = temp_frame.shape[:2][::-1]
|
||||
mask_size = tuple(crop_frame.shape[:2])
|
||||
mask_frame = create_static_mask_frame(mask_size, face_mask_blur, face_mask_padding)
|
||||
inverse_mask_frame = cv2.warpAffine(mask_frame, inverse_matrix, temp_frame_size).clip(0, 1)
|
||||
inverse_crop_frame = cv2.warpAffine(crop_frame, inverse_matrix, temp_frame_size, borderMode = cv2.BORDER_REPLICATE)
|
||||
paste_frame = temp_frame.copy()
|
||||
paste_frame[:, :, 0] = inverse_mask_frame * inverse_crop_frame[:, :, 0] + (1 - inverse_mask_frame) * temp_frame[:, :, 0]
|
||||
paste_frame[:, :, 1] = inverse_mask_frame * inverse_crop_frame[:, :, 1] + (1 - inverse_mask_frame) * temp_frame[:, :, 1]
|
||||
paste_frame[:, :, 2] = inverse_mask_frame * inverse_crop_frame[:, :, 2] + (1 - inverse_mask_frame) * temp_frame[:, :, 2]
|
||||
return paste_frame
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def create_static_mask_frame(mask_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Frame:
|
||||
mask_frame = numpy.ones(mask_size, numpy.float32)
|
||||
blur_amount = int(mask_size[0] * 0.5 * face_mask_blur)
|
||||
blur_area = max(blur_amount // 2, 1)
|
||||
mask_frame[:max(blur_area, int(mask_size[1] * face_mask_padding[0] / 100)), :] = 0
|
||||
mask_frame[-max(blur_area, int(mask_size[1] * face_mask_padding[2] / 100)):, :] = 0
|
||||
mask_frame[:, :max(blur_area, int(mask_size[0] * face_mask_padding[3] / 100))] = 0
|
||||
mask_frame[:, -max(blur_area, int(mask_size[0] * face_mask_padding[1] / 100)):] = 0
|
||||
if blur_amount > 0:
|
||||
mask_frame = cv2.GaussianBlur(mask_frame, (0, 0), blur_amount * 0.25)
|
||||
return mask_frame
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def create_static_anchors(feature_stride : int, anchor_total : int, stride_height : int, stride_width : int) -> numpy.ndarray[Any, Any]:
|
||||
y, x = numpy.mgrid[:stride_height, :stride_width][::-1]
|
||||
anchors = numpy.stack((y, x), axis = -1)
|
||||
anchors = (anchors * feature_stride).reshape((-1, 2))
|
||||
anchors = numpy.stack([ anchors ] * anchor_total, axis = 1).reshape((-1, 2))
|
||||
return anchors
|
||||
|
||||
|
||||
def distance_to_bbox(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> Bbox:
|
||||
x1 = points[:, 0] - distance[:, 0]
|
||||
y1 = points[:, 1] - distance[:, 1]
|
||||
x2 = points[:, 0] + distance[:, 2]
|
||||
y2 = points[:, 1] + distance[:, 3]
|
||||
bbox = numpy.column_stack([ x1, y1, x2, y2 ])
|
||||
return bbox
|
||||
|
||||
|
||||
def distance_to_kps(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> Kps:
|
||||
x = points[:, 0::2] + distance[:, 0::2]
|
||||
y = points[:, 1::2] + distance[:, 1::2]
|
||||
kps = numpy.stack((x, y), axis = -1)
|
||||
return kps
|
||||
|
||||
|
||||
def apply_nms(bbox_list : List[Bbox], iou_threshold : float) -> List[int]:
|
||||
keep_indices = []
|
||||
dimension_list = numpy.reshape(bbox_list, (-1, 4))
|
||||
x1 = dimension_list[:, 0]
|
||||
y1 = dimension_list[:, 1]
|
||||
x2 = dimension_list[:, 2]
|
||||
y2 = dimension_list[:, 3]
|
||||
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
indices = numpy.arange(len(bbox_list))
|
||||
while indices.size > 0:
|
||||
index = indices[0]
|
||||
remain_indices = indices[1:]
|
||||
keep_indices.append(index)
|
||||
xx1 = numpy.maximum(x1[index], x1[remain_indices])
|
||||
yy1 = numpy.maximum(y1[index], y1[remain_indices])
|
||||
xx2 = numpy.minimum(x2[index], x2[remain_indices])
|
||||
yy2 = numpy.minimum(y2[index], y2[remain_indices])
|
||||
width = numpy.maximum(0, xx2 - xx1 + 1)
|
||||
height = numpy.maximum(0, yy2 - yy1 + 1)
|
||||
iou = width * height / (areas[index] + areas[remain_indices] - width * height)
|
||||
indices = indices[numpy.where(iou <= iou_threshold)[0] + 1]
|
||||
return keep_indices
|
16
facefusion/globals.py
Normal file → Executable file
16
facefusion/globals.py
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder
|
||||
from facefusion.typing import FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, OutputVideoEncoder, FaceDetectorModel, FaceRecognizerModel, TempFrameFormat, Padding
|
||||
|
||||
# general
|
||||
source_path : Optional[str] = None
|
||||
@ -14,14 +14,22 @@ execution_providers : List[str] = []
|
||||
execution_thread_count : Optional[int] = None
|
||||
execution_queue_count : Optional[int] = None
|
||||
max_memory : Optional[int] = None
|
||||
# face recognition
|
||||
face_recognition : Optional[FaceRecognition] = None
|
||||
face_analyser_direction : Optional[FaceAnalyserDirection] = None
|
||||
# face analyser
|
||||
face_analyser_order : Optional[FaceAnalyserOrder] = None
|
||||
face_analyser_age : Optional[FaceAnalyserAge] = None
|
||||
face_analyser_gender : Optional[FaceAnalyserGender] = None
|
||||
face_detector_model : Optional[FaceDetectorModel] = None
|
||||
face_detector_size : Optional[str] = None
|
||||
face_detector_score : Optional[float] = None
|
||||
face_recognizer_model : Optional[FaceRecognizerModel] = None
|
||||
# face selector
|
||||
face_selector_mode : Optional[FaceSelectorMode] = None
|
||||
reference_face_position : Optional[int] = None
|
||||
reference_face_distance : Optional[float] = None
|
||||
reference_frame_number : Optional[int] = None
|
||||
# face mask
|
||||
face_mask_blur : Optional[float] = None
|
||||
face_mask_padding : Optional[Padding] = None
|
||||
# frame extraction
|
||||
trim_frame_start : Optional[int] = None
|
||||
trim_frame_end : Optional[int] = None
|
||||
|
@ -17,12 +17,12 @@ TORCH : Dict[str, str] =\
|
||||
}
|
||||
ONNXRUNTIMES : Dict[str, Tuple[str, str]] =\
|
||||
{
|
||||
'default': ('onnxruntime', '1.16.0'),
|
||||
'cuda': ('onnxruntime-gpu', '1.16.0'),
|
||||
'default': ('onnxruntime', '1.16.3'),
|
||||
'cuda': ('onnxruntime-gpu', '1.16.3'),
|
||||
'coreml-legacy': ('onnxruntime-coreml', '1.13.1'),
|
||||
'coreml-silicon': ('onnxruntime-silicon', '1.16.0'),
|
||||
'directml': ('onnxruntime-directml', '1.16.0'),
|
||||
'openvino': ('onnxruntime-openvino', '1.15.0')
|
||||
'directml': ('onnxruntime-directml', '1.16.3'),
|
||||
'openvino': ('onnxruntime-openvino', '1.16.0')
|
||||
}
|
||||
|
||||
|
||||
@ -46,18 +46,10 @@ def run(program : ArgumentParser) -> None:
|
||||
else:
|
||||
answers = inquirer.prompt(
|
||||
[
|
||||
inquirer.List(
|
||||
'torch',
|
||||
message = wording.get('install_dependency_help').format(dependency = 'torch'),
|
||||
choices = list(TORCH.keys())
|
||||
),
|
||||
inquirer.List(
|
||||
'onnxruntime',
|
||||
message = wording.get('install_dependency_help').format(dependency = 'onnxruntime'),
|
||||
choices = list(ONNXRUNTIMES.keys())
|
||||
)
|
||||
inquirer.List('torch', message = wording.get('install_dependency_help').format(dependency = 'torch'), choices = list(TORCH.keys())),
|
||||
inquirer.List('onnxruntime', message = wording.get('install_dependency_help').format(dependency = 'onnxruntime'), choices = list(ONNXRUNTIMES.keys()))
|
||||
])
|
||||
if answers is not None:
|
||||
if answers:
|
||||
torch = answers['torch']
|
||||
torch_wheel = TORCH[torch]
|
||||
onnxruntime = answers['onnxruntime']
|
||||
|
@ -2,7 +2,7 @@ METADATA =\
|
||||
{
|
||||
'name': 'FaceFusion',
|
||||
'description': 'Next generation face swapper and enhancer',
|
||||
'version': '1.3.1',
|
||||
'version': '2.0.0',
|
||||
'license': 'MIT',
|
||||
'author': 'Henry Ruhs',
|
||||
'url': 'https://facefusion.io'
|
||||
|
@ -1,58 +0,0 @@
|
||||
import threading
|
||||
from functools import lru_cache
|
||||
|
||||
import numpy
|
||||
import opennsfw2
|
||||
from PIL import Image
|
||||
from keras import Model
|
||||
|
||||
from facefusion.typing import Frame
|
||||
|
||||
PREDICTOR = None
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
MAX_PROBABILITY = 0.75
|
||||
FRAME_INTERVAL = 25
|
||||
STREAM_COUNTER = 0
|
||||
|
||||
|
||||
def get_predictor() -> Model:
|
||||
global PREDICTOR
|
||||
|
||||
with THREAD_LOCK:
|
||||
if PREDICTOR is None:
|
||||
PREDICTOR = opennsfw2.make_open_nsfw_model()
|
||||
return PREDICTOR
|
||||
|
||||
|
||||
def clear_predictor() -> None:
|
||||
global PREDICTOR
|
||||
|
||||
PREDICTOR = None
|
||||
|
||||
|
||||
def predict_stream(frame : Frame) -> bool:
|
||||
global STREAM_COUNTER
|
||||
|
||||
STREAM_COUNTER = STREAM_COUNTER + 1
|
||||
if STREAM_COUNTER % FRAME_INTERVAL == 0:
|
||||
return predict_frame(frame)
|
||||
return False
|
||||
|
||||
|
||||
def predict_frame(frame : Frame) -> bool:
|
||||
image = Image.fromarray(frame)
|
||||
image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO)
|
||||
views = numpy.expand_dims(image, axis = 0)
|
||||
_, probability = get_predictor().predict(views)[0]
|
||||
return probability > MAX_PROBABILITY
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def predict_image(image_path : str) -> bool:
|
||||
return opennsfw2.predict_image(image_path) > MAX_PROBABILITY
|
||||
|
||||
|
||||
@lru_cache(maxsize = None)
|
||||
def predict_video(video_path : str) -> bool:
|
||||
_, probabilities = opennsfw2.predict_video_frames(video_path = video_path, frame_interval = FRAME_INTERVAL)
|
||||
return any(probability > MAX_PROBABILITY for probability in probabilities)
|
14
facefusion/processors/frame/choices.py
Normal file → Executable file
14
facefusion/processors/frame/choices.py
Normal file → Executable file
@ -1,5 +1,13 @@
|
||||
from typing import List
|
||||
import numpy
|
||||
|
||||
face_swapper_models : List[str] = [ 'inswapper_128', 'inswapper_128_fp16' ]
|
||||
face_enhancer_models : List[str] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_512' ]
|
||||
frame_enhancer_models : List[str] = [ 'realesrgan_x2plus', 'realesrgan_x4plus', 'realesrnet_x4plus' ]
|
||||
from facefusion.processors.frame.typings import FaceSwapperModel, FaceEnhancerModel, FrameEnhancerModel, FaceDebuggerItem
|
||||
|
||||
face_swapper_models : List[FaceSwapperModel] = [ 'blendface_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial' ]
|
||||
face_enhancer_models : List[FaceEnhancerModel] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'restoreformer' ]
|
||||
frame_enhancer_models : List[FrameEnhancerModel] = [ 'real_esrgan_x2plus', 'real_esrgan_x4plus', 'real_esrnet_x4plus' ]
|
||||
|
||||
face_enhancer_blend_range : List[int] = numpy.arange(0, 101, 1).tolist()
|
||||
frame_enhancer_blend_range : List[int] = numpy.arange(0, 101, 1).tolist()
|
||||
|
||||
face_debugger_items : List[FaceDebuggerItem] = [ 'bbox', 'kps', 'face-mask', 'score' ]
|
||||
|
@ -1,7 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import psutil
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from queue import Queue
|
||||
from types import ModuleType
|
||||
@ -9,8 +7,9 @@ from typing import Any, List
|
||||
from tqdm import tqdm
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.typing import Process_Frames
|
||||
from facefusion import wording
|
||||
from facefusion.utilities import encode_execution_providers
|
||||
|
||||
FRAME_PROCESSORS_MODULES : List[ModuleType] = []
|
||||
FRAME_PROCESSORS_METHODS =\
|
||||
@ -63,15 +62,20 @@ def clear_frame_processors_modules() -> None:
|
||||
|
||||
|
||||
def multi_process_frames(source_path : str, temp_frame_paths : List[str], process_frames : Process_Frames) -> None:
|
||||
progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
|
||||
with tqdm(total = len(temp_frame_paths), desc = wording.get('processing'), unit = 'frame', dynamic_ncols = True, bar_format = progress_bar_format) as progress:
|
||||
with tqdm(total = len(temp_frame_paths), desc = wording.get('processing'), unit = 'frame', ascii = ' =') as progress:
|
||||
progress.set_postfix(
|
||||
{
|
||||
'execution_providers': encode_execution_providers(facefusion.globals.execution_providers),
|
||||
'execution_thread_count': facefusion.globals.execution_thread_count,
|
||||
'execution_queue_count': facefusion.globals.execution_queue_count
|
||||
})
|
||||
with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:
|
||||
futures = []
|
||||
queue_temp_frame_paths : Queue[str] = create_queue(temp_frame_paths)
|
||||
queue_per_future = max(len(temp_frame_paths) // facefusion.globals.execution_thread_count * facefusion.globals.execution_queue_count, 1)
|
||||
while not queue_temp_frame_paths.empty():
|
||||
payload_temp_frame_paths = pick_queue(queue_temp_frame_paths, queue_per_future)
|
||||
future = executor.submit(process_frames, source_path, payload_temp_frame_paths, lambda: update_progress(progress))
|
||||
future = executor.submit(process_frames, source_path, payload_temp_frame_paths, progress.update)
|
||||
futures.append(future)
|
||||
for future_done in as_completed(futures):
|
||||
future_done.result()
|
||||
@ -90,17 +94,3 @@ def pick_queue(queue : Queue[str], queue_per_future : int) -> List[str]:
|
||||
if not queue.empty():
|
||||
queues.append(queue.get())
|
||||
return queues
|
||||
|
||||
|
||||
def update_progress(progress : Any = None) -> None:
|
||||
process = psutil.Process(os.getpid())
|
||||
memory_usage = process.memory_info().rss / 1024 / 1024 / 1024
|
||||
progress.set_postfix(
|
||||
{
|
||||
'memory_usage': '{:.2f}'.format(memory_usage).zfill(5) + 'GB',
|
||||
'execution_providers': facefusion.globals.execution_providers,
|
||||
'execution_thread_count': facefusion.globals.execution_thread_count,
|
||||
'execution_queue_count': facefusion.globals.execution_queue_count
|
||||
})
|
||||
progress.refresh()
|
||||
progress.update(1)
|
||||
|
11
facefusion/processors/frame/globals.py
Normal file → Executable file
11
facefusion/processors/frame/globals.py
Normal file → Executable file
@ -1,7 +1,10 @@
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
face_swapper_model : Optional[str] = None
|
||||
face_enhancer_model : Optional[str] = None
|
||||
from facefusion.processors.frame.typings import FaceSwapperModel, FaceEnhancerModel, FrameEnhancerModel, FaceDebuggerItem
|
||||
|
||||
face_swapper_model : Optional[FaceSwapperModel] = None
|
||||
face_enhancer_model : Optional[FaceEnhancerModel] = None
|
||||
face_enhancer_blend : Optional[int] = None
|
||||
frame_enhancer_model : Optional[str] = None
|
||||
frame_enhancer_model : Optional[FrameEnhancerModel] = None
|
||||
frame_enhancer_blend : Optional[int] = None
|
||||
face_debugger_items : Optional[List[FaceDebuggerItem]] = None
|
||||
|
123
facefusion/processors/frame/modules/face_debugger.py
Executable file
123
facefusion/processors/frame/modules/face_debugger.py
Executable file
@ -0,0 +1,123 @@
|
||||
from typing import Any, List, Literal
|
||||
from argparse import ArgumentParser
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.processors.frame.core as frame_processors
|
||||
from facefusion import wording
|
||||
from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
|
||||
from facefusion.face_reference import get_face_reference
|
||||
from facefusion.content_analyser import clear_content_analyser
|
||||
from facefusion.typing import Face, Frame, Update_Process, ProcessMode
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.face_helper import warp_face, create_static_mask_frame
|
||||
from facefusion.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices
|
||||
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_DEBUGGER'
|
||||
|
||||
|
||||
def get_frame_processor() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def clear_frame_processor() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def get_options(key : Literal['model']) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_options(key : Literal['model'], value : Any) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--face-debugger-items', help = wording.get('face_debugger_items_help'), dest = 'face_debugger_items', default = [ 'kps', 'face-mask' ], choices = frame_processors_choices.face_debugger_items, nargs = '+')
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
args = program.parse_args()
|
||||
frame_processors_globals.face_debugger_items = args.face_debugger_items
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def pre_process(mode : ProcessMode) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def post_process() -> None:
|
||||
clear_frame_processor()
|
||||
clear_face_analyser()
|
||||
clear_content_analyser()
|
||||
|
||||
|
||||
def debug_face(source_face : Face, target_face : Face, temp_frame : Frame) -> Frame:
|
||||
primary_color = (0, 0, 255)
|
||||
secondary_color = (0, 255, 0)
|
||||
bounding_box = target_face.bbox.astype(numpy.int32)
|
||||
if 'bbox' in frame_processors_globals.face_debugger_items:
|
||||
cv2.rectangle(temp_frame, (bounding_box[0], bounding_box[1]), (bounding_box[2], bounding_box[3]), secondary_color, 2)
|
||||
if 'face-mask' in frame_processors_globals.face_debugger_items:
|
||||
crop_frame, affine_matrix = warp_face(temp_frame, target_face.kps, 'arcface_v2', (128, 128))
|
||||
inverse_matrix = cv2.invertAffineTransform(affine_matrix)
|
||||
temp_frame_size = temp_frame.shape[:2][::-1]
|
||||
mask_frame = create_static_mask_frame(crop_frame.shape[:2], 0, facefusion.globals.face_mask_padding)
|
||||
mask_frame[mask_frame > 0] = 255
|
||||
inverse_mask_frame = cv2.warpAffine(mask_frame.astype(numpy.uint8), inverse_matrix, temp_frame_size)
|
||||
inverse_mask_contours = cv2.findContours(inverse_mask_frame, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
|
||||
cv2.drawContours(temp_frame, inverse_mask_contours, 0, primary_color, 2)
|
||||
if bounding_box[3] - bounding_box[1] > 60 and bounding_box[2] - bounding_box[0] > 60:
|
||||
if 'kps' in frame_processors_globals.face_debugger_items:
|
||||
kps = target_face.kps.astype(numpy.int32)
|
||||
for index in range(kps.shape[0]):
|
||||
cv2.circle(temp_frame, (kps[index][0], kps[index][1]), 3, primary_color, -1)
|
||||
if 'score' in frame_processors_globals.face_debugger_items:
|
||||
score_text = str(round(target_face.score, 2))
|
||||
score_position = (bounding_box[0] + 10, bounding_box[1] + 20)
|
||||
cv2.putText(temp_frame, score_text, score_position, cv2.FONT_HERSHEY_SIMPLEX, 0.5, secondary_color, 2)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame) -> Frame:
|
||||
if 'reference' in facefusion.globals.face_selector_mode:
|
||||
similar_faces = find_similar_faces(temp_frame, reference_face, facefusion.globals.reference_face_distance)
|
||||
if similar_faces:
|
||||
for similar_face in similar_faces:
|
||||
temp_frame = debug_face(source_face, similar_face, temp_frame)
|
||||
if 'one' in facefusion.globals.face_selector_mode:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
temp_frame = debug_face(source_face, target_face, temp_frame)
|
||||
if 'many' in facefusion.globals.face_selector_mode:
|
||||
many_faces = get_many_faces(temp_frame)
|
||||
if many_faces:
|
||||
for target_face in many_faces:
|
||||
temp_frame = debug_face(source_face, target_face, temp_frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
|
||||
source_face = get_one_face(read_static_image(source_path))
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = read_image(temp_frame_path)
|
||||
result_frame = process_frame(source_face, reference_face, temp_frame)
|
||||
write_image(temp_frame_path, result_frame)
|
||||
update_progress()
|
||||
|
||||
|
||||
def process_image(source_path : str, target_path : str, output_path : str) -> None:
|
||||
source_face = get_one_face(read_static_image(source_path))
|
||||
target_frame = read_static_image(target_path)
|
||||
reference_face = get_one_face(target_frame, facefusion.globals.reference_face_position) if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
result_frame = process_frame(source_face, reference_face, target_frame)
|
||||
write_image(output_path, result_frame)
|
||||
|
||||
|
||||
def process_video(source_path : str, temp_frame_paths : List[str]) -> None:
|
||||
frame_processors.multi_process_frames(source_path, temp_frame_paths, process_frames)
|
101
facefusion/processors/frame/modules/face_enhancer.py
Normal file → Executable file
101
facefusion/processors/frame/modules/face_enhancer.py
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
from typing import Any, List, Tuple, Dict, Literal, Optional
|
||||
from typing import Any, List, Dict, Literal, Optional
|
||||
from argparse import ArgumentParser
|
||||
import cv2
|
||||
import threading
|
||||
@ -6,11 +6,13 @@ import numpy
|
||||
import onnxruntime
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.processors.frame.core as frame_processors
|
||||
from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import get_many_faces, clear_face_analyser
|
||||
from facefusion.typing import Face, Frame, Matrix, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
|
||||
from facefusion.face_helper import warp_face, paste_back
|
||||
from facefusion.content_analyser import clear_content_analyser
|
||||
from facefusion.typing import Face, Frame, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done, create_metavar, update_status
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
@ -24,27 +26,51 @@ MODELS : Dict[str, ModelValue] =\
|
||||
'codeformer':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/codeformer.onnx')
|
||||
'path': resolve_relative_path('../.assets/models/codeformer.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
},
|
||||
'gfpgan_1.2':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.2.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.2.onnx')
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.2.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gfpgan_1.2.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
},
|
||||
'gfpgan_1.3':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.3.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.3.onnx')
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.3.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gfpgan_1.3.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
},
|
||||
'gfpgan_1.4':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.4.onnx')
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.4.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gfpgan_1.4.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
},
|
||||
'gpen_bfr_256':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_256.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gpen_bfr_256.onnx'),
|
||||
'template': 'arcface_v2',
|
||||
'size': (128, 256)
|
||||
},
|
||||
'gpen_bfr_512':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GPEN-BFR-512.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GPEN-BFR-512.onnx')
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_512.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/gpen_bfr_512.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
},
|
||||
'restoreformer':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/restoreformer.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/restoreformer.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 512)
|
||||
}
|
||||
}
|
||||
OPTIONS : Optional[OptionsWithModel] = None
|
||||
@ -66,7 +92,7 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
def get_options(key : Literal['model']) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
@ -77,7 +103,7 @@ def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
def set_options(key : Literal['model'], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
@ -85,7 +111,7 @@ def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--face-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'face_enhancer_model', default = 'gfpgan_1.4', choices = frame_processors_choices.face_enhancer_models)
|
||||
program.add_argument('--face-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest= 'face_enhancer_blend', type = int, default= 100, choices = range(101), metavar = '[0-100]')
|
||||
program.add_argument('--face-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest = 'face_enhancer_blend', type = int, default = 80, choices = frame_processors_choices.face_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.face_enhancer_blend_range))
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
@ -123,12 +149,15 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
clear_frame_processor()
|
||||
clear_face_analyser()
|
||||
clear_content_analyser()
|
||||
read_static_image.cache_clear()
|
||||
|
||||
|
||||
def enhance_face(target_face: Face, temp_frame: Frame) -> Frame:
|
||||
frame_processor = get_frame_processor()
|
||||
crop_frame, affine_matrix = warp_face(target_face, temp_frame)
|
||||
model_template = get_options('model').get('template')
|
||||
model_size = get_options('model').get('size')
|
||||
crop_frame, affine_matrix = warp_face(temp_frame, target_face.kps, model_template, model_size)
|
||||
crop_frame = prepare_crop_frame(crop_frame)
|
||||
frame_processor_inputs = {}
|
||||
for frame_processor_input in frame_processor.get_inputs():
|
||||
@ -139,25 +168,11 @@ def enhance_face(target_face: Face, temp_frame: Frame) -> Frame:
|
||||
with THREAD_SEMAPHORE:
|
||||
crop_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
|
||||
crop_frame = normalize_crop_frame(crop_frame)
|
||||
paste_frame = paste_back(temp_frame, crop_frame, affine_matrix)
|
||||
paste_frame = paste_back(temp_frame, crop_frame, affine_matrix, facefusion.globals.face_mask_blur, (0, 0, 0, 0))
|
||||
temp_frame = blend_frame(temp_frame, paste_frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def warp_face(target_face : Face, temp_frame : Frame) -> Tuple[Frame, Matrix]:
|
||||
template = numpy.array(
|
||||
[
|
||||
[ 192.98138, 239.94708 ],
|
||||
[ 318.90277, 240.1936 ],
|
||||
[ 256.63416, 314.01935 ],
|
||||
[ 201.26117, 371.41043 ],
|
||||
[ 313.08905, 371.15118 ]
|
||||
])
|
||||
affine_matrix = cv2.estimateAffinePartial2D(target_face['kps'], template, method = cv2.LMEDS)[0]
|
||||
crop_frame = cv2.warpAffine(temp_frame, affine_matrix, (512, 512))
|
||||
return crop_frame, affine_matrix
|
||||
|
||||
|
||||
def prepare_crop_frame(crop_frame : Frame) -> Frame:
|
||||
crop_frame = crop_frame[:, :, ::-1] / 255.0
|
||||
crop_frame = (crop_frame - 0.5) / 0.5
|
||||
@ -174,26 +189,6 @@ def normalize_crop_frame(crop_frame : Frame) -> Frame:
|
||||
return crop_frame
|
||||
|
||||
|
||||
def paste_back(temp_frame : Frame, crop_frame : Frame, affine_matrix : Matrix) -> Frame:
|
||||
inverse_affine_matrix = cv2.invertAffineTransform(affine_matrix)
|
||||
temp_frame_height, temp_frame_width = temp_frame.shape[0:2]
|
||||
crop_frame_height, crop_frame_width = crop_frame.shape[0:2]
|
||||
inverse_crop_frame = cv2.warpAffine(crop_frame, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
|
||||
inverse_mask = numpy.ones((crop_frame_height, crop_frame_width, 3), dtype = numpy.float32)
|
||||
inverse_mask_frame = cv2.warpAffine(inverse_mask, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
|
||||
inverse_mask_frame = cv2.erode(inverse_mask_frame, numpy.ones((2, 2)))
|
||||
inverse_mask_border = inverse_mask_frame * inverse_crop_frame
|
||||
inverse_mask_area = numpy.sum(inverse_mask_frame) // 3
|
||||
inverse_mask_edge = int(inverse_mask_area ** 0.5) // 20
|
||||
inverse_mask_radius = inverse_mask_edge * 2
|
||||
inverse_mask_center = cv2.erode(inverse_mask_frame, numpy.ones((inverse_mask_radius, inverse_mask_radius)))
|
||||
inverse_mask_blur_size = inverse_mask_edge * 2 + 1
|
||||
inverse_mask_blur_area = cv2.GaussianBlur(inverse_mask_center, (inverse_mask_blur_size, inverse_mask_blur_size), 0)
|
||||
temp_frame = inverse_mask_blur_area * inverse_mask_border + (1 - inverse_mask_blur_area) * temp_frame
|
||||
temp_frame = temp_frame.clip(0, 255).astype(numpy.uint8)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
|
||||
face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100)
|
||||
temp_frame = cv2.addWeighted(temp_frame, face_enhancer_blend, paste_frame, 1 - face_enhancer_blend, 0)
|
||||
@ -223,4 +218,4 @@ def process_image(source_path : str, target_path : str, output_path : str) -> No
|
||||
|
||||
|
||||
def process_video(source_path : str, temp_frame_paths : List[str]) -> None:
|
||||
facefusion.processors.frame.core.multi_process_frames(None, temp_frame_paths, process_frames)
|
||||
frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
|
||||
|
169
facefusion/processors/frame/modules/face_swapper.py
Normal file → Executable file
169
facefusion/processors/frame/modules/face_swapper.py
Normal file → Executable file
@ -1,34 +1,79 @@
|
||||
from typing import Any, List, Dict, Literal, Optional
|
||||
from argparse import ArgumentParser
|
||||
import insightface
|
||||
import threading
|
||||
import numpy
|
||||
import onnx
|
||||
import onnxruntime
|
||||
from onnx import numpy_helper
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.processors.frame.core as frame_processors
|
||||
from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
|
||||
from facefusion.face_reference import get_face_reference, set_face_reference
|
||||
from facefusion.typing import Face, Frame, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
|
||||
from facefusion.face_helper import warp_face, paste_back
|
||||
from facefusion.face_reference import get_face_reference
|
||||
from facefusion.content_analyser import clear_content_analyser
|
||||
from facefusion.typing import Face, Frame, Update_Process, ProcessMode, ModelValue, OptionsWithModel, Embedding
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done, update_status
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
|
||||
FRAME_PROCESSOR = None
|
||||
MODEL_MATRIX = None
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_SWAPPER'
|
||||
MODELS : Dict[str, ModelValue] =\
|
||||
{
|
||||
'blendface_256':
|
||||
{
|
||||
'type': 'blendface',
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/blendface_256.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/blendface_256.onnx'),
|
||||
'template': 'ffhq',
|
||||
'size': (512, 256),
|
||||
'mean': [ 0.0, 0.0, 0.0 ],
|
||||
'standard_deviation': [ 1.0, 1.0, 1.0 ]
|
||||
},
|
||||
'inswapper_128':
|
||||
{
|
||||
'type': 'inswapper',
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128.onnx')
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128.onnx'),
|
||||
'template': 'arcface_v2',
|
||||
'size': (128, 128),
|
||||
'mean': [ 0.0, 0.0, 0.0 ],
|
||||
'standard_deviation': [ 1.0, 1.0, 1.0 ]
|
||||
},
|
||||
'inswapper_128_fp16':
|
||||
{
|
||||
'type': 'inswapper',
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx')
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx'),
|
||||
'template': 'arcface_v2',
|
||||
'size': (128, 128),
|
||||
'mean': [ 0.0, 0.0, 0.0 ],
|
||||
'standard_deviation': [ 1.0, 1.0, 1.0 ]
|
||||
},
|
||||
'simswap_256':
|
||||
{
|
||||
'type': 'simswap',
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_256.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/simswap_256.onnx'),
|
||||
'template': 'arcface_v1',
|
||||
'size': (112, 256),
|
||||
'mean': [ 0.485, 0.456, 0.406 ],
|
||||
'standard_deviation': [ 0.229, 0.224, 0.225 ]
|
||||
},
|
||||
'simswap_512_unofficial':
|
||||
{
|
||||
'type': 'simswap',
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_512_unofficial.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/simswap_512_unofficial.onnx'),
|
||||
'template': 'arcface_v1',
|
||||
'size': (112, 512),
|
||||
'mean': [ 0.0, 0.0, 0.0 ],
|
||||
'standard_deviation': [ 1.0, 1.0, 1.0 ]
|
||||
}
|
||||
}
|
||||
OPTIONS : Optional[OptionsWithModel] = None
|
||||
@ -40,7 +85,7 @@ def get_frame_processor() -> Any:
|
||||
with THREAD_LOCK:
|
||||
if FRAME_PROCESSOR is None:
|
||||
model_path = get_options('model').get('path')
|
||||
FRAME_PROCESSOR = insightface.model_zoo.get_model(model_path, providers = facefusion.globals.execution_providers)
|
||||
FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = facefusion.globals.execution_providers)
|
||||
return FRAME_PROCESSOR
|
||||
|
||||
|
||||
@ -50,18 +95,35 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
def get_model_matrix() -> Any:
|
||||
global MODEL_MATRIX
|
||||
|
||||
with THREAD_LOCK:
|
||||
if MODEL_MATRIX is None:
|
||||
model_path = get_options('model').get('path')
|
||||
model = onnx.load(model_path)
|
||||
MODEL_MATRIX = numpy_helper.to_array(model.graph.initializer[-1])
|
||||
return MODEL_MATRIX
|
||||
|
||||
|
||||
def clear_model_matrix() -> None:
|
||||
global MODEL_MATRIX
|
||||
|
||||
MODEL_MATRIX = None
|
||||
|
||||
|
||||
def get_options(key : Literal['model']) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
OPTIONS = \
|
||||
OPTIONS =\
|
||||
{
|
||||
'model': MODELS[frame_processors_globals.face_swapper_model]
|
||||
}
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
def set_options(key : Literal['model'], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
@ -74,6 +136,12 @@ def register_args(program : ArgumentParser) -> None:
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
args = program.parse_args()
|
||||
frame_processors_globals.face_swapper_model = args.face_swapper_model
|
||||
if args.face_swapper_model == 'blendface_256':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_blendface'
|
||||
if args.face_swapper_model == 'inswapper_128' or args.face_swapper_model == 'inswapper_128_fp16':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_inswapper'
|
||||
if args.face_swapper_model == 'simswap_256' or args.face_swapper_model == 'simswap_512_unofficial':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_simswap'
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
@ -110,21 +178,82 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
|
||||
def post_process() -> None:
|
||||
clear_frame_processor()
|
||||
clear_model_matrix()
|
||||
clear_face_analyser()
|
||||
clear_content_analyser()
|
||||
read_static_image.cache_clear()
|
||||
|
||||
|
||||
def swap_face(source_face : Face, target_face : Face, temp_frame : Frame) -> Frame:
|
||||
return get_frame_processor().get(temp_frame, target_face, source_face, paste_back = True)
|
||||
frame_processor = get_frame_processor()
|
||||
model_template = get_options('model').get('template')
|
||||
model_size = get_options('model').get('size')
|
||||
model_type = get_options('model').get('type')
|
||||
crop_frame, affine_matrix = warp_face(temp_frame, target_face.kps, model_template, model_size)
|
||||
crop_frame = prepare_crop_frame(crop_frame)
|
||||
frame_processor_inputs = {}
|
||||
for frame_processor_input in frame_processor.get_inputs():
|
||||
if frame_processor_input.name == 'source':
|
||||
if model_type == 'blendface':
|
||||
frame_processor_inputs[frame_processor_input.name] = prepare_source_frame(source_face)
|
||||
else:
|
||||
frame_processor_inputs[frame_processor_input.name] = prepare_source_embedding(source_face)
|
||||
if frame_processor_input.name == 'target':
|
||||
frame_processor_inputs[frame_processor_input.name] = crop_frame
|
||||
crop_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
|
||||
crop_frame = normalize_crop_frame(crop_frame)
|
||||
temp_frame = paste_back(temp_frame, crop_frame, affine_matrix, facefusion.globals.face_mask_blur, facefusion.globals.face_mask_padding)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def prepare_source_frame(source_face : Face) -> numpy.ndarray[Any, Any]:
|
||||
source_frame = read_static_image(facefusion.globals.source_path)
|
||||
source_frame, _ = warp_face(source_frame, source_face.kps, 'arcface_v2', (112, 112))
|
||||
source_frame = source_frame[:, :, ::-1] / 255.0
|
||||
source_frame = source_frame.transpose(2, 0, 1)
|
||||
source_frame = numpy.expand_dims(source_frame, axis = 0).astype(numpy.float32)
|
||||
return source_frame
|
||||
|
||||
|
||||
def prepare_source_embedding(source_face : Face) -> Embedding:
|
||||
model_type = get_options('model').get('type')
|
||||
if model_type == 'inswapper':
|
||||
model_matrix = get_model_matrix()
|
||||
source_embedding = source_face.embedding.reshape((1, -1))
|
||||
source_embedding = numpy.dot(source_embedding, model_matrix) / numpy.linalg.norm(source_embedding)
|
||||
else:
|
||||
source_embedding = source_face.normed_embedding.reshape(1, -1)
|
||||
return source_embedding
|
||||
|
||||
|
||||
def prepare_crop_frame(crop_frame : Frame) -> Frame:
|
||||
model_mean = get_options('model').get('mean')
|
||||
model_standard_deviation = get_options('model').get('standard_deviation')
|
||||
crop_frame = crop_frame[:, :, ::-1] / 255.0
|
||||
crop_frame = (crop_frame - model_mean) / model_standard_deviation
|
||||
crop_frame = crop_frame.transpose(2, 0, 1)
|
||||
crop_frame = numpy.expand_dims(crop_frame, axis = 0).astype(numpy.float32)
|
||||
return crop_frame
|
||||
|
||||
|
||||
def normalize_crop_frame(crop_frame : Frame) -> Frame:
|
||||
crop_frame = crop_frame.transpose(1, 2, 0)
|
||||
crop_frame = (crop_frame * 255.0).round()
|
||||
crop_frame = crop_frame[:, :, ::-1].astype(numpy.uint8)
|
||||
return crop_frame
|
||||
|
||||
|
||||
def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame) -> Frame:
|
||||
if 'reference' in facefusion.globals.face_recognition:
|
||||
if 'reference' in facefusion.globals.face_selector_mode:
|
||||
similar_faces = find_similar_faces(temp_frame, reference_face, facefusion.globals.reference_face_distance)
|
||||
if similar_faces:
|
||||
for similar_face in similar_faces:
|
||||
temp_frame = swap_face(source_face, similar_face, temp_frame)
|
||||
if 'many' in facefusion.globals.face_recognition:
|
||||
if 'one' in facefusion.globals.face_selector_mode:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
if 'many' in facefusion.globals.face_selector_mode:
|
||||
many_faces = get_many_faces(temp_frame)
|
||||
if many_faces:
|
||||
for target_face in many_faces:
|
||||
@ -134,7 +263,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
|
||||
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
|
||||
source_face = get_one_face(read_static_image(source_path))
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = read_image(temp_frame_path)
|
||||
result_frame = process_frame(source_face, reference_face, temp_frame)
|
||||
@ -145,18 +274,10 @@ def process_frames(source_path : str, temp_frame_paths : List[str], update_progr
|
||||
def process_image(source_path : str, target_path : str, output_path : str) -> None:
|
||||
source_face = get_one_face(read_static_image(source_path))
|
||||
target_frame = read_static_image(target_path)
|
||||
reference_face = get_one_face(target_frame, facefusion.globals.reference_face_position) if 'reference' in facefusion.globals.face_recognition else None
|
||||
reference_face = get_one_face(target_frame, facefusion.globals.reference_face_position) if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
result_frame = process_frame(source_face, reference_face, target_frame)
|
||||
write_image(output_path, result_frame)
|
||||
|
||||
|
||||
def process_video(source_path : str, temp_frame_paths : List[str]) -> None:
|
||||
conditional_set_face_reference(temp_frame_paths)
|
||||
frame_processors.multi_process_frames(source_path, temp_frame_paths, process_frames)
|
||||
|
||||
|
||||
def conditional_set_face_reference(temp_frame_paths : List[str]) -> None:
|
||||
if 'reference' in facefusion.globals.face_recognition and not get_face_reference():
|
||||
reference_frame = read_static_image(temp_frame_paths[facefusion.globals.reference_frame_number])
|
||||
reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
|
||||
set_face_reference(reference_face)
|
||||
|
@ -8,10 +8,10 @@ from realesrgan import RealESRGANer
|
||||
import facefusion.globals
|
||||
import facefusion.processors.frame.core as frame_processors
|
||||
from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import clear_face_analyser
|
||||
from facefusion.content_analyser import clear_content_analyser
|
||||
from facefusion.typing import Frame, Face, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done, get_device
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done, map_device, create_metavar, update_status
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
@ -22,22 +22,22 @@ THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FRAME_ENHANCER'
|
||||
MODELS: Dict[str, ModelValue] =\
|
||||
{
|
||||
'realesrgan_x2plus':
|
||||
'real_esrgan_x2plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x2plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRGAN_x2plus.pth'),
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x2plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/real_esrgan_x2plus.pth'),
|
||||
'scale': 2
|
||||
},
|
||||
'realesrgan_x4plus':
|
||||
'real_esrgan_x4plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth'),
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/real_esrgan_x4plus.pth'),
|
||||
'scale': 4
|
||||
},
|
||||
'realesrnet_x4plus':
|
||||
'real_esrnet_x4plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRNet_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRNet_x4plus.pth'),
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrnet_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/real_esrnet_x4plus.pth'),
|
||||
'scale': 4
|
||||
}
|
||||
}
|
||||
@ -58,7 +58,7 @@ def get_frame_processor() -> Any:
|
||||
num_out_ch = 3,
|
||||
scale = model_scale
|
||||
),
|
||||
device = get_device(facefusion.globals.execution_providers),
|
||||
device = map_device(facefusion.globals.execution_providers),
|
||||
scale = model_scale
|
||||
)
|
||||
return FRAME_PROCESSOR
|
||||
@ -70,26 +70,26 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
def get_options(key : Literal['model']) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
OPTIONS = \
|
||||
OPTIONS =\
|
||||
{
|
||||
'model': MODELS[frame_processors_globals.frame_enhancer_model]
|
||||
}
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
def set_options(key : Literal['model'], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--frame-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'frame_enhancer_model', default = 'realesrgan_x2plus', choices = frame_processors_choices.frame_enhancer_models)
|
||||
program.add_argument('--frame-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest = 'frame_enhancer_blend', type = int, default = 100, choices = range(101), metavar = '[0-100]')
|
||||
program.add_argument('--frame-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'frame_enhancer_model', default = 'real_esrgan_x2plus', choices = frame_processors_choices.frame_enhancer_models)
|
||||
program.add_argument('--frame-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest = 'frame_enhancer_blend', type = int, default = 80, choices = frame_processors_choices.frame_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.frame_enhancer_blend_range))
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
@ -124,6 +124,7 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
clear_frame_processor()
|
||||
clear_face_analyser()
|
||||
clear_content_analyser()
|
||||
read_static_image.cache_clear()
|
||||
|
||||
|
||||
@ -136,7 +137,8 @@ def enhance_frame(temp_frame : Frame) -> Frame:
|
||||
|
||||
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
|
||||
frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100)
|
||||
temp_frame = cv2.resize(temp_frame, (paste_frame.shape[1], paste_frame.shape[0]))
|
||||
paste_frame_height, paste_frame_width = paste_frame.shape[0:2]
|
||||
temp_frame = cv2.resize(temp_frame, (paste_frame_width, paste_frame_height))
|
||||
temp_frame = cv2.addWeighted(temp_frame, frame_enhancer_blend, paste_frame, 1 - frame_enhancer_blend, 0)
|
||||
return temp_frame
|
||||
|
||||
|
7
facefusion/processors/frame/typings.py
Normal file
7
facefusion/processors/frame/typings.py
Normal file
@ -0,0 +1,7 @@
|
||||
from typing import Literal
|
||||
|
||||
FaceSwapperModel = Literal['blendface_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial']
|
||||
FaceEnhancerModel = Literal['codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'restoreformer']
|
||||
FrameEnhancerModel = Literal['real_esrgan_x2plus', 'real_esrgan_x4plus', 'real_esrnet_x4plus']
|
||||
|
||||
FaceDebuggerItem = Literal['bbox', 'kps', 'face-mask', 'score']
|
39
facefusion/typing.py
Normal file → Executable file
39
facefusion/typing.py
Normal file → Executable file
@ -1,23 +1,40 @@
|
||||
from typing import Any, Literal, Callable, List, TypedDict, Dict
|
||||
from insightface.app.common import Face
|
||||
from collections import namedtuple
|
||||
from typing import Any, Literal, Callable, List, Tuple, Dict, TypedDict
|
||||
import numpy
|
||||
|
||||
Face = Face
|
||||
Bbox = numpy.ndarray[Any, Any]
|
||||
Kps = numpy.ndarray[Any, Any]
|
||||
Score = float
|
||||
Embedding = numpy.ndarray[Any, Any]
|
||||
Face = namedtuple('Face',
|
||||
[
|
||||
'bbox',
|
||||
'kps',
|
||||
'score',
|
||||
'embedding',
|
||||
'normed_embedding',
|
||||
'gender',
|
||||
'age'
|
||||
])
|
||||
Frame = numpy.ndarray[Any, Any]
|
||||
Matrix = numpy.ndarray[Any, Any]
|
||||
Padding = Tuple[int, int, int, int]
|
||||
|
||||
Update_Process = Callable[[], None]
|
||||
Process_Frames = Callable[[str, List[str], Update_Process], None]
|
||||
|
||||
ProcessMode = Literal[ 'output', 'preview', 'stream' ]
|
||||
FaceRecognition = Literal[ 'reference', 'many' ]
|
||||
FaceAnalyserDirection = Literal[ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small' ]
|
||||
FaceAnalyserAge = Literal[ 'child', 'teen', 'adult', 'senior' ]
|
||||
FaceAnalyserGender = Literal[ 'male', 'female' ]
|
||||
TempFrameFormat = Literal[ 'jpg', 'png' ]
|
||||
OutputVideoEncoder = Literal[ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ]
|
||||
Template = Literal['arcface_v1', 'arcface_v2', 'ffhq']
|
||||
ProcessMode = Literal['output', 'preview', 'stream']
|
||||
FaceSelectorMode = Literal['reference', 'one', 'many']
|
||||
FaceAnalyserOrder = Literal['left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best']
|
||||
FaceAnalyserAge = Literal['child', 'teen', 'adult', 'senior']
|
||||
FaceAnalyserGender = Literal['male', 'female']
|
||||
FaceDetectorModel = Literal['retinaface', 'yunet']
|
||||
FaceRecognizerModel = Literal['arcface_blendface', 'arcface_inswapper', 'arcface_simswap']
|
||||
TempFrameFormat = Literal['jpg', 'png']
|
||||
OutputVideoEncoder = Literal['libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc']
|
||||
|
||||
ModelValue = Dict['str', Any]
|
||||
ModelValue = Dict[str, Any]
|
||||
OptionsWithModel = TypedDict('OptionsWithModel',
|
||||
{
|
||||
'model' : ModelValue
|
||||
|
@ -30,3 +30,15 @@
|
||||
{
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
:root:root:root .grid-wrap.fixed-height
|
||||
{
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
:root:root:root .grid-container
|
||||
{
|
||||
grid-auto-rows: minmax(5em, 1fr);
|
||||
grid-template-columns: repeat(var(--grid-cols), minmax(5em, 1fr));
|
||||
grid-template-rows: repeat(var(--grid-rows), minmax(5em, 1fr));
|
||||
}
|
||||
|
@ -4,4 +4,4 @@ from facefusion.uis.typing import WebcamMode
|
||||
|
||||
common_options : List[str] = [ 'keep-fps', 'keep-temp', 'skip-audio', 'skip-download' ]
|
||||
webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ]
|
||||
webcam_resolutions : List[str] = [ '320x240', '640x480', '1280x720', '1920x1080', '2560x1440', '3840x2160' ]
|
||||
webcam_resolutions : List[str] = [ '320x240', '640x480', '800x600', '1024x768', '1280x720', '1280x960', '1920x1080', '2560x1440', '3840x2160' ]
|
||||
|
@ -3,7 +3,7 @@ import gradio
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.uis import choices
|
||||
from facefusion.uis import choices as uis_choices
|
||||
|
||||
COMMON_OPTIONS_CHECKBOX_GROUP : Optional[gradio.Checkboxgroup] = None
|
||||
|
||||
@ -22,7 +22,7 @@ def render() -> None:
|
||||
value.append('skip-download')
|
||||
COMMON_OPTIONS_CHECKBOX_GROUP = gradio.Checkboxgroup(
|
||||
label = wording.get('common_options_checkbox_group_label'),
|
||||
choices = choices.common_options,
|
||||
choices = uis_choices.common_options,
|
||||
value = value
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@ from typing import Optional
|
||||
import gradio
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
|
||||
EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None
|
||||
@ -13,9 +14,9 @@ def render() -> None:
|
||||
EXECUTION_QUEUE_COUNT_SLIDER = gradio.Slider(
|
||||
label = wording.get('execution_queue_count_slider_label'),
|
||||
value = facefusion.globals.execution_queue_count,
|
||||
step = 1,
|
||||
minimum = 1,
|
||||
maximum = 16
|
||||
step = facefusion.choices.execution_queue_count_range[1] - facefusion.choices.execution_queue_count_range[0],
|
||||
minimum = facefusion.choices.execution_queue_count_range[0],
|
||||
maximum = facefusion.choices.execution_queue_count_range[-1]
|
||||
)
|
||||
|
||||
|
||||
@ -25,4 +26,3 @@ def listen() -> None:
|
||||
|
||||
def update_execution_queue_count(execution_queue_count : int = 1) -> None:
|
||||
facefusion.globals.execution_queue_count = execution_queue_count
|
||||
|
||||
|
@ -2,6 +2,7 @@ from typing import Optional
|
||||
import gradio
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
|
||||
EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None
|
||||
@ -13,9 +14,9 @@ def render() -> None:
|
||||
EXECUTION_THREAD_COUNT_SLIDER = gradio.Slider(
|
||||
label = wording.get('execution_thread_count_slider_label'),
|
||||
value = facefusion.globals.execution_thread_count,
|
||||
step = 1,
|
||||
minimum = 1,
|
||||
maximum = 128
|
||||
step = facefusion.choices.execution_thread_count_range[1] - facefusion.choices.execution_thread_count_range[0],
|
||||
minimum = facefusion.choices.execution_thread_count_range[0],
|
||||
maximum = facefusion.choices.execution_thread_count_range[-1]
|
||||
)
|
||||
|
||||
|
||||
|
@ -2,49 +2,97 @@ from typing import Optional
|
||||
|
||||
import gradio
|
||||
|
||||
import facefusion.choices
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
from facefusion.typing import FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceDetectorModel
|
||||
from facefusion.uis.core import register_ui_component
|
||||
|
||||
FACE_ANALYSER_DIRECTION_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_ANALYSER_ORDER_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_ANALYSER_AGE_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_ANALYSER_GENDER_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_DETECTOR_SIZE_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_DETECTOR_SCORE_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_DETECTOR_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
|
||||
|
||||
def render() -> None:
|
||||
global FACE_ANALYSER_DIRECTION_DROPDOWN
|
||||
global FACE_ANALYSER_ORDER_DROPDOWN
|
||||
global FACE_ANALYSER_AGE_DROPDOWN
|
||||
global FACE_ANALYSER_GENDER_DROPDOWN
|
||||
global FACE_DETECTOR_SIZE_DROPDOWN
|
||||
global FACE_DETECTOR_SCORE_SLIDER
|
||||
global FACE_DETECTOR_MODEL_DROPDOWN
|
||||
|
||||
FACE_ANALYSER_DIRECTION_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_analyser_direction_dropdown_label'),
|
||||
choices = facefusion.choices.face_analyser_directions,
|
||||
value = facefusion.globals.face_analyser_direction
|
||||
with gradio.Row():
|
||||
FACE_ANALYSER_ORDER_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_analyser_order_dropdown_label'),
|
||||
choices = facefusion.choices.face_analyser_orders,
|
||||
value = facefusion.globals.face_analyser_order
|
||||
)
|
||||
FACE_ANALYSER_AGE_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_analyser_age_dropdown_label'),
|
||||
choices = ['none'] + facefusion.choices.face_analyser_ages,
|
||||
choices = [ 'none' ] + facefusion.choices.face_analyser_ages,
|
||||
value = facefusion.globals.face_analyser_age or 'none'
|
||||
)
|
||||
FACE_ANALYSER_GENDER_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_analyser_gender_dropdown_label'),
|
||||
choices = ['none'] + facefusion.choices.face_analyser_genders,
|
||||
choices = [ 'none' ] + facefusion.choices.face_analyser_genders,
|
||||
value = facefusion.globals.face_analyser_gender or 'none'
|
||||
)
|
||||
register_ui_component('face_analyser_direction_dropdown', FACE_ANALYSER_DIRECTION_DROPDOWN)
|
||||
FACE_DETECTOR_MODEL_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_detector_model_dropdown_label'),
|
||||
choices = facefusion.choices.face_detector_models,
|
||||
value = facefusion.globals.face_detector_model
|
||||
)
|
||||
FACE_DETECTOR_SIZE_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_detector_size_dropdown_label'),
|
||||
choices = facefusion.choices.face_detector_sizes,
|
||||
value = facefusion.globals.face_detector_size
|
||||
)
|
||||
FACE_DETECTOR_SCORE_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_detector_score_slider_label'),
|
||||
value = facefusion.globals.face_detector_score,
|
||||
step =facefusion.choices.face_detector_score_range[1] - facefusion.choices.face_detector_score_range[0],
|
||||
minimum = facefusion.choices.face_detector_score_range[0],
|
||||
maximum = facefusion.choices.face_detector_score_range[-1]
|
||||
)
|
||||
register_ui_component('face_analyser_order_dropdown', FACE_ANALYSER_ORDER_DROPDOWN)
|
||||
register_ui_component('face_analyser_age_dropdown', FACE_ANALYSER_AGE_DROPDOWN)
|
||||
register_ui_component('face_analyser_gender_dropdown', FACE_ANALYSER_GENDER_DROPDOWN)
|
||||
register_ui_component('face_detector_model_dropdown', FACE_DETECTOR_MODEL_DROPDOWN)
|
||||
register_ui_component('face_detector_size_dropdown', FACE_DETECTOR_SIZE_DROPDOWN)
|
||||
register_ui_component('face_detector_score_slider', FACE_DETECTOR_SCORE_SLIDER)
|
||||
|
||||
|
||||
def listen() -> None:
|
||||
FACE_ANALYSER_DIRECTION_DROPDOWN.select(lambda value: update_dropdown('face_analyser_direction', value), inputs = FACE_ANALYSER_DIRECTION_DROPDOWN)
|
||||
FACE_ANALYSER_AGE_DROPDOWN.select(lambda value: update_dropdown('face_analyser_age', value), inputs = FACE_ANALYSER_AGE_DROPDOWN)
|
||||
FACE_ANALYSER_GENDER_DROPDOWN.select(lambda value: update_dropdown('face_analyser_gender', value), inputs = FACE_ANALYSER_GENDER_DROPDOWN)
|
||||
FACE_ANALYSER_ORDER_DROPDOWN.select(update_face_analyser_order, inputs = FACE_ANALYSER_ORDER_DROPDOWN)
|
||||
FACE_ANALYSER_AGE_DROPDOWN.select(update_face_analyser_age, inputs = FACE_ANALYSER_AGE_DROPDOWN)
|
||||
FACE_ANALYSER_GENDER_DROPDOWN.select(update_face_analyser_gender, inputs = FACE_ANALYSER_GENDER_DROPDOWN)
|
||||
FACE_DETECTOR_MODEL_DROPDOWN.change(update_face_detector_model, inputs = FACE_DETECTOR_MODEL_DROPDOWN)
|
||||
FACE_DETECTOR_SIZE_DROPDOWN.select(update_face_detector_size, inputs = FACE_DETECTOR_SIZE_DROPDOWN)
|
||||
FACE_DETECTOR_SCORE_SLIDER.change(update_face_detector_score, inputs = FACE_DETECTOR_SCORE_SLIDER)
|
||||
|
||||
|
||||
def update_dropdown(name : str, value : str) -> None:
|
||||
if value == 'none':
|
||||
setattr(facefusion.globals, name, None)
|
||||
else:
|
||||
setattr(facefusion.globals, name, value)
|
||||
def update_face_analyser_order(face_analyser_order : FaceAnalyserOrder) -> None:
|
||||
facefusion.globals.face_analyser_order = face_analyser_order if face_analyser_order != 'none' else None
|
||||
|
||||
|
||||
def update_face_analyser_age(face_analyser_age : FaceAnalyserAge) -> None:
|
||||
facefusion.globals.face_analyser_age = face_analyser_age if face_analyser_age != 'none' else None
|
||||
|
||||
|
||||
def update_face_analyser_gender(face_analyser_gender : FaceAnalyserGender) -> None:
|
||||
facefusion.globals.face_analyser_gender = face_analyser_gender if face_analyser_gender != 'none' else None
|
||||
|
||||
|
||||
def update_face_detector_model(face_detector_model : FaceDetectorModel) -> None:
|
||||
facefusion.globals.face_detector_model = face_detector_model
|
||||
|
||||
|
||||
def update_face_detector_size(face_detector_size : str) -> None:
|
||||
facefusion.globals.face_detector_size = face_detector_size
|
||||
|
||||
|
||||
def update_face_detector_score(face_detector_score : float) -> None:
|
||||
facefusion.globals.face_detector_score = face_detector_score
|
||||
|
80
facefusion/uis/components/face_mask.py
Executable file
80
facefusion/uis/components/face_mask.py
Executable file
@ -0,0 +1,80 @@
|
||||
from typing import Optional
|
||||
import gradio
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
from facefusion.uis.core import register_ui_component
|
||||
|
||||
FACE_MASK_BLUR_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_MASK_PADDING_TOP_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_MASK_PADDING_RIGHT_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_MASK_PADDING_BOTTOM_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_MASK_PADDING_LEFT_SLIDER : Optional[gradio.Slider] = None
|
||||
|
||||
|
||||
def render() -> None:
|
||||
global FACE_MASK_BLUR_SLIDER
|
||||
global FACE_MASK_PADDING_TOP_SLIDER
|
||||
global FACE_MASK_PADDING_RIGHT_SLIDER
|
||||
global FACE_MASK_PADDING_BOTTOM_SLIDER
|
||||
global FACE_MASK_PADDING_LEFT_SLIDER
|
||||
|
||||
FACE_MASK_BLUR_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_mask_blur_slider_label'),
|
||||
step = facefusion.choices.face_mask_blur_range[1] - facefusion.choices.face_mask_blur_range[0],
|
||||
minimum = facefusion.choices.face_mask_blur_range[0],
|
||||
maximum = facefusion.choices.face_mask_blur_range[-1],
|
||||
value = facefusion.globals.face_mask_blur
|
||||
)
|
||||
with gradio.Group():
|
||||
with gradio.Row():
|
||||
FACE_MASK_PADDING_TOP_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_mask_padding_top_slider_label'),
|
||||
step = facefusion.choices.face_mask_padding_range[1] - facefusion.choices.face_mask_padding_range[0],
|
||||
minimum = facefusion.choices.face_mask_padding_range[0],
|
||||
maximum = facefusion.choices.face_mask_padding_range[-1],
|
||||
value = facefusion.globals.face_mask_padding[0]
|
||||
)
|
||||
FACE_MASK_PADDING_RIGHT_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_mask_padding_right_slider_label'),
|
||||
step = facefusion.choices.face_mask_padding_range[1] - facefusion.choices.face_mask_padding_range[0],
|
||||
minimum = facefusion.choices.face_mask_padding_range[0],
|
||||
maximum = facefusion.choices.face_mask_padding_range[-1],
|
||||
value = facefusion.globals.face_mask_padding[1]
|
||||
)
|
||||
with gradio.Row():
|
||||
FACE_MASK_PADDING_BOTTOM_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_mask_padding_bottom_slider_label'),
|
||||
step = facefusion.choices.face_mask_padding_range[1] - facefusion.choices.face_mask_padding_range[0],
|
||||
minimum = facefusion.choices.face_mask_padding_range[0],
|
||||
maximum = facefusion.choices.face_mask_padding_range[-1],
|
||||
value = facefusion.globals.face_mask_padding[2]
|
||||
)
|
||||
FACE_MASK_PADDING_LEFT_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_mask_padding_left_slider_label'),
|
||||
step = facefusion.choices.face_mask_padding_range[1] - facefusion.choices.face_mask_padding_range[0],
|
||||
minimum = facefusion.choices.face_mask_padding_range[0],
|
||||
maximum = facefusion.choices.face_mask_padding_range[-1],
|
||||
value = facefusion.globals.face_mask_padding[3]
|
||||
)
|
||||
register_ui_component('face_mask_blur_slider', FACE_MASK_BLUR_SLIDER)
|
||||
register_ui_component('face_mask_padding_top_slider', FACE_MASK_PADDING_TOP_SLIDER)
|
||||
register_ui_component('face_mask_padding_right_slider', FACE_MASK_PADDING_RIGHT_SLIDER)
|
||||
register_ui_component('face_mask_padding_bottom_slider', FACE_MASK_PADDING_BOTTOM_SLIDER)
|
||||
register_ui_component('face_mask_padding_left_slider', FACE_MASK_PADDING_LEFT_SLIDER)
|
||||
|
||||
|
||||
def listen() -> None:
|
||||
FACE_MASK_BLUR_SLIDER.change(update_face_mask_blur, inputs = FACE_MASK_BLUR_SLIDER)
|
||||
face_mask_padding_sliders = [ FACE_MASK_PADDING_TOP_SLIDER, FACE_MASK_PADDING_RIGHT_SLIDER, FACE_MASK_PADDING_BOTTOM_SLIDER, FACE_MASK_PADDING_LEFT_SLIDER ]
|
||||
for face_mask_padding_slider in face_mask_padding_sliders:
|
||||
face_mask_padding_slider.change(update_face_mask_padding, inputs = face_mask_padding_sliders)
|
||||
|
||||
|
||||
def update_face_mask_blur(face_mask_blur : float) -> None:
|
||||
facefusion.globals.face_mask_blur = face_mask_blur
|
||||
|
||||
|
||||
def update_face_mask_padding(face_mask_padding_top : int, face_mask_padding_right : int, face_mask_padding_bottom : int, face_mask_padding_left : int) -> None:
|
||||
facefusion.globals.face_mask_padding = (face_mask_padding_top, face_mask_padding_right, face_mask_padding_bottom, face_mask_padding_left)
|
@ -2,35 +2,35 @@ from typing import List, Optional, Tuple, Any, Dict
|
||||
|
||||
import gradio
|
||||
|
||||
import facefusion.choices
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
from facefusion.vision import get_video_frame, normalize_frame_color, read_static_image
|
||||
from facefusion.face_cache import clear_faces_cache
|
||||
from facefusion.vision import get_video_frame, read_static_image, normalize_frame_color
|
||||
from facefusion.face_analyser import get_many_faces
|
||||
from facefusion.face_reference import clear_face_reference
|
||||
from facefusion.typing import Frame, FaceRecognition
|
||||
from facefusion.typing import Frame, FaceSelectorMode
|
||||
from facefusion.utilities import is_image, is_video
|
||||
from facefusion.uis.core import get_ui_component, register_ui_component
|
||||
from facefusion.uis.typing import ComponentName
|
||||
|
||||
FACE_RECOGNITION_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_SELECTOR_MODE_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
REFERENCE_FACE_POSITION_GALLERY : Optional[gradio.Gallery] = None
|
||||
REFERENCE_FACE_DISTANCE_SLIDER : Optional[gradio.Slider] = None
|
||||
|
||||
|
||||
def render() -> None:
|
||||
global FACE_RECOGNITION_DROPDOWN
|
||||
global FACE_SELECTOR_MODE_DROPDOWN
|
||||
global REFERENCE_FACE_POSITION_GALLERY
|
||||
global REFERENCE_FACE_DISTANCE_SLIDER
|
||||
|
||||
reference_face_gallery_args: Dict[str, Any] =\
|
||||
{
|
||||
'label': wording.get('reference_face_gallery_label'),
|
||||
'height': 120,
|
||||
'object_fit': 'cover',
|
||||
'columns': 10,
|
||||
'columns': 8,
|
||||
'allow_preview': False,
|
||||
'visible': 'reference' in facefusion.globals.face_recognition
|
||||
'visible': 'reference' in facefusion.globals.face_selector_mode
|
||||
}
|
||||
if is_image(facefusion.globals.target_path):
|
||||
reference_frame = read_static_image(facefusion.globals.target_path)
|
||||
@ -38,32 +38,31 @@ def render() -> None:
|
||||
if is_video(facefusion.globals.target_path):
|
||||
reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
|
||||
reference_face_gallery_args['value'] = extract_gallery_frames(reference_frame)
|
||||
FACE_RECOGNITION_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_recognition_dropdown_label'),
|
||||
choices = facefusion.choices.face_recognitions,
|
||||
value = facefusion.globals.face_recognition
|
||||
FACE_SELECTOR_MODE_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_selector_mode_dropdown_label'),
|
||||
choices = facefusion.choices.face_selector_modes,
|
||||
value = facefusion.globals.face_selector_mode
|
||||
)
|
||||
REFERENCE_FACE_POSITION_GALLERY = gradio.Gallery(**reference_face_gallery_args)
|
||||
REFERENCE_FACE_DISTANCE_SLIDER = gradio.Slider(
|
||||
label = wording.get('reference_face_distance_slider_label'),
|
||||
value = facefusion.globals.reference_face_distance,
|
||||
step = 0.05,
|
||||
minimum = 0,
|
||||
maximum = 3,
|
||||
visible = 'reference' in facefusion.globals.face_recognition
|
||||
step = facefusion.choices.reference_face_distance_range[1] - facefusion.choices.reference_face_distance_range[0],
|
||||
minimum = facefusion.choices.reference_face_distance_range[0],
|
||||
maximum = facefusion.choices.reference_face_distance_range[-1],
|
||||
visible = 'reference' in facefusion.globals.face_selector_mode
|
||||
)
|
||||
register_ui_component('face_recognition_dropdown', FACE_RECOGNITION_DROPDOWN)
|
||||
register_ui_component('face_selector_mode_dropdown', FACE_SELECTOR_MODE_DROPDOWN)
|
||||
register_ui_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY)
|
||||
register_ui_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER)
|
||||
|
||||
|
||||
def listen() -> None:
|
||||
FACE_RECOGNITION_DROPDOWN.select(update_face_recognition, inputs = FACE_RECOGNITION_DROPDOWN, outputs = [ REFERENCE_FACE_POSITION_GALLERY, REFERENCE_FACE_DISTANCE_SLIDER ])
|
||||
REFERENCE_FACE_POSITION_GALLERY.select(clear_and_update_face_reference_position)
|
||||
FACE_SELECTOR_MODE_DROPDOWN.select(update_face_selector_mode, inputs = FACE_SELECTOR_MODE_DROPDOWN, outputs = [ REFERENCE_FACE_POSITION_GALLERY, REFERENCE_FACE_DISTANCE_SLIDER ])
|
||||
REFERENCE_FACE_POSITION_GALLERY.select(clear_and_update_reference_face_position)
|
||||
REFERENCE_FACE_DISTANCE_SLIDER.change(update_reference_face_distance, inputs = REFERENCE_FACE_DISTANCE_SLIDER)
|
||||
multi_component_names : List[ComponentName] =\
|
||||
[
|
||||
'source_image',
|
||||
'target_image',
|
||||
'target_video'
|
||||
]
|
||||
@ -71,39 +70,73 @@ def listen() -> None:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
for method in [ 'upload', 'change', 'clear' ]:
|
||||
getattr(component, method)(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
select_component_names : List[ComponentName] =\
|
||||
getattr(component, method)(update_reference_face_position)
|
||||
getattr(component, method)(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
change_one_component_names : List[ComponentName] =\
|
||||
[
|
||||
'face_analyser_direction_dropdown',
|
||||
'face_analyser_order_dropdown',
|
||||
'face_analyser_age_dropdown',
|
||||
'face_analyser_gender_dropdown'
|
||||
]
|
||||
for component_name in select_component_names:
|
||||
for component_name in change_one_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.select(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
component.change(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
change_two_component_names : List[ComponentName] =\
|
||||
[
|
||||
'face_detector_model_dropdown',
|
||||
'face_detector_size_dropdown',
|
||||
'face_detector_score_slider'
|
||||
]
|
||||
for component_name in change_two_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.change(clear_and_update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
preview_frame_slider = get_ui_component('preview_frame_slider')
|
||||
if preview_frame_slider:
|
||||
preview_frame_slider.release(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
preview_frame_slider.change(update_reference_frame_number, inputs = preview_frame_slider)
|
||||
preview_frame_slider.release(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY)
|
||||
|
||||
|
||||
def update_face_recognition(face_recognition : FaceRecognition) -> Tuple[gradio.Gallery, gradio.Slider]:
|
||||
if face_recognition == 'reference':
|
||||
facefusion.globals.face_recognition = face_recognition
|
||||
def update_face_selector_mode(face_selector_mode : FaceSelectorMode) -> Tuple[gradio.Gallery, gradio.Slider]:
|
||||
if face_selector_mode == 'reference':
|
||||
facefusion.globals.face_selector_mode = face_selector_mode
|
||||
return gradio.Gallery(visible = True), gradio.Slider(visible = True)
|
||||
if face_recognition == 'many':
|
||||
facefusion.globals.face_recognition = face_recognition
|
||||
if face_selector_mode == 'one':
|
||||
facefusion.globals.face_selector_mode = face_selector_mode
|
||||
return gradio.Gallery(visible = False), gradio.Slider(visible = False)
|
||||
if face_selector_mode == 'many':
|
||||
facefusion.globals.face_selector_mode = face_selector_mode
|
||||
return gradio.Gallery(visible = False), gradio.Slider(visible = False)
|
||||
|
||||
|
||||
def clear_and_update_face_reference_position(event: gradio.SelectData) -> gradio.Gallery:
|
||||
def clear_and_update_reference_face_position(event : gradio.SelectData) -> gradio.Gallery:
|
||||
clear_face_reference()
|
||||
return update_face_reference_position(event.index)
|
||||
clear_faces_cache()
|
||||
update_reference_face_position(event.index)
|
||||
return update_reference_position_gallery()
|
||||
|
||||
|
||||
def update_face_reference_position(reference_face_position : int = 0) -> gradio.Gallery:
|
||||
gallery_frames = []
|
||||
def update_reference_face_position(reference_face_position : int = 0) -> None:
|
||||
facefusion.globals.reference_face_position = reference_face_position
|
||||
|
||||
|
||||
def update_reference_face_distance(reference_face_distance : float) -> None:
|
||||
facefusion.globals.reference_face_distance = reference_face_distance
|
||||
|
||||
|
||||
def update_reference_frame_number(reference_frame_number : int) -> None:
|
||||
facefusion.globals.reference_frame_number = reference_frame_number
|
||||
|
||||
|
||||
def clear_and_update_reference_position_gallery() -> gradio.Gallery:
|
||||
clear_face_reference()
|
||||
clear_faces_cache()
|
||||
return update_reference_position_gallery()
|
||||
|
||||
|
||||
def update_reference_position_gallery() -> gradio.Gallery:
|
||||
gallery_frames = []
|
||||
if is_image(facefusion.globals.target_path):
|
||||
reference_frame = read_static_image(facefusion.globals.target_path)
|
||||
gallery_frames = extract_gallery_frames(reference_frame)
|
||||
@ -115,15 +148,11 @@ def update_face_reference_position(reference_face_position : int = 0) -> gradio.
|
||||
return gradio.Gallery(value = None)
|
||||
|
||||
|
||||
def update_reference_face_distance(reference_face_distance : float) -> None:
|
||||
facefusion.globals.reference_face_distance = reference_face_distance
|
||||
|
||||
|
||||
def extract_gallery_frames(reference_frame : Frame) -> List[Frame]:
|
||||
crop_frames = []
|
||||
faces = get_many_faces(reference_frame)
|
||||
for face in faces:
|
||||
start_x, start_y, end_x, end_y = map(int, face['bbox'])
|
||||
start_x, start_y, end_x, end_y = map(int, face.bbox)
|
||||
padding_x = int((end_x - start_x) * 0.25)
|
||||
padding_y = int((end_y - start_y) * 0.25)
|
||||
start_x = max(0, start_x - padding_x)
|
||||
|
47
facefusion/uis/components/frame_processors_options.py
Normal file → Executable file
47
facefusion/uis/components/frame_processors_options.py
Normal file → Executable file
@ -5,6 +5,7 @@ import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.processors.frame.core import load_frame_processor_module
|
||||
from facefusion.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices
|
||||
from facefusion.processors.frame.typings import FaceSwapperModel, FaceEnhancerModel, FrameEnhancerModel, FaceDebuggerItem
|
||||
from facefusion.uis.core import get_ui_component, register_ui_component
|
||||
|
||||
FACE_SWAPPER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
@ -12,6 +13,7 @@ FACE_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FACE_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None
|
||||
FRAME_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
|
||||
FRAME_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None
|
||||
FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
|
||||
|
||||
|
||||
def render() -> None:
|
||||
@ -20,6 +22,7 @@ def render() -> None:
|
||||
global FACE_ENHANCER_BLEND_SLIDER
|
||||
global FRAME_ENHANCER_MODEL_DROPDOWN
|
||||
global FRAME_ENHANCER_BLEND_SLIDER
|
||||
global FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP
|
||||
|
||||
FACE_SWAPPER_MODEL_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('face_swapper_model_dropdown_label'),
|
||||
@ -36,9 +39,9 @@ def render() -> None:
|
||||
FACE_ENHANCER_BLEND_SLIDER = gradio.Slider(
|
||||
label = wording.get('face_enhancer_blend_slider_label'),
|
||||
value = frame_processors_globals.face_enhancer_blend,
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 100,
|
||||
step = frame_processors_choices.face_enhancer_blend_range[1] - frame_processors_choices.face_enhancer_blend_range[0],
|
||||
minimum = frame_processors_choices.face_enhancer_blend_range[0],
|
||||
maximum = frame_processors_choices.face_enhancer_blend_range[-1],
|
||||
visible = 'face_enhancer' in facefusion.globals.frame_processors
|
||||
)
|
||||
FRAME_ENHANCER_MODEL_DROPDOWN = gradio.Dropdown(
|
||||
@ -50,16 +53,24 @@ def render() -> None:
|
||||
FRAME_ENHANCER_BLEND_SLIDER = gradio.Slider(
|
||||
label = wording.get('frame_enhancer_blend_slider_label'),
|
||||
value = frame_processors_globals.frame_enhancer_blend,
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 100,
|
||||
step = frame_processors_choices.frame_enhancer_blend_range[1] - frame_processors_choices.frame_enhancer_blend_range[0],
|
||||
minimum = frame_processors_choices.frame_enhancer_blend_range[0],
|
||||
maximum = frame_processors_choices.frame_enhancer_blend_range[-1],
|
||||
visible = 'face_enhancer' in facefusion.globals.frame_processors
|
||||
)
|
||||
FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP = gradio.CheckboxGroup(
|
||||
label = wording.get('face_debugger_items_checkbox_group_label'),
|
||||
choices = frame_processors_choices.face_debugger_items,
|
||||
value = frame_processors_globals.face_debugger_items,
|
||||
visible = 'face_debugger' in facefusion.globals.frame_processors
|
||||
)
|
||||
|
||||
register_ui_component('face_swapper_model_dropdown', FACE_SWAPPER_MODEL_DROPDOWN)
|
||||
register_ui_component('face_enhancer_model_dropdown', FACE_ENHANCER_MODEL_DROPDOWN)
|
||||
register_ui_component('face_enhancer_blend_slider', FACE_ENHANCER_BLEND_SLIDER)
|
||||
register_ui_component('frame_enhancer_model_dropdown', FRAME_ENHANCER_MODEL_DROPDOWN)
|
||||
register_ui_component('frame_enhancer_blend_slider', FRAME_ENHANCER_BLEND_SLIDER)
|
||||
register_ui_component('face_debugger_items_checkbox_group', FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP)
|
||||
|
||||
|
||||
def listen() -> None:
|
||||
@ -68,13 +79,20 @@ def listen() -> None:
|
||||
FACE_ENHANCER_BLEND_SLIDER.change(update_face_enhancer_blend, inputs = FACE_ENHANCER_BLEND_SLIDER)
|
||||
FRAME_ENHANCER_MODEL_DROPDOWN.change(update_frame_enhancer_model, inputs = FRAME_ENHANCER_MODEL_DROPDOWN, outputs = FRAME_ENHANCER_MODEL_DROPDOWN)
|
||||
FRAME_ENHANCER_BLEND_SLIDER.change(update_frame_enhancer_blend, inputs = FRAME_ENHANCER_BLEND_SLIDER)
|
||||
FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP.change(update_face_debugger_items, inputs = FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP)
|
||||
frame_processors_checkbox_group = get_ui_component('frame_processors_checkbox_group')
|
||||
if frame_processors_checkbox_group:
|
||||
frame_processors_checkbox_group.change(toggle_face_swapper_model, inputs = frame_processors_checkbox_group, outputs = [ FACE_SWAPPER_MODEL_DROPDOWN, FACE_ENHANCER_MODEL_DROPDOWN, FACE_ENHANCER_BLEND_SLIDER, FRAME_ENHANCER_MODEL_DROPDOWN, FRAME_ENHANCER_BLEND_SLIDER ])
|
||||
frame_processors_checkbox_group.change(toggle_face_swapper_model, inputs = frame_processors_checkbox_group, outputs = [ FACE_SWAPPER_MODEL_DROPDOWN, FACE_ENHANCER_MODEL_DROPDOWN, FACE_ENHANCER_BLEND_SLIDER, FRAME_ENHANCER_MODEL_DROPDOWN, FRAME_ENHANCER_BLEND_SLIDER, FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP ])
|
||||
|
||||
|
||||
def update_face_swapper_model(face_swapper_model : str) -> gradio.Dropdown:
|
||||
def update_face_swapper_model(face_swapper_model : FaceSwapperModel) -> gradio.Dropdown:
|
||||
frame_processors_globals.face_swapper_model = face_swapper_model
|
||||
if face_swapper_model == 'blendface_256':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_blendface'
|
||||
if face_swapper_model == 'inswapper_128' or face_swapper_model == 'inswapper_128_fp16':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_inswapper'
|
||||
if face_swapper_model == 'simswap_256' or face_swapper_model == 'simswap_512_unofficial':
|
||||
facefusion.globals.face_recognizer_model = 'arcface_simswap'
|
||||
face_swapper_module = load_frame_processor_module('face_swapper')
|
||||
face_swapper_module.clear_frame_processor()
|
||||
face_swapper_module.set_options('model', face_swapper_module.MODELS[face_swapper_model])
|
||||
@ -83,7 +101,7 @@ def update_face_swapper_model(face_swapper_model : str) -> gradio.Dropdown:
|
||||
return gradio.Dropdown(value = face_swapper_model)
|
||||
|
||||
|
||||
def update_face_enhancer_model(face_enhancer_model : str) -> gradio.Dropdown:
|
||||
def update_face_enhancer_model(face_enhancer_model : FaceEnhancerModel) -> gradio.Dropdown:
|
||||
frame_processors_globals.face_enhancer_model = face_enhancer_model
|
||||
face_enhancer_module = load_frame_processor_module('face_enhancer')
|
||||
face_enhancer_module.clear_frame_processor()
|
||||
@ -97,7 +115,7 @@ def update_face_enhancer_blend(face_enhancer_blend : int) -> None:
|
||||
frame_processors_globals.face_enhancer_blend = face_enhancer_blend
|
||||
|
||||
|
||||
def update_frame_enhancer_model(frame_enhancer_model : str) -> gradio.Dropdown:
|
||||
def update_frame_enhancer_model(frame_enhancer_model : FrameEnhancerModel) -> gradio.Dropdown:
|
||||
frame_processors_globals.frame_enhancer_model = frame_enhancer_model
|
||||
frame_enhancer_module = load_frame_processor_module('frame_enhancer')
|
||||
frame_enhancer_module.clear_frame_processor()
|
||||
@ -111,8 +129,13 @@ def update_frame_enhancer_blend(frame_enhancer_blend : int) -> None:
|
||||
frame_processors_globals.frame_enhancer_blend = frame_enhancer_blend
|
||||
|
||||
|
||||
def toggle_face_swapper_model(frame_processors : List[str]) -> Tuple[gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Slider]:
|
||||
def update_face_debugger_items(face_debugger_items : List[FaceDebuggerItem]) -> None:
|
||||
frame_processors_globals.face_debugger_items = face_debugger_items
|
||||
|
||||
|
||||
def toggle_face_swapper_model(frame_processors : List[str]) -> Tuple[gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Slider, gradio.CheckboxGroup]:
|
||||
has_face_swapper = 'face_swapper' in frame_processors
|
||||
has_face_enhancer = 'face_enhancer' in frame_processors
|
||||
has_frame_enhancer = 'frame_enhancer' in frame_processors
|
||||
return gradio.Dropdown(visible = has_face_swapper), gradio.Dropdown(visible = has_face_enhancer), gradio.Slider(visible = has_face_enhancer), gradio.Dropdown(visible = has_frame_enhancer), gradio.Slider(visible = has_frame_enhancer)
|
||||
has_face_debugger = 'face_debugger' in frame_processors
|
||||
return gradio.Dropdown(visible = has_face_swapper), gradio.Dropdown(visible = has_face_enhancer), gradio.Slider(visible = has_face_enhancer), gradio.Dropdown(visible = has_frame_enhancer), gradio.Slider(visible = has_frame_enhancer), gradio.CheckboxGroup(visible = has_face_debugger)
|
||||
|
@ -2,6 +2,7 @@ from typing import Optional
|
||||
import gradio
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
|
||||
MAX_MEMORY_SLIDER : Optional[gradio.Slider] = None
|
||||
@ -12,9 +13,9 @@ def render() -> None:
|
||||
|
||||
MAX_MEMORY_SLIDER = gradio.Slider(
|
||||
label = wording.get('max_memory_slider_label'),
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 128
|
||||
step = facefusion.choices.max_memory_range[1] - facefusion.choices.max_memory_range[0],
|
||||
minimum = facefusion.choices.max_memory_range[0],
|
||||
maximum = facefusion.choices.max_memory_range[-1]
|
||||
)
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@ from typing import Optional, Tuple, List
|
||||
import tempfile
|
||||
import gradio
|
||||
|
||||
import facefusion.choices
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
from facefusion.typing import OutputVideoEncoder
|
||||
from facefusion.utilities import is_image, is_video
|
||||
@ -30,9 +30,9 @@ def render() -> None:
|
||||
OUTPUT_IMAGE_QUALITY_SLIDER = gradio.Slider(
|
||||
label = wording.get('output_image_quality_slider_label'),
|
||||
value = facefusion.globals.output_image_quality,
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 100,
|
||||
step = facefusion.choices.output_image_quality_range[1] - facefusion.choices.output_image_quality_range[0],
|
||||
minimum = facefusion.choices.output_image_quality_range[0],
|
||||
maximum = facefusion.choices.output_image_quality_range[-1],
|
||||
visible = is_image(facefusion.globals.target_path)
|
||||
)
|
||||
OUTPUT_VIDEO_ENCODER_DROPDOWN = gradio.Dropdown(
|
||||
@ -44,9 +44,9 @@ def render() -> None:
|
||||
OUTPUT_VIDEO_QUALITY_SLIDER = gradio.Slider(
|
||||
label = wording.get('output_video_quality_slider_label'),
|
||||
value = facefusion.globals.output_video_quality,
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 100,
|
||||
step = facefusion.choices.output_video_quality_range[1] - facefusion.choices.output_video_quality_range[0],
|
||||
minimum = facefusion.choices.output_video_quality_range[0],
|
||||
maximum = facefusion.choices.output_video_quality_range[-1],
|
||||
visible = is_video(facefusion.globals.target_path)
|
||||
)
|
||||
register_ui_component('output_path_textbox', OUTPUT_PATH_TEXTBOX)
|
||||
|
88
facefusion/uis/components/preview.py
Normal file → Executable file
88
facefusion/uis/components/preview.py
Normal file → Executable file
@ -4,11 +4,13 @@ import gradio
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.core import conditional_set_face_reference
|
||||
from facefusion.face_cache import clear_faces_cache
|
||||
from facefusion.typing import Frame, Face
|
||||
from facefusion.vision import get_video_frame, count_video_frame_total, normalize_frame_color, resize_frame_dimension, read_static_image
|
||||
from facefusion.face_analyser import get_one_face
|
||||
from facefusion.face_reference import get_face_reference, set_face_reference
|
||||
from facefusion.predictor import predict_frame
|
||||
from facefusion.face_analyser import get_one_face, clear_face_analyser
|
||||
from facefusion.face_reference import get_face_reference, clear_face_reference
|
||||
from facefusion.content_analyser import analyse_frame
|
||||
from facefusion.processors.frame.core import load_frame_processor_module
|
||||
from facefusion.utilities import is_video, is_image
|
||||
from facefusion.uis.typing import ComponentName
|
||||
@ -37,7 +39,7 @@ def render() -> None:
|
||||
}
|
||||
conditional_set_face_reference()
|
||||
source_face = get_one_face(read_static_image(facefusion.globals.source_path))
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
if is_image(facefusion.globals.target_path):
|
||||
target_frame = read_static_image(facefusion.globals.target_path)
|
||||
preview_frame = process_preview_frame(source_face, reference_face, target_frame)
|
||||
@ -57,34 +59,31 @@ def render() -> None:
|
||||
|
||||
def listen() -> None:
|
||||
PREVIEW_FRAME_SLIDER.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
multi_component_names : List[ComponentName] =\
|
||||
multi_one_component_names : List[ComponentName] =\
|
||||
[
|
||||
'source_image',
|
||||
'target_image',
|
||||
'target_video'
|
||||
]
|
||||
for component_name in multi_component_names:
|
||||
for component_name in multi_one_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
for method in [ 'upload', 'change', 'clear' ]:
|
||||
getattr(component, method)(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
getattr(component, method)(update_preview_frame_slider, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_FRAME_SLIDER)
|
||||
update_component_names : List[ComponentName] =\
|
||||
multi_two_component_names : List[ComponentName] =\
|
||||
[
|
||||
'face_recognition_dropdown',
|
||||
'frame_processors_checkbox_group',
|
||||
'face_swapper_model_dropdown',
|
||||
'face_enhancer_model_dropdown',
|
||||
'frame_enhancer_model_dropdown'
|
||||
'target_image',
|
||||
'target_video'
|
||||
]
|
||||
for component_name in update_component_names:
|
||||
for component_name in multi_two_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
for method in [ 'upload', 'change', 'clear' ]:
|
||||
getattr(component, method)(update_preview_frame_slider, outputs = PREVIEW_FRAME_SLIDER)
|
||||
select_component_names : List[ComponentName] =\
|
||||
[
|
||||
'reference_face_position_gallery',
|
||||
'face_analyser_direction_dropdown',
|
||||
'face_analyser_order_dropdown',
|
||||
'face_analyser_age_dropdown',
|
||||
'face_analyser_gender_dropdown'
|
||||
]
|
||||
@ -92,49 +91,73 @@ def listen() -> None:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.select(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
change_component_names : List[ComponentName] =\
|
||||
change_one_component_names : List[ComponentName] =\
|
||||
[
|
||||
'reference_face_distance_slider',
|
||||
'frame_processors_checkbox_group',
|
||||
'face_debugger_items_checkbox_group',
|
||||
'face_enhancer_model_dropdown',
|
||||
'face_enhancer_blend_slider',
|
||||
'frame_enhancer_blend_slider'
|
||||
'frame_enhancer_model_dropdown',
|
||||
'frame_enhancer_blend_slider',
|
||||
'face_selector_mode_dropdown',
|
||||
'reference_face_distance_slider',
|
||||
'face_mask_blur_slider',
|
||||
'face_mask_padding_top_slider',
|
||||
'face_mask_padding_bottom_slider',
|
||||
'face_mask_padding_left_slider',
|
||||
'face_mask_padding_right_slider'
|
||||
]
|
||||
for component_name in change_component_names:
|
||||
for component_name in change_one_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
change_two_component_names : List[ComponentName] =\
|
||||
[
|
||||
'face_swapper_model_dropdown',
|
||||
'face_detector_model_dropdown',
|
||||
'face_detector_size_dropdown',
|
||||
'face_detector_score_slider'
|
||||
]
|
||||
for component_name in change_two_component_names:
|
||||
component = get_ui_component(component_name)
|
||||
if component:
|
||||
component.change(clear_and_update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
|
||||
|
||||
|
||||
def clear_and_update_preview_image(frame_number : int = 0) -> gradio.Image:
|
||||
clear_face_analyser()
|
||||
clear_face_reference()
|
||||
clear_faces_cache()
|
||||
return update_preview_image(frame_number)
|
||||
|
||||
|
||||
def update_preview_image(frame_number : int = 0) -> gradio.Image:
|
||||
conditional_set_face_reference()
|
||||
source_face = get_one_face(read_static_image(facefusion.globals.source_path))
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_selector_mode else None
|
||||
if is_image(facefusion.globals.target_path):
|
||||
target_frame = read_static_image(facefusion.globals.target_path)
|
||||
preview_frame = process_preview_frame(source_face, reference_face, target_frame)
|
||||
preview_frame = normalize_frame_color(preview_frame)
|
||||
return gradio.Image(value = preview_frame)
|
||||
if is_video(facefusion.globals.target_path):
|
||||
facefusion.globals.reference_frame_number = frame_number
|
||||
temp_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
|
||||
temp_frame = get_video_frame(facefusion.globals.target_path, frame_number)
|
||||
preview_frame = process_preview_frame(source_face, reference_face, temp_frame)
|
||||
preview_frame = normalize_frame_color(preview_frame)
|
||||
return gradio.Image(value = preview_frame)
|
||||
return gradio.Image(value = None)
|
||||
|
||||
|
||||
def update_preview_frame_slider(frame_number : int = 0) -> gradio.Slider:
|
||||
if is_image(facefusion.globals.target_path):
|
||||
return gradio.Slider(value = None, maximum = None, visible = False)
|
||||
def update_preview_frame_slider() -> gradio.Slider:
|
||||
if is_video(facefusion.globals.target_path):
|
||||
facefusion.globals.reference_frame_number = frame_number
|
||||
video_frame_total = count_video_frame_total(facefusion.globals.target_path)
|
||||
return gradio.Slider(maximum = video_frame_total, visible = True)
|
||||
return gradio.Slider()
|
||||
return gradio.Slider(value = None, maximum = None, visible = False)
|
||||
|
||||
|
||||
def process_preview_frame(source_face : Face, reference_face : Face, temp_frame : Frame) -> Frame:
|
||||
temp_frame = resize_frame_dimension(temp_frame, 640, 640)
|
||||
if predict_frame(temp_frame):
|
||||
if analyse_frame(temp_frame):
|
||||
return cv2.GaussianBlur(temp_frame, (99, 99), 0)
|
||||
for frame_processor in facefusion.globals.frame_processors:
|
||||
frame_processor_module = load_frame_processor_module(frame_processor)
|
||||
@ -145,10 +168,3 @@ def process_preview_frame(source_face : Face, reference_face : Face, temp_frame
|
||||
temp_frame
|
||||
)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def conditional_set_face_reference() -> None:
|
||||
if 'reference' in facefusion.globals.face_recognition and not get_face_reference():
|
||||
reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
|
||||
reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
|
||||
set_face_reference(reference_face)
|
||||
|
@ -3,6 +3,7 @@ import gradio
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.face_cache import clear_faces_cache
|
||||
from facefusion.face_reference import clear_face_reference
|
||||
from facefusion.utilities import is_image, is_video
|
||||
from facefusion.uis.core import register_ui_component
|
||||
@ -51,6 +52,7 @@ def listen() -> None:
|
||||
|
||||
def update(file : IO[Any]) -> Tuple[gradio.Image, gradio.Video]:
|
||||
clear_face_reference()
|
||||
clear_faces_cache()
|
||||
if file and is_image(file.name):
|
||||
facefusion.globals.target_path = file.name
|
||||
return gradio.Image(value = file.name, visible = True), gradio.Video(value = None, visible = False)
|
||||
|
@ -1,8 +1,8 @@
|
||||
from typing import Optional, Tuple
|
||||
import gradio
|
||||
|
||||
import facefusion.choices
|
||||
import facefusion.globals
|
||||
import facefusion.choices
|
||||
from facefusion import wording
|
||||
from facefusion.typing import TempFrameFormat
|
||||
from facefusion.utilities import is_video
|
||||
@ -25,9 +25,9 @@ def render() -> None:
|
||||
TEMP_FRAME_QUALITY_SLIDER = gradio.Slider(
|
||||
label = wording.get('temp_frame_quality_slider_label'),
|
||||
value = facefusion.globals.temp_frame_quality,
|
||||
step = 1,
|
||||
minimum = 0,
|
||||
maximum = 100,
|
||||
step = facefusion.choices.temp_frame_quality_range[1] - facefusion.choices.temp_frame_quality_range[0],
|
||||
minimum = facefusion.choices.temp_frame_quality_range[0],
|
||||
maximum = facefusion.choices.temp_frame_quality_range[-1],
|
||||
visible = is_video(facefusion.globals.target_path)
|
||||
)
|
||||
|
||||
|
@ -39,6 +39,7 @@ def render() -> None:
|
||||
trim_frame_end_slider_args['value'] = facefusion.globals.trim_frame_end or video_frame_total
|
||||
trim_frame_end_slider_args['maximum'] = video_frame_total
|
||||
trim_frame_end_slider_args['visible'] = True
|
||||
with gradio.Row():
|
||||
TRIM_FRAME_START_SLIDER = gradio.Slider(**trim_frame_start_slider_args)
|
||||
TRIM_FRAME_END_SLIDER = gradio.Slider(**trim_frame_end_slider_args)
|
||||
|
||||
|
@ -10,7 +10,7 @@ from tqdm import tqdm
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.predictor import predict_stream
|
||||
from facefusion.content_analyser import analyse_stream
|
||||
from facefusion.typing import Frame, Face
|
||||
from facefusion.face_analyser import get_one_face
|
||||
from facefusion.processors.frame.core import get_frame_processors_modules
|
||||
@ -19,11 +19,33 @@ from facefusion.vision import normalize_frame_color, read_static_image
|
||||
from facefusion.uis.typing import StreamMode, WebcamMode
|
||||
from facefusion.uis.core import get_ui_component
|
||||
|
||||
WEBCAM_CAPTURE : Optional[cv2.VideoCapture] = None
|
||||
WEBCAM_IMAGE : Optional[gradio.Image] = None
|
||||
WEBCAM_START_BUTTON : Optional[gradio.Button] = None
|
||||
WEBCAM_STOP_BUTTON : Optional[gradio.Button] = None
|
||||
|
||||
|
||||
def get_webcam_capture() -> Optional[cv2.VideoCapture]:
|
||||
global WEBCAM_CAPTURE
|
||||
|
||||
if WEBCAM_CAPTURE is None:
|
||||
if platform.system().lower() == 'windows':
|
||||
webcam_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
||||
else:
|
||||
webcam_capture = cv2.VideoCapture(0)
|
||||
if webcam_capture and webcam_capture.isOpened():
|
||||
WEBCAM_CAPTURE = webcam_capture
|
||||
return WEBCAM_CAPTURE
|
||||
|
||||
|
||||
def clear_webcam_capture() -> None:
|
||||
global WEBCAM_CAPTURE
|
||||
|
||||
if WEBCAM_CAPTURE:
|
||||
WEBCAM_CAPTURE.release()
|
||||
WEBCAM_CAPTURE = None
|
||||
|
||||
|
||||
def render() -> None:
|
||||
global WEBCAM_IMAGE
|
||||
global WEBCAM_START_BUTTON
|
||||
@ -50,9 +72,6 @@ def listen() -> None:
|
||||
webcam_fps_slider = get_ui_component('webcam_fps_slider')
|
||||
if webcam_mode_radio and webcam_resolution_dropdown and webcam_fps_slider:
|
||||
start_event = WEBCAM_START_BUTTON.click(start, inputs = [ webcam_mode_radio, webcam_resolution_dropdown, webcam_fps_slider ], outputs = WEBCAM_IMAGE)
|
||||
webcam_mode_radio.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
|
||||
webcam_resolution_dropdown.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
|
||||
webcam_fps_slider.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
|
||||
WEBCAM_STOP_BUTTON.click(stop, cancels = start_event)
|
||||
source_image = get_ui_component('source_image')
|
||||
if source_image:
|
||||
@ -60,28 +79,36 @@ def listen() -> None:
|
||||
getattr(source_image, method)(stop, cancels = start_event)
|
||||
|
||||
|
||||
def start(mode: WebcamMode, resolution: str, fps: float) -> Generator[Frame, None, None]:
|
||||
facefusion.globals.face_recognition = 'many'
|
||||
def start(mode : WebcamMode, resolution : str, fps : float) -> Generator[Frame, None, None]:
|
||||
facefusion.globals.face_selector_mode = 'one'
|
||||
facefusion.globals.face_analyser_order = 'large-small'
|
||||
source_face = get_one_face(read_static_image(facefusion.globals.source_path))
|
||||
stream = None
|
||||
if mode in [ 'udp', 'v4l2' ]:
|
||||
stream = open_stream(mode, resolution, fps) # type: ignore[arg-type]
|
||||
capture = capture_webcam(resolution, fps)
|
||||
if capture.isOpened():
|
||||
for capture_frame in multi_process_capture(source_face, capture):
|
||||
if stream is not None:
|
||||
stream.stdin.write(capture_frame.tobytes())
|
||||
webcam_width, webcam_height = map(int, resolution.split('x'))
|
||||
webcam_capture = get_webcam_capture()
|
||||
if webcam_capture and webcam_capture.isOpened():
|
||||
webcam_capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) # type: ignore[attr-defined]
|
||||
webcam_capture.set(cv2.CAP_PROP_FRAME_WIDTH, webcam_width)
|
||||
webcam_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, webcam_height)
|
||||
webcam_capture.set(cv2.CAP_PROP_FPS, fps)
|
||||
for capture_frame in multi_process_capture(source_face, webcam_capture, fps):
|
||||
if mode == 'inline':
|
||||
yield normalize_frame_color(capture_frame)
|
||||
else:
|
||||
stream.stdin.write(capture_frame.tobytes())
|
||||
yield None
|
||||
|
||||
|
||||
def multi_process_capture(source_face: Face, capture : cv2.VideoCapture) -> Generator[Frame, None, None]:
|
||||
progress = tqdm(desc = wording.get('processing'), unit = 'frame', dynamic_ncols = True)
|
||||
def multi_process_capture(source_face : Face, webcam_capture : cv2.VideoCapture, fps : float) -> Generator[Frame, None, None]:
|
||||
with tqdm(desc = wording.get('processing'), unit = 'frame', ascii = ' =') as progress:
|
||||
with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:
|
||||
futures = []
|
||||
deque_capture_frames : Deque[Frame] = deque()
|
||||
while True:
|
||||
_, capture_frame = capture.read()
|
||||
if predict_stream(capture_frame):
|
||||
while webcam_capture and webcam_capture.isOpened():
|
||||
_, capture_frame = webcam_capture.read()
|
||||
if analyse_stream(capture_frame, fps):
|
||||
return
|
||||
future = executor.submit(process_stream_frame, source_face, capture_frame)
|
||||
futures.append(future)
|
||||
@ -90,27 +117,15 @@ def multi_process_capture(source_face: Face, capture : cv2.VideoCapture) -> Gene
|
||||
deque_capture_frames.append(capture_frame)
|
||||
futures.remove(future_done)
|
||||
while deque_capture_frames:
|
||||
yield deque_capture_frames.popleft()
|
||||
progress.update()
|
||||
yield deque_capture_frames.popleft()
|
||||
|
||||
|
||||
def stop() -> gradio.Image:
|
||||
clear_webcam_capture()
|
||||
return gradio.Image(value = None)
|
||||
|
||||
|
||||
def capture_webcam(resolution : str, fps : float) -> cv2.VideoCapture:
|
||||
width, height = resolution.split('x')
|
||||
if platform.system().lower() == 'windows':
|
||||
capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
||||
else:
|
||||
capture = cv2.VideoCapture(0)
|
||||
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) # type: ignore[attr-defined]
|
||||
capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(width))
|
||||
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(height))
|
||||
capture.set(cv2.CAP_PROP_FPS, fps)
|
||||
return capture
|
||||
|
||||
|
||||
def process_stream_frame(source_face : Face, temp_frame : Frame) -> Frame:
|
||||
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
|
||||
if frame_processor_module.pre_process('stream'):
|
||||
|
@ -2,7 +2,7 @@ from typing import Optional
|
||||
import gradio
|
||||
|
||||
from facefusion import wording
|
||||
from facefusion.uis import choices
|
||||
from facefusion.uis import choices as uis_choices
|
||||
from facefusion.uis.core import register_ui_component
|
||||
|
||||
WEBCAM_MODE_RADIO : Optional[gradio.Radio] = None
|
||||
@ -17,13 +17,13 @@ def render() -> None:
|
||||
|
||||
WEBCAM_MODE_RADIO = gradio.Radio(
|
||||
label = wording.get('webcam_mode_radio_label'),
|
||||
choices = choices.webcam_modes,
|
||||
choices = uis_choices.webcam_modes,
|
||||
value = 'inline'
|
||||
)
|
||||
WEBCAM_RESOLUTION_DROPDOWN = gradio.Dropdown(
|
||||
label = wording.get('webcam_resolution_dropdown'),
|
||||
choices = choices.webcam_resolutions,
|
||||
value = choices.webcam_resolutions[0]
|
||||
choices = uis_choices.webcam_resolutions,
|
||||
value = uis_choices.webcam_resolutions[0]
|
||||
)
|
||||
WEBCAM_FPS_SLIDER = gradio.Slider(
|
||||
label = wording.get('webcam_fps_slider'),
|
||||
|
@ -44,13 +44,13 @@ def get_ui_layouts_modules(ui_layouts : List[str]) -> List[ModuleType]:
|
||||
return UI_LAYOUT_MODULES
|
||||
|
||||
|
||||
def get_ui_component(name: ComponentName) -> Optional[Component]:
|
||||
def get_ui_component(name : ComponentName) -> Optional[Component]:
|
||||
if name in UI_COMPONENTS:
|
||||
return UI_COMPONENTS[name]
|
||||
return None
|
||||
|
||||
|
||||
def register_ui_component(name: ComponentName, component: Component) -> None:
|
||||
def register_ui_component(name : ComponentName, component: Component) -> None:
|
||||
UI_COMPONENTS[name] = component
|
||||
|
||||
|
||||
|
@ -43,7 +43,7 @@ def render() -> gradio.Blocks:
|
||||
limit_resources.render()
|
||||
with gradio.Blocks():
|
||||
benchmark_options.render()
|
||||
with gradio.Column(scale= 5):
|
||||
with gradio.Column(scale = 5):
|
||||
with gradio.Blocks():
|
||||
benchmark.render()
|
||||
return layout
|
||||
|
15
facefusion/uis/layouts/default.py
Normal file → Executable file
15
facefusion/uis/layouts/default.py
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
import gradio
|
||||
|
||||
from facefusion.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, limit_resources, temp_frame, output_options, common_options, source, target, preview, trim_frame, face_analyser, face_selector, output
|
||||
from facefusion.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, limit_resources, temp_frame, output_options, common_options, source, target, output, preview, trim_frame, face_analyser, face_selector, face_mask
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
@ -30,6 +30,8 @@ def render() -> gradio.Blocks:
|
||||
temp_frame.render()
|
||||
with gradio.Blocks():
|
||||
output_options.render()
|
||||
with gradio.Blocks():
|
||||
common_options.render()
|
||||
with gradio.Column(scale = 2):
|
||||
with gradio.Blocks():
|
||||
source.render()
|
||||
@ -40,14 +42,14 @@ def render() -> gradio.Blocks:
|
||||
with gradio.Column(scale = 3):
|
||||
with gradio.Blocks():
|
||||
preview.render()
|
||||
with gradio.Row():
|
||||
with gradio.Blocks():
|
||||
trim_frame.render()
|
||||
with gradio.Blocks():
|
||||
face_selector.render()
|
||||
with gradio.Row():
|
||||
face_analyser.render()
|
||||
with gradio.Blocks():
|
||||
common_options.render()
|
||||
face_mask.render()
|
||||
with gradio.Blocks():
|
||||
face_analyser.render()
|
||||
return layout
|
||||
|
||||
|
||||
@ -63,11 +65,12 @@ def listen() -> None:
|
||||
common_options.listen()
|
||||
source.listen()
|
||||
target.listen()
|
||||
output.listen()
|
||||
preview.listen()
|
||||
trim_frame.listen()
|
||||
face_selector.listen()
|
||||
face_mask.listen()
|
||||
face_analyser.listen()
|
||||
output.listen()
|
||||
|
||||
|
||||
def run(ui : gradio.Blocks) -> None:
|
||||
|
@ -8,25 +8,33 @@ ComponentName = Literal\
|
||||
'target_image',
|
||||
'target_video',
|
||||
'preview_frame_slider',
|
||||
'face_recognition_dropdown',
|
||||
'face_selector_mode_dropdown',
|
||||
'reference_face_position_gallery',
|
||||
'reference_face_distance_slider',
|
||||
'face_analyser_direction_dropdown',
|
||||
'face_analyser_order_dropdown',
|
||||
'face_analyser_age_dropdown',
|
||||
'face_analyser_gender_dropdown',
|
||||
'face_detector_model_dropdown',
|
||||
'face_detector_size_dropdown',
|
||||
'face_detector_score_slider',
|
||||
'face_mask_blur_slider',
|
||||
'face_mask_padding_top_slider',
|
||||
'face_mask_padding_bottom_slider',
|
||||
'face_mask_padding_left_slider',
|
||||
'face_mask_padding_right_slider',
|
||||
'frame_processors_checkbox_group',
|
||||
'face_swapper_model_dropdown',
|
||||
'face_enhancer_model_dropdown',
|
||||
'face_enhancer_blend_slider',
|
||||
'frame_enhancer_model_dropdown',
|
||||
'frame_enhancer_blend_slider',
|
||||
'face_debugger_items_checkbox_group',
|
||||
'output_path_textbox',
|
||||
'benchmark_runs_checkbox_group',
|
||||
'benchmark_cycles_slider',
|
||||
'player_url_textbox_label',
|
||||
'webcam_mode_radio',
|
||||
'webcam_resolution_dropdown',
|
||||
'webcam_fps_slider'
|
||||
]
|
||||
WebcamMode = Literal[ 'inline', 'udp', 'v4l2' ]
|
||||
StreamMode = Literal[ 'udp', 'v4l2' ]
|
||||
WebcamMode = Literal['inline', 'udp', 'v4l2']
|
||||
StreamMode = Literal['udp', 'v4l2']
|
||||
|
@ -1,20 +1,22 @@
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from tqdm import tqdm
|
||||
import glob
|
||||
import mimetypes
|
||||
import filetype
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import ssl
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib
|
||||
import urllib.request
|
||||
import onnxruntime
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.typing import Padding
|
||||
from facefusion.vision import detect_fps
|
||||
|
||||
TEMP_DIRECTORY_PATH = os.path.join(tempfile.gettempdir(), 'facefusion')
|
||||
@ -118,21 +120,6 @@ def get_temp_output_video_path(target_path : str) -> str:
|
||||
return os.path.join(temp_directory_path, TEMP_OUTPUT_VIDEO_NAME)
|
||||
|
||||
|
||||
def normalize_output_path(source_path : Optional[str], target_path : Optional[str], output_path : Optional[str]) -> Optional[str]:
|
||||
if is_file(source_path) and is_file(target_path) and is_directory(output_path):
|
||||
source_name, _ = os.path.splitext(os.path.basename(source_path))
|
||||
target_name, target_extension = os.path.splitext(os.path.basename(target_path))
|
||||
return os.path.join(output_path, source_name + '-' + target_name + target_extension)
|
||||
if is_file(target_path) and output_path:
|
||||
target_name, target_extension = os.path.splitext(os.path.basename(target_path))
|
||||
output_name, output_extension = os.path.splitext(os.path.basename(output_path))
|
||||
output_directory_path = os.path.dirname(output_path)
|
||||
if is_directory(output_directory_path) and output_extension:
|
||||
return os.path.join(output_directory_path, output_name + target_extension)
|
||||
return None
|
||||
return output_path
|
||||
|
||||
|
||||
def create_temp(target_path : str) -> None:
|
||||
temp_directory_path = get_temp_directory_path(target_path)
|
||||
Path(temp_directory_path).mkdir(parents = True, exist_ok = True)
|
||||
@ -155,6 +142,35 @@ def clear_temp(target_path : str) -> None:
|
||||
os.rmdir(parent_directory_path)
|
||||
|
||||
|
||||
def normalize_output_path(source_path : Optional[str], target_path : Optional[str], output_path : Optional[str]) -> Optional[str]:
|
||||
if is_file(target_path) and is_directory(output_path):
|
||||
target_name, target_extension = os.path.splitext(os.path.basename(target_path))
|
||||
if is_file(source_path):
|
||||
source_name, _ = os.path.splitext(os.path.basename(source_path))
|
||||
return os.path.join(output_path, source_name + '-' + target_name + target_extension)
|
||||
return os.path.join(output_path, target_name + target_extension)
|
||||
if is_file(target_path) and output_path:
|
||||
_, target_extension = os.path.splitext(os.path.basename(target_path))
|
||||
output_name, output_extension = os.path.splitext(os.path.basename(output_path))
|
||||
output_directory_path = os.path.dirname(output_path)
|
||||
if is_directory(output_directory_path) and output_extension:
|
||||
return os.path.join(output_directory_path, output_name + target_extension)
|
||||
return None
|
||||
return output_path
|
||||
|
||||
|
||||
def normalize_padding(padding : Optional[List[int]]) -> Optional[Padding]:
|
||||
if padding and len(padding) == 1:
|
||||
return tuple([ padding[0], padding[0], padding[0], padding[0] ]) # type: ignore[return-value]
|
||||
if padding and len(padding) == 2:
|
||||
return tuple([ padding[0], padding[1], padding[0], padding[1] ]) # type: ignore[return-value]
|
||||
if padding and len(padding) == 3:
|
||||
return tuple([ padding[0], padding[1], padding[2], padding[1] ]) # type: ignore[return-value]
|
||||
if padding and len(padding) == 4:
|
||||
return tuple(padding) # type: ignore[return-value]
|
||||
return None
|
||||
|
||||
|
||||
def is_file(file_path : str) -> bool:
|
||||
return bool(file_path and os.path.isfile(file_path))
|
||||
|
||||
@ -165,19 +181,22 @@ def is_directory(directory_path : str) -> bool:
|
||||
|
||||
def is_image(image_path : str) -> bool:
|
||||
if is_file(image_path):
|
||||
mimetype, _ = mimetypes.guess_type(image_path)
|
||||
mimetype = filetype.guess(image_path).mime
|
||||
return bool(mimetype and mimetype.startswith('image/'))
|
||||
return False
|
||||
|
||||
|
||||
def is_video(video_path : str) -> bool:
|
||||
if is_file(video_path):
|
||||
mimetype, _ = mimetypes.guess_type(video_path)
|
||||
mimetype = filetype.guess(video_path).mime
|
||||
return bool(mimetype and mimetype.startswith('video/'))
|
||||
return False
|
||||
|
||||
|
||||
def conditional_download(download_directory_path : str, urls : List[str]) -> None:
|
||||
with ThreadPoolExecutor() as executor:
|
||||
for url in urls:
|
||||
executor.submit(get_download_size, url)
|
||||
for url in urls:
|
||||
download_file_path = os.path.join(download_directory_path, os.path.basename(url))
|
||||
total = get_download_size(url)
|
||||
@ -186,7 +205,7 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
|
||||
else:
|
||||
initial = 0
|
||||
if initial < total:
|
||||
with tqdm(total = total, initial = initial, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024) as progress:
|
||||
with tqdm(total = total, initial = initial, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =') as progress:
|
||||
subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ])
|
||||
current = initial
|
||||
while current < total:
|
||||
@ -198,7 +217,7 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
|
||||
@lru_cache(maxsize = None)
|
||||
def get_download_size(url : str) -> int:
|
||||
try:
|
||||
response = urllib.request.urlopen(url) # type: ignore[attr-defined]
|
||||
response = urllib.request.urlopen(url, timeout = 10)
|
||||
return int(response.getheader('Content-Length'))
|
||||
except (OSError, ValueError):
|
||||
return 0
|
||||
@ -217,7 +236,7 @@ def resolve_relative_path(path : str) -> str:
|
||||
def list_module_names(path : str) -> Optional[List[str]]:
|
||||
if os.path.exists(path):
|
||||
files = os.listdir(path)
|
||||
return [ Path(file).stem for file in files if not Path(file).stem.startswith('__') ]
|
||||
return [ Path(file).stem for file in files if not Path(file).stem.startswith(('.', '__')) ]
|
||||
return None
|
||||
|
||||
|
||||
@ -231,9 +250,19 @@ def decode_execution_providers(execution_providers: List[str]) -> List[str]:
|
||||
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) ]
|
||||
|
||||
|
||||
def get_device(execution_providers : List[str]) -> str:
|
||||
if 'CUDAExecutionProvider' in execution_providers:
|
||||
return 'cuda'
|
||||
def map_device(execution_providers : List[str]) -> str:
|
||||
if 'CoreMLExecutionProvider' in execution_providers:
|
||||
return 'mps'
|
||||
if 'CUDAExecutionProvider' in execution_providers or 'ROCMExecutionProvider' in execution_providers :
|
||||
return 'cuda'
|
||||
if 'OpenVINOExecutionProvider' in execution_providers:
|
||||
return 'mkl'
|
||||
return 'cpu'
|
||||
|
||||
|
||||
def create_metavar(ranges : List[Any]) -> str:
|
||||
return '[' + str(ranges[0]) + '-' + str(ranges[-1]) + ']'
|
||||
|
||||
|
||||
def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None:
|
||||
print('[' + scope + '] ' + message)
|
||||
|
@ -7,12 +7,12 @@ from facefusion.typing import Frame
|
||||
|
||||
def get_video_frame(video_path : str, frame_number : int = 0) -> Optional[Frame]:
|
||||
if video_path:
|
||||
capture = cv2.VideoCapture(video_path)
|
||||
if capture.isOpened():
|
||||
frame_total = capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
||||
capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
|
||||
has_frame, frame = capture.read()
|
||||
capture.release()
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
if video_capture.isOpened():
|
||||
frame_total = video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
||||
video_capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
|
||||
has_frame, frame = video_capture.read()
|
||||
video_capture.release()
|
||||
if has_frame:
|
||||
return frame
|
||||
return None
|
||||
@ -20,18 +20,18 @@ def get_video_frame(video_path : str, frame_number : int = 0) -> Optional[Frame]
|
||||
|
||||
def detect_fps(video_path : str) -> Optional[float]:
|
||||
if video_path:
|
||||
capture = cv2.VideoCapture(video_path)
|
||||
if capture.isOpened():
|
||||
return capture.get(cv2.CAP_PROP_FPS)
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
if video_capture.isOpened():
|
||||
return video_capture.get(cv2.CAP_PROP_FPS)
|
||||
return None
|
||||
|
||||
|
||||
def count_video_frame_total(video_path : str) -> int:
|
||||
if video_path:
|
||||
capture = cv2.VideoCapture(video_path)
|
||||
if capture.isOpened():
|
||||
video_frame_total = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
capture.release()
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
if video_capture.isOpened():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video_capture.release()
|
||||
return video_frame_total
|
||||
return 0
|
||||
|
||||
|
32
facefusion/wording.py
Normal file → Executable file
32
facefusion/wording.py
Normal file → Executable file
@ -7,19 +7,25 @@ WORDING =\
|
||||
'target_help': 'select a target image or video',
|
||||
'output_help': 'specify the output file or directory',
|
||||
'frame_processors_help': 'choose from the available frame processors (choices: {choices}, ...)',
|
||||
'frame_processor_model_help': 'choose from the mode for the frame processor',
|
||||
'frame_processor_model_help': 'choose the model for the frame processor',
|
||||
'frame_processor_blend_help': 'specify the blend factor for the frame processor',
|
||||
'face_debugger_items_help': 'specify the face debugger items',
|
||||
'ui_layouts_help': 'choose from the available ui layouts (choices: {choices}, ...)',
|
||||
'keep_fps_help': 'preserve the frames per second (fps) of the target',
|
||||
'keep_temp_help': 'retain temporary frames after processing',
|
||||
'skip_audio_help': 'omit audio from the target',
|
||||
'face_recognition_help': 'specify the method for face recognition',
|
||||
'face_analyser_direction_help': 'specify the direction used for face analysis',
|
||||
'face_analyser_age_help': 'specify the age used for face analysis',
|
||||
'face_analyser_gender_help': 'specify the gender used for face analysis',
|
||||
'face_analyser_order_help': 'specify the order used for the face analyser',
|
||||
'face_analyser_age_help': 'specify the age used for the face analyser',
|
||||
'face_analyser_gender_help': 'specify the gender used for the face analyser',
|
||||
'face_detector_model_help': 'specify the model used for the face detector',
|
||||
'face_detector_size_help': 'specify the size threshold used for the face detector',
|
||||
'face_detector_score_help': 'specify the score threshold used for the face detector',
|
||||
'face_selector_mode_help': 'specify the mode for the face selector',
|
||||
'reference_face_position_help': 'specify the position of the reference face',
|
||||
'reference_face_distance_help': 'specify the distance between the reference face and the target face',
|
||||
'reference_frame_number_help': 'specify the number of the reference frame',
|
||||
'face_mask_blur_help': 'specify the blur amount for face mask',
|
||||
'face_mask_padding_help': 'specify the face mask padding (top, right, bottom, left) in percent',
|
||||
'trim_frame_start_help': 'specify the start frame for extraction',
|
||||
'trim_frame_end_help': 'specify the end frame for extraction',
|
||||
'temp_frame_format_help': 'specify the image format used for frame extraction',
|
||||
@ -28,13 +34,14 @@ WORDING =\
|
||||
'output_video_encoder_help': 'specify the encoder used for the output video',
|
||||
'output_video_quality_help': 'specify the quality used for the output video',
|
||||
'max_memory_help': 'specify the maximum amount of ram to be used (in gb)',
|
||||
'execution_providers_help': 'choose from the available execution providers (choices: {choices}, ...)',
|
||||
'execution_providers_help': 'choose from the available execution providers',
|
||||
'execution_thread_count_help': 'specify the number of execution threads',
|
||||
'execution_queue_count_help': 'specify the number of execution queries',
|
||||
'skip_download_help': 'omit automate downloads and lookups',
|
||||
'headless_help': 'run the program in headless mode',
|
||||
'creating_temp': 'Creating temporary resources',
|
||||
'extracting_frames_fps': 'Extracting frames with {fps} FPS',
|
||||
'analysing': 'Analysing',
|
||||
'processing': 'Processing',
|
||||
'downloading': 'Downloading',
|
||||
'temp_frames_not_found': 'Temporary frames not found',
|
||||
@ -70,12 +77,20 @@ WORDING =\
|
||||
'execution_providers_checkbox_group_label': 'EXECUTION PROVIDERS',
|
||||
'execution_thread_count_slider_label': 'EXECUTION THREAD COUNT',
|
||||
'execution_queue_count_slider_label': 'EXECUTION QUEUE COUNT',
|
||||
'face_analyser_direction_dropdown_label': 'FACE ANALYSER DIRECTION',
|
||||
'face_analyser_order_dropdown_label': 'FACE ANALYSER ORDER',
|
||||
'face_analyser_age_dropdown_label': 'FACE ANALYSER AGE',
|
||||
'face_analyser_gender_dropdown_label': 'FACE ANALYSER GENDER',
|
||||
'face_detector_model_dropdown_label': 'FACE DETECTOR MODEL',
|
||||
'face_detector_size_dropdown_label': 'FACE DETECTOR SIZE',
|
||||
'face_detector_score_slider_label': 'FACE DETECTOR SCORE',
|
||||
'face_selector_mode_dropdown_label': 'FACE SELECTOR MODE',
|
||||
'reference_face_gallery_label': 'REFERENCE FACE',
|
||||
'face_recognition_dropdown_label': 'FACE RECOGNITION',
|
||||
'reference_face_distance_slider_label': 'REFERENCE FACE DISTANCE',
|
||||
'face_mask_blur_slider_label': 'FACE MASK BLUR',
|
||||
'face_mask_padding_top_slider_label': 'FACE MASK PADDING TOP',
|
||||
'face_mask_padding_bottom_slider_label': 'FACE MASK PADDING BOTTOM',
|
||||
'face_mask_padding_left_slider_label': 'FACE MASK PADDING LEFT',
|
||||
'face_mask_padding_right_slider_label': 'FACE MASK PADDING RIGHT',
|
||||
'max_memory_slider_label': 'MAX MEMORY',
|
||||
'output_image_or_video_label': 'OUTPUT',
|
||||
'output_path_textbox_label': 'OUTPUT PATH',
|
||||
@ -90,6 +105,7 @@ WORDING =\
|
||||
'face_enhancer_blend_slider_label': 'FACE ENHANCER BLEND',
|
||||
'frame_enhancer_model_dropdown_label': 'FRAME ENHANCER MODEL',
|
||||
'frame_enhancer_blend_slider_label': 'FRAME ENHANCER BLEND',
|
||||
'face_debugger_items_checkbox_group_label': 'FACE DEBUGGER ITEMS',
|
||||
'common_options_checkbox_group_label': 'OPTIONS',
|
||||
'temp_frame_format_dropdown_label': 'TEMP FRAME FORMAT',
|
||||
'temp_frame_quality_slider_label': 'TEMP FRAME QUALITY',
|
||||
|
@ -1,15 +1,11 @@
|
||||
basicsr==1.4.2
|
||||
gradio==3.47.1
|
||||
insightface==0.7.3
|
||||
numpy==1.24.3
|
||||
onnx==1.14.1
|
||||
filetype==1.2.0
|
||||
gradio==3.50.2
|
||||
numpy==1.26.1
|
||||
onnx==1.15.0
|
||||
onnxruntime==1.16.0
|
||||
opencv-python==4.8.1.78
|
||||
opennsfw2==0.10.2
|
||||
pillow==10.0.1
|
||||
protobuf==4.24.2
|
||||
psutil==5.9.5
|
||||
psutil==5.9.6
|
||||
realesrgan==0.3.0
|
||||
tensorflow==2.13.0
|
||||
torch==2.1.0
|
||||
tqdm==4.66.1
|
||||
|
@ -4,7 +4,7 @@ import subprocess
|
||||
import pytest
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion.utilities import conditional_download, extract_frames, create_temp, get_temp_directory_path, clear_temp, normalize_output_path, is_file, is_directory, is_image, is_video, get_download_size, is_download_done, encode_execution_providers, decode_execution_providers
|
||||
from facefusion.utilities import conditional_download, extract_frames, create_temp, get_temp_directory_path, clear_temp, normalize_output_path, normalize_padding, is_file, is_directory, is_image, is_video, get_download_size, is_download_done, encode_execution_providers, decode_execution_providers
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'module', autouse = True)
|
||||
@ -107,6 +107,7 @@ def test_normalize_output_path() -> None:
|
||||
if platform.system().lower() != 'windows':
|
||||
assert normalize_output_path('.assets/examples/source.jpg', None, '.assets/examples/target-240p.mp4') == '.assets/examples/target-240p.mp4'
|
||||
assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples/target-240p.mp4') == '.assets/examples/target-240p.mp4'
|
||||
assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples') == '.assets/examples/target-240p.mp4'
|
||||
assert normalize_output_path('.assets/examples/source.jpg', '.assets/examples/target-240p.mp4', '.assets/examples') == '.assets/examples/source-target-240p.mp4'
|
||||
assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples/output.mp4') == '.assets/examples/output.mp4'
|
||||
assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/output.mov') == '.assets/output.mp4'
|
||||
@ -116,6 +117,14 @@ def test_normalize_output_path() -> None:
|
||||
assert normalize_output_path('.assets/examples/source.jpg', '.assets/examples/target-240p.mp4', None) is None
|
||||
|
||||
|
||||
def test_normalize_padding() -> None:
|
||||
assert normalize_padding([ 0, 0, 0, 0 ]) == (0, 0, 0, 0)
|
||||
assert normalize_padding([ 1 ]) == (1, 1, 1, 1)
|
||||
assert normalize_padding([ 1, 2 ]) == (1, 2, 1, 2)
|
||||
assert normalize_padding([ 1, 2, 3 ]) == (1, 2, 3, 2)
|
||||
assert normalize_padding(None) is None
|
||||
|
||||
|
||||
def test_is_file() -> None:
|
||||
assert is_file('.assets/examples/source.jpg') is True
|
||||
assert is_file('.assets/examples') is False
|
||||
|
Loading…
Reference in New Issue
Block a user