From 3d02b267663e4badc5c7936d5295effcae3dfbc0 Mon Sep 17 00:00:00 2001 From: Henry Ruhs Date: Mon, 26 Jun 2023 07:49:43 +0200 Subject: [PATCH] Next (#583) * More accurate progress description * Add thread lock to face analyser * Use as_completed() for thread pool * Show memory usage in progress bar * Using Queue for dynamic thread processing * Fix typing * Introduce pick_quere() to allocate frames per future * Bump version and add missing hook function * Fix pick_queue() * Introduce post process (#587) * Introduce post_process to flush VRAM for example * Delete frame processor instances * Limit tensorflow usage to 1GB VRAM * Set None instead of del * Remove deprecated args * Update gui preview * Remove choices restriction from frame-processor and improve help output * faithful donation label * original donate button colors * Introduce Frame processor xxx crashed * ^_^ ^_^ ^_^ ^_^ ^_^ * Update GUI demo --------- Co-authored-by: Somdev Sangwan --- .github/workflows/ci.yml | 2 +- README.md | 39 +++++++---------- gui-demo.png | Bin 20757 -> 23444 bytes mypi.ini => mypy.ini | 0 roop/core.py | 39 ++++------------- roop/face_analyser.py | 9 ++-- roop/metadata.py | 2 +- roop/processors/frame/core.py | 56 +++++++++++++++++++------ roop/processors/frame/face_enhancer.py | 28 ++++++++----- roop/processors/frame/face_swapper.py | 34 ++++++++------- roop/ui.json | 2 +- roop/ui.py | 2 +- roop/utilities.py | 2 +- 13 files changed, 113 insertions(+), 102 deletions(-) rename mypi.ini => mypy.ini (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b8f4ce..420a32d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - run: pip install flake8 - run: pip install mypy - run: flake8 run.py roop - - run: mypy --config-file mypi.ini run.py roop + - run: mypy run.py roop test: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 58e3e02..ca3e92a 100644 --- a/README.md +++ b/README.md @@ -31,30 +31,21 @@ Additional command line arguments are given below. To learn out what they do, ch ``` options: - -h, --help show this help message and exit - -s SOURCE_PATH, --source SOURCE_PATH - select an source image - -t TARGET_PATH, --target TARGET_PATH - select an target image or video - -o OUTPUT_PATH, --output OUTPUT_PATH - select output file or directory - --frame-processor {face_swapper,face_enhancer} [{face_swapper,face_enhancer} ...] - pipeline of frame processors - --keep-fps keep original fps - --keep-audio keep original audio - --keep-frames keep temporary frames - --many-faces process every face - --video-encoder {libx264,libx265,libvpx-vp9} - adjust output video encoder - --video-quality VIDEO_QUALITY - adjust output video quality - --max-memory MAX_MEMORY - maximum amount of RAM in GB - --execution-provider {cpu,...} [{cpu,...} ...] - execution provider - --execution-threads EXECUTION_THREADS - number of execution threads - -v, --version show program's version number and exit + -h, --help show this help message and exit + -s SOURCE_PATH, --source SOURCE_PATH select an source image + -t TARGET_PATH, --target TARGET_PATH select an target image or video + -o OUTPUT_PATH, --output OUTPUT_PATH select output file or directory + --frame-processor FRAME_PROCESSOR [FRAME_PROCESSOR ...] frame processors (choices: face_swapper, face_enhancer, ...) + --keep-fps keep original fps + --keep-audio keep original audio + --keep-frames keep temporary frames + --many-faces process every face + --video-encoder {libx264,libx265,libvpx-vp9} adjust output video encoder + --video-quality [0-51] adjust output video quality + --max-memory MAX_MEMORY maximum amount of RAM in GB + --execution-provider {cpu} [{cpu} ...] available execution provider (choices: cpu, ...) + --execution-threads EXECUTION_THREADS number of execution threads + -v, --version show program's version number and exit ``` Looking for a CLI mode? Using the -s/--source argument will make the run program in cli mode. diff --git a/gui-demo.png b/gui-demo.png index f26b691d10ab1957036798b1cf1bbce50675dd96..b76a54da3ae235549d12f3046f8bdaa1882be2ba 100644 GIT binary patch literal 23444 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 Wn None: signal.signal(signal.SIGINT, lambda signal_number, frame: destroy()) - program = argparse.ArgumentParser() + program = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=100)) program.add_argument('-s', '--source', help='select an source image', dest='source_path') program.add_argument('-t', '--target', help='select an target image or video', dest='target_path') program.add_argument('-o', '--output', help='select output file or directory', dest='output_path') - program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer'], nargs='+') + program.add_argument('--frame-processor', help='frame processors (choices: face_swapper, face_enhancer, ...)', dest='frame_processor', default=['face_swapper'], nargs='+') program.add_argument('--keep-fps', help='keep original fps', dest='keep_fps', action='store_true', default=False) program.add_argument('--keep-audio', help='keep original audio', dest='keep_audio', action='store_true', default=True) program.add_argument('--keep-frames', help='keep temporary frames', dest='keep_frames', action='store_true', default=False) @@ -45,16 +45,10 @@ def parse_args() -> None: program.add_argument('--video-encoder', help='adjust output video encoder', dest='video_encoder', default='libx264', choices=['libx264', 'libx265', 'libvpx-vp9']) program.add_argument('--video-quality', help='adjust output video quality', dest='video_quality', type=int, default=18, choices=range(52), metavar='[0-51]') program.add_argument('--max-memory', help='maximum amount of RAM in GB', dest='max_memory', type=int, default=suggest_max_memory()) - program.add_argument('--execution-provider', help='execution provider', dest='execution_provider', default=['cpu'], choices=suggest_execution_providers(), nargs='+') + program.add_argument('--execution-provider', help='available execution provider (choices: cpu, ...)', dest='execution_provider', default=['cpu'], choices=suggest_execution_providers(), nargs='+') program.add_argument('--execution-threads', help='number of execution threads', dest='execution_threads', type=int, default=suggest_execution_threads()) program.add_argument('-v', '--version', action='version', version=f'{roop.metadata.name} {roop.metadata.version}') - # register deprecated args - program.add_argument('-f', '--face', help=argparse.SUPPRESS, dest='source_path_deprecated') - program.add_argument('--cpu-cores', help=argparse.SUPPRESS, dest='cpu_cores_deprecated', type=int) - program.add_argument('--gpu-vendor', help=argparse.SUPPRESS, dest='gpu_vendor_deprecated') - program.add_argument('--gpu-threads', help=argparse.SUPPRESS, dest='gpu_threads_deprecated', type=int) - args = program.parse_args() roop.globals.source_path = args.source_path @@ -72,27 +66,6 @@ def parse_args() -> None: roop.globals.execution_providers = decode_execution_providers(args.execution_provider) roop.globals.execution_threads = args.execution_threads - # translate deprecated args - if args.source_path_deprecated: - print('\033[33mArgument -f and --face are deprecated. Use -s and --source instead.\033[0m') - roop.globals.source_path = args.source_path_deprecated - roop.globals.output_path = normalize_output_path(args.source_path_deprecated, roop.globals.target_path, args.output_path) - if args.cpu_cores_deprecated: - print('\033[33mArgument --cpu-cores is deprecated. Use --execution-threads instead.\033[0m') - roop.globals.execution_threads = args.cpu_cores_deprecated - if args.gpu_vendor_deprecated == 'apple': - print('\033[33mArgument --gpu-vendor apple is deprecated. Use --execution-provider coreml instead.\033[0m') - roop.globals.execution_providers = decode_execution_providers(['coreml']) - if args.gpu_vendor_deprecated == 'nvidia': - print('\033[33mArgument --gpu-vendor nvidia is deprecated. Use --execution-provider cuda instead.\033[0m') - roop.globals.execution_providers = decode_execution_providers(['cuda']) - if args.gpu_vendor_deprecated == 'amd': - print('\033[33mArgument --gpu-vendor amd is deprecated. Use --execution-provider cuda instead.\033[0m') - roop.globals.execution_providers = decode_execution_providers(['rocm']) - if args.gpu_threads_deprecated: - print('\033[33mArgument --gpu-threads is deprecated. Use --execution-threads instead.\033[0m') - roop.globals.execution_threads = args.gpu_threads_deprecated - def encode_execution_providers(execution_providers: List[str]) -> List[str]: return [execution_provider.replace('ExecutionProvider', '').lower() for execution_provider in execution_providers] @@ -125,7 +98,9 @@ def limit_resources() -> None: # prevent tensorflow memory leak gpus = tensorflow.config.experimental.list_physical_devices('GPU') for gpu in gpus: - tensorflow.config.experimental.set_memory_growth(gpu, True) + tensorflow.config.experimental.set_virtual_device_configuration(gpu, [ + tensorflow.config.experimental.VirtualDeviceConfiguration(memory_limit=1024) + ]) # limit memory usage if roop.globals.max_memory: memory = roop.globals.max_memory * 1024 ** 3 @@ -173,6 +148,7 @@ def start() -> None: for frame_processor in get_frame_processors_modules(roop.globals.frame_processors): update_status('Progressing...', frame_processor.NAME) frame_processor.process_image(roop.globals.source_path, roop.globals.output_path, roop.globals.output_path) + frame_processor.post_process() release_resources() if is_image(roop.globals.target_path): update_status('Processing to image succeed!') @@ -190,6 +166,7 @@ def start() -> None: for frame_processor in get_frame_processors_modules(roop.globals.frame_processors): update_status('Progressing...', frame_processor.NAME) frame_processor.process_video(roop.globals.source_path, temp_frame_paths) + frame_processor.post_process() release_resources() # handles fps if roop.globals.keep_fps: diff --git a/roop/face_analyser.py b/roop/face_analyser.py index ba7803e..9c0afe4 100644 --- a/roop/face_analyser.py +++ b/roop/face_analyser.py @@ -1,3 +1,4 @@ +import threading from typing import Any import insightface @@ -5,14 +6,16 @@ import roop.globals from roop.typing import Frame FACE_ANALYSER = None +THREAD_LOCK = threading.Lock() def get_face_analyser() -> Any: global FACE_ANALYSER - if FACE_ANALYSER is None: - FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=roop.globals.execution_providers) - FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640)) + with THREAD_LOCK: + if FACE_ANALYSER is None: + FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=roop.globals.execution_providers) + FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640)) return FACE_ANALYSER diff --git a/roop/metadata.py b/roop/metadata.py index 69c387e..35b0f02 100644 --- a/roop/metadata.py +++ b/roop/metadata.py @@ -1,2 +1,2 @@ name = 'roop' -version = '1.0.1' +version = '1.1.0' diff --git a/roop/processors/frame/core.py b/roop/processors/frame/core.py index a07a9a6..c225f9d 100644 --- a/roop/processors/frame/core.py +++ b/roop/processors/frame/core.py @@ -1,6 +1,8 @@ -import sys +import os import importlib -from concurrent.futures import ThreadPoolExecutor +import psutil +from concurrent.futures import ThreadPoolExecutor, as_completed +from queue import Queue from types import ModuleType from typing import Any, List, Callable from tqdm import tqdm @@ -12,8 +14,10 @@ FRAME_PROCESSORS_INTERFACE = [ 'pre_check', 'pre_start', 'process_frame', + 'process_frames', 'process_image', - 'process_video' + 'process_video', + 'post_process' ] @@ -22,9 +26,9 @@ def load_frame_processor_module(frame_processor: str) -> Any: frame_processor_module = importlib.import_module(f'roop.processors.frame.{frame_processor}') for method_name in FRAME_PROCESSORS_INTERFACE: if not hasattr(frame_processor_module, method_name): - sys.exit() - except ImportError: - sys.exit() + raise NotImplementedError + except (ImportError, NotImplementedError): + quit(f'Frame processor {frame_processor} crashed.') return frame_processor_module @@ -38,19 +42,47 @@ def get_frame_processors_modules(frame_processors: List[str]) -> List[ModuleType return FRAME_PROCESSORS_MODULES -def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], progress: Any = None) -> None: +def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], update: Callable[[], None]) -> None: with ThreadPoolExecutor(max_workers=roop.globals.execution_threads) as executor: futures = [] - for path in temp_frame_paths: - future = executor.submit(process_frames, source_path, [path], progress) + queue = create_queue(temp_frame_paths) + queue_per_future = len(temp_frame_paths) // roop.globals.execution_threads + while not queue.empty(): + future = executor.submit(process_frames, source_path, pick_queue(queue, queue_per_future), update) futures.append(future) - for future in futures: + for future in as_completed(futures): future.result() +def create_queue(temp_frame_paths: List[str]) -> Queue[str]: + queue: Queue[str] = Queue() + for frame_path in temp_frame_paths: + queue.put(frame_path) + return queue + + +def pick_queue(queue: Queue[str], queue_per_future: int) -> List[str]: + queues = [] + for _ in range(queue_per_future): + if not queue.empty(): + queues.append(queue.get()) + return queues + + def process_video(source_path: str, frame_paths: list[str], process_frames: Callable[[str, List[str], Any], None]) -> None: progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]' total = len(frame_paths) with tqdm(total=total, desc='Processing', unit='frame', dynamic_ncols=True, bar_format=progress_bar_format) as progress: - progress.set_postfix({'execution_providers': roop.globals.execution_providers, 'threads': roop.globals.execution_threads, 'memory': roop.globals.max_memory}) - multi_process_frame(source_path, frame_paths, process_frames, progress) + multi_process_frame(source_path, frame_paths, process_frames, lambda: update_progress(progress)) + + +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': roop.globals.execution_providers, + 'execution_threads': roop.globals.execution_threads + }) + progress.refresh() + progress.update(1) diff --git a/roop/processors/frame/face_enhancer.py b/roop/processors/frame/face_enhancer.py index 50c7c54..3ff92ce 100644 --- a/roop/processors/frame/face_enhancer.py +++ b/roop/processors/frame/face_enhancer.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any, List, Callable import cv2 import threading import gfpgan @@ -16,6 +16,17 @@ THREAD_LOCK = threading.Lock() NAME = 'ROOP.FACE-ENHANCER' +def get_face_enhancer() -> Any: + global FACE_ENHANCER + + with THREAD_LOCK: + if FACE_ENHANCER is None: + model_path = resolve_relative_path('../models/GFPGANv1.4.pth') + # todo: set models path https://github.com/TencentARC/GFPGAN/issues/399 + FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined] + return FACE_ENHANCER + + def pre_check() -> bool: download_directory_path = resolve_relative_path('../models') conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/GFPGANv1.4.pth']) @@ -29,15 +40,10 @@ def pre_start() -> bool: return True -def get_face_enhancer() -> Any: +def post_process() -> None: global FACE_ENHANCER - with THREAD_LOCK: - if FACE_ENHANCER is None: - model_path = resolve_relative_path('../models/GFPGANv1.4.pth') - # todo: set models path https://github.com/TencentARC/GFPGAN/issues/399 - FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined] - return FACE_ENHANCER + FACE_ENHANCER = None def enhance_face(temp_frame: Frame) -> Frame: @@ -56,13 +62,13 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame: return temp_frame -def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None: +def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None: for temp_frame_path in temp_frame_paths: temp_frame = cv2.imread(temp_frame_path) result = process_frame(None, temp_frame) cv2.imwrite(temp_frame_path, result) - if progress: - progress.update(1) + if update: + update() def process_image(source_path: str, target_path: str, output_path: str) -> None: diff --git a/roop/processors/frame/face_swapper.py b/roop/processors/frame/face_swapper.py index 35063d2..c53b5b8 100644 --- a/roop/processors/frame/face_swapper.py +++ b/roop/processors/frame/face_swapper.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any, List, Callable import cv2 import insightface import threading @@ -15,6 +15,16 @@ THREAD_LOCK = threading.Lock() NAME = 'ROOP.FACE-SWAPPER' +def get_face_swapper() -> Any: + global FACE_SWAPPER + + with THREAD_LOCK: + if FACE_SWAPPER is None: + model_path = resolve_relative_path('../models/inswapper_128.onnx') + FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=roop.globals.execution_providers) + return FACE_SWAPPER + + def pre_check() -> bool: download_directory_path = resolve_relative_path('../models') conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx']) @@ -34,14 +44,10 @@ def pre_start() -> bool: return True -def get_face_swapper() -> Any: +def post_process() -> None: global FACE_SWAPPER - with THREAD_LOCK: - if FACE_SWAPPER is None: - model_path = resolve_relative_path('../models/inswapper_128.onnx') - FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=roop.globals.execution_providers) - return FACE_SWAPPER + FACE_SWAPPER = None def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame: @@ -61,18 +67,14 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame: return temp_frame -def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None: +def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None: source_face = get_one_face(cv2.imread(source_path)) for temp_frame_path in temp_frame_paths: temp_frame = cv2.imread(temp_frame_path) - try: - result = process_frame(source_face, temp_frame) - cv2.imwrite(temp_frame_path, result) - except Exception as exception: - print(exception) - pass - if progress: - progress.update(1) + result = process_frame(source_face, temp_frame) + cv2.imwrite(temp_frame_path, result) + if update: + update() def process_image(source_path: str, target_path: str, output_path: str) -> None: diff --git a/roop/ui.json b/roop/ui.json index 752210c..4930991 100644 --- a/roop/ui.json +++ b/roop/ui.json @@ -153,6 +153,6 @@ } }, "RoopDonate": { - "text_color": ["gray74", "gray60"] + "text_color": ["#3a7ebf", "gray60"] } } diff --git a/roop/ui.py b/roop/ui.py index f413f20..ba693da 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -94,7 +94,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C status_label = ctk.CTkLabel(root, text=None, justify='center') status_label.place(relx=0.1, rely=0.9, relwidth=0.8) - donate_label = ctk.CTkLabel(root, text='Become a GitHub Sponsor', justify='center', cursor='hand2') + donate_label = ctk.CTkLabel(root, text='^_^ Donate to project ^_^', justify='center', cursor='hand2') donate_label.place(relx=0.1, rely=0.95, relwidth=0.8) donate_label.configure(text_color=ctk.ThemeManager.theme.get('RoopDonate').get('text_color')) donate_label.bind('