mirror of
https://github.com/zebrajr/faceswap.git
synced 2025-12-06 00:20:09 +01:00
Adding new plugins (Extract_Align & Convert_Masked)
This commit is contained in:
parent
3e2976ab03
commit
bb489f4f51
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,6 @@
|
|||
!*.txt
|
||||
!Dockerfile*
|
||||
!requirements*
|
||||
!lib/*.py
|
||||
!lib
|
||||
!scripts
|
||||
!plugins
|
||||
|
|
|
|||
26
lib/aligner.py
Normal file
26
lib/aligner.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import numpy
|
||||
|
||||
from lib.umeyama import umeyama
|
||||
|
||||
mean_face_x = numpy.array([
|
||||
0.000213256, 0.0752622, 0.18113, 0.29077, 0.393397, 0.586856, 0.689483, 0.799124,
|
||||
0.904991, 0.98004, 0.490127, 0.490127, 0.490127, 0.490127, 0.36688, 0.426036,
|
||||
0.490127, 0.554217, 0.613373, 0.121737, 0.187122, 0.265825, 0.334606, 0.260918,
|
||||
0.182743, 0.645647, 0.714428, 0.793132, 0.858516, 0.79751, 0.719335, 0.254149,
|
||||
0.340985, 0.428858, 0.490127, 0.551395, 0.639268, 0.726104, 0.642159, 0.556721,
|
||||
0.490127, 0.423532, 0.338094, 0.290379, 0.428096, 0.490127, 0.552157, 0.689874,
|
||||
0.553364, 0.490127, 0.42689 ])
|
||||
|
||||
mean_face_y = numpy.array([
|
||||
0.106454, 0.038915, 0.0187482, 0.0344891, 0.0773906, 0.0773906, 0.0344891,
|
||||
0.0187482, 0.038915, 0.106454, 0.203352, 0.307009, 0.409805, 0.515625, 0.587326,
|
||||
0.609345, 0.628106, 0.609345, 0.587326, 0.216423, 0.178758, 0.179852, 0.231733,
|
||||
0.245099, 0.244077, 0.231733, 0.179852, 0.178758, 0.216423, 0.244077, 0.245099,
|
||||
0.780233, 0.745405, 0.727388, 0.742578, 0.727388, 0.745405, 0.780233, 0.864805,
|
||||
0.902192, 0.909281, 0.902192, 0.864805, 0.784792, 0.778746, 0.785343, 0.778746,
|
||||
0.784792, 0.824182, 0.831803, 0.824182 ])
|
||||
|
||||
landmarks_2D = numpy.stack( [ mean_face_x, mean_face_y ], axis=1 )
|
||||
|
||||
def get_align_mat(face):
|
||||
return umeyama( numpy.array(face.landmarks[17:]), landmarks_2D, True )[0:2]
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
import face_recognition
|
||||
import face_recognition_models
|
||||
import dlib
|
||||
from .DetectedFace import DetectedFace
|
||||
|
||||
def detect_faces(frame):
|
||||
face_locations = face_recognition.face_locations(frame)
|
||||
#face_encodings = face_recognition.face_encodings(frame, face_locations)
|
||||
landmarks = _raw_face_landmarks(frame, face_locations)
|
||||
landmarks_as_tuples = [[(p.x, p.y) for p in landmark.parts()] for landmark in landmarks]
|
||||
|
||||
for (top, right, bottom, left) in face_locations:
|
||||
for ((top, right, bottom, left), landmarks) in zip(face_locations, landmarks_as_tuples):
|
||||
x = left
|
||||
y = top
|
||||
w = right - left
|
||||
h = bottom - top
|
||||
yield DetectedFace(frame[y: y + h, x: x + w], x, w, y, h, None)
|
||||
yield DetectedFace(frame[y: y + h, x: x + w], x, w, y, h, landmarks)
|
||||
|
||||
# Copy/Paste (mostly) from private method in face_recognition
|
||||
predictor_68_point_model = face_recognition_models.pose_predictor_model_location()
|
||||
pose_predictor = dlib.shape_predictor(predictor_68_point_model)
|
||||
|
||||
def _raw_face_landmarks(face_image, face_locations):
|
||||
face_locations = [_css_to_rect(face_location) for face_location in face_locations]
|
||||
return [pose_predictor(face_image, face_location) for face_location in face_locations]
|
||||
|
||||
def _css_to_rect(css):
|
||||
return dlib.rectangle(css[3], css[0], css[1], css[2])
|
||||
# end of Copy/Paste
|
||||
|
|
@ -12,7 +12,7 @@ def get_folder(path):
|
|||
|
||||
|
||||
def get_image_paths(directory):
|
||||
return [x.path for x in scandir(directory) if x.name.endswith('.jpg') or x.name.endswith('.png')]
|
||||
return [x.path for x in scandir(directory) if x.name.endswith('.jpg') or x.name.endswith('.jpeg') or x.name.endswith('.png')]
|
||||
|
||||
|
||||
def load_images(image_paths, convert=None):
|
||||
|
|
|
|||
|
|
@ -2,28 +2,16 @@ import cv2
|
|||
import numpy
|
||||
import os
|
||||
|
||||
from lib.model import autoencoder_A
|
||||
from lib.model import autoencoder_B
|
||||
from lib.model import encoder, decoder_A, decoder_B
|
||||
|
||||
class Convert(object):
|
||||
def __init__(self,
|
||||
model_dir="models",
|
||||
swap_model=False,
|
||||
use_aligner=False):
|
||||
face_A = '/decoder_A.h5' if not swap_model else '/decoder_B.h5'
|
||||
face_B = '/decoder_B.h5' if not swap_model else '/decoder_A.h5'
|
||||
def __init__(self, autoencoder):
|
||||
self.autoencoder = autoencoder
|
||||
|
||||
self.use_smooth_mask=True
|
||||
self.use_avg_color_adjust=True
|
||||
|
||||
encoder.load_weights(model_dir + "/encoder.h5")
|
||||
decoder_A.load_weights(model_dir + face_A)
|
||||
decoder_B.load_weights(model_dir + face_B)
|
||||
|
||||
self.autoencoder = autoencoder_B
|
||||
|
||||
def convert_one_image(self,image,
|
||||
use_smooth_mask=True,
|
||||
use_avg_color_adjust=True):
|
||||
assert image.shape == (256, 256, 3)
|
||||
def convert_one_image( self, original, face_detected ):
|
||||
#assert image.shape == (256, 256, 3)
|
||||
image = cv2.resize(face_detected.image, (256, 256))
|
||||
crop = slice(48, 208)
|
||||
face = image[crop, crop]
|
||||
old_face = face.copy()
|
||||
|
|
@ -34,12 +22,14 @@ class Convert(object):
|
|||
new_face = numpy.clip(new_face * 255, 0, 255).astype(image.dtype)
|
||||
new_face = cv2.resize(new_face, (160, 160))
|
||||
|
||||
if use_avg_color_adjust:
|
||||
if self.use_avg_color_adjust:
|
||||
self.adjust_avg_color(old_face,new_face)
|
||||
if use_smooth_mask:
|
||||
if self.use_smooth_mask:
|
||||
self.smooth_mask(old_face,new_face)
|
||||
|
||||
return self.superpose(image, new_face, crop)
|
||||
new_face = self.superpose(image, new_face, crop)
|
||||
original[slice(face_detected.y, face_detected.y + face_detected.h), slice(face_detected.x, face_detected.x + face_detected.w)] = cv2.resize(new_face, (face_detected.w, face_detected.h))
|
||||
return original
|
||||
|
||||
def adjust_avg_color(self,img_old,img_new):
|
||||
w,h,c = img_new.shape
|
||||
|
|
|
|||
81
plugins/Convert_Masked.py
Normal file
81
plugins/Convert_Masked.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import cv2
|
||||
import numpy
|
||||
|
||||
from lib.aligner import get_align_mat
|
||||
|
||||
# Based on: https://gist.github.com/anonymous/d3815aba83a8f79779451262599b0955
|
||||
|
||||
class Convert():
|
||||
def __init__(self, autoencoder):
|
||||
self.autoencoder = autoencoder
|
||||
|
||||
# if args.erosionKernelSize > 0:
|
||||
# self.erosion_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(args.erosionKernelSize,args.erosionKernelSize))
|
||||
# else:
|
||||
# self.erosion_kernel = None
|
||||
|
||||
self.erosion_kernel = None
|
||||
self.blurSize = 2
|
||||
self.seamlessClone = False
|
||||
self.maskType = 'Rect' # Choose in 'FaceHullAndRect','FaceHull','Rect'
|
||||
|
||||
def convert_one_image( self, image, face_detected ):
|
||||
size = 64
|
||||
image_size = image.shape[1], image.shape[0]
|
||||
|
||||
mat = numpy.array(get_align_mat(face_detected)).reshape(2,3) * size
|
||||
|
||||
new_face = self.get_new_face(image,mat,size)
|
||||
|
||||
image_mask = self.get_image_mask( image, new_face, face_detected, mat, image_size )
|
||||
|
||||
base_image = numpy.copy( image )
|
||||
new_image = numpy.copy( image )
|
||||
|
||||
cv2.warpAffine( new_face, mat, image_size, new_image, cv2.WARP_INVERSE_MAP, cv2.BORDER_TRANSPARENT )
|
||||
|
||||
outImage = None
|
||||
if self.seamlessClone:
|
||||
masky,maskx = cv2.transform( numpy.array([ size/2,size/2 ]).reshape(1,1,2) ,cv2.invertAffineTransform(mat) ).reshape(2).astype(int)
|
||||
outimage = cv2.seamlessClone(new_image.astype(numpy.uint8),base_image.astype(numpy.uint8),(image_mask*255).astype(numpy.uint8),(masky,maskx) , cv2.NORMAL_CLONE )
|
||||
else:
|
||||
foreground = cv2.multiply(image_mask, new_image.astype(float))
|
||||
background = cv2.multiply(1.0 - image_mask, base_image.astype(float))
|
||||
outimage = cv2.add(foreground, background)
|
||||
|
||||
return outimage
|
||||
|
||||
def get_new_face(self, image, mat, size):
|
||||
face = cv2.warpAffine( image, mat, (size,size) )
|
||||
face = numpy.expand_dims( face, 0 )
|
||||
new_face = self.autoencoder.predict( face / 255.0 )[0]
|
||||
|
||||
return numpy.clip( new_face * 255, 0, 255 ).astype( image.dtype )
|
||||
|
||||
def get_image_mask(self, image, new_face, face_detected, mat, image_size):
|
||||
|
||||
face_mask = numpy.zeros(image.shape,dtype=float)
|
||||
if 'Rect' in self.maskType:
|
||||
face_src = numpy.ones(new_face.shape,dtype=float)
|
||||
cv2.warpAffine( face_src, mat, image_size, face_mask, cv2.WARP_INVERSE_MAP, cv2.BORDER_TRANSPARENT )
|
||||
|
||||
hull_mask = numpy.zeros(image.shape,dtype=float)
|
||||
if 'Hull' in self.maskType:
|
||||
hull = cv2.convexHull( numpy.array( face_detected.landmarks ).reshape((-1,2)).astype(int) ).flatten().reshape( (-1,2) )
|
||||
cv2.fillConvexPoly( hull_mask,hull,(1,1,1) )
|
||||
|
||||
if self.maskType == 'Rect':
|
||||
image_mask = face_mask
|
||||
elif self.maskType == 'FaceHull':
|
||||
image_mask = hull_mask
|
||||
else:
|
||||
image_mask = ((face_mask*hull_mask))
|
||||
|
||||
|
||||
if self.erosion_kernel is not None:
|
||||
image_mask = cv2.erode(image_mask,self.erosion_kernel,iterations = 1)
|
||||
|
||||
if self.blurSize!=0:
|
||||
image_mask = cv2.blur(image_mask,(self.blurSize,self.blurSize))
|
||||
|
||||
return image_mask
|
||||
16
plugins/Extract_Align.py
Normal file
16
plugins/Extract_Align.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import cv2
|
||||
|
||||
from lib.aligner import get_align_mat
|
||||
|
||||
class Extract(object):
|
||||
def extract(self, image, face, size):
|
||||
if face.landmarks == None:
|
||||
return cv2.resize(face.image, (size, size))
|
||||
|
||||
alignment = get_align_mat( face )
|
||||
return self.transform( image, alignment, size, 48 )
|
||||
|
||||
def transform( self, image, mat, size, padding=0 ):
|
||||
mat = mat * (size - 2 * padding)
|
||||
mat[:,2] += padding
|
||||
return cv2.warpAffine( image, mat, ( size, size ) )
|
||||
|
|
@ -2,4 +2,4 @@ import cv2
|
|||
|
||||
class Extract(object):
|
||||
def extract(self, image, face, size):
|
||||
return cv2.resize(face.image, size)
|
||||
return cv2.resize(face.image, (size, size))
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
import cv2
|
||||
from lib.cli import DirectoryProcessor, FullPaths
|
||||
|
||||
from pathlib import Path
|
||||
from lib.cli import DirectoryProcessor, FullPaths
|
||||
from lib.faces_detect import detect_faces
|
||||
|
||||
from plugins.Convert_Adjust import Convert
|
||||
from lib.model import autoencoder_A
|
||||
from lib.model import autoencoder_B
|
||||
from lib.model import encoder, decoder_A, decoder_B
|
||||
|
||||
#from plugins.Convert_Adjust import Convert
|
||||
from plugins.Convert_Masked import Convert
|
||||
|
||||
class ConvertImage(DirectoryProcessor):
|
||||
filename = ''
|
||||
|
|
@ -31,20 +37,29 @@ class ConvertImage(DirectoryProcessor):
|
|||
return parser
|
||||
|
||||
def process_image(self, filename):
|
||||
# TODO move the model load and the converter creation in a method called on init, but after the arg parsing
|
||||
(face_A,face_B) = ('/decoder_A.h5', '/decoder_B.h5') if not self.arguments.swap_model else ('/decoder_B.h5', '/decoder_A.h5')
|
||||
|
||||
model_dir = self.arguments.model_dir
|
||||
encoder.load_weights(model_dir + "/encoder.h5")
|
||||
decoder_A.load_weights(model_dir + face_A)
|
||||
decoder_B.load_weights(model_dir + face_B)
|
||||
|
||||
converter = Convert(autoencoder_B)
|
||||
|
||||
try:
|
||||
converter = Convert(self.arguments.model_dir, self.arguments.swap_model)
|
||||
image = cv2.imread(filename)
|
||||
for (idx, face) in enumerate(detect_faces(image)):
|
||||
if idx > 0 and self.arguments.verbose:
|
||||
print('- Found more than one face!')
|
||||
self.verify_output = True
|
||||
|
||||
new_face = converter.convert_one_image(cv2.resize(face.image, (256, 256)))
|
||||
image[slice(face.y, face.y + face.h), slice(face.x, face.x + face.w)] = cv2.resize(new_face, (face.w, face.h))
|
||||
image = converter.convert_one_image(image, face)
|
||||
self.faces_detected = self.faces_detected + 1
|
||||
|
||||
output_file = self.output_dir / Path(filename).name
|
||||
cv2.imwrite(str(output_file), image)
|
||||
except Exception as e:
|
||||
print('Failed to extract from image: {}. Reason: {}'.format(filename, e))
|
||||
print('Failed to convert image: {}. Reason: {}'.format(filename, e))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import cv2
|
|||
from pathlib import Path
|
||||
from lib.cli import DirectoryProcessor
|
||||
from lib.faces_detect import detect_faces
|
||||
from plugins.Extract_Crop import Extract
|
||||
from plugins.Extract_Align import Extract
|
||||
|
||||
class ExtractTrainingData(DirectoryProcessor):
|
||||
def create_parser(self, subparser, command, description):
|
||||
|
|
@ -24,7 +24,7 @@ class ExtractTrainingData(DirectoryProcessor):
|
|||
print('- Found more than one face!')
|
||||
self.verify_output = True
|
||||
|
||||
resized_image = extractor.extract(image, face, (256, 256))
|
||||
resized_image = extractor.extract(image, face, 256)
|
||||
output_file = self.output_dir / Path(filename).stem
|
||||
cv2.imwrite(str(output_file) + str(idx) + Path(filename).suffix, resized_image)
|
||||
self.faces_detected = self.faces_detected + 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user