mirror of
https://github.com/deepfakes/faceswap.git
synced 2026-10-20 00:28:31 +08:00
837bc2d51d
* FaceSwap 3 (#1515) * Update extract pipeline * Update requirements + setup for nvidia * Remove allow-growth option * tf.keras to keras updates * lib.model.losses - Port + fix all loss functions for Keras3 * lib.model - port initializers, layers. normalization to Keras3 * lib.model.autoclip to Keras 3 * Update mixed precision layer storage * model file to .keras format * Restructure nn_blocks to initialize layers in __init__ * Tensorboard - Trainer: Add Torch compatible Tensorboard callbacks - GUI event reader remove TF dependency * Loss logging - Flush TB logs on save - Replace TB live iterator for GUI * Backup models on total loss drop rather than per side * Update all models to Keras3 Compat * Remove lib.model.session * Update clip ViT to Keras 3 * plugins.extract.mask.unet-dfl - Fix for Keras3/Torch backend * Port AdaBelief to Keras 3 * setup.py: - Add --dev flag for dev tool install * Fix Keras 3 syntax * Fix LR Finder for Keras 3 * Fix mixed precision switching for Keras 3 * Add more optimizers + open up config setting * train: Remove updating FS1 weights to FS2 models * Alignments: Remove support for legacy .json files * tools.model: - Remove TF Saved Format saving - Fix Backup/Restore + Nan-Scan * Fix inference model creation for Keras 3 * Preview tool: Fix for Keras3 * setup.py: Configure keras backend * train: Migration of FS2 models to FS3 * Training: Default coverage to 100% * Remove DirectML backend * Update setup for MacOS * GUI: Force line reading to UTF-8 * Remove redundant Tensorflow references * Remove redundant code * Legacy model loading: Fix TFLamdaOp scalar ops and DepthwiseConv2D * Add vertical offset option for training * Github actions: Add more python versions * Add python version to workflow names * Github workflow: Exclude Python 3.12 for macOS * Implement custom training loop * Fs3 - Add RTX5xxx and ROCm 6.1-6.4 support (#1511) * setup.py: Add Cuda/ROCm version select options * bump minimum python version to 3.11 * Switch from setup.cgf to pyproject.toml * Documentation: Update all docs to use automodapi * Allow sysinfo to run with missing packages + correctly install tk under Linux * Bugfix: dot naming convention in clip models * lib.config: Centralise globally rather than passing as object - Add torch DataParallel for multi-gpu training - GUI: Group switches together when generating cli args - CLI: Remove deprecated multi-character argparse args - Refactor: - Centralise tensorboard reading/writing + unit tests - Create trainer plugin interfaces + add original + distributed * Update installers
198 lines
7.2 KiB
Python
198 lines
7.2 KiB
Python
#!/usr/bin/python
|
|
""" The pop up preview window for Faceswap.
|
|
|
|
If Tkinter is installed, then this will be used to manage the preview image, otherwise we
|
|
fallback to opencv's imshow
|
|
"""
|
|
from __future__ import annotations
|
|
import logging
|
|
import typing as T
|
|
|
|
from threading import Event, Lock
|
|
from time import sleep
|
|
|
|
import cv2
|
|
|
|
from lib.utils import get_module_objects
|
|
|
|
if T.TYPE_CHECKING:
|
|
from collections.abc import Generator
|
|
import numpy as np
|
|
|
|
logger = logging.getLogger(__name__)
|
|
TriggerType = dict[T.Literal["toggle_mask", "refresh", "save", "quit", "shutdown"], Event]
|
|
TriggerKeysType = T.Literal["m", "r", "s", "enter"]
|
|
TriggerNamesType = T.Literal["toggle_mask", "refresh", "save", "quit"]
|
|
|
|
|
|
class PreviewBuffer():
|
|
""" A thread safe class for holding preview images """
|
|
def __init__(self) -> None:
|
|
logger.debug("Initializing: %s", self.__class__.__name__)
|
|
self._images: dict[str, np.ndarray] = {}
|
|
self._lock = Lock()
|
|
self._updated = Event()
|
|
logger.debug("Initialized: %s", self.__class__.__name__)
|
|
|
|
@property
|
|
def is_updated(self) -> bool:
|
|
""" bool: ``True`` when new images have been loaded into the preview buffer """
|
|
return self._updated.is_set()
|
|
|
|
def add_image(self, name: str, image: np.ndarray) -> None:
|
|
""" Add an image to the preview buffer in a thread safe way """
|
|
logger.debug("Adding image: (name: '%s', shape: %s)", name, image.shape)
|
|
with self._lock:
|
|
self._images[name] = image
|
|
logger.debug("Added images: %s", list(self._images))
|
|
self._updated.set()
|
|
|
|
def get_images(self) -> Generator[tuple[str, np.ndarray], None, None]:
|
|
""" Get the latest images from the preview buffer. When iterator is exhausted clears the
|
|
:attr:`updated` event.
|
|
|
|
Yields
|
|
------
|
|
name: str
|
|
The name of the image
|
|
:class:`numpy.ndarray`
|
|
The image in BGR format
|
|
"""
|
|
logger.debug("Retrieving images: %s", list(self._images))
|
|
with self._lock:
|
|
for name, image in self._images.items():
|
|
logger.debug("Yielding: '%s' (%s)", name, image.shape)
|
|
yield name, image
|
|
if self.is_updated:
|
|
logger.debug("Clearing updated event")
|
|
self._updated.clear()
|
|
logger.debug("Retrieved images")
|
|
|
|
|
|
class PreviewBase(): # pylint:disable=too-few-public-methods
|
|
""" Parent class for OpenCV and Tkinter Preview Windows
|
|
|
|
Parameters
|
|
----------
|
|
preview_buffer: :class:`PreviewBuffer`
|
|
The thread safe object holding the preview images
|
|
triggers: dict, optional
|
|
Dictionary of event triggers for pop-up preview. Not required when running inside the GUI.
|
|
Default: `None`
|
|
"""
|
|
def __init__(self,
|
|
preview_buffer: PreviewBuffer,
|
|
triggers: TriggerType | None = None) -> None:
|
|
logger.debug("Initializing %s parent (triggers: %s)",
|
|
self.__class__.__name__, triggers)
|
|
self._triggers = triggers
|
|
self._buffer = preview_buffer
|
|
self._keymaps: dict[TriggerKeysType, TriggerNamesType] = {"m": "toggle_mask",
|
|
"r": "refresh",
|
|
"s": "save",
|
|
"enter": "quit"}
|
|
self._title = ""
|
|
logger.debug("Initialized %s parent", self.__class__.__name__)
|
|
|
|
@property
|
|
def _should_shutdown(self) -> bool:
|
|
""" bool: ``True`` if the preview has received an external signal to shutdown otherwise
|
|
``False`` """
|
|
if self._triggers is None or not self._triggers["shutdown"].is_set():
|
|
return False
|
|
logger.debug("Shutdown signal received")
|
|
return True
|
|
|
|
def _launch(self) -> None:
|
|
""" Wait until an image is loaded into the preview buffer and call the child's
|
|
:func:`_display_preview` function """
|
|
logger.debug("Launching %s", self.__class__.__name__)
|
|
while True:
|
|
if self._should_shutdown:
|
|
logger.debug("Shutdown received")
|
|
return
|
|
if not self._buffer.is_updated:
|
|
logger.debug("Waiting for preview image")
|
|
sleep(1)
|
|
continue
|
|
break
|
|
logger.debug("Launching preview")
|
|
self._display_preview()
|
|
|
|
def _display_preview(self) -> None:
|
|
""" Override for preview viewer's display loop """
|
|
raise NotImplementedError()
|
|
|
|
|
|
class PreviewCV(PreviewBase): # pylint:disable=too-few-public-methods
|
|
""" Simple fall back preview viewer using OpenCV for when TKinter is not available
|
|
|
|
Parameters
|
|
----------
|
|
preview_buffer: :class:`PreviewBuffer`
|
|
The thread safe object holding the preview images
|
|
triggers: dict
|
|
Dictionary of event triggers for pop-up preview.
|
|
"""
|
|
def __init__(self,
|
|
preview_buffer: PreviewBuffer,
|
|
triggers: TriggerType) -> None:
|
|
logger.debug("Unable to import Tkinter. Falling back to OpenCV")
|
|
super().__init__(preview_buffer, triggers=triggers)
|
|
self._triggers: TriggerType = self._triggers
|
|
self._windows: list[str] = []
|
|
|
|
self._lookup = {ord(key): val
|
|
for key, val in self._keymaps.items() if key != "enter"}
|
|
self._lookup[ord("\n")] = self._keymaps["enter"]
|
|
self._lookup[ord("\r")] = self._keymaps["enter"]
|
|
|
|
self._launch()
|
|
|
|
@property
|
|
def _window_closed(self) -> bool:
|
|
""" bool: ``True`` if any window has been closed otherwise ``False`` """
|
|
retval = any(cv2.getWindowProperty(win, cv2.WND_PROP_VISIBLE) < 1 for win in self._windows)
|
|
if retval:
|
|
logger.debug("Window closed detected")
|
|
return retval
|
|
|
|
def _check_keypress(self, key: int):
|
|
""" Check whether we have received a valid key press from OpenCV window and handle
|
|
accordingly.
|
|
|
|
Parameters
|
|
----------
|
|
key_press: int
|
|
The key press received from OpenCV
|
|
"""
|
|
if not key or key == -1 or key not in self._lookup:
|
|
return
|
|
|
|
if key == ord("r"):
|
|
print("\x1b[2K", end="\r") # clear last line
|
|
logger.info("Refresh preview requested...")
|
|
|
|
self._triggers[self._lookup[key]].set()
|
|
logger.debug("Processed keypress '%s'. Set event for '%s'", key, self._lookup[key])
|
|
|
|
def _display_preview(self):
|
|
""" Handle the displaying of the images currently in :attr:`_preview_buffer`"""
|
|
while True:
|
|
if self._buffer.is_updated or self._window_closed:
|
|
for name, image in self._buffer.get_images():
|
|
logger.debug("showing image: '%s' (%s)", name, image.shape)
|
|
cv2.imshow(name, image)
|
|
self._windows.append(name)
|
|
|
|
key = cv2.waitKey(1000)
|
|
self._check_keypress(key)
|
|
|
|
if self._triggers["shutdown"].is_set():
|
|
logger.debug("Shutdown received")
|
|
break
|
|
logger.debug("%s shutdown", self.__class__.__name__)
|
|
|
|
|
|
__all__ = get_module_objects(__name__)
|