v3 - Multifaceswap Support

This commit is contained in:
Jagrut 2025-07-14 13:05:34 +05:30
parent f1cb8c9a4b
commit 7abcc8b89b
6 changed files with 59 additions and 23 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
models models
temp temp
__pycache__ __pycache__
*.jpg

View File

@ -27,6 +27,14 @@ Start the program with arguments:
``` ```
python run.py [options] python run.py [options]
```
## For multiface swap
```
python run.py --many-faces --source PATH1 PATH2 --target TARGET_PATH --output ./result.jpg
```
```
-h, --help show this help message and exit -h, --help show this help message and exit
-s SOURCE_PATH, --source SOURCE_PATH select an source image -s SOURCE_PATH, --source SOURCE_PATH select an source image

View File

@ -29,7 +29,7 @@ warnings.filterwarnings('ignore', category=UserWarning, module='torchvision')
def parse_args() -> None: def parse_args() -> None:
signal.signal(signal.SIGINT, lambda signal_number, frame: destroy()) signal.signal(signal.SIGINT, lambda signal_number, frame: destroy())
program = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=100)) 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('-s', '--source', help='select source image(s)', dest='source_path', nargs='+')
program.add_argument('-t', '--target', help='select an target image or video', dest='target_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('-o', '--output', help='select output file or directory', dest='output_path')
program.add_argument('--frame-processor', help='frame processors (choices: face_swapper, face_enhancer, ...)', dest='frame_processor', default=['face_swapper'], nargs='+') program.add_argument('--frame-processor', help='frame processors (choices: face_swapper, face_enhancer, ...)', dest='frame_processor', default=['face_swapper'], nargs='+')
@ -51,7 +51,7 @@ def parse_args() -> None:
args = program.parse_args() args = program.parse_args()
roop.globals.source_path = args.source_path roop.globals.source_path = args.source_path # Now a list of paths
roop.globals.target_path = args.target_path roop.globals.target_path = args.target_path
roop.globals.output_path = normalize_output_path(roop.globals.source_path, roop.globals.target_path, args.output_path) roop.globals.output_path = normalize_output_path(roop.globals.source_path, roop.globals.target_path, args.output_path)
roop.globals.headless = roop.globals.source_path is not None and roop.globals.target_path is not None and roop.globals.output_path is not None roop.globals.headless = roop.globals.source_path is not None and roop.globals.target_path is not None and roop.globals.output_path is not None

View File

@ -1,6 +1,6 @@
from typing import List, Optional from typing import List, Optional
source_path: Optional[str] = None source_path: Optional[List[str]] = None
target_path: Optional[str] = None target_path: Optional[str] = None
output_path: Optional[str] = None output_path: Optional[str] = None
headless: Optional[bool] = None headless: Optional[bool] = None

View File

@ -39,12 +39,26 @@ def pre_check() -> bool:
def pre_start() -> bool: def pre_start() -> bool:
source_paths = roop.globals.source_path
if isinstance(source_paths, list):
images = [cv2.imread(p) for p in source_paths]
# process each image as needed
else:
image = cv2.imread(source_paths)
# process single image as needed
if not is_image(roop.globals.source_path): if not is_image(roop.globals.source_path):
update_status('Select an image for source path.', NAME) update_status('Select an image for source path.', NAME)
return False return False
elif not get_one_face(cv2.imread(roop.globals.source_path)): if isinstance(roop.globals.source_path, list):
update_status('No face in source path detected.', NAME) for p in roop.globals.source_path:
return False if not get_one_face(cv2.imread(p)):
update_status(f'No face detected in source path: {p}', NAME)
return False
else:
if not get_one_face(cv2.imread(roop.globals.source_path)):
update_status('No face in source path detected.', NAME)
return False
if not is_image(roop.globals.target_path) and not is_video(roop.globals.target_path): if not is_image(roop.globals.target_path) and not is_video(roop.globals.target_path):
update_status('Select an image or video for target path.', NAME) update_status('Select an image or video for target path.', NAME)
return False return False
@ -60,41 +74,49 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True) return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True)
def process_frame(source_face: Face, reference_face: Face, temp_frame: Frame) -> Frame: def process_frame(source_faces: List[Face], reference_face: Face, temp_frame: Frame) -> Frame:
if roop.globals.many_faces: if roop.globals.many_faces:
many_faces = get_many_faces(temp_frame) many_faces = get_many_faces(temp_frame)
if many_faces: if many_faces:
for target_face in many_faces: for i, target_face in enumerate(many_faces):
# Use corresponding source face or fallback to first
source_face = source_faces[i] if i < len(source_faces) else source_faces[0]
temp_frame = swap_face(source_face, target_face, temp_frame) temp_frame = swap_face(source_face, target_face, temp_frame)
else: else:
target_face = find_similar_face(temp_frame, reference_face) target_face = find_similar_face(temp_frame, reference_face)
if target_face: if target_face:
temp_frame = swap_face(source_face, target_face, temp_frame) temp_frame = swap_face(source_faces[0], target_face, temp_frame)
return temp_frame return temp_frame
def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None: def process_frames(source_paths: List[str], temp_frame_paths: List[str], update: Callable[[], None]) -> None:
source_face = get_one_face(cv2.imread(source_path)) source_faces = [get_one_face(cv2.imread(path)) for path in source_paths]
reference_face = None if roop.globals.many_faces else get_face_reference() reference_face = None if roop.globals.many_faces else get_face_reference()
for temp_frame_path in temp_frame_paths: for temp_frame_path in temp_frame_paths:
temp_frame = cv2.imread(temp_frame_path) temp_frame = cv2.imread(temp_frame_path)
result = process_frame(source_face, reference_face, temp_frame) if temp_frame is None:
update_status(f'Could not load frame: {temp_frame_path}', NAME)
continue
result = process_frame(source_faces, reference_face, temp_frame)
cv2.imwrite(temp_frame_path, result) cv2.imwrite(temp_frame_path, result)
if update: if update:
update() update()
def process_image(source_path: str, target_path: str, output_path: str) -> None: def process_image(source_paths: List[str], target_path: str, output_path: str) -> None:
source_face = get_one_face(cv2.imread(source_path)) source_faces = [get_one_face(cv2.imread(path)) for path in source_paths]
target_frame = cv2.imread(target_path) target_frame = cv2.imread(target_path)
if target_frame is None:
update_status(f'Could not load target image: {target_path}', NAME)
return
reference_face = None if roop.globals.many_faces else get_one_face(target_frame, roop.globals.reference_face_position) reference_face = None if roop.globals.many_faces else get_one_face(target_frame, roop.globals.reference_face_position)
result = process_frame(source_face, reference_face, target_frame) result = process_frame(source_faces, reference_face, target_frame)
cv2.imwrite(output_path, result) cv2.imwrite(output_path, result)
def process_video(source_path: str, temp_frame_paths: List[str]) -> None: def process_video(source_paths: List[str], temp_frame_paths: List[str]) -> None:
if not roop.globals.many_faces and not get_face_reference(): if not roop.globals.many_faces and not get_face_reference():
reference_frame = cv2.imread(temp_frame_paths[roop.globals.reference_frame_number]) reference_frame = cv2.imread(temp_frame_paths[roop.globals.reference_frame_number])
reference_face = get_one_face(reference_frame, roop.globals.reference_face_position) reference_face = get_one_face(reference_frame, roop.globals.reference_face_position)
set_face_reference(reference_face) set_face_reference(reference_face)
roop.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames) roop.processors.frame.core.process_video(source_paths, temp_frame_paths, process_frames)

View File

@ -84,12 +84,15 @@ def get_temp_output_path(target_path: str) -> str:
return os.path.join(temp_directory_path, TEMP_VIDEO_FILE) return os.path.join(temp_directory_path, TEMP_VIDEO_FILE)
def normalize_output_path(source_path: str, target_path: str, output_path: str) -> Optional[str]: def normalize_output_path(source_path, target_path, output_path):
if source_path and target_path and output_path: # If source_path is a list, use the first item for naming
if isinstance(source_path, list):
source_name, _ = os.path.splitext(os.path.basename(source_path[0]))
else:
source_name, _ = os.path.splitext(os.path.basename(source_path)) source_name, _ = os.path.splitext(os.path.basename(source_path))
target_name, target_extension = os.path.splitext(os.path.basename(target_path)) target_name, _ = os.path.splitext(os.path.basename(target_path))
if os.path.isdir(output_path): if output_path is None:
return os.path.join(output_path, source_name + '-' + target_name + target_extension) return f'{source_name}_to_{target_name}.jpg'
return output_path return output_path
@ -119,7 +122,9 @@ def has_image_extension(image_path: str) -> bool:
return image_path.lower().endswith(('png', 'jpg', 'jpeg', 'webp')) return image_path.lower().endswith(('png', 'jpg', 'jpeg', 'webp'))
def is_image(image_path: str) -> bool: def is_image(image_path):
if isinstance(image_path, list):
return all(is_image(p) for p in image_path)
if image_path and os.path.isfile(image_path): if image_path and os.path.isfile(image_path):
mimetype, _ = mimetypes.guess_type(image_path) mimetype, _ = mimetypes.guess_type(image_path)
return bool(mimetype and mimetype.startswith('image/')) return bool(mimetype and mimetype.startswith('image/'))