Skip to content

Commit 3d02b26

Browse files
henryruhss0md3v
andauthored
Next (s0md3v#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 (s0md3v#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 <[email protected]>
1 parent b41149e commit 3d02b26

13 files changed

+113
-102
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- run: pip install flake8
1616
- run: pip install mypy
1717
- run: flake8 run.py roop
18-
- run: mypy --config-file mypi.ini run.py roop
18+
- run: mypy run.py roop
1919
test:
2020
runs-on: ubuntu-latest
2121
steps:

README.md

+15-24
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,21 @@ Additional command line arguments are given below. To learn out what they do, ch
3131

3232
```
3333
options:
34-
-h, --help show this help message and exit
35-
-s SOURCE_PATH, --source SOURCE_PATH
36-
select an source image
37-
-t TARGET_PATH, --target TARGET_PATH
38-
select an target image or video
39-
-o OUTPUT_PATH, --output OUTPUT_PATH
40-
select output file or directory
41-
--frame-processor {face_swapper,face_enhancer} [{face_swapper,face_enhancer} ...]
42-
pipeline of frame processors
43-
--keep-fps keep original fps
44-
--keep-audio keep original audio
45-
--keep-frames keep temporary frames
46-
--many-faces process every face
47-
--video-encoder {libx264,libx265,libvpx-vp9}
48-
adjust output video encoder
49-
--video-quality VIDEO_QUALITY
50-
adjust output video quality
51-
--max-memory MAX_MEMORY
52-
maximum amount of RAM in GB
53-
--execution-provider {cpu,...} [{cpu,...} ...]
54-
execution provider
55-
--execution-threads EXECUTION_THREADS
56-
number of execution threads
57-
-v, --version show program's version number and exit
34+
-h, --help show this help message and exit
35+
-s SOURCE_PATH, --source SOURCE_PATH select an source image
36+
-t TARGET_PATH, --target TARGET_PATH select an target image or video
37+
-o OUTPUT_PATH, --output OUTPUT_PATH select output file or directory
38+
--frame-processor FRAME_PROCESSOR [FRAME_PROCESSOR ...] frame processors (choices: face_swapper, face_enhancer, ...)
39+
--keep-fps keep original fps
40+
--keep-audio keep original audio
41+
--keep-frames keep temporary frames
42+
--many-faces process every face
43+
--video-encoder {libx264,libx265,libvpx-vp9} adjust output video encoder
44+
--video-quality [0-51] adjust output video quality
45+
--max-memory MAX_MEMORY maximum amount of RAM in GB
46+
--execution-provider {cpu} [{cpu} ...] available execution provider (choices: cpu, ...)
47+
--execution-threads EXECUTION_THREADS number of execution threads
48+
-v, --version show program's version number and exit
5849
```
5950

6051
Looking for a CLI mode? Using the -s/--source argument will make the run program in cli mode.

gui-demo.png

2.62 KB
Loading

mypi.ini mypy.ini

File renamed without changes.

roop/core.py

+8-31
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,22 @@
3333

3434
def parse_args() -> None:
3535
signal.signal(signal.SIGINT, lambda signal_number, frame: destroy())
36-
program = argparse.ArgumentParser()
36+
program = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=100))
3737
program.add_argument('-s', '--source', help='select an source image', dest='source_path')
3838
program.add_argument('-t', '--target', help='select an target image or video', dest='target_path')
3939
program.add_argument('-o', '--output', help='select output file or directory', dest='output_path')
40-
program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer'], nargs='+')
40+
program.add_argument('--frame-processor', help='frame processors (choices: face_swapper, face_enhancer, ...)', dest='frame_processor', default=['face_swapper'], nargs='+')
4141
program.add_argument('--keep-fps', help='keep original fps', dest='keep_fps', action='store_true', default=False)
4242
program.add_argument('--keep-audio', help='keep original audio', dest='keep_audio', action='store_true', default=True)
4343
program.add_argument('--keep-frames', help='keep temporary frames', dest='keep_frames', action='store_true', default=False)
4444
program.add_argument('--many-faces', help='process every face', dest='many_faces', action='store_true', default=False)
4545
program.add_argument('--video-encoder', help='adjust output video encoder', dest='video_encoder', default='libx264', choices=['libx264', 'libx265', 'libvpx-vp9'])
4646
program.add_argument('--video-quality', help='adjust output video quality', dest='video_quality', type=int, default=18, choices=range(52), metavar='[0-51]')
4747
program.add_argument('--max-memory', help='maximum amount of RAM in GB', dest='max_memory', type=int, default=suggest_max_memory())
48-
program.add_argument('--execution-provider', help='execution provider', dest='execution_provider', default=['cpu'], choices=suggest_execution_providers(), nargs='+')
48+
program.add_argument('--execution-provider', help='available execution provider (choices: cpu, ...)', dest='execution_provider', default=['cpu'], choices=suggest_execution_providers(), nargs='+')
4949
program.add_argument('--execution-threads', help='number of execution threads', dest='execution_threads', type=int, default=suggest_execution_threads())
5050
program.add_argument('-v', '--version', action='version', version=f'{roop.metadata.name} {roop.metadata.version}')
5151

52-
# register deprecated args
53-
program.add_argument('-f', '--face', help=argparse.SUPPRESS, dest='source_path_deprecated')
54-
program.add_argument('--cpu-cores', help=argparse.SUPPRESS, dest='cpu_cores_deprecated', type=int)
55-
program.add_argument('--gpu-vendor', help=argparse.SUPPRESS, dest='gpu_vendor_deprecated')
56-
program.add_argument('--gpu-threads', help=argparse.SUPPRESS, dest='gpu_threads_deprecated', type=int)
57-
5852
args = program.parse_args()
5953

6054
roop.globals.source_path = args.source_path
@@ -72,27 +66,6 @@ def parse_args() -> None:
7266
roop.globals.execution_providers = decode_execution_providers(args.execution_provider)
7367
roop.globals.execution_threads = args.execution_threads
7468

75-
# translate deprecated args
76-
if args.source_path_deprecated:
77-
print('\033[33mArgument -f and --face are deprecated. Use -s and --source instead.\033[0m')
78-
roop.globals.source_path = args.source_path_deprecated
79-
roop.globals.output_path = normalize_output_path(args.source_path_deprecated, roop.globals.target_path, args.output_path)
80-
if args.cpu_cores_deprecated:
81-
print('\033[33mArgument --cpu-cores is deprecated. Use --execution-threads instead.\033[0m')
82-
roop.globals.execution_threads = args.cpu_cores_deprecated
83-
if args.gpu_vendor_deprecated == 'apple':
84-
print('\033[33mArgument --gpu-vendor apple is deprecated. Use --execution-provider coreml instead.\033[0m')
85-
roop.globals.execution_providers = decode_execution_providers(['coreml'])
86-
if args.gpu_vendor_deprecated == 'nvidia':
87-
print('\033[33mArgument --gpu-vendor nvidia is deprecated. Use --execution-provider cuda instead.\033[0m')
88-
roop.globals.execution_providers = decode_execution_providers(['cuda'])
89-
if args.gpu_vendor_deprecated == 'amd':
90-
print('\033[33mArgument --gpu-vendor amd is deprecated. Use --execution-provider cuda instead.\033[0m')
91-
roop.globals.execution_providers = decode_execution_providers(['rocm'])
92-
if args.gpu_threads_deprecated:
93-
print('\033[33mArgument --gpu-threads is deprecated. Use --execution-threads instead.\033[0m')
94-
roop.globals.execution_threads = args.gpu_threads_deprecated
95-
9669

9770
def encode_execution_providers(execution_providers: List[str]) -> List[str]:
9871
return [execution_provider.replace('ExecutionProvider', '').lower() for execution_provider in execution_providers]
@@ -125,7 +98,9 @@ def limit_resources() -> None:
12598
# prevent tensorflow memory leak
12699
gpus = tensorflow.config.experimental.list_physical_devices('GPU')
127100
for gpu in gpus:
128-
tensorflow.config.experimental.set_memory_growth(gpu, True)
101+
tensorflow.config.experimental.set_virtual_device_configuration(gpu, [
102+
tensorflow.config.experimental.VirtualDeviceConfiguration(memory_limit=1024)
103+
])
129104
# limit memory usage
130105
if roop.globals.max_memory:
131106
memory = roop.globals.max_memory * 1024 ** 3
@@ -173,6 +148,7 @@ def start() -> None:
173148
for frame_processor in get_frame_processors_modules(roop.globals.frame_processors):
174149
update_status('Progressing...', frame_processor.NAME)
175150
frame_processor.process_image(roop.globals.source_path, roop.globals.output_path, roop.globals.output_path)
151+
frame_processor.post_process()
176152
release_resources()
177153
if is_image(roop.globals.target_path):
178154
update_status('Processing to image succeed!')
@@ -190,6 +166,7 @@ def start() -> None:
190166
for frame_processor in get_frame_processors_modules(roop.globals.frame_processors):
191167
update_status('Progressing...', frame_processor.NAME)
192168
frame_processor.process_video(roop.globals.source_path, temp_frame_paths)
169+
frame_processor.post_process()
193170
release_resources()
194171
# handles fps
195172
if roop.globals.keep_fps:

roop/face_analyser.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
import threading
12
from typing import Any
23
import insightface
34

45
import roop.globals
56
from roop.typing import Frame
67

78
FACE_ANALYSER = None
9+
THREAD_LOCK = threading.Lock()
810

911

1012
def get_face_analyser() -> Any:
1113
global FACE_ANALYSER
1214

13-
if FACE_ANALYSER is None:
14-
FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=roop.globals.execution_providers)
15-
FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
15+
with THREAD_LOCK:
16+
if FACE_ANALYSER is None:
17+
FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=roop.globals.execution_providers)
18+
FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
1619
return FACE_ANALYSER
1720

1821

roop/metadata.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
name = 'roop'
2-
version = '1.0.1'
2+
version = '1.1.0'

roop/processors/frame/core.py

+44-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import sys
1+
import os
22
import importlib
3-
from concurrent.futures import ThreadPoolExecutor
3+
import psutil
4+
from concurrent.futures import ThreadPoolExecutor, as_completed
5+
from queue import Queue
46
from types import ModuleType
57
from typing import Any, List, Callable
68
from tqdm import tqdm
@@ -12,8 +14,10 @@
1214
'pre_check',
1315
'pre_start',
1416
'process_frame',
17+
'process_frames',
1518
'process_image',
16-
'process_video'
19+
'process_video',
20+
'post_process'
1721
]
1822

1923

@@ -22,9 +26,9 @@ def load_frame_processor_module(frame_processor: str) -> Any:
2226
frame_processor_module = importlib.import_module(f'roop.processors.frame.{frame_processor}')
2327
for method_name in FRAME_PROCESSORS_INTERFACE:
2428
if not hasattr(frame_processor_module, method_name):
25-
sys.exit()
26-
except ImportError:
27-
sys.exit()
29+
raise NotImplementedError
30+
except (ImportError, NotImplementedError):
31+
quit(f'Frame processor {frame_processor} crashed.')
2832
return frame_processor_module
2933

3034

@@ -38,19 +42,47 @@ def get_frame_processors_modules(frame_processors: List[str]) -> List[ModuleType
3842
return FRAME_PROCESSORS_MODULES
3943

4044

41-
def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], progress: Any = None) -> None:
45+
def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], update: Callable[[], None]) -> None:
4246
with ThreadPoolExecutor(max_workers=roop.globals.execution_threads) as executor:
4347
futures = []
44-
for path in temp_frame_paths:
45-
future = executor.submit(process_frames, source_path, [path], progress)
48+
queue = create_queue(temp_frame_paths)
49+
queue_per_future = len(temp_frame_paths) // roop.globals.execution_threads
50+
while not queue.empty():
51+
future = executor.submit(process_frames, source_path, pick_queue(queue, queue_per_future), update)
4652
futures.append(future)
47-
for future in futures:
53+
for future in as_completed(futures):
4854
future.result()
4955

5056

57+
def create_queue(temp_frame_paths: List[str]) -> Queue[str]:
58+
queue: Queue[str] = Queue()
59+
for frame_path in temp_frame_paths:
60+
queue.put(frame_path)
61+
return queue
62+
63+
64+
def pick_queue(queue: Queue[str], queue_per_future: int) -> List[str]:
65+
queues = []
66+
for _ in range(queue_per_future):
67+
if not queue.empty():
68+
queues.append(queue.get())
69+
return queues
70+
71+
5172
def process_video(source_path: str, frame_paths: list[str], process_frames: Callable[[str, List[str], Any], None]) -> None:
5273
progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
5374
total = len(frame_paths)
5475
with tqdm(total=total, desc='Processing', unit='frame', dynamic_ncols=True, bar_format=progress_bar_format) as progress:
55-
progress.set_postfix({'execution_providers': roop.globals.execution_providers, 'threads': roop.globals.execution_threads, 'memory': roop.globals.max_memory})
56-
multi_process_frame(source_path, frame_paths, process_frames, progress)
76+
multi_process_frame(source_path, frame_paths, process_frames, lambda: update_progress(progress))
77+
78+
79+
def update_progress(progress: Any = None) -> None:
80+
process = psutil.Process(os.getpid())
81+
memory_usage = process.memory_info().rss / 1024 / 1024 / 1024
82+
progress.set_postfix({
83+
'memory_usage': '{:.2f}'.format(memory_usage).zfill(5) + 'GB',
84+
'execution_providers': roop.globals.execution_providers,
85+
'execution_threads': roop.globals.execution_threads
86+
})
87+
progress.refresh()
88+
progress.update(1)

roop/processors/frame/face_enhancer.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, List
1+
from typing import Any, List, Callable
22
import cv2
33
import threading
44
import gfpgan
@@ -16,6 +16,17 @@
1616
NAME = 'ROOP.FACE-ENHANCER'
1717

1818

19+
def get_face_enhancer() -> Any:
20+
global FACE_ENHANCER
21+
22+
with THREAD_LOCK:
23+
if FACE_ENHANCER is None:
24+
model_path = resolve_relative_path('../models/GFPGANv1.4.pth')
25+
# todo: set models path https://github.com/TencentARC/GFPGAN/issues/399
26+
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
27+
return FACE_ENHANCER
28+
29+
1930
def pre_check() -> bool:
2031
download_directory_path = resolve_relative_path('../models')
2132
conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/GFPGANv1.4.pth'])
@@ -29,15 +40,10 @@ def pre_start() -> bool:
2940
return True
3041

3142

32-
def get_face_enhancer() -> Any:
43+
def post_process() -> None:
3344
global FACE_ENHANCER
3445

35-
with THREAD_LOCK:
36-
if FACE_ENHANCER is None:
37-
model_path = resolve_relative_path('../models/GFPGANv1.4.pth')
38-
# todo: set models path https://github.com/TencentARC/GFPGAN/issues/399
39-
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
40-
return FACE_ENHANCER
46+
FACE_ENHANCER = None
4147

4248

4349
def enhance_face(temp_frame: Frame) -> Frame:
@@ -56,13 +62,13 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
5662
return temp_frame
5763

5864

59-
def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None:
65+
def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None:
6066
for temp_frame_path in temp_frame_paths:
6167
temp_frame = cv2.imread(temp_frame_path)
6268
result = process_frame(None, temp_frame)
6369
cv2.imwrite(temp_frame_path, result)
64-
if progress:
65-
progress.update(1)
70+
if update:
71+
update()
6672

6773

6874
def process_image(source_path: str, target_path: str, output_path: str) -> None:

roop/processors/frame/face_swapper.py

+18-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, List
1+
from typing import Any, List, Callable
22
import cv2
33
import insightface
44
import threading
@@ -15,6 +15,16 @@
1515
NAME = 'ROOP.FACE-SWAPPER'
1616

1717

18+
def get_face_swapper() -> Any:
19+
global FACE_SWAPPER
20+
21+
with THREAD_LOCK:
22+
if FACE_SWAPPER is None:
23+
model_path = resolve_relative_path('../models/inswapper_128.onnx')
24+
FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=roop.globals.execution_providers)
25+
return FACE_SWAPPER
26+
27+
1828
def pre_check() -> bool:
1929
download_directory_path = resolve_relative_path('../models')
2030
conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx'])
@@ -34,14 +44,10 @@ def pre_start() -> bool:
3444
return True
3545

3646

37-
def get_face_swapper() -> Any:
47+
def post_process() -> None:
3848
global FACE_SWAPPER
3949

40-
with THREAD_LOCK:
41-
if FACE_SWAPPER is None:
42-
model_path = resolve_relative_path('../models/inswapper_128.onnx')
43-
FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=roop.globals.execution_providers)
44-
return FACE_SWAPPER
50+
FACE_SWAPPER = None
4551

4652

4753
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:
6167
return temp_frame
6268

6369

64-
def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None:
70+
def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None:
6571
source_face = get_one_face(cv2.imread(source_path))
6672
for temp_frame_path in temp_frame_paths:
6773
temp_frame = cv2.imread(temp_frame_path)
68-
try:
69-
result = process_frame(source_face, temp_frame)
70-
cv2.imwrite(temp_frame_path, result)
71-
except Exception as exception:
72-
print(exception)
73-
pass
74-
if progress:
75-
progress.update(1)
74+
result = process_frame(source_face, temp_frame)
75+
cv2.imwrite(temp_frame_path, result)
76+
if update:
77+
update()
7678

7779

7880
def process_image(source_path: str, target_path: str, output_path: str) -> None:

roop/ui.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,6 @@
153153
}
154154
},
155155
"RoopDonate": {
156-
"text_color": ["gray74", "gray60"]
156+
"text_color": ["#3a7ebf", "gray60"]
157157
}
158158
}

0 commit comments

Comments
 (0)