Adding new plugins (Extract_Align & Convert_Masked)

This commit is contained in:
Clorr 2018-01-02 15:48:20 +01:00
parent 3e2976ab03
commit bb489f4f51
10 changed files with 180 additions and 37 deletions

2
.gitignore vendored
View File

@ -4,6 +4,6 @@
!*.txt
!Dockerfile*
!requirements*
!lib/*.py
!lib
!scripts
!plugins

26
lib/aligner.py Normal file
View 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]

View File

@ -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

View File

@ -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):

View File

@ -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
View 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
View 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 ) )

View File

@ -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))

View File

@ -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))

View File

@ -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