Skip to content
This repository was archived by the owner on Nov 13, 2024. It is now read-only.

Commit 6bd5a44

Browse files
committed
initial
1 parent 73de93b commit 6bd5a44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+8448
-0
lines changed

.github/ISSUE_TEMPLATE.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Expected behavior
2+
3+
*Describe, in some detail, what you are trying to do and what the output is that you expect from the program.*
4+
5+
## Actual behavior
6+
7+
*Describe, in some detail, what the program does instead. Be sure to include any error message or screenshots.*
8+
9+
## Steps to reproduce
10+
11+
*Describe, in some detail, the steps you tried that resulted in the behavior described above.*
12+
13+
## Other relevant information
14+
- **Command lined used (if not specified in steps to reproduce)**: main.py ...
15+
- **Operating system and version:** Windows, macOS, Linux
16+
- **Python version:** 3.5, 3.6.4, ...

.gitignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
*
2+
!*.py
3+
!*.md
4+
!*.txt
5+
!*.jpg
6+
!requirements*
7+
!doc
8+
!facelib
9+
!gpufmkmgr
10+
!localization
11+
!mainscripts
12+
!mathlib
13+
!models
14+
!nnlib
15+
!utils

CODEGUIDELINES

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Please don't ruin the code and this good (as I think) architecture.
2+
3+
Please follow the same logic and brevity/pithiness.
4+
5+
Don't abstract the code into huge classes if you only win some lines of code in one place, because this can prevent programmers from understanding it quickly.

README.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
## **DeepFaceLab** is a tool that utilizes deep learning to recognize and swap faces in pictures and videos.
2+
3+
Based on original FaceSwap repo. **Facesets** of FaceSwap or FakeApp are **not compatible** with this repo. You should to run extract again.
4+
5+
### **Features**:
6+
7+
- new models
8+
9+
- new architecture, easy to experiment with models
10+
11+
- works on 2GB old cards , such as GT730. Example of fake trained on 2GB gtx850m notebook in 18 hours https://www.youtube.com/watch?v=bprVuRxBA34
12+
13+
- face data embedded to png files
14+
15+
- automatic GPU manager, chooses best gpu(s) and supports --multi-gpu
16+
17+
- new preview window
18+
19+
- extractor in parallel
20+
21+
- converter in parallel
22+
23+
- added **--debug** option for all stages
24+
25+
- added **MTCNN extractor** which produce less jittered aligned face than DLIBCNN, but can produce more false faces. Comparison dlib (at left) vs mtcnn on hard case:
26+
![](https://i.imgur.com/5qLiiOV.gif)
27+
MTCNN produces less jitter.
28+
29+
- added **Manual extractor**. You can fix missed faces manually or do full manual extract, click on video:
30+
[![Watch the video](https://i.imgur.com/BDrPKR2.jpg)](https://webm.video/i/ogL0DL.mp4)
31+
![Result](https://user-images.githubusercontent.com/8076202/38454756-0fa7a86c-3a7e-11e8-9065-182b4a8a7a43.gif)
32+
33+
- standalone zero dependencies ready to work prebuilt binary for all windows versions, see below
34+
35+
### **Model types**:
36+
37+
- **H64 (2GB+)** - half face with 64 resolution. It is as original FakeApp or FaceSwap, but with new TensorFlow 1.8 DSSIM Loss func and separated mask decoder + better ConverterMasked. for 2GB and 3GB VRAM model works in reduced mode.
38+
* H64 Robert Downey Jr.:
39+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/H64_Downey_0.jpg)
40+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/H64_Downey_1.jpg)
41+
42+
- **H128 (3GB+)** - as H64, but in 128 resolution. Better face details. for 3GB and 4GB VRAM model works in reduced mode.
43+
* H128 Cage:
44+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/H128_Cage_0.jpg)
45+
* H128 asian face on blurry target:
46+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/H128_Asian_0.jpg)
47+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/H128_Asian_1.jpg)
48+
- **DF (5GB+)** - @dfaker model. As H128, but fullface model.
49+
* DF example - later
50+
51+
- **LIAEF128 (5GB+)** - new model. Result of combining DF, IAE, + experiments. Model tries to morph src face to dst, while keeping facial features of src face, but less agressive morphing. Model has problems with closed eyes recognizing.
52+
* LIAEF128 Cage:
53+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/LIAEF128_Cage_0.jpg)
54+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/LIAEF128_Cage_1.jpg)
55+
* LIAEF128 Cage video:
56+
* [![Watch the video](https://img.youtube.com/vi/mRsexePEVco/0.jpg)](https://www.youtube.com/watch?v=mRsexePEVco)
57+
- **LIAEF128YAW (5GB+)** - currently testing. Useful when your src faceset has too many side faces vs dst faceset. It feeds NN by sorted samples by yaw.
58+
- **MIAEF128 (5GB+)** - as LIAEF128, but also it tries to match brightness/color features.
59+
* MIAEF128 model diagramm:
60+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/MIAEF128_diagramm.png)
61+
* MIAEF128 Ford success case:
62+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/MIAEF128_Ford_0.jpg)
63+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/MIAEF128_Ford_1.jpg)
64+
* MIAEF128 Cage fail case:
65+
* ![](https://github.com/iperov/OpenDeepFaceSwap/blob/master/doc/MIAEF128_Cage_fail.jpg)
66+
- **AVATAR (4GB+)** - face controlling model. Usage:
67+
* src - controllable face (Cage)
68+
* dst - controller face (your face)
69+
* converter --input-dir contains aligned dst faces in sequence to be converted, its mean you can train on 1500 dst faces, but use only 100 for convert.
70+
71+
### **Sort tool**:
72+
73+
`hist` groups images by similar content
74+
75+
`hist-dissim` places most similar to each other images to end.
76+
77+
`hist-blur` sort by blur in groups of similar content
78+
79+
`brightness`
80+
81+
`hue`
82+
83+
`face` and `face-dissim` currently useless
84+
85+
Best practice for gather src faceset:
86+
87+
1) delete first unsorted aligned groups of images what you can to delete. Dont touch target face mixed with others.
88+
2) `blur` -> delete ~half of them
89+
3) `hist` -> delete groups of similar and leave only target face
90+
4) `hist-blur` -> delete blurred at end of groups of similar
91+
5) `hist-dissim` -> leave only first **1000-1500 faces**, because number of src faces can affect result. For YAW feeder model skip this step.
92+
6) `face-yaw` -> just for finalize faceset
93+
94+
Best practice for dst faces:
95+
96+
1) delete first unsorted aligned groups of images what you can to delete. Dont touch target face mixed with others.
97+
2) `hist` -> delete groups of similar and leave only target face
98+
99+
### **Prebuilt binary**:
100+
101+
Windows 7,8,8.1,10 zero dependency binary except NVidia Video Drivers can be downloaded from torrent.
102+
103+
Torrent page: https://rutracker.org/forum/viewtopic.php?p=75318742 (magnet link inside)
104+
105+
### **Facesets**:
106+
107+
- Nicolas Cage.
108+
109+
- Cage/Trump workspace
110+
111+
download from here: https://mega.nz/#F!y1ERHDaL!PPwg01PQZk0FhWLVo5_MaQ
112+
113+
### **Pull requesting**:
114+
115+
I understand some people want to help. But result of mass people contribution we can see in deepfakes\faceswap.
116+
High chance I will decline PR. Therefore before PR better ask me what you want to change or add to save your time.

doc/H128_Asian_0.jpg

83 KB
Loading

doc/H128_Asian_1.jpg

85.8 KB
Loading

doc/H128_Cage_0.jpg

92 KB
Loading

doc/H64_Downey_0.jpg

41.7 KB
Loading

doc/H64_Downey_1.jpg

42.4 KB
Loading

doc/LIAEF128_Cage_0.jpg

112 KB
Loading

doc/LIAEF128_Cage_1.jpg

113 KB
Loading

doc/MIAEF128_Cage_fail.jpg

92.1 KB
Loading

doc/MIAEF128_Ford_0.jpg

76.6 KB
Loading

doc/MIAEF128_Ford_1.jpg

75.4 KB
Loading

doc/MIAEF128_diagramm.png

30.9 KB
Loading

doc/landmarks.jpg

450 KB
Loading

facelib/2DFAN-4.h5

92.9 MB
Binary file not shown.

facelib/DLIBExtractor.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import numpy as np
2+
import os
3+
import cv2
4+
5+
from pathlib import Path
6+
7+
class DLIBExtractor(object):
8+
def __init__(self, dlib):
9+
self.scale_to = 1850
10+
#3100 eats ~1.687GB VRAM on 2GB 730 desktop card, but >4Gb on 6GB card,
11+
#but 3100 doesnt work on 2GB 850M notebook card, I cant understand this behaviour
12+
#1850 works on 2GB 850M notebook card, works faster than 3100, produces good result
13+
self.dlib = dlib
14+
15+
def __enter__(self):
16+
self.dlib_cnn_face_detector = self.dlib.cnn_face_detection_model_v1( str(Path(__file__).parent / "mmod_human_face_detector.dat") )
17+
self.dlib_cnn_face_detector ( np.zeros ( (self.scale_to, self.scale_to, 3), dtype=np.uint8), 0 )
18+
return self
19+
20+
def __exit__(self, exc_type=None, exc_value=None, traceback=None):
21+
del self.dlib_cnn_face_detector
22+
return False #pass exception between __enter__ and __exit__ to outter level
23+
24+
def extract_from_bgr (self, input_image):
25+
input_image = input_image[:,:,::-1].copy()
26+
(h, w, ch) = input_image.shape
27+
28+
detected_faces = []
29+
input_scale = self.scale_to / (w if w > h else h)
30+
input_image = cv2.resize (input_image, ( int(w*input_scale), int(h*input_scale) ), interpolation=cv2.INTER_LINEAR)
31+
detected_faces = self.dlib_cnn_face_detector(input_image, 0)
32+
33+
result = []
34+
for d_rect in detected_faces:
35+
if type(d_rect) == self.dlib.mmod_rectangle:
36+
d_rect = d_rect.rect
37+
left, top, right, bottom = d_rect.left(), d_rect.top(), d_rect.right(), d_rect.bottom()
38+
result.append ( (int(left/input_scale), int(top/input_scale), int(right/input_scale), int(bottom/input_scale)) )
39+
40+
return result

facelib/FaceType.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from enum import IntEnum
2+
3+
class FaceType(IntEnum):
4+
HALF = 0,
5+
FULL = 1,
6+
HEAD = 2,
7+
AVATAR = 3, #centered nose only
8+
MARK_ONLY = 4, #no align at all, just embedded faceinfo
9+
QTY = 5
10+
11+
@staticmethod
12+
def fromString (s):
13+
r = from_string_dict.get (s.lower())
14+
if r is None:
15+
raise Exception ('FaceType.fromString value error')
16+
return r
17+
18+
@staticmethod
19+
def toString (face_type):
20+
return to_string_list[face_type]
21+
22+
from_string_dict = {'half_face': FaceType.HALF,
23+
'full_face': FaceType.FULL,
24+
'head' : FaceType.HEAD,
25+
'avatar' : FaceType.AVATAR,
26+
'mark_only' : FaceType.MARK_ONLY,
27+
}
28+
to_string_list = [ 'half_face',
29+
'full_face',
30+
'head',
31+
'avatar',
32+
'mark_only'
33+
]
34+

facelib/LandmarksExtractor.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import numpy as np
2+
import os
3+
import cv2
4+
from pathlib import Path
5+
6+
from utils import std_utils
7+
8+
9+
10+
def transform(point, center, scale, resolution):
11+
pt = np.array ( [point[0], point[1], 1.0] )
12+
h = 200.0 * scale
13+
m = np.eye(3)
14+
m[0,0] = resolution / h
15+
m[1,1] = resolution / h
16+
m[0,2] = resolution * ( -center[0] / h + 0.5 )
17+
m[1,2] = resolution * ( -center[1] / h + 0.5 )
18+
m = np.linalg.inv(m)
19+
return np.matmul (m, pt)[0:2]
20+
21+
def crop(image, center, scale, resolution=256.0):
22+
ul = transform([1, 1], center, scale, resolution).astype( np.int )
23+
br = transform([resolution, resolution], center, scale, resolution).astype( np.int )
24+
if image.ndim > 2:
25+
newDim = np.array([br[1] - ul[1], br[0] - ul[0], image.shape[2]], dtype=np.int32)
26+
newImg = np.zeros(newDim, dtype=np.uint8)
27+
else:
28+
newDim = np.array([br[1] - ul[1], br[0] - ul[0]], dtype=np.int)
29+
newImg = np.zeros(newDim, dtype=np.uint8)
30+
ht = image.shape[0]
31+
wd = image.shape[1]
32+
newX = np.array([max(1, -ul[0] + 1), min(br[0], wd) - ul[0]], dtype=np.int32)
33+
newY = np.array([max(1, -ul[1] + 1), min(br[1], ht) - ul[1]], dtype=np.int32)
34+
oldX = np.array([max(1, ul[0] + 1), min(br[0], wd)], dtype=np.int32)
35+
oldY = np.array([max(1, ul[1] + 1), min(br[1], ht)], dtype=np.int32)
36+
newImg[newY[0] - 1:newY[1], newX[0] - 1:newX[1] ] = image[oldY[0] - 1:oldY[1], oldX[0] - 1:oldX[1], :]
37+
newImg = cv2.resize(newImg, dsize=(int(resolution), int(resolution)), interpolation=cv2.INTER_LINEAR)
38+
return newImg
39+
40+
def get_pts_from_predict(a, center, scale):
41+
b = a.reshape ( (a.shape[0], a.shape[1]*a.shape[2]) )
42+
c = b.argmax(1).reshape ( (a.shape[0], 1) ).repeat(2, axis=1).astype(np.float)
43+
c[:,0] %= a.shape[2]
44+
c[:,1] = np.apply_along_axis ( lambda x: np.floor(x / a.shape[2]), 0, c[:,1] )
45+
46+
for i in range(a.shape[0]):
47+
pX, pY = int(c[i,0]), int(c[i,1])
48+
if pX > 0 and pX < 63 and pY > 0 and pY < 63:
49+
diff = np.array ( [a[i,pY,pX+1]-a[i,pY,pX-1], a[i,pY+1,pX]-a[i,pY-1,pX]] )
50+
c[i] += np.sign(diff)*0.25
51+
52+
c += 0.5
53+
return [ transform (c[i], center, scale, a.shape[2]) for i in range(a.shape[0]) ]
54+
55+
56+
class LandmarksExtractor(object):
57+
def __init__ (self, keras):
58+
self.keras = keras
59+
K = self.keras.backend
60+
class TorchBatchNorm2D(self.keras.engine.topology.Layer):
61+
def __init__(self, axis=-1, momentum=0.99, epsilon=1e-3, **kwargs):
62+
super(TorchBatchNorm2D, self).__init__(**kwargs)
63+
self.supports_masking = True
64+
self.axis = axis
65+
self.momentum = momentum
66+
self.epsilon = epsilon
67+
68+
def build(self, input_shape):
69+
dim = input_shape[self.axis]
70+
if dim is None:
71+
raise ValueError('Axis ' + str(self.axis) + ' of ' 'input tensor should have a defined dimension ' 'but the layer received an input with shape ' + str(input_shape) + '.')
72+
shape = (dim,)
73+
self.gamma = self.add_weight(shape=shape, name='gamma', initializer='ones', regularizer=None, constraint=None)
74+
self.beta = self.add_weight(shape=shape, name='beta', initializer='zeros', regularizer=None, constraint=None)
75+
self.moving_mean = self.add_weight(shape=shape, name='moving_mean', initializer='zeros', trainable=False)
76+
self.moving_variance = self.add_weight(shape=shape, name='moving_variance', initializer='ones', trainable=False)
77+
self.built = True
78+
79+
def call(self, inputs, training=None):
80+
input_shape = K.int_shape(inputs)
81+
82+
broadcast_shape = [1] * len(input_shape)
83+
broadcast_shape[self.axis] = input_shape[self.axis]
84+
85+
broadcast_moving_mean = K.reshape(self.moving_mean, broadcast_shape)
86+
broadcast_moving_variance = K.reshape(self.moving_variance, broadcast_shape)
87+
broadcast_gamma = K.reshape(self.gamma, broadcast_shape)
88+
broadcast_beta = K.reshape(self.beta, broadcast_shape)
89+
invstd = K.ones (shape=broadcast_shape, dtype='float32') / K.sqrt(broadcast_moving_variance + K.constant(self.epsilon, dtype='float32'))
90+
91+
return (inputs - broadcast_moving_mean) * invstd * broadcast_gamma + broadcast_beta
92+
93+
def get_config(self):
94+
config = { 'axis': self.axis, 'momentum': self.momentum, 'epsilon': self.epsilon }
95+
base_config = super(TorchBatchNorm2D, self).get_config()
96+
return dict(list(base_config.items()) + list(config.items()))
97+
self.TorchBatchNorm2D = TorchBatchNorm2D
98+
99+
def __enter__(self):
100+
keras_model_path = Path(__file__).parent / "2DFAN-4.h5"
101+
if not keras_model_path.exists():
102+
return None
103+
104+
self.keras_model = self.keras.models.load_model ( str(keras_model_path), custom_objects={'TorchBatchNorm2D': self.TorchBatchNorm2D} )
105+
106+
return self
107+
108+
def __exit__(self, exc_type=None, exc_value=None, traceback=None):
109+
del self.keras_model
110+
return False #pass exception between __enter__ and __exit__ to outter level
111+
112+
def extract_from_bgr (self, input_image, rects):
113+
input_image = input_image[:,:,::-1].copy()
114+
(h, w, ch) = input_image.shape
115+
116+
landmarks = []
117+
for (left, top, right, bottom) in rects:
118+
119+
center = np.array( [ (left + right) / 2.0, (top + bottom) / 2.0] )
120+
center[1] -= (bottom - top) * 0.12
121+
scale = (right - left + bottom - top) / 195.0
122+
123+
image = crop(input_image, center, scale).transpose ( (2,0,1) ).astype(np.float32) / 255.0
124+
image = np.expand_dims(image, 0)
125+
126+
with std_utils.suppress_stdout_stderr():
127+
predicted = self.keras_model.predict (image)
128+
129+
pts_img = get_pts_from_predict ( predicted[-1][0], center, scale)
130+
pts_img = [ ( int(pt[0]), int(pt[1]) ) for pt in pts_img ]
131+
landmarks.append ( ( (left, top, right, bottom),pts_img ) )
132+
133+
return landmarks

0 commit comments

Comments
 (0)