diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..39a0ccb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: jmhummel +patreon: faceshiftlabs +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index 16a6020..48276bc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ !*.jpg !requirements* !Dockerfile* -!*.sh \ No newline at end of file +!*.sh +convert.py +randomColor.py +train.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..244731f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,154 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.8.0] - 2021-06-20 +### Added +- Morph factor option +- Migrated options from SAEHD to AMP: + - Loss function + - Random downsample + - Random noise + - Random blur + - Random jpeg + - Background Power + - CT mode: fs-aug + - Random color + +## [1.7.3] - 2021-06-16 +### Fixed +- AMP mask type + +## [1.7.2] - 2021-06-15 +### Added +- New sample degradation options (only affects input, similar to random warp): + - Random noise (gaussian/laplace/poisson) + - Random blur (gaussian/motion) + - Random jpeg compression + - Random downsampling +- New "warped" preview(s): Shows the input samples with any/all distortions. + +## [1.7.1] - 2021-06-15 +### Added +- New autobackup options: + - Session name + - ISO Timestamps (instead of numbered) + - Max number of backups to keep (use "0" for unlimited) + +## [1.7.0] - 2021-06-15 +### Updated +- Merged in latest changes from upstream, including new AMP model + +## [1.6.2] - 2021-05-08 +### Fixed +- Fixed bug with GAN smoothing/noisy labels with certain versions of Tensorflow + +## [1.6.1] - 2021-05-04 +### Fixed +- Fixed bug when `fs-aug` used on model with same resolution as dataset + +## [1.6.0] - 2021-05-04 +### Added +- New loss function "MS-SSIM+L1", based on ["Loss Functions for Image Restoration with Neural Networks"](https://research.nvidia.com/publication/loss-functions-image-restoration-neural-networks) + +## [1.5.1] - 2021-04-23 +### Fixed +- Fixes bug with MS-SSIM when using a version of tensorflow < 1.14 + +## [1.5.0] - 2021-03-29 +### Changed +- Web UI previews now show preview pane as PNG (loss-less), instead of JPG (lossy), so we can see the same output + as on desktop, without any changes from JPG compression. This has the side-effect of the preview images loading slower + over web, as they are now larger, a future update may be considered which would give the option to view as JPG + instead. + +## [1.4.2] - 2021-03-26 +### Fixed +- Fixes bug in background power with MS-SSIM, that misattributed loss from dst to src + +## [1.4.1] - 2021-03-25 +### Fixed +- When both Background Power and MS-SSIM were enabled, the src and dst losses were being overwritten with the + "background power" losses. Fixed so "background power" losses are properly added with the total losses. + - *Note: since all the other losses were being skipped when ms-ssim and background loss were being enabled, this had + the side-effect of lowering the memory requirements (and raising the max batch size). With this fix, you may + experience an OOM error on models ran with both these features enabled. I may revisit this in another feature, + allowing you to manually disable certain loss calculations, for similar performance benefits.* + +## [1.4.0] - 2021-03-24 +### Added +- [MS-SSIM loss training option](doc/features/ms-ssim) +- GAN version option (v2 - late 2020 or v3 - current GAN) +- [GAN label smoothing and label noise options](doc/features/gan-options) +### Fixed +- Background Power now uses the entire image, not just the area outside of the mask for comparison. +This should help with rough areas directly next to the mask + +## [1.3.0] - 2021-03-20 +### Added +- [Background Power training option](doc/features/background-power/README.md) + +## [1.2.1] - 2021-03-20 +### Fixed +- Fixes bug with `fs-aug` color mode. + +## [1.2.0] - 2021-03-17 +### Added +- [Random color training option](doc/features/random-color/README.md) + +## [1.1.5] - 2021-03-16 +### Fixed +- Fixed unclosed websocket in Web UI client when exiting + +## [1.1.4] - 2021-03-16 +### Fixed +- Fixed bug when exiting from Web UI + +## [1.1.3] - 2021-03-16 +### Changed +- Updated changelog with unreleased features, links to working branches + +## [1.1.2] - 2021-03-12 +### Fixed +- [Fixed missing predicted src mask in 'SAEHD masked' preview](doc/fixes/predicted_src_mask/README.md) + +## [1.1.1] - 2021-03-12 +### Added +- CHANGELOG file for tracking updates, new features, and bug fixes +- Documentation for Web UI +- Link to CHANGELOG at top of README + +## [1.1.0] - 2021-03-11 +### Added +- [Web UI for training preview](doc/features/webui/README.md) + +## [1.0.0] - 2021-03-09 +### Initialized +- Reset stale master branch to [seranus/DeepFaceLab](https://github.com/seranus/DeepFaceLab), + 21 commits ahead of [iperov/DeepFaceLab](https://github.com/iperov/DeepFaceLab) ([compare](https://github.com/iperov/DeepFaceLab/compare/4818183...seranus:3f5ae05)) + +[1.8.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.7.3...v1.8.0 +[1.7.3]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.7.2...v1.7.3 +[1.7.2]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.7.1...v1.7.2 +[1.7.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.6.2...v1.7.0 +[1.6.2]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.6.1...v1.6.2 +[1.6.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.6.0...v1.6.1 +[1.6.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.5.1...v1.6.0 +[1.5.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.5.0...v1.5.1 +[1.5.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.4.2...v1.5.0 +[1.4.2]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.4.1...v1.4.2 +[1.4.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.3.0...v1.4.0 +[1.3.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.2.1...v1.3.0 +[1.2.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.5...v1.2.0 +[1.1.5]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.4...v1.1.5 +[1.1.4]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.3...v1.1.4 +[1.1.3]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.2...v1.1.3 +[1.1.2]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/faceshiftlabs/DeepFaceLab/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/faceshiftlabs/DeepFaceLab/releases/tag/v1.0.0 diff --git a/README.md b/README.md index f232adc..5e62e13 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
| @@ -53,133 +53,13 @@ DeepFaceLab is used by such popular youtube channels as | ||
| + | ||
| -# What can I do using DeepFaceLab? - - | ||
-
-## Replace the face
-
-
-
- | ||
| - -## De-age the face - - | ||
-
-
-
- |
-
-
-
-
- | |
| - - https://www.youtube.com/watch?v=Ddx5B-84ebo - - | ||
| - -## Replace the head - - | ||
-
-
-
- |
-
-
-
-
- | |
| - - https://www.youtube.com/watch?v=xr5FHd0AdlQ - - | ||
-
-
-
- |
-
-
-
-
- | |
| - - https://www.youtube.com/watch?v=RTjgkhMugVw - - | ||
-
-
-
- |
-
-
-
-
- | |
| - - https://www.youtube.com/watch?v=R9f7WD0gKPo - - | ||
-
-## Manipulate politicians lips
-(voice replacement is not included!)
-(also requires a skill in video editors such as *Adobe After Effects* or *Davinci Resolve*)
-
-
-
- https://www.youtube.com/watch?v=IvY-Abd2FfM
-
-
-
- https://www.youtube.com/watch?v=ERQlaJ_czHU
-
- | ||
| - -# Deepfake native resolution progress - - | ||
-
-
-
- | ||
-
-
-
-Unfortunately, there is no "make everything ok" button in DeepFaceLab. You should spend time studying the workflow and growing your skills. A skill in programs such as *AfterEffects* or *Davinci Resolve* is also desirable.
-
- | ||
| + | ||
| ## Mini tutorial @@ -205,8 +85,8 @@ Unfortunately, there is no "make everything ok" button in DeepFaceLab. You shoul | Contains new and prev releases. | |
| -Google Colab (github) - | by @chervonij . You can train fakes for free using Google Colab. | Personal fork from @chervonij repository. You can train fakes for free using Google Colab. |
|
Linux (github)
diff --git a/XSegEditor/QStringDB.py b/XSegEditor/QStringDB.py
index b9100d2..5a660bb 100644
--- a/XSegEditor/QStringDB.py
+++ b/XSegEditor/QStringDB.py
@@ -85,6 +85,16 @@ class QStringDB():
'zh' : '保存并转到下一张图片\n按住SHIFT : 加快\n按住CTRL : 跳过未标记的\n',
}[lang]
+ QStringDB.spinner_label = { 'en' : 'Step size',
+ 'ru' : 'Размер шага',
+ 'zh' : '台阶大小'
+ }[lang]
+
+ QStringDB.spinner_label_tip = { 'en' : 'Minimum 5\nMaximum 500',
+ 'ru' : 'Минимум 5\nМаксимум 500',
+ 'zh' : '最少5个\n最多500'
+ }[lang]
+
QStringDB.btn_delete_image_tip = { 'en' : 'Move to _trash and Next image\n',
'ru' : 'Переместить в _trash и следующее изображение\n',
'zh' : '移至_trash,转到下一张图片 ',
diff --git a/XSegEditor/XSegEditor.py b/XSegEditor/XSegEditor.py
index affc9f6..c2d4065 100644
--- a/XSegEditor/XSegEditor.py
+++ b/XSegEditor/XSegEditor.py
@@ -1173,6 +1173,8 @@ class MainWindow(QXMainWindow):
self.cached_images = {}
self.cached_has_ie_polys = {}
+ self.spin_box = QSpinBox()
+
self.initialize_ui()
# Loader
@@ -1297,7 +1299,7 @@ class MainWindow(QXMainWindow):
def process_prev_image(self):
key_mods = QApplication.keyboardModifiers()
- step = 5 if key_mods == Qt.ShiftModifier else 1
+ step = self.spin_box.value() if key_mods == Qt.ShiftModifier else 1
only_has_polys = key_mods == Qt.ControlModifier
if self.canvas.op.is_initialized():
@@ -1323,7 +1325,7 @@ class MainWindow(QXMainWindow):
def process_next_image(self, first_initialization=False):
key_mods = QApplication.keyboardModifiers()
- step = 0 if first_initialization else 5 if key_mods == Qt.ShiftModifier else 1
+ step = 0 if first_initialization else self.spin_box.value() if key_mods == Qt.ShiftModifier else 1
only_has_polys = False if first_initialization else key_mods == Qt.ControlModifier
if self.canvas.op.is_initialized():
@@ -1373,6 +1375,13 @@ class MainWindow(QXMainWindow):
pad_image = QWidget()
pad_image.setFixedSize(QUIConfig.preview_bar_icon_q_size)
+
+ self.spin_box.setFocusPolicy(Qt.ClickFocus)
+ self.spin_box.setRange(5, 500)
+ self.spin_box.setSingleStep(1)
+ self.spin_box.installEventFilter(self)
+ self.spin_box.valueChanged.connect(self.on_spinbox_value_changed)
+ self.spin_box.setToolTip(QStringDB.spinner_label_tip)
preview_image_bar_frame_l = QHBoxLayout()
preview_image_bar_frame_l.setContentsMargins(0,0,0,0)
@@ -1404,14 +1413,25 @@ class MainWindow(QXMainWindow):
preview_image_bar.setLayout(preview_image_bar_l)
label_font = QFont('Courier New')
+
self.filename_label = QLabel()
self.filename_label.setFont(label_font)
self.has_ie_polys_count_label = QLabel()
+ status_frame_1_2 = QHBoxLayout()
+ status_frame_1_2.setContentsMargins(0,0,0,0)
+
+ step_string_label = QLabel()
+ step_string_label.setFont(label_font)
+ step_string_label.setText(QStringDB.spinner_label)
+
+ status_frame_1_2.addWidget (step_string_label, alignment=Qt.AlignRight)
+ status_frame_1_2.addWidget (self.spin_box, alignment=Qt.AlignLeft)
+
status_frame_l = QHBoxLayout()
status_frame_l.setContentsMargins(0,0,0,0)
- status_frame_l.addWidget ( QLabel(), alignment=Qt.AlignCenter)
+ status_frame_l.addLayout (status_frame_1_2)
status_frame_l.addWidget (self.filename_label, alignment=Qt.AlignCenter)
status_frame_l.addWidget (self.has_ie_polys_count_label, alignment=Qt.AlignCenter)
status_frame = QFrame()
@@ -1438,6 +1458,21 @@ class MainWindow(QXMainWindow):
else:
self.move( QPoint(0,0))
+ def eventFilter(self, obj, event):
+ if event.type() == QEvent.KeyPress and obj is self.spin_box:
+ if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter and self.spin_box.hasFocus():
+ self.spin_box.clearFocus()
+
+ if event.type() == QEvent.MouseButtonPress and obj is self.spin_box:
+ if event.button() == Qt.LeftButton and self.spin_box.hasFocus():
+ self.spin_box.clearFocus()
+
+ return super().eventFilter(obj, event)
+
+ def on_spinbox_value_changed(self, value):
+ if value == self.spin_box.maximum() or value == self.spin_box.minimum():
+ self.spin_box.clearFocus()
+
def get_has_ie_polys_count(self):
return self.has_ie_polys_count
diff --git a/core/imagelib/__init__.py b/core/imagelib/__init__.py
index 11234a5..1ed95dc 100644
--- a/core/imagelib/__init__.py
+++ b/core/imagelib/__init__.py
@@ -12,7 +12,7 @@ from .warp import gen_warp_params, warp_by_params
from .reduce_colors import reduce_colors
-from .color_transfer import color_transfer, color_transfer_mix, color_transfer_sot, color_transfer_mkl, color_transfer_idt, color_hist_match, reinhard_color_transfer, linear_color_transfer
+from .color_transfer import color_transfer, color_transfer_mix, color_transfer_sot, color_transfer_mkl, color_transfer_idt, color_hist_match, reinhard_color_transfer, linear_color_transfer, color_augmentation
from .common import random_crop, normalize_channels, cut_odd_image, overlay_alpha_image
diff --git a/core/imagelib/color_transfer.py b/core/imagelib/color_transfer.py
index d269de2..6a19d30 100644
--- a/core/imagelib/color_transfer.py
+++ b/core/imagelib/color_transfer.py
@@ -1,6 +1,9 @@
import cv2
import numexpr as ne
import numpy as np
+from numpy import linalg as npla
+import random
+from scipy.stats import special_ortho_group
import scipy as sp
from numpy import linalg as npla
@@ -9,14 +12,12 @@ def color_transfer_sot(src,trg, steps=10, batch_size=5, reg_sigmaXY=16.0, reg_si
"""
Color Transform via Sliced Optimal Transfer
ported by @iperov from https://github.com/dcoeurjo/OTColorTransfer
-
src - any float range any channel image
dst - any float range any channel image, same shape as src
steps - number of solver steps
batch_size - solver batch size
reg_sigmaXY - apply regularization and sigmaXY of filter, otherwise set to 0.0
reg_sigmaV - sigmaV of filter
-
return value - clip it manually
"""
if not np.issubdtype(src.dtype, np.floating):
@@ -334,3 +335,72 @@ def color_transfer(ct_mode, img_src, img_trg):
else:
raise ValueError(f"unknown ct_mode {ct_mode}")
return out
+
+
+# imported from faceswap
+def color_augmentation(img, seed=None):
+ """ Color adjust RGB image """
+ img = img.astype(np.float32)
+ face = img
+ face = np.clip(face*255.0, 0, 255).astype(np.uint8)
+ face = random_clahe(face, seed)
+ face = random_lab(face, seed)
+ img[:, :, :3] = face
+ return (face / 255.0).astype(np.float32)
+
+def random_lab_rotation(image, seed=None):
+ """
+ Randomly rotates image color around the L axis in LAB colorspace,
+ keeping perceptual lightness constant.
+ """
+ image = cv2.cvtColor(image.astype(np.float32), cv2.COLOR_BGR2LAB)
+ M = np.eye(3)
+ M[1:, 1:] = special_ortho_group.rvs(2, 1, seed)
+ image = image.dot(M)
+ l, a, b = cv2.split(image)
+ l = np.clip(l, 0, 100)
+ a = np.clip(a, -127, 127)
+ b = np.clip(b, -127, 127)
+ image = cv2.merge([l, a, b])
+ image = cv2.cvtColor(image.astype(np.float32), cv2.COLOR_LAB2BGR)
+ np.clip(image, 0, 1, out=image)
+ return image
+
+def random_lab(image, seed=None):
+ """ Perform random color/lightness adjustment in L*a*b* colorspace """
+ random.seed(seed)
+ amount_l = 30 / 100
+ amount_ab = 8 / 100
+ randoms = [(random.random() * amount_l * 2) - amount_l, # L adjust
+ (random.random() * amount_ab * 2) - amount_ab, # A adjust
+ (random.random() * amount_ab * 2) - amount_ab] # B adjust
+ image = cv2.cvtColor( # pylint:disable=no-member
+ image, cv2.COLOR_BGR2LAB).astype("float32") / 255.0 # pylint:disable=no-member
+
+ for idx, adjustment in enumerate(randoms):
+ if adjustment >= 0:
+ image[:, :, idx] = ((1 - image[:, :, idx]) * adjustment) + image[:, :, idx]
+ else:
+ image[:, :, idx] = image[:, :, idx] * (1 + adjustment)
+ image = cv2.cvtColor((image * 255.0).astype("uint8"), # pylint:disable=no-member
+ cv2.COLOR_LAB2BGR) # pylint:disable=no-member
+ return image
+
+def random_clahe(image, seed=None):
+ """ Randomly perform Contrast Limited Adaptive Histogram Equalization """
+ random.seed(seed)
+ contrast_random = random.random()
+ if contrast_random > 50 / 100:
+ return image
+
+ # base_contrast = image.shape[0] // 128
+ base_contrast = 1 # testing because it breaks on small sizes
+ grid_base = random.random() * 4
+ contrast_adjustment = int(grid_base * (base_contrast / 2))
+ grid_size = base_contrast + contrast_adjustment
+
+ clahe = cv2.createCLAHE(clipLimit=2.0, # pylint: disable=no-member
+ tileGridSize=(grid_size, grid_size))
+ for chan in range(3):
+ image[:, :, chan] = clahe.apply(image[:, :, chan])
+ return image
diff --git a/core/leras/__init__.py b/core/leras/__init__.py
index 7d9fb2b..735a81e 100644
--- a/core/leras/__init__.py
+++ b/core/leras/__init__.py
@@ -1 +1 @@
-from .nn import nn
\ No newline at end of file
+from .nn import nn
diff --git a/core/leras/layers/MsSsim.py b/core/leras/layers/MsSsim.py
new file mode 100644
index 0000000..d4987ed
--- /dev/null
+++ b/core/leras/layers/MsSsim.py
@@ -0,0 +1,50 @@
+from core.leras import nn
+tf = nn.tf
+
+
+class MsSsim(nn.LayerBase):
+ default_power_factors = (0.0448, 0.2856, 0.3001, 0.2363, 0.1333)
+ default_l1_alpha = 0.84
+
+ def __init__(self, batch_size, in_ch, resolution, kernel_size=11, use_l1=False, **kwargs):
+ # restrict mssim factors to those greater/equal to kernel size
+ power_factors = [p for i, p in enumerate(self.default_power_factors) if resolution//(2**i) >= kernel_size]
+ # normalize power factors if reduced because of size
+ if sum(power_factors) < 1.0:
+ power_factors = [x/sum(power_factors) for x in power_factors]
+ self.power_factors = power_factors
+ self.num_scale = len(power_factors)
+ self.kernel_size = kernel_size
+ self.use_l1 = use_l1
+ if use_l1:
+ self.gaussian_weights = nn.get_gaussian_weights(batch_size, in_ch, resolution, num_scale=self.num_scale)
+
+ super().__init__(**kwargs)
+
+ def __call__(self, y_true, y_pred, max_val):
+ # Transpose images from NCHW to NHWC
+ y_true_t = tf.transpose(tf.cast(y_true, tf.float32), [0, 2, 3, 1])
+ y_pred_t = tf.transpose(tf.cast(y_pred, tf.float32), [0, 2, 3, 1])
+
+ # ssim_multiscale returns values in range [0, 1] (where 1 is completely identical)
+ # subtract from 1 to get loss
+ if tf.__version__ >= "1.14":
+ ms_ssim_loss = 1.0 - tf.image.ssim_multiscale(y_true_t, y_pred_t, max_val, power_factors=self.power_factors, filter_size=self.kernel_size)
+ else:
+ ms_ssim_loss = 1.0 - tf.image.ssim_multiscale(y_true_t, y_pred_t, max_val, power_factors=self.power_factors)
+
+ # If use L1 is enabled, use mix of ms-ssim and L1 (weighted by gaussian filters)
+ # H. Zhao, O. Gallo, I. Frosio and J. Kautz, "Loss Functions for Image Restoration With Neural Networks,"
+ # in IEEE Transactions on Computational Imaging, vol. 3, no. 1, pp. 47-57, March 2017,
+ # doi: 10.1109/TCI.2016.2644865.
+ # https://research.nvidia.com/publication/loss-functions-image-restoration-neural-networks
+
+ if self.use_l1:
+ diff = tf.tile(tf.expand_dims(tf.abs(y_true - y_pred), axis=0), multiples=[self.num_scale, 1, 1, 1, 1])
+ l1_loss = tf.reduce_mean(tf.reduce_sum(self.gaussian_weights[-1, :, :, :, :] * diff, axis=[0, 3, 4]), axis=[1])
+ return self.default_l1_alpha * ms_ssim_loss + (1 - self.default_l1_alpha) * l1_loss
+
+ return ms_ssim_loss
+
+
+nn.MsSsim = MsSsim
diff --git a/core/leras/layers/__init__.py b/core/leras/layers/__init__.py
index 4accaf6..c9c3077 100644
--- a/core/leras/layers/__init__.py
+++ b/core/leras/layers/__init__.py
@@ -15,4 +15,6 @@ from .TLU import *
from .ScaleAdd import *
from .DenseNorm import *
from .AdaIN import *
-from .TanhPolar import *
\ No newline at end of file
+from .TanhPolar import *
+from .MsSsim import *
+from .TanhPolar import *
diff --git a/core/leras/models/PatchDiscriminator.py b/core/leras/models/PatchDiscriminator.py
index 9b94e9f..5bad9e4 100644
--- a/core/leras/models/PatchDiscriminator.py
+++ b/core/leras/models/PatchDiscriminator.py
@@ -111,7 +111,7 @@ class UNetPatchDiscriminator(nn.ModelBase):
for i in range(layers_count-1):
st = 1 + (1 if val & (1 << i) !=0 else 0 )
layers.append ( [3, st ])
- sum_st += st
+ sum_st += st
rf = self.calc_receptive_field_size(layers)
@@ -133,7 +133,6 @@ class UNetPatchDiscriminator(nn.ModelBase):
def on_build(self, patch_size, in_ch, base_ch = 16, use_fp16 = False):
self.use_fp16 = use_fp16
conv_dtype = tf.float16 if use_fp16 else tf.float32
-
class ResidualBlock(nn.ModelBase):
def on_build(self, ch, kernel_size=3 ):
self.conv1 = nn.Conv2D( ch, ch, kernel_size=kernel_size, padding='SAME', dtype=conv_dtype)
@@ -150,7 +149,7 @@ class UNetPatchDiscriminator(nn.ModelBase):
self.convs = []
self.upconvs = []
layers = self.find_archi(patch_size)
-
+
level_chs = { i-1:v for i,v in enumerate([ min( base_ch * (2**i), 512 ) for i in range(len(layers)+1)]) }
self.in_conv = nn.Conv2D( in_ch, level_chs[-1], kernel_size=1, padding='VALID', dtype=conv_dtype)
@@ -158,8 +157,14 @@ class UNetPatchDiscriminator(nn.ModelBase):
for i, (kernel_size, strides) in enumerate(layers):
self.convs.append ( nn.Conv2D( level_chs[i-1], level_chs[i], kernel_size=kernel_size, strides=strides, padding='SAME', dtype=conv_dtype) )
+ self.res1.append ( ResidualBlock(level_chs[i]) )
+ self.res2.append ( ResidualBlock(level_chs[i]) )
+
self.upconvs.insert (0, nn.Conv2DTranspose( level_chs[i]*(2 if i != len(layers)-1 else 1), level_chs[i-1], kernel_size=kernel_size, strides=strides, padding='SAME', dtype=conv_dtype) )
+ self.upres1.insert (0, ResidualBlock(level_chs[i-1]*2) )
+ self.upres2.insert (0, ResidualBlock(level_chs[i-1]*2) )
+
self.out_conv = nn.Conv2D( level_chs[-1]*2, 1, kernel_size=1, padding='VALID', dtype=conv_dtype)
self.center_out = nn.Conv2D( level_chs[len(layers)-1], 1, kernel_size=1, padding='VALID', dtype=conv_dtype)
@@ -169,14 +174,15 @@ class UNetPatchDiscriminator(nn.ModelBase):
def forward(self, x):
if self.use_fp16:
x = tf.cast(x, tf.float16)
-
x = tf.nn.leaky_relu( self.in_conv(x), 0.2 )
encs = []
for conv in self.convs:
encs.insert(0, x)
x = tf.nn.leaky_relu( conv(x), 0.2 )
-
+ x = res1(x)
+ x = res2(x)
+
center_out, x = self.center_out(x), tf.nn.leaky_relu( self.center_conv(x), 0.2 )
for i, (upconv, enc) in enumerate(zip(self.upconvs, encs)):
@@ -192,3 +198,129 @@ class UNetPatchDiscriminator(nn.ModelBase):
return center_out, x
nn.UNetPatchDiscriminator = UNetPatchDiscriminator
+
+class UNetPatchDiscriminatorV2(nn.ModelBase):
+ """
+ Inspired by https://arxiv.org/abs/2002.12655 "A U-Net Based Discriminator for Generative Adversarial Networks"
+ """
+ def calc_receptive_field_size(self, layers):
+ """
+ result the same as https://fomoro.com/research/article/receptive-field-calculatorindex.html
+ """
+ rf = 0
+ ts = 1
+ for i, (k, s) in enumerate(layers):
+ if i == 0:
+ rf = k
+ else:
+ rf += (k-1)*ts
+ ts *= s
+ return rf
+
+ def find_archi(self, target_patch_size, max_layers=6):
+ """
+ Find the best configuration of layers using only 3x3 convs for target patch size
+ """
+ s = {}
+ for layers_count in range(1,max_layers+1):
+ val = 1 << (layers_count-1)
+ while True:
+ val -= 1
+
+ layers = []
+ sum_st = 0
+ for i in range(layers_count-1):
+ st = 1 + (1 if val & (1 << i) !=0 else 0 )
+ layers.append ( [3, st ])
+ sum_st += st
+ layers.append ( [3, 2])
+ sum_st += 2
+
+ rf = self.calc_receptive_field_size(layers)
+
+ s_rf = s.get(rf, None)
+ if s_rf is None:
+ s[rf] = (layers_count, sum_st, layers)
+ else:
+ if layers_count < s_rf[0] or \
+ ( layers_count == s_rf[0] and sum_st > s_rf[1] ):
+ s[rf] = (layers_count, sum_st, layers)
+
+ if val == 0:
+ break
+
+ x = sorted(list(s.keys()))
+ q=x[np.abs(np.array(x)-target_patch_size).argmin()]
+ return s[q][2]
+
+ def on_build(self, patch_size, in_ch, use_fp16 = False):
+ self.use_fp16 = use_fp16
+ conv_dtype = tf.float16 if use_fp16 else tf.float32
+
+ class ResidualBlock(nn.ModelBase):
+ def on_build(self, ch, kernel_size=3 ):
+ self.conv1 = nn.Conv2D( ch, ch, kernel_size=kernel_size, padding='SAME', dtype=conv_dtype)
+ self.conv2 = nn.Conv2D( ch, ch, kernel_size=kernel_size, padding='SAME', dtype=conv_dtype)
+
+ def forward(self, inp):
+ x = self.conv1(inp)
+ x = tf.nn.leaky_relu(x, 0.2)
+ x = self.conv2(x)
+ x = tf.nn.leaky_relu(inp + x, 0.2)
+ return x
+
+ prev_ch = in_ch
+ self.convs = []
+ self.res = []
+ self.upconvs = []
+ self.upres = []
+ layers = self.find_archi(patch_size)
+ base_ch = 16
+
+ level_chs = { i-1:v for i,v in enumerate([ min( base_ch * (2**i), 512 ) for i in range(len(layers)+1)]) }
+
+ self.in_conv = nn.Conv2D( in_ch, level_chs[-1], kernel_size=1, padding='VALID', dtype=conv_dtype)
+
+ for i, (kernel_size, strides) in enumerate(layers):
+ self.convs.append ( nn.Conv2D( level_chs[i-1], level_chs[i], kernel_size=kernel_size, strides=strides, padding='SAME', dtype=conv_dtype) )
+
+ self.res.append ( ResidualBlock(level_chs[i]) )
+
+ self.upconvs.insert (0, nn.Conv2DTranspose( level_chs[i]*(2 if i != len(layers)-1 else 1), level_chs[i-1], kernel_size=kernel_size, strides=strides, padding='SAME', dtype=conv_dtype) )
+
+ self.upres.insert (0, ResidualBlock(level_chs[i-1]*2) )
+
+ self.out_conv = nn.Conv2D( level_chs[-1]*2, 1, kernel_size=1, padding='VALID', dtype=conv_dtype)
+
+ self.center_out = nn.Conv2D( level_chs[len(layers)-1], 1, kernel_size=1, padding='VALID', dtype=conv_dtype)
+ self.center_conv = nn.Conv2D( level_chs[len(layers)-1], level_chs[len(layers)-1], kernel_size=1, padding='VALID', dtype=conv_dtype)
+
+
+ def forward(self, x):
+ if self.use_fp16:
+ x = tf.cast(x, tf.float16)
+
+ x = tf.nn.leaky_relu( self.in_conv(x), 0.1 )
+
+ encs = []
+ for conv, res in zip(self.convs, self.res):
+ encs.insert(0, x)
+ x = tf.nn.leaky_relu( conv(x), 0.1 )
+ x = res(x)
+
+ center_out, x = self.center_out(x), self.center_conv(x)
+
+ for i, (upconv, enc, upres) in enumerate(zip(self.upconvs, encs, self.upres)):
+ x = tf.nn.leaky_relu( upconv(x), 0.1 )
+ x = tf.concat( [enc, x], axis=nn.conv2d_ch_axis)
+ x = upres(x)
+
+ x = self.out_conv(x)
+
+ if self.use_fp16:
+ center_out = tf.cast(center_out, tf.float32)
+ x = tf.cast(x, tf.float32)
+
+ return center_out, x
+
+nn.UNetPatchDiscriminatorV2 = UNetPatchDiscriminatorV2
diff --git a/core/leras/nn.py b/core/leras/nn.py
index f392aaf..7c28874 100644
--- a/core/leras/nn.py
+++ b/core/leras/nn.py
@@ -40,7 +40,7 @@ class nn():
conv2d_spatial_axes = None
floatx = None
-
+
@staticmethod
def initialize(device_config=None, floatx="float32", data_format="NHWC"):
@@ -87,7 +87,7 @@ class nn():
# Disable tensorflow warnings
tf_logger = logging.getLogger('tensorflow')
tf_logger.setLevel(logging.ERROR)
-
+
if tf_version[0] == '2':
tf.disable_v2_behavior()
nn.tf = tf
@@ -99,7 +99,7 @@ class nn():
import core.leras.optimizers
import core.leras.models
import core.leras.archis
-
+
# Configure tensorflow session-config
if len(device_config.devices) == 0:
config = tf.ConfigProto(device_count={'GPU': 0})
@@ -107,13 +107,13 @@ class nn():
else:
nn.tf_default_device_name = f'/{device_config.devices[0].tf_dev_type}:0'
- config = tf.ConfigProto()
+ config = tf.ConfigProto(allow_soft_placement=True)
config.gpu_options.visible_device_list = ','.join([str(device.index) for device in device_config.devices])
config.gpu_options.force_gpu_compatible = True
config.gpu_options.allow_growth = True
nn.tf_sess_config = config
-
+
if nn.tf_sess is None:
nn.tf_sess = tf.Session(config=nn.tf_sess_config)
@@ -260,7 +260,7 @@ class nn():
@staticmethod
def ask_choose_device(*args, **kwargs):
return nn.DeviceConfig.GPUIndexes( nn.ask_choose_device_idxs(*args,**kwargs) )
-
+
def __init__ (self, devices=None):
devices = devices or []
diff --git a/core/leras/ops/__init__.py b/core/leras/ops/__init__.py
index bd690da..aeb077f 100644
--- a/core/leras/ops/__init__.py
+++ b/core/leras/ops/__init__.py
@@ -244,6 +244,19 @@ def gaussian_blur(input, radius=2.0):
return x
nn.gaussian_blur = gaussian_blur
+def get_gaussian_weights(batch_size, in_ch, resolution, num_scale=5, sigma=(0.5, 1., 2., 4., 8.)):
+ w = np.empty((num_scale, batch_size, in_ch, resolution, resolution))
+ for i in range(num_scale):
+ gaussian = np.exp(-1.*np.arange(-(resolution/2-0.5), resolution/2+0.5)**2/(2*sigma[i]**2))
+ gaussian = np.outer(gaussian, gaussian.reshape((resolution, 1))) # extend to 2D
+ gaussian = gaussian/np.sum(gaussian) # normalization
+ gaussian = np.reshape(gaussian, (1, 1, resolution, resolution)) # reshape to 3D
+ gaussian = np.tile(gaussian, (batch_size, in_ch, 1, 1))
+ w[i, :, :, :, :] = gaussian
+ return w
+
+nn.get_gaussian_weights = get_gaussian_weights
+
def style_loss(target, style, gaussian_blur_radius=0.0, loss_weight=1.0, step_size=1):
def sd(content, style, loss_weight):
content_nc = content.shape[ nn.conv2d_ch_axis ]
@@ -475,4 +488,3 @@ def bilinear_sampler(img, x, y):
return out
nn.bilinear_sampler = bilinear_sampler
-
diff --git a/doc/dfl_cover.png b/doc/dfl_cover.png
new file mode 100644
index 0000000..53f2b7f
Binary files /dev/null and b/doc/dfl_cover.png differ
diff --git a/doc/features/background-power/README.md b/doc/features/background-power/README.md
new file mode 100644
index 0000000..0c83e52
--- /dev/null
+++ b/doc/features/background-power/README.md
@@ -0,0 +1,32 @@
+# Background Power option
+
+Allows you to train the model to include the background, which may help with areas around the mask.
+Unlike **Background Style Power**, this does not use any additional VRAM, and does not require lowering the batch size.
+
+- [DESCRIPTION](#description)
+- [USAGE](#usage)
+- [DIFFERENCE WITH BACKGROUND STYLE POWER](#difference-with-background-style-power)
+
+*Examples trained with background power `0.3`:*
+
+
+
+## DESCRIPTION
+
+Applies the same loss calculation used for the area *inside* the mask, to the area *outside* the mask, multiplied with
+the chosen background power value.
+
+E.g. (simplified): Source Loss = Masked area image difference + Background Power * Non-masked area image difference
+
+## USAGE
+
+`[0.0] Background power ( 0.0..1.0 ?:help ) : 0.3`
+
+## DIFFERENCE WITH BACKGROUND STYLE POWER
+
+**Background Style Power** applies a loss to the source by comparing the background of the dest to that of the
+predicted src/dest (5th column). This operation requires additional VRAM, due to the face that the predicted src/dest
+outputs are not normally used in training (other then being viewable in the preview window).
+
+**Background Power** does *not* use the src/dest images whatsoever, instead comparing the background of the predicted
+source to that of the original source, and the same for the background of the dest images.
diff --git a/doc/features/background-power/example.jpeg b/doc/features/background-power/example.jpeg
new file mode 100644
index 0000000..004d149
Binary files /dev/null and b/doc/features/background-power/example.jpeg differ
diff --git a/doc/features/background-power/example2.jpeg b/doc/features/background-power/example2.jpeg
new file mode 100644
index 0000000..7d00de9
Binary files /dev/null and b/doc/features/background-power/example2.jpeg differ
diff --git a/doc/features/gan-options/README.md b/doc/features/gan-options/README.md
new file mode 100644
index 0000000..45d1adb
--- /dev/null
+++ b/doc/features/gan-options/README.md
@@ -0,0 +1,50 @@
+# GAN Options
+
+Allows you to use one-sided label smoothing and noisy labels when training the discriminator.
+
+- [ONE-SIDED LABEL SMOOTHING](#one-sided-label-smoothing)
+- [NOISY LABELS](#noisy-labels)
+
+## ONE-SIDED LABEL SMOOTHING
+
+
+
+> Deep networks may suffer from overconfidence. For example, it uses very few features to classify an object. To
+> mitigate the problem, deep learning uses regulation and dropout to avoid overconfidence.
+>
+> In GAN, if the discriminator depends on a small set of features to detect real images, the generator may just produce
+> these features only to exploit the discriminator. The optimization may turn too greedy and produces no long term
+> benefit. In GAN, overconfidence hurts badly. To avoid the problem, we penalize the discriminator when the prediction
+> for any real images go beyond 0.9 (D(real image)>0.9). This is done by setting our target label value to be 0.9
+> instead of 1.0.
+ - [GAN — Ways to improve GAN performance](https://towardsdatascience.com/gan-ways-to-improve-gan-performance-acf37f9f59b)
+
+By setting the label smoothing value to any value > 0, the target label value used with the discriminator will be:
+```
+target label value = 1 - (label smoothing value)
+```
+### USAGE
+
+```
+[0.1] GAN label smoothing ( 0 - 0.5 ?:help ) : 0.1
+```
+
+## NOISY LABELS
+
+> make the labels the noisy for the discriminator: occasionally flip the labels when training the discriminator
+ - [How to Train a GAN? Tips and tricks to make GANs work](https://github.com/soumith/ganhacks/blob/master/README.md#6-use-soft-and-noisy-labels)
+
+By setting the noisy labels value to any value > 0, then the target labels used with the discriminator will be flipped
+("fake" => "real" / "real" => "fake") with probability p (where p is the noisy label value).
+
+E.g., if the value is 0.05, then ~5% of the labels will be flipped when training the discriminator
+
+### USAGE
+```
+[0.05] GAN noisy labels ( 0 - 0.5 ?:help ) : 0.05
+```
+
+
+
+
+
diff --git a/doc/features/gan-options/tutorial-on-theory-and-application-of-generative-adversarial-networks-54-638.jpg b/doc/features/gan-options/tutorial-on-theory-and-application-of-generative-adversarial-networks-54-638.jpg
new file mode 100644
index 0000000..e3698e9
Binary files /dev/null and b/doc/features/gan-options/tutorial-on-theory-and-application-of-generative-adversarial-networks-54-638.jpg differ
diff --git a/doc/features/ms-ssim/README.md b/doc/features/ms-ssim/README.md
new file mode 100644
index 0000000..41039d3
--- /dev/null
+++ b/doc/features/ms-ssim/README.md
@@ -0,0 +1,43 @@
+# Multiscale SSIM (MS-SSIM)
+
+Allows you to train using the MS-SSIM (multiscale structural similarity index measure) as the main loss metric,
+a perceptually more accurate measure of image quality than MSE (mean squared error).
+
+As an added benefit, you may see a decrease in ms/iteration (when using the same batch size) with Multiscale loss
+enabled. You may also be able to train with a larger batch size with it enabled.
+
+- [DESCRIPTION](#description)
+- [USAGE](#usage)
+
+## DESCRIPTION
+
+[SSIM](https://en.wikipedia.org/wiki/Structural_similarity) is metric for comparing the perceptial quality of an image:
+> SSIM is a perception-based model that considers image degradation as perceived change in structural information,
+> while also incorporating important perceptual phenomena, including both luminance masking and contrast masking terms.
+> [...]
+> Structural information is the idea that the pixels have strong inter-dependencies especially when they are spatially
+> close. These dependencies carry important information about the structure of the objects in the visual scene.
+> Luminance masking is a phenomenon whereby image distortions (in this context) tend to be less visible in bright
+> regions, while contrast masking is a phenomenon whereby distortions become less visible where there is significant
+> activity or "texture" in the image.
+
+The current loss metric is a combination of SSIM (structural similarity index measure) and
+[MSE](https://en.wikipedia.org/wiki/Mean_squared_error) (mean squared error).
+
+[Multiscale SSIM](https://en.wikipedia.org/wiki/Structural_similarity#Multi-Scale_SSIM) is a variant of SSIM that
+improves upon SSIM by comparing the similarity at multiple scales (e.g.: full-size, half-size, 1/4 size, etc.)
+By using MS-SSIM as our main loss metric, we should expect the image similarity to improve across each scale, improving
+both the large scale and small scale detail of the predicted images.
+
+Original paper: [Wang, Zhou, Eero P. Simoncelli, and Alan C. Bovik.
+"Multiscale structural similarity for image quality assessment."
+Signals, Systems and Computers, 2004.](https://www.cns.nyu.edu/pub/eero/wang03b.pdf)
+
+## USAGE
+
+```
+[n] Use multiscale loss? ( y/n ?:help ) : y
+```
+
+
+
diff --git a/doc/features/random-color/README.md b/doc/features/random-color/README.md
new file mode 100644
index 0000000..d1aeac1
--- /dev/null
+++ b/doc/features/random-color/README.md
@@ -0,0 +1,25 @@
+# Random Color option
+
+Helps train the model to generalize perceptual color and lightness, and improves color transfer between src and dst.
+
+- [DESCRIPTION](#description)
+- [USAGE](#usage)
+
+
+
+## DESCRIPTION
+
+Converts images to [CIE L\*a\*b* colorspace](https://en.wikipedia.org/wiki/CIELAB_color_space),
+and then randomly rotates around the `L*` axis. While the perceptual lightness stays constant, only the `a*` and `b*`
+color channels are modified. After rotation, converts back to BGR (blue/green/red) colorspace.
+
+If visualized using the [CIE L\*a\*b* cylindical model](https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindrical_model),
+this is a random rotation of `h°` (hue angle, angle of the hue in the CIELAB color wheel),
+maintaining the same `C*` (chroma, relative saturation).
+
+## USAGE
+
+```
+[n] Random color ( y/n ?:help ) : y
+```
+
diff --git a/doc/features/random-color/example.jpeg b/doc/features/random-color/example.jpeg
new file mode 100644
index 0000000..2a69632
Binary files /dev/null and b/doc/features/random-color/example.jpeg differ
diff --git a/doc/features/webui/README.md b/doc/features/webui/README.md
new file mode 100644
index 0000000..c181fca
--- /dev/null
+++ b/doc/features/webui/README.md
@@ -0,0 +1,45 @@
+# Web UI
+
+View and interact with the training preview window with your web browser.
+Allows you to view and control the preview remotely, and train on headless machines.
+
+- [INSTALLATION](#installation)
+- [DESCRIPTION](#description)
+- [USAGE](#usage)
+- [SSH PORT FORWARDING](#ssh-port-forwarding)
+
+
+
+## INSTALLATION
+
+Requires additional Python dependencies to be installed:
+- [Flask](https://palletsprojects.com/p/flask/),
+ version [1.1.1](https://pypi.org/project/Flask/1.1.1/)
+- [Flask-SocketIO](https://github.com/miguelgrinberg/Flask-SocketIO/),
+ version [4.2.1](https://pypi.org/project/Flask-SocketIO/4.2.1/)
+
+```
+pip install Flask==1.1.1
+pip install Flask-SocketIO==4.2.1
+```
+
+## DESCRIPTION
+
+Launches a Flask web application which sends commands to the training thread
+(save/exit/fetch new preview, etc.), and displays live updates for the log output
+e.g.: `[09:50:53][#106913][0503ms][0.3109][0.2476]`, and updates the graph/preview image.
+
+## USAGE
+
+Enable the Web UI by appending `--flask-preview` to the `train` command.
+Once training begins, Web UI will start, and can be accessed at http://localhost:5000/
+
+## SSH PORT FORWARDING
+
+When running on a remote/headless box, view the Web UI in your local browser simply by
+adding the ssh option `-L 5000:localhost:5000`. Once connected, the Web UI can be viewed
+locally at http://localhost:5000/
+
+Several Android/iOS SSH apps (such as [JuiceSSH](https://juicessh.com/)
+exist which support port forwarding, allowing you to interact with the preview pane
+from anywhere with your phone.
diff --git a/doc/features/webui/example.png b/doc/features/webui/example.png
new file mode 100644
index 0000000..4cc7c05
Binary files /dev/null and b/doc/features/webui/example.png differ
diff --git a/doc/fixes/predicted_src_mask/README.md b/doc/fixes/predicted_src_mask/README.md
new file mode 100644
index 0000000..1247442
--- /dev/null
+++ b/doc/fixes/predicted_src_mask/README.md
@@ -0,0 +1,5 @@
+# Example of bug:
+
+
+# Demonstration of fix:
+
diff --git a/doc/fixes/predicted_src_mask/preview_image_bug.jpeg b/doc/fixes/predicted_src_mask/preview_image_bug.jpeg
new file mode 100644
index 0000000..52767fa
Binary files /dev/null and b/doc/fixes/predicted_src_mask/preview_image_bug.jpeg differ
diff --git a/doc/fixes/predicted_src_mask/preview_image_fix.jpeg b/doc/fixes/predicted_src_mask/preview_image_fix.jpeg
new file mode 100644
index 0000000..43a8b19
Binary files /dev/null and b/doc/fixes/predicted_src_mask/preview_image_fix.jpeg differ
diff --git a/facelib/FaceType.py b/facelib/FaceType.py
index 745cff3..edb40ff 100644
--- a/facelib/FaceType.py
+++ b/facelib/FaceType.py
@@ -7,6 +7,7 @@ class FaceType(IntEnum):
FULL = 2
FULL_NO_ALIGN = 3
WHOLE_FACE = 4
+ CUSTOM = 5
HEAD = 10
HEAD_NO_ALIGN = 20
@@ -30,7 +31,8 @@ to_string_dict = { FaceType.HALF : 'half_face',
FaceType.WHOLE_FACE : 'whole_face',
FaceType.HEAD : 'head',
FaceType.HEAD_NO_ALIGN : 'head_no_align',
-
+ FaceType.CUSTOM : 'mve_custom',
+
FaceType.MARK_ONLY :'mark_only',
}
diff --git a/facelib/LandmarksProcessor.py b/facelib/LandmarksProcessor.py
index 8e5d51b..3db7419 100644
--- a/facelib/LandmarksProcessor.py
+++ b/facelib/LandmarksProcessor.py
@@ -382,11 +382,9 @@ def expand_eyebrows(lmrks, eyebrows_expand_mod=1.0):
# Adjust eyebrow arrays
lmrks[17:22] = top_l + eyebrows_expand_mod * 0.5 * (top_l - bot_l)
lmrks[22:27] = top_r + eyebrows_expand_mod * 0.5 * (top_r - bot_r)
+
return lmrks
-
-
-
def get_image_hull_mask (image_shape, image_landmarks, eyebrows_expand_mod=1.0 ):
hull_mask = np.zeros(image_shape[0:2]+(1,),dtype=np.float32)
@@ -441,7 +439,7 @@ def get_image_mouth_mask (image_shape, image_landmarks):
image_landmarks = image_landmarks.astype(np.int)
- cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[60:]), (1,) )
+ cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[48:60]), (1,) )
dilate = h // 32
hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dilate,dilate)), iterations = 1 )
diff --git a/flaskr/__init__.py b/flaskr/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flaskr/app.py b/flaskr/app.py
new file mode 100644
index 0000000..b56943e
--- /dev/null
+++ b/flaskr/app.py
@@ -0,0 +1,102 @@
+from pathlib import Path
+
+from flask import Flask, send_file, Response, render_template, render_template_string, request, g
+from flask_socketio import SocketIO, emit
+import logging
+
+
+def create_flask_app(s2c, c2s, s2flask, kwargs):
+ app = Flask(__name__, template_folder="templates", static_folder="static")
+ log = logging.getLogger('werkzeug')
+ log.disabled = True
+ model_path = Path(kwargs.get('saved_models_path', ''))
+ filename = 'preview.png'
+ preview_file = str(model_path / filename)
+
+ def gen():
+ frame = open(preview_file, 'rb').read()
+ while True:
+ try:
+ frame = open(preview_file, 'rb').read()
+ except:
+ pass
+ yield b'--frame\r\nContent-Type: image/png\r\n\r\n'
+ yield frame
+ yield b'\r\n\r\n'
+
+ def send(queue, op):
+ queue.put({'op': op})
+
+ def send_and_wait(queue, op):
+ while not s2flask.empty():
+ s2flask.get()
+ queue.put({'op': op})
+ while s2flask.empty():
+ pass
+ s2flask.get()
+
+ @app.route('/save', methods=['POST'])
+ def save():
+ send(s2c, 'save')
+ return '', 204
+
+ @app.route('/exit', methods=['POST'])
+ def exit():
+ send(c2s, 'close')
+ request.environ.get('werkzeug.server.shutdown')()
+ return '', 204
+
+ @app.route('/update', methods=['POST'])
+ def update():
+ send(c2s, 'update')
+ return '', 204
+
+ @app.route('/next_preview', methods=['POST'])
+ def next_preview():
+ send(c2s, 'next_preview')
+ return '', 204
+
+ @app.route('/change_history_range', methods=['POST'])
+ def change_history_range():
+ send(c2s, 'change_history_range')
+ return '', 204
+
+ @app.route('/zoom_prev', methods=['POST'])
+ def zoom_prev():
+ send(c2s, 'zoom_prev')
+ return '', 204
+
+ @app.route('/zoom_next', methods=['POST'])
+ def zoom_next():
+ send(c2s, 'zoom_next')
+ return '', 204
+
+ @app.route('/')
+ def index():
+ return render_template('index.html')
+
+ # @app.route('/preview_image')
+ # def preview_image():
+ # return Response(gen(), mimetype='multipart/x-mixed-replace;boundary=frame')
+
+ @app.route('/preview_image')
+ def preview_image():
+ return send_file(preview_file, mimetype='image/png', cache_timeout=-1)
+
+ socketio = SocketIO(app)
+
+ @socketio.on('connect', namespace='/')
+ def test_connect():
+ emit('my response', {'data': 'Connected'})
+
+ @socketio.on('disconnect', namespace='/test')
+ def test_disconnect():
+ print('Client disconnected')
+
+ return socketio, app
+
+
+
+
+
+
diff --git a/flaskr/static/favicon.ico b/flaskr/static/favicon.ico
new file mode 100644
index 0000000..46aec07
Binary files /dev/null and b/flaskr/static/favicon.ico differ
diff --git a/flaskr/templates/index.html b/flaskr/templates/index.html
new file mode 100644
index 0000000..4ab78dd
--- /dev/null
+++ b/flaskr/templates/index.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ Training Preview
+
+
+
+
+
+
+
+
+
+
+ | ||