This commit is contained in:
smthem
2024-07-11 22:16:40 +08:00
parent c1f7607984
commit 544414937d
139 changed files with 17179 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
from .stable_makeup_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+9
View File
@@ -0,0 +1,9 @@
"""
Face Morpher module init code
"""
from .morpher import morpher, list_imgpaths
from .averager import averager
__all__ = ['list_imgpaths',
'morpher',
'averager']
+133
View File
@@ -0,0 +1,133 @@
"""
Align face and image sizes
"""
import cv2
import numpy as np
def positive_cap(num):
""" Cap a number to ensure positivity
:param num: positive or negative number
:returns: (overflow, capped_number)
"""
if num < 0:
return 0, abs(num)
else:
return num, 0
def roi_coordinates(rect, size, scale):
""" Align the rectangle into the center and return the top-left coordinates
within the new size. If rect is smaller, we add borders.
:param rect: (x, y, w, h) bounding rectangle of the face
:param size: (width, height) are the desired dimensions
:param scale: scaling factor of the rectangle to be resized
:returns: 4 numbers. Top-left coordinates of the aligned ROI.
(x, y, border_x, border_y). All values are > 0.
"""
rectx, recty, rectw, recth = rect
new_height, new_width = size
mid_x = int((rectx + rectw/2) * scale)
mid_y = int((recty + recth/2) * scale)
roi_x = mid_x - int(new_width/2)
roi_y = mid_y - int(new_height/2)
roi_x, border_x = positive_cap(roi_x)
roi_y, border_y = positive_cap(roi_y)
return roi_x, roi_y, border_x, border_y
def scaling_factor(rect, size):
""" Calculate the scaling factor for the current image to be
resized to the new dimensions
:param rect: (x, y, w, h) bounding rectangle of the face
:param size: (width, height) are the desired dimensions
:returns: floating point scaling factor
"""
new_height, new_width = size
rect_h, rect_w = rect[2:]
height_ratio = rect_h / new_height
width_ratio = rect_w / new_width
scale = 1
if height_ratio > width_ratio:
new_recth = 0.8 * new_height
scale = new_recth / rect_h
else:
new_rectw = 0.8 * new_width
scale = new_rectw / rect_w
return scale
def resize_image(img, scale):
""" Resize image with the provided scaling factor
:param img: image to be resized
:param scale: scaling factor for resizing the image
"""
cur_height, cur_width = img.shape[:2]
new_scaled_height = int(scale * cur_height)
new_scaled_width = int(scale * cur_width)
return cv2.resize(img, (new_scaled_width, new_scaled_height))
def de_resize_image(img, scale):
""" Resize image with the provided scaling factor
:param img: image to be resized
:param scale: scaling factor for resizing the image
"""
cur_height, cur_width = img.shape[:2]
new_scaled_height = int(cur_height / scale)
new_scaled_width = int(cur_width / scale)
return cv2.resize(img, (new_scaled_width, new_scaled_height))
def resize_align(img, points, size):
""" Resize image and associated points, align face to the center
and crop to the desired size
:param img: image to be resized
:param points: *m* x 2 array of points
:param size: (height, width) tuple of new desired size
"""
new_height, new_width = size # 910 910
# Resize image based on bounding rectangle
rect = cv2.boundingRect(np.array([points], np.int32)) # face rect
scale = scaling_factor(rect, size)
img = resize_image(img, scale)
# Align bounding rect to center
cur_height, cur_width = img.shape[:2]
roi_x, roi_y, border_x, border_y = roi_coordinates(rect, size, scale)
roi_h = np.min([new_height-border_y, cur_height-roi_y])
roi_w = np.min([new_width-border_x, cur_width-roi_x])
# Crop to supplied size
crop = np.zeros((new_height, new_width, 3), img.dtype)
crop[border_y:border_y+roi_h, border_x:border_x+roi_w] = (
img[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w])
# Scale and align face points to the crop
points[:, 0] = (points[:, 0] * scale) + (border_x - roi_x)
points[:, 1] = (points[:, 1] * scale) + (border_y - roi_y)
return (crop, points)
def restore_image(img, points, size, crop):
new_height, new_width = size # 910 910
# Resize image based on bounding rectangle
rect = cv2.boundingRect(np.array([points], np.int32)) # face rect
scale = scaling_factor(rect, size)
img = resize_image(img, scale)
# Align bounding rect to center
cur_height, cur_width = img.shape[:2]
roi_x, roi_y, border_x, border_y = roi_coordinates(rect, size, scale)
roi_h = np.min([new_height-border_y, cur_height-roi_y])
roi_w = np.min([new_width-border_x, cur_width-roi_x])
img[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w] = crop[border_y:border_y + roi_h, border_x:border_x + roi_w]
img = de_resize_image(img, scale)
return img
+135
View File
@@ -0,0 +1,135 @@
"""
::
Face averager
Usage:
averager.py --images=<images_folder> [--blur] [--plot]
[--background=(black|transparent|average)]
[--width=<width>] [--height=<height>]
[--out=<filename>] [--destimg=<filename>]
Options:
-h, --help Show this screen.
--images=<folder> Folder to images (.jpg, .jpeg, .png)
--blur Flag to blur edges of image [default: False]
--width=<width> Custom width of the images/video [default: 500]
--height=<height> Custom height of the images/video [default: 600]
--out=<filename> Filename to save the average face [default: result.png]
--destimg=<filename> Destination face image to overlay average face
--plot Flag to display the average face [default: False]
--background=<bg> Background of image to be one of (black|transparent|average) [default: black]
--version Show version.
"""
from docopt import docopt
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from facemorpher import locator
from facemorpher import aligner
from facemorpher import warper
from facemorpher import blender
from facemorpher import plotter
# import locator
# import aligner
# import warper
# import blender
# import plotter
def list_imgpaths(imgfolder):
for fname in os.listdir(imgfolder):
if (fname.lower().endswith('.jpg') or
fname.lower().endswith('.png') or
fname.lower().endswith('.jpeg')):
yield os.path.join(imgfolder, fname)
def sharpen(img):
blured = cv2.GaussianBlur(img, (0, 0), 2.5)
return cv2.addWeighted(img, 1.4, blured, -0.4, 0)
def load_image_points(path, size):
img = cv2.imread(path)
points = locator.face_points(img)
if len(points) == 0:
print('No face in %s' % path)
return None, None
else:
return aligner.resize_align(img, points, size)
def averager(imgpaths, dest_filename=None, width=500, height=600, background='black',
blur_edges=False, out_filename='result.png', plot=False):
size = (height, width)
images = []
point_set = []
for path in imgpaths:
img, points = load_image_points(path, size)
if img is not None:
images.append(img)
point_set.append(points)
if len(images) == 0:
raise FileNotFoundError('Could not find any valid images.' +
' Supported formats are .jpg, .png, .jpeg')
if dest_filename is not None:
dest_img, dest_points = load_image_points(dest_filename, size)
if dest_img is None or dest_points is None:
raise Exception('No face or detected face points in dest img: ' + dest_filename)
else:
dest_img = np.zeros(images[0].shape, np.uint8)
dest_points = locator.average_points(point_set)
num_images = len(images)
result_images = np.zeros(images[0].shape, np.float32)
for i in range(num_images):
result_images += warper.warp_image(images[i], point_set[i],
dest_points, size, np.float32)
result_image = np.uint8(result_images / num_images)
face_indexes = np.nonzero(result_image)
dest_img[face_indexes] = result_image[face_indexes]
mask = blender.mask_from_points(size, dest_points)
if blur_edges:
blur_radius = 10
mask = cv2.blur(mask, (blur_radius, blur_radius))
if background in ('transparent', 'average'):
dest_img = np.dstack((dest_img, mask))
if background == 'average':
average_background = locator.average_points(images)
dest_img = blender.overlay_image(dest_img, mask, average_background)
print('Averaged {} images'.format(num_images))
cv2.imwrite(out_filename, dest_img)
def main():
list_imgpaths = ["/share2/zhangyuxuan/project/reenactment_diffusion/FFHQ_format/test_face/warp.png", "/share2/zhangyuxuan/project/reenactment_diffusion/FFHQ_format/test_face/warp.png"]
destimg = "/share2/zhangyuxuan/project/reenactment_diffusion/FFHQ_format/test_face/warp.png"
width = 910
height = 910
background = 'transparent'
blur = True
out = "/share2/zhangyuxuan/project/reenactment_diffusion/FFHQ_format/test_face/1.jpg"
plot = False
try:
averager(list_imgpaths, destimg,
width, height,
background, blur, out, plot)
except Exception as e:
print(e)
if __name__ == "__main__":
main()
+133
View File
@@ -0,0 +1,133 @@
import cv2
import numpy as np
import scipy.sparse
def mask_from_points(size, points):
""" Create a mask of supplied size from supplied points
:param size: tuple of output mask size
:param points: array of [x, y] points
:returns: mask of values 0 and 255 where
255 indicates the convex hull containing the points
"""
radius = 10 # kernel size
kernel = np.ones((radius, radius), np.uint8)
mask = np.zeros(size, np.uint8)
cv2.fillConvexPoly(mask, cv2.convexHull(points), 255)
mask = cv2.erode(mask, kernel)
return mask
def overlay_image(foreground_image, mask, background_image):
""" Overlay foreground image onto the background given a mask
:param foreground_image: foreground image points
:param mask: [0-255] values in mask
:param background_image: background image points
:returns: image with foreground where mask > 0 overlaid on background image
"""
foreground_pixels = mask > 0
background_image[..., :3][foreground_pixels] = foreground_image[..., :3][foreground_pixels]
return background_image
def apply_mask(img, mask):
""" Apply mask to supplied image
:param img: max 3 channel image
:param mask: [0-255] values in mask
:returns: new image with mask applied
"""
masked_img = np.copy(img)
num_channels = 3
for c in range(num_channels):
masked_img[..., c] = img[..., c] * (mask / 255)
return masked_img
def weighted_average(img1, img2, percent=0.5):
if percent <= 0:
return img2
elif percent >= 1:
return img1
else:
return cv2.addWeighted(img1, percent, img2, 1-percent, 0)
def alpha_feathering(src_img, dest_img, img_mask, blur_radius=15):
mask = cv2.blur(img_mask, (blur_radius, blur_radius))
mask = mask / 255.0
result_img = np.empty(src_img.shape, np.uint8)
for i in range(3):
result_img[..., i] = src_img[..., i] * mask + dest_img[..., i] * (1-mask)
return result_img
def poisson_blend(img_source, dest_img, img_mask, offset=(0, 0)):
# http://opencv.jp/opencv2-x-samples/poisson-blending
img_target = np.copy(dest_img)
import pyamg
# compute regions to be blended
region_source = (
max(-offset[0], 0),
max(-offset[1], 0),
min(img_target.shape[0] - offset[0], img_source.shape[0]),
min(img_target.shape[1] - offset[1], img_source.shape[1]))
region_target = (
max(offset[0], 0),
max(offset[1], 0),
min(img_target.shape[0], img_source.shape[0] + offset[0]),
min(img_target.shape[1], img_source.shape[1] + offset[1]))
region_size = (region_source[2] - region_source[0],
region_source[3] - region_source[1])
# clip and normalize mask image
img_mask = img_mask[region_source[0]:region_source[2],
region_source[1]:region_source[3]]
# create coefficient matrix
coff_mat = scipy.sparse.identity(np.prod(region_size), format='lil')
for y in range(region_size[0]):
for x in range(region_size[1]):
if img_mask[y, x]:
index = x + y * region_size[1]
coff_mat[index, index] = 4
if index + 1 < np.prod(region_size):
coff_mat[index, index + 1] = -1
if index - 1 >= 0:
coff_mat[index, index - 1] = -1
if index + region_size[1] < np.prod(region_size):
coff_mat[index, index + region_size[1]] = -1
if index - region_size[1] >= 0:
coff_mat[index, index - region_size[1]] = -1
coff_mat = coff_mat.tocsr()
# create poisson matrix for b
poisson_mat = pyamg.gallery.poisson(img_mask.shape)
# for each layer (ex. RGB)
for num_layer in range(img_target.shape[2]):
# get subimages
t = img_target[region_target[0]:region_target[2],
region_target[1]:region_target[3], num_layer]
s = img_source[region_source[0]:region_source[2],
region_source[1]:region_source[3], num_layer]
t = t.flatten()
s = s.flatten()
# create b
b = poisson_mat * s
for y in range(region_size[0]):
for x in range(region_size[1]):
if not img_mask[y, x]:
index = x + y * region_size[1]
b[index] = t[index]
# solve Ax = b
x = pyamg.solve(coff_mat, b, verb=False, tol=1e-10)
# assign x to target image
x = np.reshape(x, region_size)
x[x > 255] = 255
x[x < 0] = 0
x = np.array(x, img_target.dtype)
img_target[region_target[0]:region_target[2],
region_target[1]:region_target[3], num_layer] = x
return img_target
+111
View File
@@ -0,0 +1,111 @@
"""
Locate face points
"""
import cv2
import numpy as np
import os.path as path
import dlib
import os
DATA_DIR = "/share2/zhangyuxuan/project/reenactment_diffusion/FFHQ_format"
# DATA_DIR = "/mnt/nj-aigc/usr/zhangyuxuan1/project/FFHQ_format"
dlib_detector = dlib.get_frontal_face_detector()
dlib_predictor = dlib.shape_predictor(path.join(DATA_DIR, 'shape_predictor_68_face_landmarks.dat'))
def boundary_points(points, width_percent=0.1, height_percent=0.1):
""" Produce additional boundary points
:param points: *m* x 2 array of x,y points
:param width_percent: [-1, 1] percentage of width to taper inwards. Negative for opposite direction
:param height_percent: [-1, 1] percentage of height to taper downwards. Negative for opposite direction
:returns: 2 additional points at the top corners
"""
x, y, w, h = cv2.boundingRect(np.array([points], np.int32))
spacerw = int(w * width_percent)
spacerh = int(h * height_percent)
return [[x+spacerw, y+spacerh],
[x+w-spacerw, y+spacerh]]
def face_points(img, add_boundary_points=True):
return face_points_dlib(img, add_boundary_points)
def face_points_dlib(img, add_boundary_points=True):
""" Locates 68 face points using dlib (http://dlib.net)
Requires shape_predictor_68_face_landmarks.dat to be in face_morpher/data
Download at: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
:param img: an image array
:param add_boundary_points: bool to add additional boundary points
:returns: Array of x,y face points. Empty array if no face found
"""
try:
points = []
rgbimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
rects = dlib_detector(rgbimg, 1)
if rects and len(rects) > 0:
# We only take the first found face
shapes = dlib_predictor(rgbimg, rects[0])
points = np.array([(shapes.part(i).x, shapes.part(i).y) for i in range(68)], np.int32)
if add_boundary_points:
# Add more points inwards and upwards as dlib only detects up to eyebrows
points = np.vstack([
points,
boundary_points(points, 0.1, -0.03),
boundary_points(points, 0.13, -0.05),
boundary_points(points, 0.15, -0.08),
boundary_points(points, 0.33, -0.12)])
return points
except Exception as e:
print(e)
return []
def face_points_stasm(img, add_boundary_points=True):
import stasm
""" Locates 77 face points using stasm (http://www.milbo.users.sonic.net/stasm)
:param img: an image array
:param add_boundary_points: bool to add 2 additional points
:returns: Array of x,y face points. Empty array if no face found
"""
try:
points = stasm.search_single(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))
except Exception as e:
print('Failed finding face points: ', e)
return []
points = points.astype(np.int32)
if len(points) == 0:
return points
if add_boundary_points:
return np.vstack([points, boundary_points(points)])
return points
def average_points(point_set):
""" Averages a set of face points from images
:param point_set: *n* x *m* x 2 array of face points. \\
*n* = number of images. *m* = number of face points per image
"""
return np.mean(point_set, 0).astype(np.int32)
def weighted_average_points(start_points, end_points, percent=0.5):
""" Weighted average of two sets of supplied points
:param start_points: *m* x 2 array of start face points.
:param end_points: *m* x 2 array of end face points.
:param percent: [0, 1] percentage weight on start_points
:returns: *m* x 2 array of weighted average points
"""
if percent <= 0:
return end_points
elif percent >= 1:
return start_points
else:
return np.asarray(start_points*percent + end_points*(1-percent), np.int32)
+153
View File
@@ -0,0 +1,153 @@
"""
::
Morph from source to destination face or
Morph through all images in a folder
Usage:
morpher.py (--src=<src_path> --dest=<dest_path> | --images=<folder>)
[--width=<width>] [--height=<height>]
[--num=<num_frames>] [--fps=<frames_per_second>]
[--out_frames=<folder>] [--out_video=<filename>]
[--plot] [--background=(black|transparent|average)]
Options:
-h, --help Show this screen.
--src=<src_imgpath> Filepath to source image (.jpg, .jpeg, .png)
--dest=<dest_imgpath> Filepath to destination image (.jpg, .jpeg, .png)
--images=<folder> Folderpath to images
--width=<width> Custom width of the images/video [default: 500]
--height=<height> Custom height of the images/video [default: 600]
--num=<num_frames> Number of morph frames [default: 20]
--fps=<fps> Number frames per second for the video [default: 10]
--out_frames=<folder> Folder path to save all image frames
--out_video=<filename> Filename to save a video
--plot Flag to plot images to result.png [default: False]
--background=<bg> Background of images to be one of (black|transparent|average) [default: black]
--version Show version.
"""
from docopt import docopt
import os
import numpy as np
import cv2
from facemorpher import locator
from facemorpher import aligner
from facemorpher import warper
from facemorpher import blender
from facemorpher import plotter
from facemorpher import videoer
def verify_args(args):
if args['--images'] is None:
valid = os.path.isfile(args['--src']) & os.path.isfile(args['--dest'])
if not valid:
print('--src=%s or --dest=%s file does not exist. Double check the supplied paths' % (
args['--src'], args['--dest']))
exit(1)
else:
valid = os.path.isdir(args['--images'])
if not valid:
print('--images=%s is not a valid directory' % args['--images'])
exit(1)
def load_image_points(path, size):
img = cv2.imread(path)
points = locator.face_points(img)
if len(points) == 0:
print('No face in %s' % path)
return None, None
else:
return aligner.resize_align(img, points, size)
def load_valid_image_points(imgpaths, size):
for path in imgpaths:
img, points = load_image_points(path, size)
if img is not None:
print(path)
yield (img, points)
def list_imgpaths(images_folder=None, src_image=None, dest_image=None):
if images_folder is None:
yield src_image
yield dest_image
else:
for fname in os.listdir(images_folder):
if (fname.lower().endswith('.jpg') or
fname.lower().endswith('.png') or
fname.lower().endswith('.jpeg')):
yield os.path.join(images_folder, fname)
def morph(src_img, src_points, dest_img, dest_points,
video, width=500, height=600, num_frames=20, fps=10,
out_frames=None, out_video=None, plot=False, background='black'):
"""
Create a morph sequence from source to destination image
:param src_img: ndarray source image
:param src_points: source image array of x,y face points
:param dest_img: ndarray destination image
:param dest_points: destination image array of x,y face points
:param video: facemorpher.videoer.Video object
"""
size = (height, width)
stall_frames = np.clip(int(fps*0.15), 1, fps) # Show first & last longer
plt = plotter.Plotter(plot, num_images=num_frames, out_folder=out_frames)
num_frames -= (stall_frames * 2) # No need to process src and dest image
plt.plot_one(src_img)
video.write(src_img, 1)
# Produce morph frames!
for percent in np.linspace(1, 0, num=num_frames):
points = locator.weighted_average_points(src_points, dest_points, percent)
src_face = warper.warp_image(src_img, src_points, points, size)
end_face = warper.warp_image(dest_img, dest_points, points, size)
average_face = blender.weighted_average(src_face, end_face, percent)
if background in ('transparent', 'average'):
mask = blender.mask_from_points(average_face.shape[:2], points)
average_face = np.dstack((average_face, mask))
if background == 'average':
average_background = blender.weighted_average(src_img, dest_img, percent)
average_face = blender.overlay_image(average_face, mask, average_background)
plt.plot_one(average_face)
plt.save(average_face)
video.write(average_face)
plt.plot_one(dest_img)
video.write(dest_img, stall_frames)
plt.show()
def morpher(imgpaths, width=500, height=600, num_frames=20, fps=10,
out_frames=None, out_video=None, plot=False, background='black'):
"""
Create a morph sequence from multiple images in imgpaths
:param imgpaths: array or generator of image paths
"""
video = videoer.Video(out_video, fps, width, height)
images_points_gen = load_valid_image_points(imgpaths, (height, width))
src_img, src_points = next(images_points_gen)
for dest_img, dest_points in images_points_gen:
morph(src_img, src_points, dest_img, dest_points, video,
width, height, num_frames, fps, out_frames, out_video, plot, background)
src_img, src_points = dest_img, dest_points
video.end()
def main():
args = docopt(__doc__, version='Face Morpher 1.0')
verify_args(args)
morpher(list_imgpaths(args['--images'], args['--src'], args['--dest']),
int(args['--width']), int(args['--height']),
int(args['--num']), int(args['--fps']),
args['--out_frames'], args['--out_video'],
args['--plot'], args['--background'])
if __name__ == "__main__":
main()
+90
View File
@@ -0,0 +1,90 @@
"""
Plot and save images
"""
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import os.path
import numpy as np
import cv2
def bgr2rgb(img):
# OpenCV's BGR to RGB
rgb = np.copy(img)
rgb[..., 0], rgb[..., 2] = img[..., 2], img[..., 0]
return rgb
def check_do_plot(func):
def inner(self, *args, **kwargs):
if self.do_plot:
func(self, *args, **kwargs)
return inner
def check_do_save(func):
def inner(self, *args, **kwargs):
if self.do_save:
func(self, *args, **kwargs)
return inner
class Plotter(object):
def __init__(self, plot=True, rows=0, cols=0, num_images=0, out_folder=None, out_filename=None):
self.save_counter = 1
self.plot_counter = 1
self.do_plot = plot
self.do_save = out_filename is not None
self.out_filename = out_filename
self.set_filepath(out_folder)
if (rows + cols) == 0 and num_images > 0:
# Auto-calculate the number of rows and cols for the figure
self.rows = np.ceil(np.sqrt(num_images / 2.0))
self.cols = np.ceil(num_images / self.rows)
else:
self.rows = rows
self.cols = cols
def set_filepath(self, folder):
if folder is None:
self.filepath = None
return
if not os.path.exists(folder):
os.makedirs(folder)
self.filepath = os.path.join(folder, 'frame{0:03d}.png')
self.do_save = True
@check_do_save
def save(self, img, filename=None):
if self.filepath:
filename = self.filepath.format(self.save_counter)
self.save_counter += 1
elif filename is None:
filename = self.out_filename
mpimg.imsave(filename, bgr2rgb(img))
print(filename + ' saved')
@check_do_plot
def plot_one(self, img):
p = plt.subplot(self.rows, self.cols, self.plot_counter)
p.axes.get_xaxis().set_visible(False)
p.axes.get_yaxis().set_visible(False)
plt.imshow(bgr2rgb(img))
self.plot_counter += 1
@check_do_plot
def show(self):
plt.gcf().subplots_adjust(hspace=0.05, wspace=0,
left=0, bottom=0, right=1, top=0.98)
plt.axis('off')
#plt.show()
plt.savefig('result.png')
@check_do_plot
def plot_mesh(self, points, tri, color='k'):
""" plot triangles """
for tri_indices in tri.simplices:
t_ext = [tri_indices[0], tri_indices[1], tri_indices[2], tri_indices[0]]
plt.plot(points[t_ext, 0], points[t_ext, 1], color)
+36
View File
@@ -0,0 +1,36 @@
"""
Create a video with image frames
"""
import cv2
import numpy as np
def check_write_video(func):
def inner(self, *args, **kwargs):
if self.video:
return func(self, *args, **kwargs)
else:
pass
return inner
class Video(object):
def __init__(self, filename, fps, w, h):
self.filename = filename
if filename is None:
self.video = None
else:
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
self.video = cv2.VideoWriter(filename, fourcc, fps, (w, h), True)
@check_write_video
def write(self, img, num_times=1):
for i in range(num_times):
self.video.write(img[..., :3])
@check_write_video
def end(self):
print(self.filename + ' saved')
self.video.release()
+139
View File
@@ -0,0 +1,139 @@
import numpy as np
import scipy.spatial as spatial
def bilinear_interpolate(img, coords):
""" Interpolates over every image channel
http://en.wikipedia.org/wiki/Bilinear_interpolation
:param img: max 3 channel image
:param coords: 2 x _m_ array. 1st row = xcoords, 2nd row = ycoords
:returns: array of interpolated pixels with same shape as coords
"""
int_coords = np.int32(coords)
x0, y0 = int_coords
dx, dy = coords - int_coords
# 4 Neighour pixels
q11 = img[y0, x0]
q21 = img[y0, x0+1]
q12 = img[y0+1, x0]
q22 = img[y0+1, x0+1]
btm = q21.T * dx + q11.T * (1 - dx)
top = q22.T * dx + q12.T * (1 - dx)
inter_pixel = top * dy + btm * (1 - dy)
return inter_pixel.T
def grid_coordinates(points):
""" x,y grid coordinates within the ROI of supplied points
:param points: points to generate grid coordinates
:returns: array of (x, y) coordinates
"""
xmin = np.min(points[:, 0])
xmax = np.max(points[:, 0]) + 1
ymin = np.min(points[:, 1])
ymax = np.max(points[:, 1]) + 1
return np.asarray([(x, y) for y in range(ymin, ymax)
for x in range(xmin, xmax)], np.uint32)
def process_warp(src_img, result_img, tri_affines, dst_points, delaunay):
"""
Warp each triangle from the src_image only within the
ROI of the destination image (points in dst_points).
"""
roi_coords = grid_coordinates(dst_points)
# indices to vertices. -1 if pixel is not in any triangle
roi_tri_indices = delaunay.find_simplex(roi_coords)
for simplex_index in range(len(delaunay.simplices)):
coords = roi_coords[roi_tri_indices == simplex_index]
num_coords = len(coords)
out_coords = np.dot(tri_affines[simplex_index],
np.vstack((coords.T, np.ones(num_coords))))
x, y = coords.T
result_img[y, x] = bilinear_interpolate(src_img, out_coords)
return None
def triangular_affine_matrices(vertices, src_points, dest_points):
"""
Calculate the affine transformation matrix for each
triangle (x,y) vertex from dest_points to src_points
:param vertices: array of triplet indices to corners of triangle
:param src_points: array of [x, y] points to landmarks for source image
:param dest_points: array of [x, y] points to landmarks for destination image
:returns: 2 x 3 affine matrix transformation for a triangle
"""
ones = [1, 1, 1]
for tri_indices in vertices:
src_tri = np.vstack((src_points[tri_indices, :].T, ones))
dst_tri = np.vstack((dest_points[tri_indices, :].T, ones))
mat = np.dot(src_tri, np.linalg.inv(dst_tri))[:2, :]
yield mat
def warp_image(src_img, src_points, dest_points, dest_shape, dtype=np.uint8):
# Resultant image will not have an alpha channel
num_chans = 3
src_img = src_img[:, :, :3]
rows, cols = dest_shape[:2]
result_img = np.zeros((rows, cols, num_chans), dtype)
delaunay = spatial.Delaunay(dest_points)
tri_affines = np.asarray(list(triangular_affine_matrices(
delaunay.simplices, src_points, dest_points)))
process_warp(src_img, result_img, tri_affines, dest_points, delaunay)
return result_img
def test_local():
from functools import partial
import cv2
import scipy.misc
import locator
import aligner
from matplotlib import pyplot as plt
# Load source image
face_points_func = partial(locator.face_points, '../data')
base_path = '../females/Screenshot 2015-03-04 17.11.12.png'
src_path = '../females/BlDmB5QCYAAY8iw.jpg'
src_img = cv2.imread(src_path)
# Define control points for warps
src_points = face_points_func(src_path)
base_img = cv2.imread(base_path)
base_points = face_points_func(base_path)
size = (600, 500)
src_img, src_points = aligner.resize_align(src_img, src_points, size)
base_img, base_points = aligner.resize_align(base_img, base_points, size)
result_points = locator.weighted_average_points(src_points, base_points, 0.2)
# Perform transform
dst_img1 = warp_image(src_img, src_points, result_points, size)
dst_img2 = warp_image(base_img, base_points, result_points, size)
import blender
ave = blender.weighted_average(dst_img1, dst_img2, 0.6)
mask = blender.mask_from_points(size, result_points)
blended_img = blender.poisson_blend(dst_img1, dst_img2, mask)
plt.subplot(2, 2, 1)
plt.imshow(ave)
plt.subplot(2, 2, 2)
plt.imshow(dst_img1)
plt.subplot(2, 2, 3)
plt.imshow(dst_img2)
plt.subplot(2, 2, 4)
plt.imshow(blended_img)
plt.show()
if __name__ == "__main__":
test_local()
+26
View File
@@ -0,0 +1,26 @@
###############
# conststants #
###############
DEFAULT_TARGET_GUIDANCE_SCALE = 15
DEFAULT_SOURCE_GUIDANCE_SCALE = 3.5
DEFAULT_DIFFUSION_STEPS = 100
DEFAULT_SKIP_STEPS = 36
DEFAULT_SEED = 0
DEFAULT_SEGA_CONCEPT_GUIDANCE_SCALE = 7
DEFAULT_WARMUP_STEPS = 2
DEFAULT_THRESHOLD = 0.95
DEFAULT_NEGATIVE_GUIDANCE=False
STYLE_SEGA_CONCEPT_GUIDANCE_SCALE = 8
STYLE_WARMUP_STEPS = 2
STYLE_THRESHOLD = 0.92
FACE_SEGA_CONCEPT_GUIDANCE_SCALE = 5
FACE_WARMUP_STEPS = 2
FACE_THRESHOLD = 0.95
OBJECT_SEGA_CONCEPT_GUIDANCE_SCALE = 15
OBJECT_WARMUP_STEPS = 5
OBJECT_THRESHOLD = 0.95
+275
View File
@@ -0,0 +1,275 @@
import torch
import os
from tqdm import tqdm
from PIL import Image, ImageDraw ,ImageFont
from matplotlib import pyplot as plt
import torchvision.transforms as T
import os
import yaml
import numpy as np
def load_512(image_path, left=0, right=0, top=0, bottom=0, device=None):
if type(image_path) is str:
image = np.array(Image.open(image_path).convert('RGB'))[:, :, :3]
else:
image = image_path
h, w, c = image.shape
left = min(left, w-1)
right = min(right, w - left - 1)
top = min(top, h - left - 1)
bottom = min(bottom, h - top - 1)
image = image[top:h-bottom, left:w-right]
h, w, c = image.shape
if h < w:
offset = (w - h) // 2
image = image[:, offset:offset + h]
elif w < h:
offset = (h - w) // 2
image = image[offset:offset + w]
image = np.array(Image.fromarray(image).resize((512, 512)))
image = torch.from_numpy(image).float() / 127.5 - 1
image = image.permute(2, 0, 1).unsqueeze(0).to(device, dtype =torch.float32)
return image
def mu_tilde(model, xt,x0, timestep):
"mu_tilde(x_t, x_0) DDPM paper eq. 7"
prev_timestep = timestep - model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps
alpha_prod_t_prev = model.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else model.scheduler.final_alpha_cumprod
alpha_t = model.scheduler.alphas[timestep]
beta_t = 1 - alpha_t
alpha_bar = model.scheduler.alphas_cumprod[timestep]
return ((alpha_prod_t_prev ** 0.5 * beta_t) / (1-alpha_bar)) * x0 + ((alpha_t**0.5 *(1-alpha_prod_t_prev)) / (1- alpha_bar))*xt
def sample_xts_from_x0(model, x0, num_inference_steps=50):
"""
Samples from P(x_1:T|x_0)
"""
# torch.manual_seed(43256465436)
alpha_bar = model.scheduler.alphas_cumprod
sqrt_one_minus_alpha_bar = (1-alpha_bar) ** 0.5
alphas = model.scheduler.alphas
betas = 1 - alphas
variance_noise_shape = (
num_inference_steps,
model.unet.in_channels,
model.unet.sample_size,
model.unet.sample_size)
timesteps = model.scheduler.timesteps.to(model.device)
t_to_idx = {int(v):k for k,v in enumerate(timesteps)}
xts = torch.zeros(variance_noise_shape).to(x0.device, dtype =torch.float16)
for t in reversed(timesteps):
idx = t_to_idx[int(t)]
xts[idx] = x0 * (alpha_bar[t] ** 0.5) + torch.randn_like(x0, dtype =torch.float16) * sqrt_one_minus_alpha_bar[t]
xts = torch.cat([xts, x0 ],dim = 0)
return xts
def encode_text(model, prompts):
text_input = model.tokenizer(
prompts,
padding="max_length",
max_length=model.tokenizer.model_max_length,
truncation=True,
return_tensors="pt",
)
with torch.no_grad():
text_encoding = model.text_encoder(text_input.input_ids.to(model.device))[0]
return text_encoding
def forward_step(model, model_output, timestep, sample):
next_timestep = min(model.scheduler.config.num_train_timesteps - 2,
timestep + model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps)
# 2. compute alphas, betas
alpha_prod_t = model.scheduler.alphas_cumprod[timestep]
# alpha_prod_t_next = self.scheduler.alphas_cumprod[next_timestep] if next_ltimestep >= 0 else self.scheduler.final_alpha_cumprod
beta_prod_t = 1 - alpha_prod_t
# 3. compute predicted original sample from predicted noise also called
# "predicted x_0" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
pred_original_sample = (sample - beta_prod_t ** (0.5) * model_output) / alpha_prod_t ** (0.5)
# 5. TODO: simple noising implementatiom
next_sample = model.scheduler.add_noise(pred_original_sample,
model_output,
torch.LongTensor([next_timestep]))
return next_sample
def get_variance(model, timestep): #, prev_timestep):
prev_timestep = timestep - model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps
alpha_prod_t = model.scheduler.alphas_cumprod[timestep]
alpha_prod_t_prev = model.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else model.scheduler.final_alpha_cumprod
beta_prod_t = 1 - alpha_prod_t
beta_prod_t_prev = 1 - alpha_prod_t_prev
variance = (beta_prod_t_prev / beta_prod_t) * (1 - alpha_prod_t / alpha_prod_t_prev)
return variance
def inversion_forward_process(model, x0,
etas = None,
prog_bar = False,
prompt = "",
cfg_scale = 3.5,
num_inference_steps=50, eps = None):
if not prompt=="":
text_embeddings = encode_text(model, prompt)
uncond_embedding = encode_text(model, "")
timesteps = model.scheduler.timesteps.to(model.device)
variance_noise_shape = (
num_inference_steps,
model.unet.in_channels,
model.unet.sample_size,
model.unet.sample_size)
if etas is None or (type(etas) in [int, float] and etas == 0):
eta_is_zero = True
zs = None
else:
eta_is_zero = False
if type(etas) in [int, float]: etas = [etas]*model.scheduler.num_inference_steps
xts = sample_xts_from_x0(model, x0, num_inference_steps=num_inference_steps)
alpha_bar = model.scheduler.alphas_cumprod
zs = torch.zeros(size=variance_noise_shape, device=model.device, dtype =torch.float16)
t_to_idx = {int(v):k for k,v in enumerate(timesteps)}
xt = x0
op = tqdm(reversed(timesteps), desc= "Inverting...") if prog_bar else reversed(timesteps)
for t in op:
idx = t_to_idx[int(t)]
# 1. predict noise residual
if not eta_is_zero:
xt = xts[idx][None]
with torch.no_grad():
out = model.unet.forward(xt, timestep = t, encoder_hidden_states = uncond_embedding)
if not prompt=="":
cond_out = model.unet.forward(xt, timestep=t, encoder_hidden_states = text_embeddings)
if not prompt=="":
## classifier free guidance
noise_pred = out.sample + cfg_scale * (cond_out.sample - out.sample)
else:
noise_pred = out.sample
if eta_is_zero:
# 2. compute more noisy image and set x_t -> x_t+1
xt = forward_step(model, noise_pred, t, xt)
else:
xtm1 = xts[idx+1][None]
# pred of x0
pred_original_sample = (xt - (1-alpha_bar[t]) ** 0.5 * noise_pred ) / alpha_bar[t] ** 0.5
# direction to xt
prev_timestep = t - model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps
alpha_prod_t_prev = model.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else model.scheduler.final_alpha_cumprod
variance = get_variance(model, t)
pred_sample_direction = (1 - alpha_prod_t_prev - etas[idx] * variance ) ** (0.5) * noise_pred
mu_xt = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction
z = (xtm1 - mu_xt ) / ( etas[idx] * variance ** 0.5 )
zs[idx] = z
# correction to avoid error accumulation
xtm1 = mu_xt + ( etas[idx] * variance ** 0.5 )*z
xts[idx+1] = xtm1
if not zs is None:
zs[-1] = torch.zeros_like(zs[-1])
return xt, zs, xts
def reverse_step(model, model_output, timestep, sample, eta = 0, variance_noise=None):
# 1. get previous step value (=t-1)
prev_timestep = timestep - model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps
# 2. compute alphas, betas
alpha_prod_t = model.scheduler.alphas_cumprod[timestep]
alpha_prod_t_prev = model.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else model.scheduler.final_alpha_cumprod
beta_prod_t = 1 - alpha_prod_t
# 3. compute predicted original sample from predicted noise also called
# "predicted x_0" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
pred_original_sample = (sample - beta_prod_t ** (0.5) * model_output) / alpha_prod_t ** (0.5)
# 5. compute variance: "sigma_t(η)" -> see formula (16)
# σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1)
# variance = self.scheduler._get_variance(timestep, prev_timestep)
variance = get_variance(model, timestep) #, prev_timestep)
std_dev_t = eta * variance ** (0.5)
# Take care of asymetric reverse process (asyrp)
model_output_direction = model_output
# 6. compute "direction pointing to x_t" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
# pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * model_output_direction
pred_sample_direction = (1 - alpha_prod_t_prev - eta * variance) ** (0.5) * model_output_direction
# 7. compute x_t without "random noise" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
prev_sample = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction
# 8. Add noice if eta > 0
if eta > 0:
if variance_noise is None:
variance_noise = torch.randn(model_output.shape, device=model.device, dtype =torch.float16)
sigma_z = eta * variance ** (0.5) * variance_noise
prev_sample = prev_sample + sigma_z
return prev_sample
def inversion_reverse_process(model,
xT,
etas = 0,
prompts = "",
cfg_scales = None,
prog_bar = False,
zs = None,
controller=None,
asyrp = False):
batch_size = len(prompts)
cfg_scales_tensor = torch.Tensor(cfg_scales).view(-1,1,1,1).to(model.device, dtype=torch.float16)
text_embeddings = encode_text(model, prompts)
uncond_embedding = encode_text(model, [""] * batch_size)
if etas is None: etas = 0
if type(etas) in [int, float]: etas = [etas]*model.scheduler.num_inference_steps
assert len(etas) == model.scheduler.num_inference_steps
timesteps = model.scheduler.timesteps.to(model.device)
xt = xT.expand(batch_size, -1, -1, -1)
op = tqdm(timesteps[-zs.shape[0]:]) if prog_bar else timesteps[-zs.shape[0]:]
t_to_idx = {int(v):k for k,v in enumerate(timesteps[-zs.shape[0]:])}
for t in op:
idx = t_to_idx[int(t)]
## Unconditional embedding
with torch.no_grad():
uncond_out = model.unet.forward(xt, timestep = t,
encoder_hidden_states = uncond_embedding)
## Conditional embedding
if prompts:
with torch.no_grad():
cond_out = model.unet.forward(xt, timestep = t,
encoder_hidden_states = text_embeddings)
z = zs[idx] if not zs is None else None
z = z.expand(batch_size, -1, -1, -1)
if prompts:
## classifier free guidance
noise_pred = uncond_out.sample + cfg_scales_tensor * (cond_out.sample - uncond_out.sample)
else:
noise_pred = uncond_out.sample
# 2. compute less noisy image and set x_t -> x_t-1
xt = reverse_step(model, noise_pred, t, xt, eta = etas[idx], variance_noise = z)
if controller is not None:
xt = controller.step_callback(xt)
return xt, zs
@@ -0,0 +1,135 @@
import gradio as gr
import torch
import numpy as np
import requests
import random
from io import BytesIO
from utils import *
from constants import *
from inversion_utils import *
from modified_pipeline_semantic_stable_diffusion import SemanticStableDiffusionPipeline
from torch import autocast, inference_mode
from diffusers import StableDiffusionPipeline
from diffusers import DDIMScheduler
from transformers import AutoProcessor, BlipForConditionalGeneration
from share_btn import community_icon_html, loading_icon_html, share_js
from PIL import ImageFile
import random
# load pipelines
sd_model_id = "sd_model_v1-5"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
sd_pipe = StableDiffusionPipeline.from_pretrained(sd_model_id).to(device)
sd_pipe.scheduler = DDIMScheduler.from_config(sd_model_id, subfolder="scheduler")
sega_pipe = SemanticStableDiffusionPipeline.from_pretrained(sd_model_id).to(device)
ImageFile.LOAD_TRUNCATED_IMAGES = True
input_image = "./examples/00001.png" # @param
source_prompt = "human face" # @param
target_prompt = "make up like a clown" # @param
num_diffusion_steps = 100 # @param
source_guidance_scale = 0 # @param
reconstruct = True # @param
skip_steps = 50 # @param
target_guidance_scale = 10 # @param
# SEGA only params
edit_concepts = ["star makeup", "heart makeup"] # @param
edit_guidance_scales = [7, 15] # @param
warmup_steps = [1, 1] # @param
reverse_editing = [True, False] # @param
thresholds = [0.95, 0.95] # @param
def invert(x0: torch.FloatTensor, prompt_src: str = "", num_inference_steps=100, cfg_scale_src=3.5, eta=1):
# inverts a real image according to Algorihm 1 in https://arxiv.org/pdf/2304.06140.pdf,
# based on the code in https://github.com/inbarhub/DDPM_inversion
# returns wt, zs, wts:
# wt - inverted latent
# wts - intermediate inverted latents
# zs - noise maps
sd_pipe.scheduler.set_timesteps(num_diffusion_steps)
# vae encode image
with autocast("cuda"), inference_mode():
w0 = (sd_pipe.vae.encode(x0).latent_dist.mode() * 0.18215).float()
# find Zs and wts - forward process
wt, zs, wts = inversion_forward_process(sd_pipe, w0, etas=eta, prompt=prompt_src, cfg_scale=cfg_scale_src,
prog_bar=True, num_inference_steps=num_diffusion_steps)
return zs, wts
def sample(zs, wts, prompt_tar="", cfg_scale_tar=15, skip=36, eta=1):
# reverse process (via Zs and wT)
w0, _ = inversion_reverse_process(sd_pipe, xT=wts[skip], etas=eta, prompts=[prompt_tar], cfg_scales=[cfg_scale_tar],
prog_bar=True, zs=zs[skip:])
# vae decode image
with autocast("cuda"), inference_mode():
x0_dec = sd_pipe.vae.decode(1 / 0.18215 * w0).sample
if x0_dec.dim() < 4:
x0_dec = x0_dec[None, :, :, :]
img = image_grid(x0_dec)
return img
def edit(wts, zs,
tar_prompt="",
steps=100,
skip=36,
tar_cfg_scale=8,
edit_concept="",
guidnace_scale=7,
warmup=1,
neg_guidance=False,
threshold=0.95
):
# SEGA
# parse concepts and neg guidance
editing_args = dict(
editing_prompt=edit_concept,
reverse_editing_direction=neg_guidance,
edit_warmup_steps=warmup,
edit_guidance_scale=guidnace_scale,
edit_threshold=threshold,
edit_momentum_scale=0.5,
edit_mom_beta=0.6,
eta=1,
)
latnets = wts[skip].expand(1, -1, -1, -1)
sega_out = sega_pipe(prompt=tar_prompt, latents=latnets, guidance_scale=tar_cfg_scale,
num_images_per_prompt=1,
num_inference_steps=steps,
use_ddpm=True, wts=wts, zs=zs[skip:], **editing_args)
return sega_out.images[0]
with open('prompt.txt') as file:
lines = file.readlines()
lines = [line.strip() for line in lines]
for i in range(10000):
try:
input_image = 'origin_face' + str(i + 1) + '.png'
randn = random.randint(0, len(lines)-1)
target_prompt = lines[randn]
x0 = load_512(input_image, device=device)
# noise maps and latents
zs, wts = invert(x0=x0, prompt_src=source_prompt, num_inference_steps=num_diffusion_steps,
cfg_scale_src=source_guidance_scale)
if reconstruct:
ddpm_out_img = sample(zs, wts, prompt_tar=target_prompt, skip=skip_steps, cfg_scale_tar=target_guidance_scale)
ddpm_out_img.save(f'makeup/edit_{i+1}.png')
except:
continue
@@ -0,0 +1,754 @@
import inspect
import warnings
from itertools import repeat
from typing import Callable, List, Optional, Union
import torch
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer
from diffusers.image_processor import VaeImageProcessor
from diffusers.models import AutoencoderKL, UNet2DConditionModel
from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
from diffusers.schedulers import KarrasDiffusionSchedulers
from diffusers.utils import logging
from diffusers.utils.torch_utils import randn_tensor
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput
# from . import SemanticStableDiffusionPipelineOutput
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
class SemanticStableDiffusionPipeline(DiffusionPipeline):
r"""
Pipeline for text-to-image generation with latent editing.
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
This model builds on the implementation of ['StableDiffusionPipeline']
Args:
vae ([`AutoencoderKL`]):
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
text_encoder ([`CLIPTextModel`]):
Frozen text-encoder. Stable Diffusion uses the text portion of
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
tokenizer (`CLIPTokenizer`):
Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents.
scheduler ([`SchedulerMixin`]):
A scheduler to be used in combination with `unet` to denoise the encoded image latens. Can be one of
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
safety_checker ([`Q16SafetyChecker`]):
Classification module that estimates whether generated images could be considered offensive or harmful.
Please, refer to the [model card](https://huggingface.co/CompVis/stable-diffusion-v1-4) for details.
feature_extractor ([`CLIPImageProcessor`]):
Model that extracts features from generated images to be used as inputs for the `safety_checker`.
"""
_optional_components = ["safety_checker", "feature_extractor"]
def __init__(
self,
vae: AutoencoderKL,
text_encoder: CLIPTextModel,
tokenizer: CLIPTokenizer,
unet: UNet2DConditionModel,
scheduler: KarrasDiffusionSchedulers,
safety_checker: StableDiffusionSafetyChecker,
feature_extractor: CLIPImageProcessor,
requires_safety_checker: bool = True,
):
super().__init__()
if safety_checker is None and requires_safety_checker:
logger.warning(
f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure"
" that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered"
" results in services or applications open to the public. Both the diffusers team and Hugging Face"
" strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling"
" it only for use-cases that involve analyzing network behavior or auditing its results. For more"
" information, please have a look at https://github.com/huggingface/diffusers/pull/254 ."
)
if safety_checker is not None and feature_extractor is None:
raise ValueError(
"Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety"
" checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead."
)
self.register_modules(
vae=vae,
text_encoder=text_encoder,
tokenizer=tokenizer,
unet=unet,
scheduler=scheduler,
safety_checker=safety_checker,
feature_extractor=feature_extractor,
)
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
self.register_to_config(requires_safety_checker=requires_safety_checker)
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker
def run_safety_checker(self, image, device, dtype):
if self.safety_checker is None:
has_nsfw_concept = None
else:
if torch.is_tensor(image):
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
else:
feature_extractor_input = self.image_processor.numpy_to_pil(image)
safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
image, has_nsfw_concept = self.safety_checker(
images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
)
return image, has_nsfw_concept
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents
def decode_latents(self, latents):
warnings.warn(
"The decode_latents method is deprecated and will be removed in a future version. Please"
" use VaeImageProcessor instead",
FutureWarning,
)
latents = 1 / self.vae.config.scaling_factor * latents
image = self.vae.decode(latents, return_dict=False)[0]
image = (image / 2 + 0.5).clamp(0, 1)
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
return image
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
def prepare_extra_step_kwargs(self, generator, eta):
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
# and should be between [0, 1]
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
extra_step_kwargs = {}
if accepts_eta:
extra_step_kwargs["eta"] = eta
# check if the scheduler accepts generator
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
if accepts_generator:
extra_step_kwargs["generator"] = generator
return extra_step_kwargs
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.check_inputs
def check_inputs(
self,
prompt,
height,
width,
callback_steps,
negative_prompt=None,
prompt_embeds=None,
negative_prompt_embeds=None,
):
if height % 8 != 0 or width % 8 != 0:
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
if (callback_steps is None) or (
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
):
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
if prompt is not None and prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
elif prompt is None and prompt_embeds is None:
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
if negative_prompt is not None and negative_prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
if prompt_embeds is not None and negative_prompt_embeds is not None:
if prompt_embeds.shape != negative_prompt_embeds.shape:
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor)
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
latents = latents.to(device)
# scale the initial noise by the standard deviation required by the scheduler
latents = latents * self.scheduler.init_noise_sigma
return latents
@torch.no_grad()
def __call__(
self,
prompt: Union[str, List[str]],
height: Optional[int] = None,
width: Optional[int] = None,
num_inference_steps: int = 50,
guidance_scale: float = 7.5,
negative_prompt: Optional[Union[str, List[str]]] = None,
num_images_per_prompt: int = 1,
eta: float = 0.0,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
latents: Optional[torch.FloatTensor] = None,
output_type: Optional[str] = "pil",
return_dict: bool = True,
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
callback_steps: int = 1,
editing_prompt: Optional[Union[str, List[str]]] = None,
editing_prompt_embeddings: Optional[torch.Tensor] = None,
reverse_editing_direction: Optional[Union[bool, List[bool]]] = False,
edit_guidance_scale: Optional[Union[float, List[float]]] = 5,
edit_warmup_steps: Optional[Union[int, List[int]]] = 10,
edit_cooldown_steps: Optional[Union[int, List[int]]] = None,
edit_threshold: Optional[Union[float, List[float]]] = 0.9,
edit_momentum_scale: Optional[float] = 0.1,
edit_mom_beta: Optional[float] = 0.4,
edit_weights: Optional[List[float]] = None,
sem_guidance: Optional[List[torch.Tensor]] = None,
# DDPM additions
use_ddpm: bool = False,
wts: Optional[List[torch.Tensor]] = None,
zs: Optional[List[torch.Tensor]] = None
):
r"""
Function invoked when calling the pipeline for generation.
Args:
prompt (`str` or `List[str]`):
The prompt or prompts to guide the image generation.
height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
The height in pixels of the generated image.
width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
The width in pixels of the generated image.
num_inference_steps (`int`, *optional*, defaults to 50):
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
expense of slower inference.
guidance_scale (`float`, *optional*, defaults to 7.5):
Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
`guidance_scale` is defined as `w` of equation 2. of [Imagen
Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
usually at the expense of lower image quality.
negative_prompt (`str` or `List[str]`, *optional*):
The prompt or prompts not to guide the image generation. Ignored when not using guidance (i.e., ignored
if `guidance_scale` is less than `1`).
num_images_per_prompt (`int`, *optional*, defaults to 1):
The number of images to generate per prompt.
eta (`float`, *optional*, defaults to 0.0):
Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to
[`schedulers.DDIMScheduler`], will be ignored for others.
generator (`torch.Generator`, *optional*):
One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
to make generation deterministic.
latents (`torch.FloatTensor`, *optional*):
Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
tensor will ge generated by sampling using the supplied random `generator`.
output_type (`str`, *optional*, defaults to `"pil"`):
The output format of the generate image. Choose between
[PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
return_dict (`bool`, *optional*, defaults to `True`):
Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a
plain tuple.
callback (`Callable`, *optional*):
A function that will be called every `callback_steps` steps during inference. The function will be
called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
callback_steps (`int`, *optional*, defaults to 1):
The frequency at which the `callback` function will be called. If not specified, the callback will be
called at every step.
editing_prompt (`str` or `List[str]`, *optional*):
The prompt or prompts to use for Semantic guidance. Semantic guidance is disabled by setting
`editing_prompt = None`. Guidance direction of prompt should be specified via
`reverse_editing_direction`.
editing_prompt_embeddings (`torch.Tensor>`, *optional*):
Pre-computed embeddings to use for semantic guidance. Guidance direction of embedding should be
specified via `reverse_editing_direction`.
reverse_editing_direction (`bool` or `List[bool]`, *optional*, defaults to `False`):
Whether the corresponding prompt in `editing_prompt` should be increased or decreased.
edit_guidance_scale (`float` or `List[float]`, *optional*, defaults to 5):
Guidance scale for semantic guidance. If provided as list values should correspond to `editing_prompt`.
`edit_guidance_scale` is defined as `s_e` of equation 6 of [SEGA
Paper](https://arxiv.org/pdf/2301.12247.pdf).
edit_warmup_steps (`float` or `List[float]`, *optional*, defaults to 10):
Number of diffusion steps (for each prompt) for which semantic guidance will not be applied. Momentum
will still be calculated for those steps and applied once all warmup periods are over.
`edit_warmup_steps` is defined as `delta` (δ) of [SEGA Paper](https://arxiv.org/pdf/2301.12247.pdf).
edit_cooldown_steps (`float` or `List[float]`, *optional*, defaults to `None`):
Number of diffusion steps (for each prompt) after which semantic guidance will no longer be applied.
edit_threshold (`float` or `List[float]`, *optional*, defaults to 0.9):
Threshold of semantic guidance.
edit_momentum_scale (`float`, *optional*, defaults to 0.1):
Scale of the momentum to be added to the semantic guidance at each diffusion step. If set to 0.0
momentum will be disabled. Momentum is already built up during warmup, i.e. for diffusion steps smaller
than `sld_warmup_steps`. Momentum will only be added to latent guidance once all warmup periods are
finished. `edit_momentum_scale` is defined as `s_m` of equation 7 of [SEGA
Paper](https://arxiv.org/pdf/2301.12247.pdf).
edit_mom_beta (`float`, *optional*, defaults to 0.4):
Defines how semantic guidance momentum builds up. `edit_mom_beta` indicates how much of the previous
momentum will be kept. Momentum is already built up during warmup, i.e. for diffusion steps smaller
than `edit_warmup_steps`. `edit_mom_beta` is defined as `beta_m` (β) of equation 8 of [SEGA
Paper](https://arxiv.org/pdf/2301.12247.pdf).
edit_weights (`List[float]`, *optional*, defaults to `None`):
Indicates how much each individual concept should influence the overall guidance. If no weights are
provided all concepts are applied equally. `edit_mom_beta` is defined as `g_i` of equation 9 of [SEGA
Paper](https://arxiv.org/pdf/2301.12247.pdf).
sem_guidance (`List[torch.Tensor]`, *optional*):
List of pre-generated guidance vectors to be applied at generation. Length of the list has to
correspond to `num_inference_steps`.
Returns:
[`~pipelines.semantic_stable_diffusion.SemanticStableDiffusionPipelineOutput`] or `tuple`:
[`~pipelines.semantic_stable_diffusion.SemanticStableDiffusionPipelineOutput`] if `return_dict` is True,
otherwise a `tuple. When returning a tuple, the first element is a list with the generated images, and the
second element is a list of `bool`s denoting whether the corresponding generated image likely represents
"not-safe-for-work" (nsfw) content, according to the `safety_checker`.
"""
# 0. Default height and width to unet
height = height or self.unet.config.sample_size * self.vae_scale_factor
width = width or self.unet.config.sample_size * self.vae_scale_factor
# 1. Check inputs. Raise error if not correct
self.check_inputs(prompt, height, width, callback_steps)
# 2. Define call parameters
batch_size = 1 if isinstance(prompt, str) else len(prompt)
if editing_prompt:
enable_edit_guidance = True
if isinstance(editing_prompt, str):
editing_prompt = [editing_prompt]
enabled_editing_prompts = len(editing_prompt)
elif editing_prompt_embeddings is not None:
enable_edit_guidance = True
enabled_editing_prompts = editing_prompt_embeddings.shape[0]
else:
enabled_editing_prompts = 0
enable_edit_guidance = False
# get prompt text embeddings
text_inputs = self.tokenizer(
prompt,
padding="max_length",
max_length=self.tokenizer.model_max_length,
return_tensors="pt",
)
text_input_ids = text_inputs.input_ids
if text_input_ids.shape[-1] > self.tokenizer.model_max_length:
removed_text = self.tokenizer.batch_decode(text_input_ids[:, self.tokenizer.model_max_length :])
logger.warning(
"The following part of your input was truncated because CLIP can only handle sequences up to"
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
)
text_input_ids = text_input_ids[:, : self.tokenizer.model_max_length]
text_embeddings = self.text_encoder(text_input_ids.to(self.device))[0]
# duplicate text embeddings for each generation per prompt, using mps friendly method
bs_embed, seq_len, _ = text_embeddings.shape
text_embeddings = text_embeddings.repeat(1, num_images_per_prompt, 1)
text_embeddings = text_embeddings.view(bs_embed * num_images_per_prompt, seq_len, -1)
if enable_edit_guidance:
# get safety text embeddings
if editing_prompt_embeddings is None:
edit_concepts_input = self.tokenizer(
[x for item in editing_prompt for x in repeat(item, batch_size)],
padding="max_length",
max_length=self.tokenizer.model_max_length,
return_tensors="pt",
)
edit_concepts_input_ids = edit_concepts_input.input_ids
if edit_concepts_input_ids.shape[-1] > self.tokenizer.model_max_length:
removed_text = self.tokenizer.batch_decode(
edit_concepts_input_ids[:, self.tokenizer.model_max_length :]
)
logger.warning(
"The following part of your input was truncated because CLIP can only handle sequences up to"
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
)
edit_concepts_input_ids = edit_concepts_input_ids[:, : self.tokenizer.model_max_length]
edit_concepts = self.text_encoder(edit_concepts_input_ids.to(self.device))[0]
else:
edit_concepts = editing_prompt_embeddings.to(self.device).repeat(batch_size, 1, 1)
# duplicate text embeddings for each generation per prompt, using mps friendly method
bs_embed_edit, seq_len_edit, _ = edit_concepts.shape
edit_concepts = edit_concepts.repeat(1, num_images_per_prompt, 1)
edit_concepts = edit_concepts.view(bs_embed_edit * num_images_per_prompt, seq_len_edit, -1)
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
# corresponds to doing no classifier free guidance.
do_classifier_free_guidance = guidance_scale > 1.0
# get unconditional embeddings for classifier free guidance
if do_classifier_free_guidance:
uncond_tokens: List[str]
if negative_prompt is None:
uncond_tokens = [""]
elif type(prompt) is not type(negative_prompt):
raise TypeError(
f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
f" {type(prompt)}."
)
elif isinstance(negative_prompt, str):
uncond_tokens = [negative_prompt]
elif batch_size != len(negative_prompt):
raise ValueError(
f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
" the batch size of `prompt`."
)
else:
uncond_tokens = negative_prompt
max_length = text_input_ids.shape[-1]
uncond_input = self.tokenizer(
uncond_tokens,
padding="max_length",
max_length=max_length,
truncation=True,
return_tensors="pt",
)
uncond_embeddings = self.text_encoder(uncond_input.input_ids.to(self.device))[0]
# duplicate unconditional embeddings for each generation per prompt, using mps friendly method
seq_len = uncond_embeddings.shape[1]
uncond_embeddings = uncond_embeddings.repeat(batch_size, num_images_per_prompt, 1)
uncond_embeddings = uncond_embeddings.view(batch_size * num_images_per_prompt, seq_len, -1)
# For classifier free guidance, we need to do two forward passes.
# Here we concatenate the unconditional and text embeddings into a single batch
# to avoid doing two forward passes
if enable_edit_guidance:
text_embeddings = torch.cat([uncond_embeddings, text_embeddings, edit_concepts])
else:
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
# get the initial random noise unless the user supplied it
# 4. Prepare timesteps
self.scheduler.set_timesteps(num_inference_steps, device=self.device)
timesteps = self.scheduler.timesteps
if use_ddpm:
t_to_idx = {int(v):k for k,v in enumerate(timesteps[-zs.shape[0]:])}
timesteps = timesteps[-zs.shape[0]:]
# 5. Prepare latent variables
num_channels_latents = self.unet.config.in_channels
latents = self.prepare_latents(
batch_size * num_images_per_prompt,
num_channels_latents,
height,
width,
text_embeddings.dtype,
self.device,
generator,
latents,
)
# 6. Prepare extra step kwargs.
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
# Initialize edit_momentum to None
edit_momentum = None
self.uncond_estimates = None
self.text_estimates = None
self.edit_estimates = None
self.sem_guidance = None
for i, t in enumerate(self.progress_bar(timesteps)):
# expand the latents if we are doing classifier free guidance
latent_model_input = (
torch.cat([latents] * (2 + enabled_editing_prompts)) if do_classifier_free_guidance else latents
)
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
# predict the noise residual
noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
# perform guidance
if do_classifier_free_guidance:
noise_pred_out = noise_pred.chunk(2 + enabled_editing_prompts) # [b,4, 64, 64]
noise_pred_uncond, noise_pred_text = noise_pred_out[0], noise_pred_out[1]
noise_pred_edit_concepts = noise_pred_out[2:]
# default text guidance
noise_guidance = guidance_scale * (noise_pred_text - noise_pred_uncond)
# noise_guidance = (noise_pred_text - noise_pred_edit_concepts[0])
if self.uncond_estimates is None:
self.uncond_estimates = torch.zeros((num_inference_steps + 1, *noise_pred_uncond.shape))
self.uncond_estimates[i] = noise_pred_uncond.detach().cpu()
if self.text_estimates is None:
self.text_estimates = torch.zeros((num_inference_steps + 1, *noise_pred_text.shape))
self.text_estimates[i] = noise_pred_text.detach().cpu()
if self.edit_estimates is None and enable_edit_guidance:
self.edit_estimates = torch.zeros(
(num_inference_steps + 1, len(noise_pred_edit_concepts), *noise_pred_edit_concepts[0].shape)
)
if self.sem_guidance is None:
self.sem_guidance = torch.zeros((num_inference_steps + 1, *noise_pred_text.shape))
if edit_momentum is None:
edit_momentum = torch.zeros_like(noise_guidance)
if enable_edit_guidance:
concept_weights = torch.zeros(
(len(noise_pred_edit_concepts), noise_guidance.shape[0]),
device=self.device,
dtype=noise_guidance.dtype,
)
noise_guidance_edit = torch.zeros(
(len(noise_pred_edit_concepts), *noise_guidance.shape),
device=self.device,
dtype=noise_guidance.dtype,
)
# noise_guidance_edit = torch.zeros_like(noise_guidance)
warmup_inds = []
for c, noise_pred_edit_concept in enumerate(noise_pred_edit_concepts):
self.edit_estimates[i, c] = noise_pred_edit_concept
if isinstance(edit_guidance_scale, list):
edit_guidance_scale_c = edit_guidance_scale[c]
else:
edit_guidance_scale_c = edit_guidance_scale
if isinstance(edit_threshold, list):
edit_threshold_c = edit_threshold[c]
else:
edit_threshold_c = edit_threshold
if isinstance(reverse_editing_direction, list):
reverse_editing_direction_c = reverse_editing_direction[c]
else:
reverse_editing_direction_c = reverse_editing_direction
if edit_weights:
edit_weight_c = edit_weights[c]
else:
edit_weight_c = 1.0
if isinstance(edit_warmup_steps, list):
edit_warmup_steps_c = edit_warmup_steps[c]
else:
edit_warmup_steps_c = edit_warmup_steps
if isinstance(edit_cooldown_steps, list):
edit_cooldown_steps_c = edit_cooldown_steps[c]
elif edit_cooldown_steps is None:
edit_cooldown_steps_c = i + 1
else:
edit_cooldown_steps_c = edit_cooldown_steps
if i >= edit_warmup_steps_c:
warmup_inds.append(c)
if i >= edit_cooldown_steps_c:
noise_guidance_edit[c, :, :, :, :] = torch.zeros_like(noise_pred_edit_concept)
continue
noise_guidance_edit_tmp = noise_pred_edit_concept - noise_pred_uncond
# tmp_weights = (noise_pred_text - noise_pred_edit_concept).sum(dim=(1, 2, 3))
tmp_weights = (noise_guidance - noise_pred_edit_concept).sum(dim=(1, 2, 3))
tmp_weights = torch.full_like(tmp_weights, edit_weight_c) # * (1 / enabled_editing_prompts)
if reverse_editing_direction_c:
noise_guidance_edit_tmp = noise_guidance_edit_tmp * -1
concept_weights[c, :] = tmp_weights
noise_guidance_edit_tmp = noise_guidance_edit_tmp * edit_guidance_scale_c
# torch.quantile function expects float32
if noise_guidance_edit_tmp.dtype == torch.float32:
tmp = torch.quantile(
torch.abs(noise_guidance_edit_tmp).flatten(start_dim=2),
edit_threshold_c,
dim=2,
keepdim=False,
)
else:
tmp = torch.quantile(
torch.abs(noise_guidance_edit_tmp).flatten(start_dim=2).to(torch.float32),
edit_threshold_c,
dim=2,
keepdim=False,
).to(noise_guidance_edit_tmp.dtype)
noise_guidance_edit_tmp = torch.where(
torch.abs(noise_guidance_edit_tmp) >= tmp[:, :, None, None],
noise_guidance_edit_tmp,
torch.zeros_like(noise_guidance_edit_tmp),
)
noise_guidance_edit[c, :, :, :, :] = noise_guidance_edit_tmp
# noise_guidance_edit = noise_guidance_edit + noise_guidance_edit_tmp
warmup_inds = torch.tensor(warmup_inds).to(self.device)
if len(noise_pred_edit_concepts) > warmup_inds.shape[0] > 0:
concept_weights = concept_weights.to("cpu") # Offload to cpu
noise_guidance_edit = noise_guidance_edit.to("cpu")
concept_weights_tmp = torch.index_select(concept_weights.to(self.device), 0, warmup_inds)
concept_weights_tmp = torch.where(
concept_weights_tmp < 0, torch.zeros_like(concept_weights_tmp), concept_weights_tmp
)
concept_weights_tmp = concept_weights_tmp / concept_weights_tmp.sum(dim=0)
# concept_weights_tmp = torch.nan_to_num(concept_weights_tmp)
noise_guidance_edit_tmp = torch.index_select(
noise_guidance_edit.to(self.device), 0, warmup_inds
)
noise_guidance_edit_tmp = torch.einsum(
"cb,cbijk->bijk", concept_weights_tmp, noise_guidance_edit_tmp
)
noise_guidance_edit_tmp = noise_guidance_edit_tmp
noise_guidance = noise_guidance + noise_guidance_edit_tmp
self.sem_guidance[i] = noise_guidance_edit_tmp.detach().cpu()
del noise_guidance_edit_tmp
del concept_weights_tmp
concept_weights = concept_weights.to(self.device)
noise_guidance_edit = noise_guidance_edit.to(self.device)
concept_weights = torch.where(
concept_weights < 0, torch.zeros_like(concept_weights), concept_weights
)
concept_weights = torch.nan_to_num(concept_weights)
noise_guidance_edit = torch.einsum("cb,cbijk->bijk", concept_weights, noise_guidance_edit)
noise_guidance_edit = noise_guidance_edit + edit_momentum_scale * edit_momentum
edit_momentum = edit_mom_beta * edit_momentum + (1 - edit_mom_beta) * noise_guidance_edit
if warmup_inds.shape[0] == len(noise_pred_edit_concepts):
noise_guidance = noise_guidance + noise_guidance_edit
self.sem_guidance[i] = noise_guidance_edit.detach().cpu()
if sem_guidance is not None:
edit_guidance = sem_guidance[i].to(self.device)
noise_guidance = noise_guidance + edit_guidance
noise_pred = noise_pred_uncond + noise_guidance
## ddpm ###########################################################
if use_ddpm:
idx = t_to_idx[int(t)]
z = zs[idx] if not zs is None else None
# 1. get previous step value (=t-1)
prev_timestep = t - self.scheduler.config.num_train_timesteps // self.scheduler.num_inference_steps
# 2. compute alphas, betas
alpha_prod_t = self.scheduler.alphas_cumprod[t]
alpha_prod_t_prev = self.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.scheduler.final_alpha_cumprod
beta_prod_t = 1 - alpha_prod_t
# 3. compute predicted original sample from predicted noise also called
# "predicted x_0" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
pred_original_sample = (latents - beta_prod_t ** (0.5) * noise_pred) / alpha_prod_t ** (0.5)
# 5. compute variance: "sigma_t(η)" -> see formula (16)
# σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1)
# variance = self.scheduler._get_variance(timestep, prev_timestep)
# variance = get_variance(model, t) #, prev_timestep)
beta_prod_t_prev = 1 - alpha_prod_t_prev
variance = (beta_prod_t_prev / beta_prod_t) * (1 - alpha_prod_t / alpha_prod_t_prev)
std_dev_t = eta * variance ** (0.5)
# Take care of asymetric reverse process (asyrp)
noise_pred_direction = noise_pred
# 6. compute "direction pointing to x_t" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
# pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * model_output_direction
pred_sample_direction = (1 - alpha_prod_t_prev - eta * variance) ** (0.5) * noise_pred_direction
# 7. compute x_t without "random noise" of formula (12) from https://arxiv.org/pdf/2010.02502.pdf
prev_sample = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction
# 8. Add noice if eta > 0
if eta > 0:
if z is None:
z = torch.randn(noise_pred.shape, device=self.device)
sigma_z = eta * variance ** (0.5) * z
latents = prev_sample + sigma_z
## ddpm ##########################################################
# compute the previous noisy sample x_t -> x_t-1
else: #if not use_ddpm:
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
# call the callback, if provided
if callback is not None and i % callback_steps == 0:
callback(i, t, latents)
# # 8. Post-processing
# image = self.decode_latents(latents)
# # 9. Run safety checker
# image, has_nsfw_concept = self.run_safety_checker(image, self.device, text_embeddings.dtype)
# # 10. Convert to PIL
# if output_type == "pil":
# image = self.numpy_to_pil(image)
# if not return_dict:
# return (image, has_nsfw_concept)
# return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
# 8. Post-processing
if not output_type == "latent":
image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0]
image, has_nsfw_concept = self.run_safety_checker(image, self.device, text_embeddings.dtype)
else:
image = latents
has_nsfw_concept = None
if has_nsfw_concept is None:
do_denormalize = [True] * image.shape[0]
else:
do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept]
image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize)
if not return_dict:
return (image, has_nsfw_concept)
return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
+292
View File
@@ -0,0 +1,292 @@
Make it qilin makeup expressing mythical mercies
Make it jackalope makeup combining whimsy and wanderlust
Make it thunderbird makeup harnessing stormy supremacy
Make it nuckelavee makeup spreading haunting maritime malice
Make it cockatrice makeup radiating curious calamity
Make it manticore makeup manifesting monstrous majesty
Make it basilisk makeup hypnotizing with hidden handsomeness
Make it minotaur makeup representing rugged resilience
Make it chimera makeup captivating with extraordinary eclecticism
Make it centaur makeup celebrating untamed vitality
Make it yeti makeup symbolizing solemn solitude
Make it kraken makeup oozing ancient aqueous authority
Make it hydra makeup asserting immense indifference
Make it kitsune makeup weaving supernatural surprise
Make it cerberus makeup guarding against trespassing terrors
Make it gryphon makeup amalgamating aerie alacrity
Make it wyvern makeup whispering windswept wonderment
Make it pegasus makeup prancing with persistent passion
Make it phoenix makeup perpetuating primal persistency
Make it ceres makeup commanding natural nobility
Make it hecate makeup harnessing haunting lunar luminosity
Make it neptune makeup navigating numinous mysteries
Make it aphrodite makeup radiating romantic rapture
Make it thanatos makeup transporting ephemeral allure
Make it dionysus makeup indulging Dionysian delights
Make it apollo makeup enlightening oracular omniscience
Make it poseidon makeup dominating dashing dominance
Make it hades makeup haunting the underworld's obscure obsessions
Make it athena makeup accentuating adaptive acumen
Make it zeus makeup bearing bolts of benevolent bravado
Make it ra makeup blazing ferocious ethereal fire
Make it amaterasu makeup illuminating innate inspiration
Make it freyja makeup captivating with celestial sensuality
Make it loki makeup twisting tales with mischievous mirth
Make it odin makeup cultivating arcane acumen and acuity
Make it persephone makeup tempting with transient temptations
Make it morrigan makeup coursing ferocity with flecks of fear
Make it cauchemar makeup spreading nocturnal nightmares
Make it nain rouge makeup rousing Dread among the daring
Make it jack o'lantern makeup gleaming with grinning ghosts
Make it yuki-onna makeup whispering winter's wildest wonders
Make it krampus makeup punishing with primordial panic
Make it la llorona makeup luring with lamenting lures
Make it veles makeup communicating communion with courts cryptic
Make it melusine makeup shimmering with serpentine splendor
Make it aswang makeup transforming traveling terrors
Make it nue makeup taking tangled temerity forms
Make it tengu makeup uttering otherworldly omens
Make it cherry soul makeup blooming bittersweet beauty
Make it azuki makeup radiating good fortune's fortunate finds
Make it tsuchigumo makeup scheming shrewd silken shrouds
Make it ubume makeup haunting history's harshest horrors
Make it kappa makeup keeping waters mysterious motives masked
Make it raiju makeup rumbling rambunctious nature's nuance
Make it bake-kujira makeup emanating ethereal evening emanations
Make it ishutski makeup invoking imaginings of icicle islands
Make it futakuchi-onna makeup multiplying misdirecting mobilities
Make it noppera-bo makeup tricking travels in tenebrous times
Make it rokurokubi makeup lengthening lurid legends legacies
Make it namahage makeup menacing midnight mountain mysteries
Make it obake makeup morphing many marvelous manifestations
Make it nogitsune makeup navigating nature's nightly nuances
Make it itsumade makeup imbuing intimations of impermanence
Make it mujina makeup manifesting mercurial metamorphoses
Make it orochi makeup oozing ostentatious octuple opulence
Make it yamabiko makeup resonating resounding rocky regions
Make it mono no ke makeup concealing countenance with cryptic calm
Make it iwanko makeup wandering wistful woodland ways
Make it merlion makeup mixing marine mystique
Make it qilin makeup expressing mythical mysticism
Make it jackalope makeup conjuring whimsical woodland magic
Make it thunderbird makeup manifesting majestic avian aura
Make it cockatrice makeup radiating legendary lethal loveliness
Make it manticore makeup menacing with monstrous magnificence
Make it basilisk makeup hypnotizing with hypnotic handsomeness
Make it minotaur makeup representing mythical muscular might
Make it chimera makeup captivating with chimerical charisma
Make it centaur makeup celebrating classical crossbred charm
Make it yeti makeup yodeling elusive alpine allure
Make it kraken makeup oozing ominous oceanic omnipotence
Make it hydra makeup haunting with heads of horrifying handsomeness
Make it kitsune makeup masking with supernatural slyness
Make it cerberus makeup guarding with ghastly canine grandeur
Make it griffin makeup galvanizing with golden grandeur
Make it wyvern makeup whispering windswept wonder
Make it pegasus makeup prancing with perennial passion
Make it phoenix makeup perpetuating primeval powers
Make it ceres makeup commanding cosmic carnal control
Make it hecate makeup harnessing hauntedlunar luminosity
Make it neptune makeup navigating nepenthean mystique
Make it aphrodite makeup radiating romantic rapture
Make it thanatos makeup transporting ephemeral eternal allure
Make it dionysus makeup indulging divine decadence
Make it apollo makeup enlightening oracular allure
Make it poseidon makeup dominating dashing aquatic style
Make it hades makeup haunting the underworld's enigmatic embrace
Make it athena makeup accentuating agile aesthetic acumen
Make it zeus makeup bearing bold bolts of beauty
Make it ra makeup blazing ferocious ethereal allure
Make it amaterasu makeup illuminating celestial comeliness
Make it freyja makeup captivating with celestial sensuality
Make it loki makeup twisting wiles with a wicked wink
Make it odin makeup patining mystical wisdom and war
Make it persephone makeup tempting with seasonal allure
Make it morrigan makeup couraging ferocity with flecksof fear
Make it fairy makeup bringing bubbly bliss
Make it angel makeup emanating ethereal elegance
Make it knight makeup exuding exemplary excellence
Make it pirate makeup exuding eccentric exuberance
Make it dinosaur makeup roaring with rugged resilience
Make it monkey makeup swinging with silly spirits
Make it peacock makeup displaying dazzling decorations
Make it raccoon makeup masked with mischievous mystery
Make it deer makeup gleaming with gentle grace
Make it owl makeup wise with watchful eyes
Make it llama makeup laughing with likable levity
Make it bee makeup buzzing with busy beauty
Make it shark makeup swimming with savage strength
Make it octopus makeup mystifying with mobile motion
Make it koala makeup cozy with calm care
Make it ladybug makeup lucky with loveable looks
Make it flamingo makeup pretty with pink poise
Make it horse makeup handsome with heroic hustle
Make it tiger makeup tough with tenacious talent
Make it elephant makeup elegant with enduring endurance
Make it penguin makeup playful with preppy polish
Make it dolphin makeup dancing with delightful dexterity
Make it bear makeup brawny with burly brawn
Make it camel makeup companions with considerate care
Make it kangaroo makeup jumping with joyous joviality
Make it mouse makeup merry with mindful mannerisms
Make it turtle makeup tranquil with trustworthy truth
Make it snail makeup steady with surefooted slowness
Make it goat makeup gleeful with gusto and glee
Make it sheep makeup following with faithful flock
Make it donkey makeup dauntless with durable diligence
Make it moose makeup majestic with mighty magnitude
Make it buffalo makeup bold with benevolent brawn
Make it hamster makeup happy with hurried hustle
Make it parrot makeup personable with pretty plumes
Make it deer makeup gracious with gentle grace
Make it otter makeup playful with a love of water
Make it chipmunk makeup charming with cheerfully busy habits
Make it bunny makeup bouncy with a brisk and bold spirit
Make it panda makeup precious with peerless peacefulness
Make it fox makeup foxy with discreet daring
Make it raccoon makeup rascal with reserved resourcefulness
Make it squirrel makeup spirited with swift and sprightly styles
Make it woodpecker makeup zany with zippy zealousness
Make it skunk makeup sly with a striking social scheme
Make it badger makeup brave with bearing and backbone
Make it armadillo makeup armored with amiable armor
Make it opossum makeup opportunistic with occasional optimism
Make it clown makeup spotlighting funny features
Make it zombie makeup emphasizing ghoulish details
Make it unicorn makeup accenting magical aura
Make it cartoon makeup bringing whimsical drawings to life
Make it vampire makeup enhancing ominous presence
Make it fairy makeup highlighting ethereal glow
Make it cat makeup emphasizing feline facial lines
Make it princess makeup framing regal radiance
Make it mime makeup articulating silent stories
Make it bat makeup hovering mysterious shadows
Make it pig makeup oinking humor out front
Make it devil makeup flaming fiendish functions
Make it skeleton makeup gleaming grim grin
Make it mermaid makeup glimmering undersea ambience
Make it witch makeup hexing bewitching beauty
Make it superhero makeup spotlighting courageous character
Make it harlequin makeup jestering jovial jokes
Make it butterfly makeup fluttering fanciful features
Make it panda makeup bearing bearish black and white
Make it flower makeup blossoming botanical beauty
Make it pumpkin makeup gourdifying ghoulish glee
Make it robot makeup powering mechanical mischief
Make it cow makeup mooing mirthful moments
Make it fox makeup cunning crafty complexion
Make it ninja makeup concealing covert craft
Make it peacock makeup feathering flashy face
Make it elf makeup evoking ethereal enchantment
Make it wolf makeup prowling predatory persona
Make it star makeup beaming bright visage
Make it scarecrow makeup stuffing strawfilled fright
Make it angel makeup basking beatific beauty
Make it zombie bride makeup rising reanimated radiance
Make it bumblebee makeup buzzing bold bugliness
Make it dalmatian makeup spotting canine comeliness
Make it phoenix makeup igniting fiery features
Make it chameleon makeup adapting active appearance
Make it tiger makeup stalking striped strength
Make it snake makeup slithering sly visage
Make it raccoon makeup banditing bashful brigandry
Make it spider makeup weaving wicked wonder
Make it geisha makeup flowing fanciful femininity
Make it ballerina makeup dancing delicate disposition
Make it giraffe makeup lengthening lofty looks
Make it frog makeup hopping herpetological humor
Make it merman makeup plunging amphibious aesthetics
Make it pirate makeup plundering privateering panache
Make it rainbow makeup painting prismatic perspective
Make it pegasus makeup soaring celestial spirit
Make it knight makeup marching heroic honor
Make it butterfly makeup fluttering colorful composition
Make it dragon makeup exhaling exotic elegance
Make it witch makeup casting captivating charms
Make it reindeer makeup prancing picturesque personality
Make it disco makeup boogying brassy bombast
Make it teddy makeup bear hugging homey honesty
Make it churro makeup sweetening spirited savoriness
Make it rainbow makeup tinting pristine panoply
Make it mermaid makeup swirling oceanic opulence
Make it unicorn makeup radiating rainbow resplendence
Make it fairy makeup conveying fantastical facets
Make it zombie makeup decomposing dreadful decay
Make it devil makeup depicting damnable dimensions
Make it angel makeup expressing ethereal ends
Make it vampire makeup vivifying vicious visage
Make it witch makeup weaving wizardly worlds
Make it gypsy makeup guiding gyrovago grandeur
Make it ninja makeup narrating nuanced obfuscation
Make it geisha makeup granting gracious gentility
Make it pirate makeup pilfering plundered prosperity
Make it knight makeup chronicling chivalric chronicles
Make it princess makeup privileging pomp and pageantry
Make it wizard makeup weaving wondrous wonders
Make it zombie makeup memorializing macabre mortality
Make it phoenix makeup portending perpetual progression
Make it mermaid makeup chronicling coral chronologies
Make it dragon makeup depicting dazzling diversions
Make it tiger makeup tracking tenacious triumphs
Make it unicorn makeup ushering utopian visions
Make it phoenix makeup projecting prophetic pronouncements
Make it elf makeup emitting ethereal effulgence
Make it wizard makeup wielding wizened wizardry
Make it fairy makeup facilitating fanciful forays
Make it werewolf makeup warding wild wilderness
Make it angel makeup announcing altruistic arrivals
Make it witch makeup working wicked wonders
Make it pirate makeup pilfering precious plunder
Make it princess makeup portraying pure principles
Make it geisha makeup granting gracious gestures
Make it genie makeup granting grandiose gifts
Make it unicorn makeup upholding utopian unity
Make it elf makeup ensuring evanescent enchantment
Make it mermaid makeup maintaining mystical mysteries
Make it phoenix makeup promising perpetual perfection
Make it werewolf makeup howling haunting hymns
Make it vampire makeup venerating vehement victory
Make it witch makeup weaving wonderful worlds
Make it princess makeup portraying peaceful purposes
Make it angel makeup advocating altruistic action
Make it unicorn makeup upholding utopian undertakings
Make it phoenix makeup projecting Promised prosperity
Make it mermaid makeup maintaining mesmeric melodies
Make it elf makeup ensuring effervescent energies
Make it wizard makeup wielding wondrous wonders
Make it genie makeup granting glorious gifts
Make it werewolf makeup howling hallowed horizons
Make it vampire makeup venerating vehement vigors
Make it witch makeup weaving whimsical wonders
Make it mermaid makeup maintaining mystical miracles
Make it phoenix makeup promising perpetual progress
Make it unicorn makeup upholding utopian unity
Make it elf makeup ensuring ethereal enchantments
Make it wizard makeup wielding wizened wisdom
Make it genie makeup granting grand gestures
Make it werewolf makeup howling haunting hymns
Make it vampire makeup venerating vigorous victories
Make it witch makeup weaving whimsical worlds
Make it mermaid makeup maintaining mystical marvels
Make it phoenix makeup portending perpetual prosperity
Make it unicorn makeup upholding utopian understanding
Make it elf makeup ensuring evanescent elation
Make it wizard makeup wielding wizardly works
Make it genie makeup granting glorious gifts
Make it werewolf makeup howling hallowed harmonies
Make it vampire makeup venerating vehement virtues
Make it witch makeup warping wondrous wonders
Make it mermaid makeup maintaining mesmeric mysteries
Make it phoenix makeup promising perpetual perfection
Make it unicorn makeup upholding utopian unity
Make it elf makeup ensuring effervescent endeavors
Make it wizard makeup wielding wizened wisdom
Make it jorogumo makeup spinning sinister silk sorcery
Make it inugami makeup masking merciless mystical motives
Make it nurikabe makeup emerging earthen elegance
Make it tenome makeup fighting fierce phantom forces
Make it tanuki makeup taking trickster talents to task
Make it Yamawaro makeup yodeling yields from young yokai realms
Make it jikininki makeup displaying death's dreaded degradation
Make it nopperabo makeup navigating nightly natural nuances
Make it onikuma makeup merging monstrous mystique with mountain majesty
+110
View File
@@ -0,0 +1,110 @@
community_icon_html = """<svg id="share-btn-share-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32">
<path d="M20.6081 3C21.7684 3 22.8053 3.49196 23.5284 4.38415C23.9756 4.93678 24.4428 5.82749 24.4808 7.16133C24.9674 7.01707 25.4353 6.93643 25.8725 6.93643C26.9833 6.93643 27.9865 7.37587 28.696 8.17411C29.6075 9.19872 30.0124 10.4579 29.8361 11.7177C29.7523 12.3177 29.5581 12.8555 29.2678 13.3534C29.8798 13.8646 30.3306 14.5763 30.5485 15.4322C30.719 16.1032 30.8939 17.5006 29.9808 18.9403C30.0389 19.0342 30.0934 19.1319 30.1442 19.2318C30.6932 20.3074 30.7283 21.5229 30.2439 22.6548C29.5093 24.3704 27.6841 25.7219 24.1397 27.1727C21.9347 28.0753 19.9174 28.6523 19.8994 28.6575C16.9842 29.4379 14.3477 29.8345 12.0653 29.8345C7.87017 29.8345 4.8668 28.508 3.13831 25.8921C0.356375 21.6797 0.754104 17.8269 4.35369 14.1131C6.34591 12.058 7.67023 9.02782 7.94613 8.36275C8.50224 6.39343 9.97271 4.20438 12.4172 4.20438H12.4179C12.6236 4.20438 12.8314 4.2214 13.0364 4.25468C14.107 4.42854 15.0428 5.06476 15.7115 6.02205C16.4331 5.09583 17.134 4.359 17.7682 3.94323C18.7242 3.31737 19.6794 3 20.6081 3ZM20.6081 5.95917C20.2427 5.95917 19.7963 6.1197 19.3039 6.44225C17.7754 7.44319 14.8258 12.6772 13.7458 14.7131C13.3839 15.3952 12.7655 15.6837 12.2086 15.6837C11.1036 15.6837 10.2408 14.5497 12.1076 13.1085C14.9146 10.9402 13.9299 7.39584 12.5898 7.1776C12.5311 7.16799 12.4731 7.16355 12.4172 7.16355C11.1989 7.16355 10.6615 9.33114 10.6615 9.33114C10.6615 9.33114 9.0863 13.4148 6.38031 16.206C3.67434 18.998 3.5346 21.2388 5.50675 24.2246C6.85185 26.2606 9.42666 26.8753 12.0653 26.8753C14.8021 26.8753 17.6077 26.2139 19.1799 25.793C19.2574 25.7723 28.8193 22.984 27.6081 20.6107C27.4046 20.212 27.0693 20.0522 26.6471 20.0522C24.9416 20.0522 21.8393 22.6726 20.5057 22.6726C20.2076 22.6726 19.9976 22.5416 19.9116 22.222C19.3433 20.1173 28.552 19.2325 27.7758 16.1839C27.639 15.6445 27.2677 15.4256 26.746 15.4263C24.4923 15.4263 19.4358 19.5181 18.3759 19.5181C18.2949 19.5181 18.2368 19.4937 18.2053 19.4419C17.6743 18.557 17.9653 17.9394 21.7082 15.6009C25.4511 13.2617 28.0783 11.8545 26.5841 10.1752C26.4121 9.98141 26.1684 9.8956 25.8725 9.8956C23.6001 9.89634 18.2311 14.9403 18.2311 14.9403C18.2311 14.9403 16.7821 16.496 15.9057 16.496C15.7043 16.496 15.533 16.4139 15.4169 16.2112C14.7956 15.1296 21.1879 10.1286 21.5484 8.06535C21.7928 6.66715 21.3771 5.95917 20.6081 5.95917Z" fill="#FF9D00"></path>
<path d="M5.50686 24.2246C3.53472 21.2387 3.67446 18.9979 6.38043 16.206C9.08641 13.4147 10.6615 9.33111 10.6615 9.33111C10.6615 9.33111 11.2499 6.95933 12.59 7.17757C13.93 7.39581 14.9139 10.9401 12.1069 13.1084C9.29997 15.276 12.6659 16.7489 13.7459 14.713C14.8258 12.6772 17.7747 7.44316 19.304 6.44221C20.8326 5.44128 21.9089 6.00204 21.5484 8.06532C21.188 10.1286 14.795 15.1295 15.4171 16.2118C16.0391 17.2934 18.2312 14.9402 18.2312 14.9402C18.2312 14.9402 25.0907 8.49588 26.5842 10.1752C28.0776 11.8545 25.4512 13.2616 21.7082 15.6008C17.9646 17.9393 17.6744 18.557 18.2054 19.4418C18.7372 20.3266 26.9998 13.1351 27.7759 16.1838C28.5513 19.2324 19.3434 20.1173 19.9117 22.2219C20.48 24.3274 26.3979 18.2382 27.6082 20.6107C28.8193 22.9839 19.2574 25.7722 19.18 25.7929C16.0914 26.62 8.24723 28.3726 5.50686 24.2246Z" fill="#FFD21E"></path>
</svg>"""
loading_icon_html = """<svg id="share-btn-loading-icon" style="display:none;" class="animate-spin"
style="color: #ffffff;
"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" fill="none" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="white" stroke-width="4"></circle><path style="opacity: 0.75;" fill="white" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>"""
share_js = r"""async () => {
async function uploadFile(file){
const UPLOAD_URL = 'https://huggingface.co/uploads';
const response = await fetch(UPLOAD_URL, {
method: 'POST',
headers: {
'Content-Type': file.type,
'X-Requested-With': 'XMLHttpRequest',
},
body: file,
});
const url = await response.text();
return url;
}
function getButtonText(componentId) {
const buttonEl = gradioEl.querySelector(`${componentId} button`);
return buttonEl ? buttonEl.textContent : '';
}
const gradioEl = document.querySelector('body > gradio-app');
const imgEls = [gradioEl.querySelector('#input_image img'), gradioEl.querySelector('#output_image img')];
const concepts = [
{ value: getButtonText('#box1'), parent: gradioEl.querySelector('#box1 span[data-testid="block-info"]') },
{ value: getButtonText('#box2'), parent: gradioEl.querySelector('#box2 span[data-testid="block-info"]') },
{ value: getButtonText('#box3'), parent: gradioEl.querySelector('#box3 span[data-testid="block-info"]') }
];
const promptTxt = gradioEl.querySelector('#target_prompt input').value;
const shareBtnEl = gradioEl.querySelector('#share-btn');
const shareIconEl = gradioEl.querySelector('#share-btn-share-icon');
const loadingIconEl = gradioEl.querySelector('#share-btn-loading-icon');
if(!imgEls[1]){
return;
};
shareBtnEl.style.pointerEvents = 'none';
shareIconEl.style.display = 'none';
loadingIconEl.style.removeProperty('display');
async function processImage(imgEl, imgId) {
const res = await fetch(imgEl.src);
const blob = await res.blob();
const fileType = blob.type.includes('png') ? 'png' : 'jpg';
const fileName = `diffuse-the-rest-${imgId}.${fileType}`;
return new File([blob], fileName, { type: blob.type });
}
const files = await Promise.all(imgEls.map((imgEl, index) => processImage(imgEl, Date.now() + index % 200)));
const urls = await Promise.all(files.map((file) => uploadFile(file)));
const labels = ['Source image', 'Target image'];
const htmlImgs = urls.map((url, index) => `<div>${labels[index]}: <img src='${url}' width='400' height='400' style="margin: 0"></div>`);
let descriptionMd = `<div style='display: flex; flex-wrap: wrap; column-gap: 0.75rem;'>${htmlImgs.join(`\n`)}</div>`;
if (promptTxt) {
descriptionMd += `<span style="font-size:1.2em">Target image prompt: <b>${promptTxt}</b></span><br>`;
} else {
descriptionMd += `<span style="font-size:1.2em">Target image prompt: <b>""</b></span><br>`;
}
const conceptHeaders = [];
const conceptDescriptions = [];
const conceptTableRows = [];
concepts.forEach((concept, index) => {
if (concept.value) {
const label = concept.parent.textContent.includes('Negative') ? `remove concept` : `add concept`;
conceptHeaders.push(`<th>${label}</th>`);
conceptDescriptions.push(`${label}: ${concept.value}`);
conceptTableRows.push(`<td>${concept.value}</td>`);
}
});
let title = 'Editing';
if (promptTxt) {
title += ` "${promptTxt}"`;
}
if (conceptDescriptions.length > 0) {
title += ` to ${conceptDescriptions.join(', ')}`;
descriptionMd += `<table style="font-size: 1.1em">
<tr>
${conceptHeaders.join('\n')}
</tr>
<tr>
${conceptTableRows.join('\n')}
</tr>
</table>`;
}
const params = new URLSearchParams({
title: title,
description: descriptionMd,
preview: true,
});
const paramsStr = params.toString();
window.open(`https://huggingface.co/spaces/editing-images/ledits/discussions/new?${paramsStr}`, '_blank');
shareBtnEl.style.removeProperty('pointer-events');
shareIconEl.style.removeProperty('display');
loadingIconEl.style.display = 'none';
}"""
+114
View File
@@ -0,0 +1,114 @@
import PIL
from PIL import Image, ImageDraw ,ImageFont
from matplotlib import pyplot as plt
import torchvision.transforms as T
import os
import torch
import yaml
def show_torch_img(img):
img = to_np_image(img)
plt.imshow(img)
plt.axis("off")
def to_np_image(all_images):
all_images = (all_images.permute(0, 2, 3, 1) * 127.5 + 128).clamp(0, 255).to(torch.uint8).cpu().numpy()[0]
return all_images
def tensor_to_pil(tensor_imgs):
if type(tensor_imgs) == list:
tensor_imgs = torch.cat(tensor_imgs)
tensor_imgs = (tensor_imgs / 2 + 0.5).clamp(0, 1)
to_pil = T.ToPILImage()
pil_imgs = [to_pil(img) for img in tensor_imgs]
return pil_imgs
def pil_to_tensor(pil_imgs):
to_torch = T.ToTensor()
if type(pil_imgs) == PIL.Image.Image:
tensor_imgs = to_torch(pil_imgs).unsqueeze(0)*2-1
elif type(pil_imgs) == list:
tensor_imgs = torch.cat([to_torch(pil_imgs).unsqueeze(0)*2-1 for img in pil_imgs]).to(device)
else:
raise Exception("Input need to be PIL.Image or list of PIL.Image")
return tensor_imgs
## TODO implement this
# n = 10
# num_rows = 4
# num_col = n // num_rows
# num_col = num_col + 1 if n % num_rows else num_col
# num_col
def add_margin(pil_img, top = 0, right = 0, bottom = 0,
left = 0, color = (255,255,255)):
width, height = pil_img.size
new_width = width + right + left
new_height = height + top + bottom
result = Image.new(pil_img.mode, (new_width, new_height), color)
result.paste(pil_img, (left, top))
return result
def image_grid(imgs, rows = 1, cols = None,
size = None,
titles = None, text_pos = (0, 0)):
if type(imgs) == list and type(imgs[0]) == torch.Tensor:
imgs = torch.cat(imgs)
if type(imgs) == torch.Tensor:
imgs = tensor_to_pil(imgs)
if not size is None:
imgs = [img.resize((size,size)) for img in imgs]
if cols is None:
cols = len(imgs)
assert len(imgs) >= rows*cols
top=20
w, h = imgs[0].size
delta = 0
if len(imgs)> 1 and not imgs[1].size[1] == h:
delta = top
h = imgs[1].size[1]
if not titles is None:
font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf",
size = 20, encoding="unic")
h = top + h
grid = Image.new('RGB', size=(cols*w, rows*h+delta))
for i, img in enumerate(imgs):
if not titles is None:
img = add_margin(img, top = top, bottom = 0,left=0)
draw = ImageDraw.Draw(img)
draw.text(text_pos, titles[i],(0,0,0),
font = font)
if not delta == 0 and i > 0:
grid.paste(img, box=(i%cols*w, i//cols*h+delta))
else:
grid.paste(img, box=(i%cols*w, i//cols*h))
return grid
"""
input_folder - dataset folder
"""
def load_dataset(input_folder):
# full_file_names = glob.glob(input_folder)
# class_names = [x[0] for x in os.walk(input_folder)]
class_names = next(os.walk(input_folder))[1]
class_names[:] = [d for d in class_names if not d[0] == '.']
file_names=[]
for class_name in class_names:
cur_path = os.path.join(input_folder, class_name)
filenames = next(os.walk(cur_path), (None, None, []))[2]
filenames = [f for f in filenames if not f[0] == '.']
file_names.append(filenames)
return class_names, file_names
def dataset_from_yaml(yaml_location):
with open(yaml_location, 'r') as stream:
data_loaded = yaml.safe_load(stream)
return data_loaded
+211
View File
@@ -0,0 +1,211 @@
import numpy as np
import tqdm
from PIL import Image
from datasets import load_dataset
from ..spiga.inference.config import ModelConfig
from ..spiga.inference.framework import SPIGAFramework
from ..facelib import FaceDetector
import cv2
import os
processor = SPIGAFramework(ModelConfig("300wpublic"))
def center_crop(image, size):
width, height = image.size
left = (width - size) // 2
top = (height - size) // 2
right = left + size
bottom = top + size
cropped_image = image.crop((left, top, right, bottom))
return cropped_image
def resize(image, size):
width, height = image.size
if width > height:
# 按宽度进行比例调整
new_width = size
new_height = int(height * (size / width))
else:
# 按高度进行比例调整
new_height = size
new_width = int(width * (size / height))
resized_image = image.resize((new_width, new_height))
return resized_image
def preprocess(example, name, path):
image = resize(example, 512)
# 调用中心剪裁函数
cropped_image = center_crop(image, 512)
# 保存剪裁后的图像
cropped_image.save(path+name)
return cropped_image
# We obtain the bbox from the existing landmarks in the dataset.
# We could use `dlib`, but this should be faster.
# Note that the `landmarks` are stored as strings.
def get_landmarks(image, detector):
image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
faces, boxes, scores, landmarks = detector.detect_align(image) # 一定要用align啊
boxes = boxes.cpu().numpy()
box_ls = []
for box in boxes:
x, y, x1, y1 = box
box = x, y, x1 - x, y1 - y
box_ls.append(box)
if len(box_ls) == 0:
return []
else:
features = processor.inference(image, box_ls)
landmarks = np.array(features['landmarks'])
return landmarks
def parse_landmarks(landmarks):
ldm = []
for landmark in landmarks:
ldm.append([(float(x), float(y)) for x, y in landmark])
return ldm
def bbox_from_landmarks(landmarks_):
landmarks = parse_landmarks(landmarks_)
bbox = []
for ldm in landmarks:
landmarks_x, landmarks_y = zip(*ldm)
x_min, x_max = min(landmarks_x), max(landmarks_x)
y_min, y_max = min(landmarks_y), max(landmarks_y)
width = x_max - x_min
height = y_max - y_min
# Give it a little room; I think it works anyway
x_min -= 5
y_min -= 5
width += 10
height += 10
bbox.append((x_min, y_min, width, height))
return bbox
def spiga_process(example, detector):
ldms = get_landmarks(example, detector)
if len(ldms) == 0:
return False
else:
image = example
image = np.array(image)
# BGR
image = image[:, :, ::-1]
bbox = bbox_from_landmarks(ldms)
features = processor.inference(image, [*bbox])
landmarks = features["landmarks"]
spigas = landmarks
return spigas
# For some reason this map doesn't work with num_proc > 1 :(
# TODO: run inference on GPU
# ## "Segmentation"
# We use bezier paths to draw contours and areas.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.path import Path
import PIL
def get_patch(landmarks, color='lime', closed=False):
contour = landmarks
ops = [Path.MOVETO] + [Path.LINETO] * (len(contour) - 1)
facecolor = (0, 0, 0, 0) # Transparent fill color, if open
if closed:
contour.append(contour[0])
ops.append(Path.CLOSEPOLY)
facecolor = color
path = Path(contour, ops)
return patches.PathPatch(path, facecolor=facecolor, edgecolor=color, lw=4)
# Draw to a buffer.
def conditioning_from_landmarks(landmarks_, size=512):
# Precisely control output image size
dpi = 72
fig, ax = plt.subplots(1, figsize=[size / dpi, size / dpi], tight_layout={'pad': 0})
fig.set_dpi(dpi)
black = np.zeros((size, size, 3))
ax.imshow(black)
for landmarks in landmarks_:
face_patch = get_patch(landmarks[0:17])
l_eyebrow = get_patch(landmarks[17:22], color='yellow')
r_eyebrow = get_patch(landmarks[22:27], color='yellow')
nose_v = get_patch(landmarks[27:31], color='orange')
nose_h = get_patch(landmarks[31:36], color='orange')
l_eye = get_patch(landmarks[36:42], color='magenta', closed=True)
r_eye = get_patch(landmarks[42:48], color='magenta', closed=True)
outer_lips = get_patch(landmarks[48:60], color='cyan', closed=True)
inner_lips = get_patch(landmarks[60:68], color='blue', closed=True)
ax.add_patch(face_patch)
ax.add_patch(l_eyebrow)
ax.add_patch(r_eyebrow)
ax.add_patch(nose_v)
ax.add_patch(nose_h)
ax.add_patch(l_eye)
ax.add_patch(r_eye)
ax.add_patch(outer_lips)
ax.add_patch(inner_lips)
plt.axis('off')
fig.canvas.draw()
buffer, (width, height) = fig.canvas.print_to_buffer()
assert width == height
assert width == size
buffer = np.frombuffer(buffer, np.uint8).reshape((height, width, 4))
buffer = buffer[:, :, 0:3]
plt.close(fig)
return PIL.Image.fromarray(buffer)
def spiga_segmentation(spiga):
landmarks = spiga
spiga_seg = conditioning_from_landmarks(landmarks)
return spiga_seg
if __name__ == '__main__':
# ## Obtain SPIGA features
processor = SPIGAFramework(ModelConfig("300wpublic"))
detector = FaceDetector(weight_path="mobilenet0.25_Final.pth")
id_folder = "id"
pose_folder = "pose"
if not os.path.exists(pose_folder):
os.makedirs(pose_folder)
pbar = tqdm.tqdm(os.listdir(id_folder))
for name in pbar:
face = Image.open(id_folder+name).convert("RGB").resize((512, 512))
face.save(id_folder+name)
spigas = spiga_process(face, detector)
if spigas == False:
height = 512
width = 512
channels = 3
black_image = np.zeros((height, width, channels), dtype=np.uint8)
black_image_cv2 = cv2.cvtColor(black_image, cv2.COLOR_RGB2BGR)
continue
else:
spigas_faces = spiga_segmentation(spigas)
spigas_faces.save(pose_folder + name)
View File
File diff suppressed because it is too large Load Diff
+687
View File
@@ -0,0 +1,687 @@
# modified from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py
import torch
import torch.nn as nn
import torch.nn.functional as F
from diffusers.utils.import_utils import is_xformers_available
from torchvision import transforms
if is_xformers_available():
import xformers
import xformers.ops
else:
xformers = None
class SSRAttnProcessor(nn.Module):
r"""
Attention processor for SSR-Adapater.
"""
def __init__(self, hidden_size, cross_attention_dim=None, scale=1):
super().__init__()
self.hidden_size = hidden_size
self.cross_attention_dim = cross_attention_dim
self.scale = scale
# self.to_q_SSR = nn.Linear(hidden_size, hidden_size, bias=False)
self.to_k_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
self.to_v_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
# query = self.to_q_SSR(hidden_states)
query = attn.to_q(hidden_states)
query = attn.head_to_batch_dim(query)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
_hidden_states = encoder_hidden_states
_key = self.to_k_SSR(_hidden_states)
_value = self.to_v_SSR(_hidden_states)
_key = attn.head_to_batch_dim(_key)
_value = attn.head_to_batch_dim(_value)
_attention_probs = attn.get_attention_scores(query, _key, None)
_hidden_states = torch.bmm(_attention_probs, _value)
_hidden_states = attn.batch_to_head_dim(_hidden_states)
hidden_states = self.scale * _hidden_states
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class SSRAttnProcessor2_0(torch.nn.Module):
r"""
Attention processor for SSR-Adapater for PyTorch 2.0.
"""
def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0):
super().__init__()
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
self.hidden_size = hidden_size
self.cross_attention_dim = cross_attention_dim
self.scale = scale
# self.to_q_SSR = nn.Linear(hidden_size, hidden_size, bias=False)
self.to_k_SSR = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
self.to_v_SSR = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1])
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
# query = self.to_q_SSR(hidden_states)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
# split hidden states
_hidden_states = encoder_hidden_states
_key = self.to_k_SSR(_hidden_states)
_value = self.to_v_SSR(_hidden_states)
inner_dim = _key.shape[-1]
head_dim = inner_dim // attn.heads
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
_key = _key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
_value = _value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
_hidden_states = F.scaled_dot_product_attention(
query, _key, _value, attn_mask=None, dropout_p=0.0, is_causal=False
)
_hidden_states = _hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
_hidden_states = _hidden_states.to(query.dtype)
hidden_states = self.scale * _hidden_states
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class AttnProcessor2_0(torch.nn.Module):
r"""
Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0).
"""
def __init__(
self,
hidden_size=None,
cross_attention_dim=None,
):
super().__init__()
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1])
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
inner_dim = key.shape[-1]
head_dim = inner_dim // attn.heads
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
hidden_states = F.scaled_dot_product_attention(
query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
)
hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
hidden_states = hidden_states.to(query.dtype)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class AttnProcessor(nn.Module):
r"""
Default processor for performing attention-related computations.
"""
def __init__(
self,
hidden_size=None,
cross_attention_dim=None,
):
super().__init__()
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
query = attn.head_to_batch_dim(query)
key = attn.head_to_batch_dim(key)
value = attn.head_to_batch_dim(value)
attention_probs = attn.get_attention_scores(query, key, attention_mask)
hidden_states = torch.bmm(attention_probs, value)
hidden_states = attn.batch_to_head_dim(hidden_states)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class ConvAttnProcessor:
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
):
## map to 2D
if len(hidden_states.shape) == 4:
shape = hidden_states.shape
hidden_states = torch.reshape(hidden_states, (shape[0], shape[1], shape[2] * shape[3]))
hidden_states = hidden_states.permute(0, 2, 1)
if encoder_hidden_states is not None:
if len(encoder_hidden_states.shape) == 4:
kv_shape = encoder_hidden_states.shape
encoder_hidden_states = torch.reshape(
encoder_hidden_states, (kv_shape[0], kv_shape[1], kv_shape[2] * kv_shape[3])
)
encoder_hidden_states = encoder_hidden_states.permute(0, 2, 1)
# the same to standard attn
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_cross(encoder_hidden_states)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
query = attn.head_to_batch_dim(query)
key = attn.head_to_batch_dim(key)
value = attn.head_to_batch_dim(value)
attention_probs = attn.get_attention_scores(query, key, attention_mask)
hidden_states = torch.bmm(attention_probs, value)
hidden_states = attn.batch_to_head_dim(hidden_states)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
# map back to 4D
if len(hidden_states.shape) == 3:
hidden_states = hidden_states.permute(0, 2, 1)
hidden_states = torch.reshape(hidden_states, (shape[0], shape[1], shape[2], shape[3]))
return hidden_states
class SSRAttnProcessor_text(nn.Module):
r"""
Attention processor for SSR-Adapater.
"""
def __init__(self, hidden_size, cross_attention_dim=None, scale=1):
super().__init__()
self.text_context_len = 77
self.hidden_size = hidden_size
self.cross_attention_dim = cross_attention_dim
self.scale = scale
self.to_k_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
self.to_v_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
query = attn.to_q(hidden_states)
query = attn.head_to_batch_dim(query)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
# split hidden states
encoder_hidden_states, _hidden_states = encoder_hidden_states[:, :self.text_context_len,
:], encoder_hidden_states[:, self.text_context_len:, :]
encoder_hidden_states = encoder_hidden_states[:, :, :768]
# for text
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
key = attn.head_to_batch_dim(key)
value = attn.head_to_batch_dim(value)
attention_probs = attn.get_attention_scores(query, key, attention_mask)
hidden_states = torch.bmm(attention_probs, value)
hidden_states = attn.batch_to_head_dim(hidden_states)
# for image
_key = self.to_k_SSR(_hidden_states)
_value = self.to_v_SSR(_hidden_states)
_key = attn.head_to_batch_dim(_key)
_value = attn.head_to_batch_dim(_value)
_attention_probs = attn.get_attention_scores(query, _key, None)
_hidden_states = torch.bmm(_attention_probs, _value)
_hidden_states = attn.batch_to_head_dim(_hidden_states)
hidden_states = self.scale * _hidden_states + hidden_states
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class SSRAttnProcessor2_0_text(torch.nn.Module):
r"""
Attention processor for SSR-Adapater for PyTorch 2.0.
"""
def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0):
super().__init__()
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
self.text_context_len = 77
self.hidden_size = hidden_size
self.cross_attention_dim = cross_attention_dim
self.scale = scale
self.to_k_SSR = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
self.to_v_SSR = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False)
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1])
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
# split hidden states
encoder_hidden_states, _hidden_states = encoder_hidden_states[:, :self.text_context_len,
:], encoder_hidden_states[:, self.text_context_len:, :]
encoder_hidden_states = encoder_hidden_states[:, :, :768]
# for text
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
inner_dim = key.shape[-1]
head_dim = inner_dim // attn.heads
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
hidden_states = F.scaled_dot_product_attention(
query, key, value, attn_mask=attention_mask, dropout_p = 0.0, is_causal = False
)
hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
hidden_states = hidden_states.to(query.dtype)
# for image
_key = self.to_k_SSR(_hidden_states)
_value = self.to_v_SSR(_hidden_states)
inner_dim = _key.shape[-1]
head_dim = inner_dim // attn.heads
_key = _key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
_value = _value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
_hidden_states = F.scaled_dot_product_attention(
query, _key, _value, attn_mask=None, dropout_p=0.0, is_causal=False
)
_hidden_states = _hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
_hidden_states = _hidden_states.to(query.dtype)
hidden_states = self.scale * _hidden_states + hidden_states
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class SSRAttnProcessor_visual(nn.Module):
r"""
Attention processor for attn visualization.
"""
def __init__(self, hidden_size, cross_attention_dim=None, scale=1, attnstore=None, place_in_unet=None):
super().__init__()
self.hidden_size = hidden_size
self.cross_attention_dim = cross_attention_dim
self.scale = scale
self.to_k_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
self.to_v_SSR = nn.Linear(cross_attention_dim, hidden_size, bias=False)
self.attnstore = attnstore
self.place_in_unet = place_in_unet
def __call__(
self,
attn,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
)
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
# query = self.to_q_SSR(hidden_states)
query = attn.to_q(hidden_states)
query = attn.head_to_batch_dim(query)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
_hidden_states = encoder_hidden_states
_key = self.to_k_SSR(_hidden_states)
_value = self.to_v_SSR(_hidden_states)
_key = attn.head_to_batch_dim(_key)
_value = attn.head_to_batch_dim(_value)
_attention_probs = attn.get_attention_scores(query, _key, None)
# store attention maps
is_cross = encoder_hidden_states is not None
self.attnstore(_attention_probs, is_cross, self.place_in_unet)
_hidden_states = torch.bmm(_attention_probs, _value)
_hidden_states = attn.batch_to_head_dim(_hidden_states)
hidden_states = self.scale * _hidden_states
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
+113
View File
@@ -0,0 +1,113 @@
from typing import List
import torch
from torchvision import transforms
from transformers import CLIPImageProcessor
from transformers import CLIPVisionModel as OriginalCLIPVisionModel
from ._clip import CLIPVisionModel
from PIL import Image
import torch.nn.functional as F
import torch.nn as nn
import os
def is_torch2_available():
return hasattr(F, "scaled_dot_product_attention")
if is_torch2_available():
from .attention_processor import SSRAttnProcessor2_0 as SSRAttnProcessor, AttnProcessor2_0 as AttnProcessor
else:
from .attention_processor import SSRAttnProcessor, AttnProcessor
from .resampler import Resampler
class detail_encoder(torch.nn.Module):
"""from SSR-encoder"""
def __init__(self, unet, image_encoder_path, device="cuda", dtype=torch.float32):
super().__init__()
self.device = device
self.dtype = dtype
# load image encoder
clip_encoder = OriginalCLIPVisionModel.from_pretrained(image_encoder_path)
self.image_encoder = CLIPVisionModel(clip_encoder.config)
state_dict = clip_encoder.state_dict()
self.image_encoder.load_state_dict(state_dict, strict=False)
self.image_encoder.to(self.device, self.dtype)
del clip_encoder
self.clip_image_processor = CLIPImageProcessor()
# load SSR layers
attn_procs = {}
for name in unet.attn_processors.keys():
cross_attention_dim = None if name.endswith("attn1.processor") else unet.config.cross_attention_dim
if name.startswith("mid_block"):
hidden_size = unet.config.block_out_channels[-1]
elif name.startswith("up_blocks"):
block_id = int(name[len("up_blocks.")])
hidden_size = list(reversed(unet.config.block_out_channels))[block_id]
elif name.startswith("down_blocks"):
block_id = int(name[len("down_blocks.")])
hidden_size = unet.config.block_out_channels[block_id]
if cross_attention_dim is None:
attn_procs[name] = AttnProcessor()
else:
attn_procs[name] = SSRAttnProcessor(hidden_size=hidden_size, cross_attention_dim=1024, scale=1).to(self.device, dtype=self.dtype)
unet.set_attn_processor(attn_procs)
adapter_modules = torch.nn.ModuleList(unet.attn_processors.values())
self.SSR_layers = adapter_modules
self.SSR_layers.to(self.device, dtype=self.dtype)
self.resampler = self.init_proj()
def init_proj(self):
resampler = Resampler().to(self.device, dtype=self.dtype)
return resampler
def forward(self, img):
image_embeds = self.image_encoder(img, output_hidden_states=True)['hidden_states'][2::2]
image_embeds = torch.cat(image_embeds, dim=1)
image_embeds = self.resampler(image_embeds)
return image_embeds
@torch.inference_mode()
def get_image_embeds(self, pil_image):
if isinstance(pil_image, Image.Image):
pil_image = [pil_image]
clip_image = []
for pil in pil_image:
tensor_image = self.clip_image_processor(images=pil, return_tensors="pt").pixel_values.to(self.device, dtype=self.dtype)
clip_image.append(tensor_image)
clip_image = torch.cat(clip_image, dim=0)
# cond
clip_image_embeds = self.image_encoder(clip_image, output_hidden_states=True)['hidden_states'][2::2] # 1 257*12 1024
clip_image_embeds = torch.cat(clip_image_embeds, dim=1)
uncond_clip_image_embeds = self.image_encoder(torch.zeros_like(clip_image), output_hidden_states=True)['hidden_states'][2::2]
uncond_clip_image_embeds = torch.cat(uncond_clip_image_embeds, dim=1)
clip_image_embeds = self.resampler(clip_image_embeds)
uncond_clip_image_embeds = self.resampler(uncond_clip_image_embeds)
return clip_image_embeds, uncond_clip_image_embeds
def generate(
self,
id_image,
makeup_image,
seed=None,
guidance_scale=2,
num_inference_steps=30,
pipe=None,
**kwargs,
):
image_prompt_embeds, uncond_image_prompt_embeds = self.get_image_embeds(makeup_image)
prompt_embeds = image_prompt_embeds
negative_prompt_embeds = uncond_image_prompt_embeds
generator = torch.Generator(self.device).manual_seed(seed) if seed is not None else None
image = pipe(
image=id_image,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
guidance_scale=guidance_scale,
num_inference_steps=num_inference_steps,
generator=generator,
**kwargs,
).images[0]
return image
+112
View File
@@ -0,0 +1,112 @@
import torch
import math
import torch.nn.functional as F
from torch import nn, einsum
from inspect import isfunction
def exists(val):
return val is not None
def uniq(arr):
return{el: True for el in arr}.keys()
def default(val, d):
if exists(val):
return val
return d() if isfunction(d) else d
def max_neg_value(t):
return -torch.finfo(t.dtype).max
def init_(tensor):
dim = tensor.shape[-1]
std = 1 / math.sqrt(dim)
tensor.uniform_(-std, std)
return tensor
# feedforward
class GEGLU(nn.Module):
def __init__(self, dim_in, dim_out):
super().__init__()
self.proj = nn.Linear(dim_in, dim_out * 2)
def forward(self, x):
x, gate = self.proj(x).chunk(2, dim=-1)
return x * F.gelu(gate)
class FeedForward(nn.Module):
def __init__(self, dim, dim_out=None, mult=4, glu=True, dropout=0.):
super().__init__()
inner_dim = int(dim * mult)
dim_out = default(dim_out, dim)
project_in = nn.Sequential(
nn.Linear(dim, inner_dim),
nn.GELU()
) if not glu else GEGLU(dim, inner_dim)
self.net = nn.Sequential(
project_in,
nn.Dropout(dropout),
nn.Linear(inner_dim, dim_out)
)
def forward(self, x):
return self.net(x)
class SelfAttention(nn.Module):
def __init__(self, query_dim, heads=8, dim_head=64, dropout=0.):
super().__init__()
inner_dim = dim_head * heads
self.scale = dim_head ** -0.5
self.heads = heads
self.to_q = nn.Linear(query_dim, inner_dim, bias=False)
self.to_k = nn.Linear(query_dim, inner_dim, bias=False)
self.to_v = nn.Linear(query_dim, inner_dim, bias=False)
self.to_out = nn.Sequential(nn.Linear(inner_dim, query_dim), nn.Dropout(dropout) )
def forward(self, x):
q = self.to_q(x) # B*N*(H*C)
k = self.to_k(x) # B*N*(H*C)
v = self.to_v(x) # B*N*(H*C)
B, N, HC = q.shape
H = self.heads
C = HC // H
q = q.view(B,N,H,C).permute(0,2,1,3).reshape(B*H,N,C) # (B*H)*N*C
k = k.view(B,N,H,C).permute(0,2,1,3).reshape(B*H,N,C) # (B*H)*N*C
v = v.view(B,N,H,C).permute(0,2,1,3).reshape(B*H,N,C) # (B*H)*N*C
sim = torch.einsum('b i c, b j c -> b i j', q, k) * self.scale # (B*H)*N*N
attn = sim.softmax(dim=-1) # (B*H)*N*N
out = torch.einsum('b i j, b j c -> b i c', attn, v) # (B*H)*N*C
out = out.view(B,H,N,C).permute(0,2,1,3).reshape(B,N,(H*C)) # B*N*(H*C)
return self.to_out(out)
class Resampler(nn.Module):
def __init__(self, query_dim=1024, n_heads=8, d_head=64):
super().__init__()
self.attn = SelfAttention(query_dim=query_dim, heads=n_heads, dim_head=d_head)
self.ff = FeedForward(query_dim, glu=True)
self.norm1 = nn.LayerNorm(query_dim)
self.norm2 = nn.LayerNorm(query_dim)
def forward(self, x):
x = x + self.attn(self.norm1(x))
x = x + self.ff(self.norm2(x))
return x
+62
View File
@@ -0,0 +1,62 @@
from .models.model import ShuffleneTiny, ShuffleneFull
from ..utils import download_weight
import torch
import os
class AgeGenderEstimator:
def __init__(self, name='full', weight_path=None, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
"""
Age and gender Detector
:param name: name of backbone (full or tiny)
:param device: model run in cpu or gpu (cuda, cpu)
:param weight_path: path of network weight
Notice: image size must be 112x112
but cun run with 224x224
Method detect:
:param faces: 4d tensor of face for example size(1, 3, 112, 112)
:returns genders list and ages list
"""
if name == 'tiny':
model = ShuffleneTiny()
elif name == 'full':
model = ShuffleneFull()
else:
exit('from AgeGender Detector: model dose not support just(tiny, full)')
# download the default weigth
if weight_path is None:
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ShufflenetFull.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/ShufflenetFull.pth')
if os.path.isfile(weight_path) == False:
print('from AgeGenderEstimator: download defualt weight started')
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://drive.google.com/uc?export=download&id=1rnOZo46RjGZYrUb6Wup6sSOP37ol5I9E', file_name=file_name)
os.rename(file_name, weight_path)
model.load_state_dict(torch.load(weight_path, map_location=device))
model.to(device).eval()
self.model = model
self.device = device
print('from AgeGenderEstimator: weights loaded')
def detect(self, faces):
faces = faces.permute(0, 3, 1, 2)
faces = faces.float().div(255).to(self.device)
mu = torch.as_tensor([0.485, 0.456, 0.406], dtype=faces.dtype, device=faces.device)
std = torch.as_tensor([0.229, 0.224, 0.225], dtype=faces.dtype, device=faces.device)
faces[:].sub_(mu[:, None, None]).div_(std[:, None, None])
outputs = self.model(faces)
genders = []
ages = []
for out in outputs:
gender = torch.argmax(out[:2])
gender = 'Male' if gender == 0 else 'Female'
genders.append(gender)
ages.append(int(out[-1]))
return genders, ages
View File
+37
View File
@@ -0,0 +1,37 @@
# from facelib import FaceDetector
# from facelib import AgeGenderEstimator
# from facelib import special_draw
from ..Retinaface.Retinaface import FaceDetector
from ..AgeGender.Detector import AgeGenderEstimator
from ..InsightFace.models.utils import special_draw
import torch
import cv2
class WebcamAgeGenderEstimator:
def __init__(self, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
print('loading ...')
self.face_detector = FaceDetector(device=device)
self.age_gender_detector = AgeGenderEstimator(device=device)
def run(self, camera_index=0):
cap = cv2.VideoCapture(camera_index)
cap.set(3, 1280)
cap.set(4, 720)
print('type q for exit')
while cap.isOpened():
ret , frame = cap.read()
if ret == False:
raise Exception('the camera not recognized: change camera_index param to ' + str(0 if camera_index == 1 else 1))
faces, boxes, scores, landmarks = self.face_detector.detect_align(frame)
if len(faces.shape) > 1:
genders, ages = self.age_gender_detector.detect(faces)
for i, b in enumerate(boxes):
special_draw(frame, b, landmarks[i], name=genders[i]+' '+str(ages[i]))
cv2.imshow('frame', frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()
+144
View File
@@ -0,0 +1,144 @@
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models
import time
import sys
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
sz = 112
class ShuffleneTiny(nn.Module):
def __init__(self):
super(ShuffleneTiny, self).__init__()
self.model = models.shufflenet_v2_x0_5()
self.model.fc = nn.Sequential(
nn.BatchNorm1d(1024),
nn.Linear(1024, 3))
def forward(self, x):
return self.model(x)
class ShuffleneFull(nn.Module):
def __init__(self):
super(ShuffleneFull, self).__init__()
self.model = models.shufflenet_v2_x1_0()
self.model.fc = nn.Sequential(
nn.BatchNorm1d(1024),
nn.Linear(1024, 3))
def forward(self, x):
return self.model(x)
class TrainModel:
def __init__(self, model, train_dl, valid_dl, optimizer, certrion, scheduler, num_epochs):
self.num_epochs = num_epochs
self.model = model
self.scheduler = scheduler
self.train_dl = train_dl
self.valid_dl = valid_dl
self.optimizer = optimizer
self.certrion = certrion
self.loss_history = []
self.best_acc_valid = 0.0
self.best_wieght = None
self.training()
def training(self):
valid_acc = 0
for epoch in range(self.num_epochs):
print('Epoch %2d/%2d' % (epoch + 1, self.num_epochs))
print('-' * 15)
t0 = time.time()
train_acc = self.train_model()
valid_acc = self.valid_model()
if self.scheduler:
self.scheduler.step()
time_elapsed = time.time() - t0
print(' Training complete in: %.0fm %.0fs' % (time_elapsed // 60, time_elapsed % 60))
print('| val_acc_gender | val_l1_loss | acc_gender | l1_loss |')
print('| %.3f | %.3f | %.3f | %.3f \n' % (valid_acc[0], valid_acc[1], train_acc[0], train_acc[1]))
if valid_acc[0] > self.best_acc_valid:
self.best_acc_valid = valid_acc[1]
self.best_wieght = self.model.state_dict().copy()
return
def train_model(self):
self.model.train()
N = len(self.train_dl.dataset)
step = N // self.train_dl.batch_size
avg_loss = 0.0
acc_gender = 0.0
loss_age = 0.0
for i, (x, y) in enumerate(self.train_dl):
x, y = x.cuda(), y.cuda()
# forward
pred_8 = self.model(x)
# loss
loss = self.certrion(pred_8, y)
# backward
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# statistics of model training
avg_loss = (avg_loss * i + loss) / (i + 1)
acc_gender += accuracy_gender(pred_8, y)
loss_age += l1loss_age(pred_8, y)
self.loss_history.append(avg_loss)
# report statistics
sys.stdout.flush()
sys.stdout.write("\r Train_Step: %d/%d | runing_loss: %.4f" % (i + 1, step, avg_loss))
sys.stdout.flush()
return torch.tensor([acc_gender, loss_age]) / N
def valid_model(self):
print()
self.model.eval()
N = len(self.valid_dl.dataset)
step = N // self.valid_dl.batch_size
acc_gender = 0.0
loss_age = 0.0
with torch.no_grad():
for i, (x, y) in enumerate(self.valid_dl):
x, y = x.cuda(), y.cuda()
score = self.model(x)
acc_gender += accuracy_gender(score, y)
loss_age += l1loss_age(score, y)
sys.stdout.flush()
sys.stdout.write("\r Vaild_Step: %d/%d" % (i, step))
sys.stdout.flush()
return torch.tensor([acc_gender, loss_age]) / N
def accuracy_gender(input, targs):
pred = torch.argmax(input[:, :2], dim=1)
y = targs[:, 0]
return torch.sum(pred == y)
def l1loss_age(input, targs):
return F.l1_loss(input[:, -1], targs[:, -1]).mean()
+83
View File
@@ -0,0 +1,83 @@
"""
Sajjad Ayoubi: Age Gender Detection
I use UTKFace DataSet
from: https://susanqq.github.io/UTKFace/
download it and put it on FaceSet dir
and I create a annotation file data.npy
which there is in weights folder
"""
from PIL import Image
import numpy as np
import torch
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
import torchvision.transforms as transforms
from .model import ShuffleneFull, TrainModel
class MultitaskDataset(Dataset):
def __init__(self, data, tfms, root='FaceSet/'):
self.root = root
self.tfms = tfms
self.ages = data[:, 3]
self.races = data[:, 2]
self.genders = data[:, 1]
self.imgs = data[:, 0]
def __len__(self):
return len(self.imgs)
def __getitem__(self, i):
return self.tfms(Image.open(self.root + self.imgs[i])), torch.tensor(
[self.genders[i], self.races[i], self.ages[i]]).float()
def __repr__(self):
return f'{type(self).__name__} of len {len(self)}'
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
sz = 112
bs = 256
tf = {'train': transforms.Compose([
transforms.RandomRotation(degrees=0.2),
transforms.RandomHorizontalFlip(p=.5),
transforms.RandomGrayscale(p=.2),
transforms.Resize((sz, sz)),
transforms.ToTensor(),
transforms.Normalize(mean, std)]),
'test': transforms.Compose([
transforms.Resize((sz, sz)),
transforms.ToTensor(),
transforms.Normalize(mean, std)])}
data = np.load('data.npy', allow_pickle=True)
train_data = data[data[:, -1] == 1]
valid_data = data[data[:, -1] == 0]
valid_ds = MultitaskDataset(data=valid_data, tfms=tf['test'])
train_ds = MultitaskDataset(data=train_data, tfms=tf['train'])
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True, num_workers=8)
valid_dl = DataLoader(valid_ds, batch_size=bs, shuffle=True, num_workers=4)
def multitask_loss(input, target):
input_gender = input[:, :2]
input_age = input[:, -1]
loss_gender = F.cross_entropy(input_gender, target[:, 0].long())
loss_age = F.l1_loss(input_age, target[:, 2])
return loss_gender / (.16) + loss_age * 2
model = ShuffleneFull().cuda()
optimizer = optim.Adam(params=model.parameters(), lr=0.001, weight_decay=0.001)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=0.9)
history = TrainModel(model, train_dl, valid_dl, optimizer, multitask_loss, scheduler, 5)
print(history)
Binary file not shown.
@@ -0,0 +1,59 @@
import torch
import os
import numpy as np
from ..utils import download_weight
from .models.densenet import densenet121
from .models.resnet import resnet34
labels = np.array(['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral'])
class EmotionDetector:
def __init__(self, name='densnet121', weight_path=None, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
"""
Residual Masking Emotion Detector from a list of labels
:param name: name of backbone of networks (resnet34, densenet121)
:param device: model run in cpu or gpu (cuda, cpu)
:param weight_path: path of network weight
Notice: image size must be 224x224
Method detect_emotion:
:param faces: 4d tensor of face for example size(1, 3, 224, 224)
:returns emotions list and probability of emotions
"""
self.device = device
self.model = None
if name == 'resnet34':
self.model = resnet34()
elif name == 'densnet121':
self.model = densenet121()
else:
exit('from EmotionDetector: Network does not support!! \n just(resnet34, densnet121)')
# download the default weigth
if weight_path is None:
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'densnet121.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/densnet121.pth')
if os.path.isfile(weight_path) == False:
print('from EmotionDetector: download defualt weight started')
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://drive.google.com/uc?export=download&id=1G3VsfgiQb16VyFnOwEVDgm2g8-9qN0-9', file_name=file_name)
os.rename(file_name, weight_path)
self.model.load_state_dict(torch.load(weight_path, map_location=device))
self.model.to(device).eval()
print('from EmotionDetector: weights loaded')
def detect_emotion(self, faces):
if len(faces) > 0:
faces = faces.permute(0, 3, 1, 2)
faces = faces.float().div(255).to(self.device)
emotions = self.model(faces)
prob = torch.softmax(emotions, dim=1)
emo_prob, emo_idx = torch.max(prob, dim=1)
return labels[emo_idx.tolist()], emo_prob.tolist()
else:
return 0, 0
+38
View File
@@ -0,0 +1,38 @@
# from facelib import FaceDetector
# from facelib import EmotionDetector
# from facelib import special_draw
from ..Retinaface.Retinaface import FaceDetector
from .FaceExpression import EmotionDetector
from ..InsightFace.models.utils import special_draw
import torch
import cv2
class WebcamEmotionDetector:
def __init__(self, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
print('loading ...')
self.face_detector = FaceDetector(face_size=(224, 224), device=device)
self.emotion_detector = EmotionDetector(device=device)
def run(self, camera_index=0):
cap = cv2.VideoCapture(camera_index)
cap.set(3, 1280)
cap.set(4, 720)
print('type q for exit')
while cap.isOpened():
ret , frame = cap.read()
if ret == False:
raise Exception('the camera not recognized: change camera_index param to ' + str(0 if camera_index == 1 else 1))
faces, boxes, scores, landmarks = self.face_detector.detect_align(frame)
if len(faces.shape) > 1:
emotions, emo_probs = self.emotion_detector.detect_emotion(faces)
for i, b in enumerate(boxes):
special_draw(frame, b, landmarks[i], name=emotions[i], score=emo_probs[i])
cv2.imshow('frame', frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()
+246
View File
@@ -0,0 +1,246 @@
import re
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as cp
from collections import OrderedDict
try:
from torch.hub import load_state_dict_from_url
except ImportError:
from torch.utils.model_zoo import load_url as load_state_dict_from_url
__all__ = ['DenseNet', 'densenet121', 'densenet169', 'densenet201', 'densenet161']
model_urls = {
'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
'densenet169': 'https://download.pytorch.org/models/densenet169-b2777c0a.pth',
'densenet201': 'https://download.pytorch.org/models/densenet201-c1103571.pth',
'densenet161': 'https://download.pytorch.org/models/densenet161-8d451a50.pth',
}
def _bn_function_factory(norm, relu, conv):
def bn_function(*inputs):
concated_features = torch.cat(inputs, 1)
bottleneck_output = conv(relu(norm(concated_features)))
return bottleneck_output
return bn_function
class _DenseLayer(nn.Sequential):
def __init__(self, num_input_features, growth_rate, bn_size, drop_rate, memory_efficient=False):
super(_DenseLayer, self).__init__()
self.add_module('norm1', nn.BatchNorm2d(num_input_features)),
self.add_module('relu1', nn.ReLU(inplace=True)),
self.add_module('conv1', nn.Conv2d(num_input_features, bn_size *
growth_rate, kernel_size=1, stride=1,
bias=False)),
self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate)),
self.add_module('relu2', nn.ReLU(inplace=True)),
self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate,
kernel_size=3, stride=1, padding=1,
bias=False)),
self.drop_rate = drop_rate
self.memory_efficient = memory_efficient
def forward(self, *prev_features):
bn_function = _bn_function_factory(self.norm1, self.relu1, self.conv1)
if self.memory_efficient and any(prev_feature.requires_grad for prev_feature in prev_features):
bottleneck_output = cp.checkpoint(bn_function, *prev_features)
else:
bottleneck_output = bn_function(*prev_features)
new_features = self.conv2(self.relu2(self.norm2(bottleneck_output)))
if self.drop_rate > 0:
new_features = F.dropout(new_features, p=self.drop_rate,
training=self.training)
return new_features
class _DenseBlock(nn.Module):
def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate, memory_efficient=False):
super(_DenseBlock, self).__init__()
for i in range(num_layers):
layer = _DenseLayer(
num_input_features + i * growth_rate,
growth_rate=growth_rate,
bn_size=bn_size,
drop_rate=drop_rate,
memory_efficient=memory_efficient,
)
self.add_module('denselayer%d' % (i + 1), layer)
def forward(self, init_features):
features = [init_features]
for name, layer in self.named_children():
new_features = layer(*features)
features.append(new_features)
return torch.cat(features, 1)
class _Transition(nn.Sequential):
def __init__(self, num_input_features, num_output_features):
super(_Transition, self).__init__()
self.add_module('norm', nn.BatchNorm2d(num_input_features))
self.add_module('relu', nn.ReLU(inplace=True))
self.add_module('conv', nn.Conv2d(num_input_features, num_output_features,
kernel_size=1, stride=1, bias=False))
self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))
class DenseNet(nn.Module):
r"""Densenet-BC model class, based on
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
Args:
growth_rate (int) - how many filters to add each layer (`k` in paper)
block_config (list of 4 ints) - how many layers in each pooling block
num_init_features (int) - the number of filters to learn in the first convolution layer
bn_size (int) - multiplicative factor for number of bottle neck layers
(i.e. bn_size * k features in the bottleneck layer)
drop_rate (float) - dropout rate after each dense layer
num_classes (int) - number of classification classes
memory_efficient (bool) - If True, uses checkpointing. Much more memory efficient,
but slower. Default: *False*. See `"paper" <https://arxiv.org/pdf/1707.06990.pdf>`_
"""
def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), #in_channels=3, num_classes=7,
num_init_features=64, bn_size=4, drop_rate=0, memory_efficient=False):
super(DenseNet, self).__init__()
# First convolution
self.features = nn.Sequential(OrderedDict([
('conv0', nn.Conv2d(3, num_init_features, kernel_size=7, stride=2,
padding=3, bias=False)),
('norm0', nn.BatchNorm2d(num_init_features)),
('relu0', nn.ReLU(inplace=True)),
('pool0', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)),
]))
# Each denseblock
num_features = num_init_features
for i, num_layers in enumerate(block_config):
block = _DenseBlock(
num_layers=num_layers,
num_input_features=num_features,
bn_size=bn_size,
growth_rate=growth_rate,
drop_rate=drop_rate,
memory_efficient=memory_efficient
)
self.features.add_module('denseblock%d' % (i + 1), block)
num_features = num_features + num_layers * growth_rate
if i != len(block_config) - 1:
trans = _Transition(num_input_features=num_features,
num_output_features=num_features // 2)
self.features.add_module('transition%d' % (i + 1), trans)
num_features = num_features // 2
# Final batch norm
self.features.add_module('norm5', nn.BatchNorm2d(num_features))
# Linear layer
# NOTE: strictly set to 1000 to load pretrained model
# self.classifier = nn.Linear(num_features, num_classes)
self.classifier = nn.Linear(num_features, 1000)
# Official init from torch repo.
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.constant_(m.bias, 0)
def forward(self, x):
features = self.features(x)
out = F.relu(features, inplace=True)
out = F.adaptive_avg_pool2d(out, (1, 1))
out = torch.flatten(out, 1)
out = self.classifier(out)
return out
def _load_state_dict(model, model_url, progress):
# '.'s are no longer allowed in module names, but previous _DenseLayer
# has keys 'norm.1', 'relu.1', 'conv.1', 'norm.2', 'relu.2', 'conv.2'.
# They are also in the checkpoints in model_urls. This pattern is used
# to find such keys.
pattern = re.compile(
r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
state_dict = load_state_dict_from_url(model_url, progress=progress)
for key in list(state_dict.keys()):
res = pattern.match(key)
if res:
new_key = res.group(1) + res.group(2)
state_dict[new_key] = state_dict[key]
del state_dict[key]
model.load_state_dict(state_dict)
def _densenet(arch, growth_rate, block_config, num_init_features, pretrained, progress, **kwargs):
model = DenseNet(growth_rate, block_config, num_init_features, **kwargs)
if pretrained:
_load_state_dict(model, model_urls[arch], progress)
model.classifier = nn.Linear(1024, 7)
return model
def densenet121(pretrained=False, progress=True, **kwargs):
r"""Densenet-121 model from `"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
memory_efficient (bool) - If True, uses checkpointing. Much more memory efficient,
but slower. Default: *False*. See `"paper" <https://arxiv.org/pdf/1707.06990.pdf>`_
"""
return _densenet('densenet121', 32, (6, 12, 24, 16), 64, pretrained, progress, **kwargs)
def densenet161(pretrained=False, progress=True, **kwargs):
r"""Densenet-161 model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
memory_efficient (bool) - If True, uses checkpointing. Much more memory efficient,
but slower. Default: *False*. See `"paper" <https://arxiv.org/pdf/1707.06990.pdf>`_
"""
return _densenet('densenet161', 48, (6, 12, 36, 24), 96, pretrained, progress,
**kwargs)
def densenet169(pretrained=False, progress=True, **kwargs):
r"""Densenet-169 model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
memory_efficient (bool) - If True, uses checkpointing. Much more memory efficient,
but slower. Default: *False*. See `"paper" <https://arxiv.org/pdf/1707.06990.pdf>`_
"""
return _densenet('densenet169', 32, (6, 12, 32, 32), 64, pretrained, progress,
**kwargs)
def densenet201(pretrained=False, progress=True, **kwargs):
r"""Densenet-201 model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
memory_efficient (bool) - If True, uses checkpointing. Much more memory efficient,
but slower. Default: *False*. See `"paper" <https://arxiv.org/pdf/1707.06990.pdf>`_
"""
return _densenet('densenet201', 32, (6, 12, 48, 32), 64, pretrained, progress,
**kwargs)
+362
View File
@@ -0,0 +1,362 @@
import torch
import torch.nn as nn
try:
from torch.hub import load_state_dict_from_url
except ImportError:
from torch.utils.model_zoo import load_url as load_state_dict_from_url
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
'wide_resnet50_2', 'wide_resnet101_2']
model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
}
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=dilation, groups=groups, bias=False, dilation=dilation)
def conv1x1(in_planes, out_planes, stride=1):
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
class BasicBlock(nn.Module):
expansion = 1
__constants__ = ['downsample']
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(BasicBlock, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
if groups != 1 or base_width != 64:
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
if dilation > 1:
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class Bottleneck(nn.Module):
expansion = 4
__constants__ = ['downsample']
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(Bottleneck, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
width = int(planes * (base_width / 64.)) * groups
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv1x1(inplanes, width)
self.bn1 = norm_layer(width)
self.conv2 = conv3x3(width, width, stride, groups, dilation)
self.bn2 = norm_layer(width)
self.conv3 = conv1x1(width, planes * self.expansion)
self.bn3 = norm_layer(planes * self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, in_channels=3, num_classes=7, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
super(ResNet, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
self._norm_layer = norm_layer
self.inplanes = 64
self.dilation = 1
if replace_stride_with_dilation is None:
# each element in the tuple indicates if we should replace
# the 2x2 stride with a dilated convolution instead
replace_stride_with_dilation = [False, False, False]
if len(replace_stride_with_dilation) != 3:
raise ValueError("replace_stride_with_dilation should be None "
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
self.groups = groups
self.base_width = width_per_group
# NOTE: strictly set the in_channels = 3 to load the pretrained model
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = norm_layer(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
# NOTE: strictly set the num_classes = 1000 to load the pretrained model
self.fc = nn.Linear(512 * block.expansion, 1000)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
# Zero-initialize the last BN in each residual branch,
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
if zero_init_residual:
for m in self.modules():
if isinstance(m, Bottleneck):
nn.init.constant_(m.bn3.weight, 0)
elif isinstance(m, BasicBlock):
nn.init.constant_(m.bn2.weight, 0)
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],progress=progress)
model.load_state_dict(state_dict)
return model
def resnet18(pretrained=False, progress=True, **kwargs):
r"""ResNet-18 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
**kwargs)
# model.fc = nn.Linear(512, kwargs['num_classes'])
model.fc = nn.Linear(512, 7)
return model
def resnet34(pretrained=False, progress=True, **kwargs):
r"""ResNet-34 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,**kwargs)
model.fc = nn.Linear(512, 7)
return model
def resnet50(pretrained=False, progress=True, **kwargs):
r"""ResNet-50 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)
model.fc = nn.Linear(2048, kwargs['num_classes'])
return model
def resnet101(pretrained=False, progress=True, **kwargs):
r"""ResNet-101 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
**kwargs)
model.fc = nn.Linear(2048, kwargs['num_classes'])
return model
def resnet152(pretrained=False, progress=True, **kwargs):
r"""ResNet-152 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
**kwargs)
model.fc = nn.Linear(2048, kwargs['num_classes'])
return model
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-50 32x4d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 4
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-101 32x8d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 8
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-50-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-101-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
View File
+94
View File
@@ -0,0 +1,94 @@
import os
import cv2
import torch
from ..Retinaface.Retinaface import FaceDetector
from pathlib import Path
def add_from_webcam(person_name='unknow', camera_index=0):
print('loading ...')
# create facebank folder if is not exists
save_path = Path(os.path.dirname(os.path.realpath(__file__)), 'models/data/facebank')
if not save_path.exists():
save_path.mkdir()
# create a new folder with (name) for new person
save_path = save_path/person_name
if not save_path.exists():
save_path.mkdir()
print('for exit: use q keyword')
# init camera
cap = cv2.VideoCapture(camera_index)
cap.set(3, 1280)
cap.set(4, 720)
# init detector
detector = FaceDetector()
count = 4
print('type q for exit')
while cap.isOpened():
ret , frame = cap.read()
if ret == False:
raise Exception('the camera not recognized: change camera_index param to ' + str(0 if camera_index == 1 else 1))
frame = cv2.putText(
frame, f'Press t to take {count} pictures', (10, 50),
cv2.FONT_HERSHEY_SIMPLEX, 2, (0,100,0), 3, cv2.LINE_AA)
if cv2.waitKey(1) & 0xFF == ord('t'):
count -= 1
faces = detector.detect_align(frame)[0].cpu().numpy()
if len(faces.shape) > 1:
cv2.imwrite(f'{save_path}/{person_name}_{count}.jpg', faces[0])
if count <= 0:
break
else:
print('there is not face in this frame')
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.imshow("add new person from camera", frame)
cap.release()
cv2.destroyAllWindows()
print('images saved in this path: ', save_path)
def add_from_folder(folder_path='./', person_name='unknow'):
print('only a face in each image and all image from the same person')
dir_path = Path(folder_path)
if not dir_path.is_dir():
exit('dir does not exists !!')
# create facebank folder if is not exists
save_path = Path(os.path.dirname(os.path.realpath(__file__)), 'models/data/facebank')
if not save_path.exists():
save_path.mkdir()
save_path = Path(f'{save_path}/{person_name}')
if not save_path.exists():
save_path.mkdir()
print('loading ...')
# init detector
detector = FaceDetector()
counter = 0
for img_path in dir_path.iterdir():
img = cv2.imread(str(img_path))
if img is None:
raise Exception('this image has a problem: ', img_path)
face = detector.detect_align(img)[0].cpu().numpy()
if len(face.shape) > 1:
save_name = f'{save_path}/{dir_path.name}_{counter}.jpg'
cv2.imwrite(save_name, face[0])
counter += 1
else:
print(img_path, 'in this image did not detect any face')
print('images saved in this path: ', save_path)
+167
View File
@@ -0,0 +1,167 @@
from .model import Backbone, Arcface, MobileFaceNet, l2_norm
from .evaluatation import evaluate
import torch
from torch import optim
import numpy as np
import os
from tqdm import tqdm
from ...utils import download_weight
from .utils import get_time, gen_plot, separate_bn_paras
from .utils import faces_preprocessing
from PIL import Image
from torchvision import transforms as trans
import math
from matplotlib import pyplot as plt
plt.switch_backend('agg')
class FaceRecognizer:
def __init__(self, conf, verbose=True):
self.device = conf.device
self.threshold = conf.threshold
try:
if conf.use_mobilfacenet:
self.model = MobileFaceNet(conf.embedding_size).to(conf.device)
# download the default weigth
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mobilenet.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/mobilenet.pth')
if os.path.isfile(weight_path) == False:
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://drive.google.com/uc?export=download&id=1W9nM7LE6zUKQ4tncL6OnBn-aXNyiRPNH',
file_name=file_name,
verbose=verbose
)
os.rename(file_name, weight_path)
self.model.load_state_dict(torch.load(weight_path, map_location=conf.device))
else:
self.model = Backbone(conf.net_depth, conf.drop_ratio, conf.net_mode).to(conf.device)
# download the default weigth
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ir_se50.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/ir_se50.pth')
if os.path.isfile(weight_path) == False:
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://www.dropbox.com/s/a570ucg6a5z22zf/ir_se50.pth?dl=1',
file_name=file_name,
verbose=verbose
)
os.rename(file_name, weight_path)
self.model.load_state_dict(torch.load(weight_path, map_location=conf.device))
except IOError as e:
exit(f'from FaceRecognizer Exit: the weight does not exist,'
f' \n download and putting up in "{conf.work_path}" folder \n {e}')
self.model.eval()
torch.set_grad_enabled(False)
def evaluate(self, conf, carray, issame, nrof_folds=5, tta=False):
self.model.eval()
idx = 0
embeddings = np.zeros([len(carray), conf.embedding_size])
with torch.no_grad():
while idx + conf.batch_size <= len(carray):
batch = torch.tensor(carray[idx:idx + conf.batch_size])
if tta:
fliped = batch.flip(-1) # I do not test in this case
emb_batch = self.model(batch.to(conf.device)) + self.model(fliped.to(conf.device))
embeddings[idx:idx + conf.batch_size] = l2_norm(emb_batch)
else:
embeddings[idx:idx + conf.batch_size] = self.model(batch.to(conf.device)).cpu()
idx += conf.batch_size
if idx < len(carray):
batch = torch.tensor(carray[idx:])
if tta:
fliped = batch.flip(-1)
emb_batch = self.model(batch.to(conf.device)) + self.model(fliped.to(conf.device))
embeddings[idx:] = l2_norm(emb_batch)
else:
embeddings[idx:] = self.model(batch.to(conf.device)).cpu()
tpr, fpr, accuracy, best_thresholds = evaluate(embeddings, issame, nrof_folds)
buf = gen_plot(fpr, tpr)
roc_curve = Image.open(buf)
roc_curve_tensor = trans.ToTensor()(roc_curve)
return accuracy.mean(), best_thresholds.mean(), roc_curve_tensor
def train(self, conf, epochs):
self.model.train()
running_loss = 0.
for e in range(epochs):
print('epoch {} started'.format(e))
if e == self.milestones[0]:
self.schedule_lr()
if e == self.milestones[1]:
self.schedule_lr()
if e == self.milestones[2]:
self.schedule_lr()
for imgs, labels in tqdm(iter(self.loader)):
imgs = imgs.to(conf.device)
labels = labels.to(conf.device)
self.optimizer.zero_grad()
embeddings = self.model(imgs)
thetas = self.head(embeddings, labels)
loss = conf.ce_loss(thetas, labels)
loss.backward()
running_loss += loss.item()
self.optimizer.step()
if self.step % self.board_loss_every == 0 and self.step != 0:
loss_board = running_loss / self.board_loss_every
running_loss = 0.
if self.step % self.evaluate_every == 0 and self.step != 0:
accuracy, best_threshold, roc_curve_tensor = self.evaluate(conf, self.agedb_30,
self.agedb_30_issame)
accuracy, best_threshold, roc_curve_tensor = self.evaluate(conf, self.lfw, self.lfw_issame)
accuracy, best_threshold, roc_curve_tensor = self.evaluate(conf, self.cfp_fp, self.cfp_fp_issame)
self.model.train()
if self.step % self.save_every == 0 and self.step != 0:
self.save_state(conf, accuracy)
self.step += 1
self.save_state(conf, accuracy, to_save_folder=True, extra='final')
def schedule_lr(self):
for params in self.optimizer.param_groups:
params['lr'] /= 10
print(self.optimizer)
def infer(self, faces, target_embs, tta=False):
"""
faces : list of PIL Image
target_embs : [n, 512] computed embeddings of faces in facebank
names : recorded names of faces in facebank
tta : test time augmentation (hfilp, that's all)
"""
faces = faces_preprocessing(faces, self.device)
if tta:
faces_emb = self.model(faces)
hflip_emb = self.model(faces.flip(-1)) # image horizontal flip
embs = l2_norm((faces_emb + hflip_emb)/2) # take mean
else:
embs = self.model(faces)
diff = embs.unsqueeze(-1) - target_embs.transpose(1, 0).unsqueeze(0)
dist = torch.sum(torch.pow(diff, 2), dim=1)
minimum, min_idx = torch.min(dist, dim=1)
min_idx[minimum > self.threshold] = -1 # if no match, set idx to -1
return min_idx, minimum
def feature_extractor(self, faces, tta=False):
"""
faces : stack of torch.tensors
tta : test time augmentation (hfilp, that's all)
"""
faces = faces_preprocessing(faces, self.device)
if tta:
faces_emb = self.model(faces)
hflip_emb = self.model(faces.flip(-1)) # horizontal flip
embs = l2_norm((faces_emb + hflip_emb)/2) # take mean
else:
embs = self.model(faces)
return embs
+46
View File
@@ -0,0 +1,46 @@
from easydict import EasyDict as edict
from pathlib import Path
import os
import torch
from torch.nn import CrossEntropyLoss
def get_config(inference=True):
conf = edict()
conf.data_path = Path(os.path.dirname(os.path.realpath(__file__)))
conf.work_path = Path('weights/')
conf.model_path = conf.work_path / 'models'
conf.log_path = conf.work_path / 'log'
conf.save_path = conf.work_path
conf.input_size = [112, 112]
conf.embedding_size = 512
conf.use_mobilfacenet = True
conf.net_depth = 50
conf.drop_ratio = 0.6
conf.net_mode = 'ir_se' # or 'ir'
conf.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
conf.data_mode = 'emore'
conf.vgg_folder = conf.data_path / 'faces_vgg_112x112'
conf.ms1m_folder = conf.data_path / 'faces_ms1m_112x112'
conf.emore_folder = conf.data_path / 'faces_emore'
conf.batch_size = 100 # irse net depth 50
# conf.batch_size = 200 # mobilefacenet
if inference:
# --------------------Inference Config ------------------------
conf.facebank_path = conf.data_path / 'facebank'
conf.threshold = 1.5
conf.face_limit = 10
# when inference, at maximum detect 10 faces in one image, my laptop is slow
else:
# --------------------Training Config ------------------------
conf.log_path = conf.work_path / 'log'
conf.save_path = conf.work_path / 'save'
# conf.weight_decay = 5e-4
conf.lr = 1e-3
conf.momentum = 0.9
conf.pin_memory = True
# conf.num_workers = 4 # when batchsize is 200
conf.num_workers = 3
conf.ce_loss = CrossEntropyLoss()
return conf
+168
View File
@@ -0,0 +1,168 @@
""""Helper for evaluation on the Labeled Faces in the Wild dataset"""
# MIT License
#
# Copyright (c) 2016 David Sandberg
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import numpy as np
from sklearn.model_selection import KFold
from sklearn.decomposition import PCA
import sklearn
from scipy import interpolate
def calculate_roc(thresholds, embeddings1, embeddings2, actual_issame, nrof_folds=10, pca=0):
assert (embeddings1.shape[0] == embeddings2.shape[0])
assert (embeddings1.shape[1] == embeddings2.shape[1])
nrof_pairs = min(len(actual_issame), embeddings1.shape[0])
nrof_thresholds = len(thresholds)
k_fold = KFold(n_splits=nrof_folds, shuffle=False)
tprs = np.zeros((nrof_folds, nrof_thresholds))
fprs = np.zeros((nrof_folds, nrof_thresholds))
accuracy = np.zeros((nrof_folds))
best_thresholds = np.zeros((nrof_folds))
indices = np.arange(nrof_pairs)
# print('pca', pca)
if pca == 0:
diff = np.subtract(embeddings1, embeddings2)
dist = np.sum(np.square(diff), 1)
for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)):
# print('train_set', train_set)
# print('test_set', test_set)
if pca > 0:
print('doing pca on', fold_idx)
embed1_train = embeddings1[train_set]
embed2_train = embeddings2[train_set]
_embed_train = np.concatenate((embed1_train, embed2_train), axis=0)
# print(_embed_train.shape)
pca_model = PCA(n_components=pca)
pca_model.fit(_embed_train)
embed1 = pca_model.transform(embeddings1)
embed2 = pca_model.transform(embeddings2)
embed1 = sklearn.preprocessing.normalize(embed1)
embed2 = sklearn.preprocessing.normalize(embed2)
# print(embed1.shape, embed2.shape)
diff = np.subtract(embed1, embed2)
dist = np.sum(np.square(diff), 1)
# Find the best threshold for the fold
acc_train = np.zeros((nrof_thresholds))
for threshold_idx, threshold in enumerate(thresholds):
_, _, acc_train[threshold_idx] = calculate_accuracy(threshold, dist[train_set], actual_issame[train_set])
best_threshold_index = np.argmax(acc_train)
# print('best_threshold_index', best_threshold_index, acc_train[best_threshold_index])
best_thresholds[fold_idx] = thresholds[best_threshold_index]
for threshold_idx, threshold in enumerate(thresholds):
tprs[fold_idx, threshold_idx], fprs[fold_idx, threshold_idx], _ = calculate_accuracy(threshold,
dist[test_set],
actual_issame[
test_set])
_, _, accuracy[fold_idx] = calculate_accuracy(thresholds[best_threshold_index], dist[test_set],
actual_issame[test_set])
tpr = np.mean(tprs, 0)
fpr = np.mean(fprs, 0)
return tpr, fpr, accuracy, best_thresholds
def calculate_accuracy(threshold, dist, actual_issame):
predict_issame = np.less(dist, threshold)
tp = np.sum(np.logical_and(predict_issame, actual_issame))
fp = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame)))
tn = np.sum(np.logical_and(np.logical_not(predict_issame), np.logical_not(actual_issame)))
fn = np.sum(np.logical_and(np.logical_not(predict_issame), actual_issame))
tpr = 0 if (tp + fn == 0) else float(tp) / float(tp + fn)
fpr = 0 if (fp + tn == 0) else float(fp) / float(fp + tn)
acc = float(tp + tn) / dist.size
return tpr, fpr, acc
def calculate_val(thresholds, embeddings1, embeddings2, actual_issame, far_target, nrof_folds=10):
'''
Copy from [insightface](https://github.com/deepinsight/insightface)
:param thresholds:
:param embeddings1:
:param embeddings2:
:param actual_issame:
:param far_target:
:param nrof_folds:
:return:
'''
assert (embeddings1.shape[0] == embeddings2.shape[0])
assert (embeddings1.shape[1] == embeddings2.shape[1])
nrof_pairs = min(len(actual_issame), embeddings1.shape[0])
nrof_thresholds = len(thresholds)
k_fold = KFold(n_splits=nrof_folds, shuffle=False)
val = np.zeros(nrof_folds)
far = np.zeros(nrof_folds)
diff = np.subtract(embeddings1, embeddings2)
dist = np.sum(np.square(diff), 1)
indices = np.arange(nrof_pairs)
for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)):
# Find the threshold that gives FAR = far_target
far_train = np.zeros(nrof_thresholds)
for threshold_idx, threshold in enumerate(thresholds):
_, far_train[threshold_idx] = calculate_val_far(threshold, dist[train_set], actual_issame[train_set])
if np.max(far_train) >= far_target:
f = interpolate.interp1d(far_train, thresholds, kind='slinear')
threshold = f(far_target)
else:
threshold = 0.0
val[fold_idx], far[fold_idx] = calculate_val_far(threshold, dist[test_set], actual_issame[test_set])
val_mean = np.mean(val)
far_mean = np.mean(far)
val_std = np.std(val)
return val_mean, val_std, far_mean
def calculate_val_far(threshold, dist, actual_issame):
predict_issame = np.less(dist, threshold)
true_accept = np.sum(np.logical_and(predict_issame, actual_issame))
false_accept = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame)))
n_same = np.sum(actual_issame)
n_diff = np.sum(np.logical_not(actual_issame))
val = float(true_accept) / float(n_same)
far = float(false_accept) / float(n_diff)
return val, far
def evaluate(embeddings, actual_issame, nrof_folds=10, pca=0):
# Calculate evaluation metrics
thresholds = np.arange(0, 4, 0.01)
embeddings1 = embeddings[0::2]
embeddings2 = embeddings[1::2]
tpr, fpr, accuracy, best_thresholds = calculate_roc(thresholds, embeddings1, embeddings2,
np.asarray(actual_issame), nrof_folds=nrof_folds, pca=pca)
# thresholds = np.arange(0, 4, 0.001)
# val, val_std, far = calculate_val(thresholds, embeddings1, embeddings2,
# np.asarray(actual_issame), 1e-3, nrof_folds=nrof_folds)
# return tpr, fpr, accuracy, best_thresholds, val, val_std, far
return tpr, fpr, accuracy, best_thresholds
+309
View File
@@ -0,0 +1,309 @@
from torch.nn import Linear, Conv2d, BatchNorm1d, BatchNorm2d, PReLU, ReLU, Sigmoid, Dropout, \
MaxPool2d, AdaptiveAvgPool2d, Sequential, Module, Parameter
import torch
from collections import namedtuple
import math
################################## Original Arcface Model ############################################################
class Flatten(Module):
def forward(self, input):
return input.view(input.size(0), -1)
def l2_norm(input, axis=1):
norm = torch.norm(input, 2, axis, True)
output = torch.div(input, norm)
return output
class SEModule(Module):
def __init__(self, channels, reduction):
super(SEModule, self).__init__()
self.avg_pool = AdaptiveAvgPool2d(1)
self.fc1 = Conv2d(channels, channels // reduction, kernel_size=1, padding=0, bias=False)
self.relu = ReLU(inplace=True)
self.fc2 = Conv2d(channels // reduction, channels, kernel_size=1, padding=0, bias=False)
self.sigmoid = Sigmoid()
def forward(self, x):
module_input = x
x = self.avg_pool(x)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.sigmoid(x)
return module_input * x
class bottleneck_IR(Module):
def __init__(self, in_channel, depth, stride):
super(bottleneck_IR, self).__init__()
if in_channel == depth:
self.shortcut_layer = MaxPool2d(1, stride)
else:
self.shortcut_layer = Sequential(
Conv2d(in_channel, depth, (1, 1), stride, bias=False), BatchNorm2d(depth))
self.res_layer = Sequential(
BatchNorm2d(in_channel),
Conv2d(in_channel, depth, (3, 3), (1, 1), 1, bias=False), PReLU(depth),
Conv2d(depth, depth, (3, 3), stride, 1, bias=False), BatchNorm2d(depth))
def forward(self, x):
shortcut = self.shortcut_layer(x)
res = self.res_layer(x)
return res + shortcut
class bottleneck_IR_SE(Module):
def __init__(self, in_channel, depth, stride):
super(bottleneck_IR_SE, self).__init__()
if in_channel == depth:
self.shortcut_layer = MaxPool2d(1, stride)
else:
self.shortcut_layer = Sequential(Conv2d(in_channel, depth, (1, 1), stride, bias=False), BatchNorm2d(depth))
self.res_layer = Sequential(
BatchNorm2d(in_channel),
Conv2d(in_channel, depth, (3, 3), (1, 1), 1, bias=False),
PReLU(depth),
Conv2d(depth, depth, (3, 3), stride, 1, bias=False),
BatchNorm2d(depth),
SEModule(depth, 16)
)
def forward(self, x):
shortcut = self.shortcut_layer(x)
res = self.res_layer(x)
return res + shortcut
class Bottleneck(namedtuple('Block', ['in_channel', 'depth', 'stride'])):
'''A named tuple describing a ResNet block.'''
def get_block(in_channel, depth, num_units, stride=2):
return [Bottleneck(in_channel, depth, stride)] + [Bottleneck(depth, depth, 1) for i in range(num_units - 1)]
def get_blocks(num_layers):
if num_layers == 50:
blocks = [
get_block(in_channel=64, depth=64, num_units=3),
get_block(in_channel=64, depth=128, num_units=4),
get_block(in_channel=128, depth=256, num_units=14),
get_block(in_channel=256, depth=512, num_units=3)
]
elif num_layers == 100:
blocks = [
get_block(in_channel=64, depth=64, num_units=3),
get_block(in_channel=64, depth=128, num_units=13),
get_block(in_channel=128, depth=256, num_units=30),
get_block(in_channel=256, depth=512, num_units=3)
]
elif num_layers == 152:
blocks = [
get_block(in_channel=64, depth=64, num_units=3),
get_block(in_channel=64, depth=128, num_units=8),
get_block(in_channel=128, depth=256, num_units=36),
get_block(in_channel=256, depth=512, num_units=3)
]
return blocks
class Backbone(Module):
def __init__(self, num_layers, drop_ratio, mode='ir'):
super(Backbone, self).__init__()
assert num_layers in [50, 100, 152], 'num_layers should be 50,100, or 152'
assert mode in ['ir', 'ir_se'], 'mode should be ir or ir_se'
blocks = get_blocks(num_layers)
if mode == 'ir':
unit_module = bottleneck_IR
elif mode == 'ir_se':
unit_module = bottleneck_IR_SE
self.input_layer = Sequential(Conv2d(3, 64, (3, 3), 1, 1, bias=False), BatchNorm2d(64), PReLU(64))
self.output_layer = Sequential(BatchNorm2d(512),
Dropout(drop_ratio),
Flatten(),
Linear(512 * 7 * 7, 512),
BatchNorm1d(512))
modules = []
for block in blocks:
for bottleneck in block:
modules.append(unit_module(bottleneck.in_channel, bottleneck.depth, bottleneck.stride))
self.body = Sequential(*modules)
def forward(self, x):
x = self.input_layer(x)
x = self.body(x)
x = self.output_layer(x)
return l2_norm(x)
################################## MobileFaceNet #############################################################
class Conv_block(Module):
def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1):
super(Conv_block, self).__init__()
self.conv = Conv2d(in_c, out_channels=out_c, kernel_size=kernel, groups=groups, stride=stride, padding=padding,
bias=False)
self.bn = BatchNorm2d(out_c)
self.prelu = PReLU(out_c)
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = self.prelu(x)
return x
class Linear_block(Module):
def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1):
super(Linear_block, self).__init__()
self.conv = Conv2d(in_c, out_channels=out_c, kernel_size=kernel, groups=groups, stride=stride, padding=padding,
bias=False)
self.bn = BatchNorm2d(out_c)
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
return x
class Depth_Wise(Module):
def __init__(self, in_c, out_c, residual=False, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=1):
super(Depth_Wise, self).__init__()
self.conv = Conv_block(in_c, out_c=groups, kernel=(1, 1), padding=(0, 0), stride=(1, 1))
self.conv_dw = Conv_block(groups, groups, groups=groups, kernel=kernel, padding=padding, stride=stride)
self.project = Linear_block(groups, out_c, kernel=(1, 1), padding=(0, 0), stride=(1, 1))
self.residual = residual
def forward(self, x):
if self.residual:
short_cut = x
x = self.conv(x)
x = self.conv_dw(x)
x = self.project(x)
if self.residual:
output = short_cut + x
else:
output = x
return output
class Residual(Module):
def __init__(self, c, num_block, groups, kernel=(3, 3), stride=(1, 1), padding=(1, 1)):
super(Residual, self).__init__()
modules = []
for _ in range(num_block):
modules.append(
Depth_Wise(c, c, residual=True, kernel=kernel, padding=padding, stride=stride, groups=groups))
self.model = Sequential(*modules)
def forward(self, x):
return self.model(x)
class MobileFaceNet(Module):
def __init__(self, embedding_size):
super(MobileFaceNet, self).__init__()
self.conv1 = Conv_block(3, 64, kernel=(3, 3), stride=(2, 2), padding=(1, 1))
self.conv2_dw = Conv_block(64, 64, kernel=(3, 3), stride=(1, 1), padding=(1, 1), groups=64)
self.conv_23 = Depth_Wise(64, 64, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=128)
self.conv_3 = Residual(64, num_block=4, groups=128, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
self.conv_34 = Depth_Wise(64, 128, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=256)
self.conv_4 = Residual(128, num_block=6, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
self.conv_45 = Depth_Wise(128, 128, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=512)
self.conv_5 = Residual(128, num_block=2, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
self.conv_6_sep = Conv_block(128, 512, kernel=(1, 1), stride=(1, 1), padding=(0, 0))
self.conv_6_dw = Linear_block(512, 512, groups=512, kernel=(7, 7), stride=(1, 1), padding=(0, 0))
self.conv_6_flatten = Flatten()
self.linear = Linear(512, embedding_size, bias=False)
self.bn = BatchNorm1d(embedding_size)
def forward(self, x):
out = self.conv1(x)
out = self.conv2_dw(out)
out = self.conv_23(out)
out = self.conv_3(out)
out = self.conv_34(out)
out = self.conv_4(out)
out = self.conv_45(out)
out = self.conv_5(out)
out = self.conv_6_sep(out)
out = self.conv_6_dw(out)
out = self.conv_6_flatten(out)
out = self.linear(out)
out = self.bn(out)
return l2_norm(out)
################################## Arcface head #############################################################
class Arcface(Module):
# implementation of additive margin softmax loss in https://arxiv.org/abs/1801.05599
def __init__(self, embedding_size=512, classnum=51332, s=64., m=0.5):
super(Arcface, self).__init__()
self.classnum = classnum
self.kernel = Parameter(torch.Tensor(embedding_size, classnum))
# initial kernel
self.kernel.data.uniform_(-1, 1).renorm_(2, 1, 1e-5).mul_(1e5)
self.m = m # the margin value, default is 0.5
self.s = s # scalar value default is 64, see normface https://arxiv.org/abs/1704.06369
self.cos_m = math.cos(m)
self.sin_m = math.sin(m)
self.mm = self.sin_m * m # issue 1
self.threshold = math.cos(math.pi - m)
def forward(self, embbedings, label):
# weights norm
nB = len(embbedings)
kernel_norm = l2_norm(self.kernel, axis=0)
# cos(theta+m)
cos_theta = torch.mm(embbedings, kernel_norm)
# output = torch.mm(embbedings,kernel_norm)
cos_theta = cos_theta.clamp(-1, 1) # for numerical stability
cos_theta_2 = torch.pow(cos_theta, 2)
sin_theta_2 = 1 - cos_theta_2
sin_theta = torch.sqrt(sin_theta_2)
cos_theta_m = (cos_theta * self.cos_m - sin_theta * self.sin_m)
# this condition controls the theta+m should in range [0, pi]
# 0<=theta+m<=pi
# -m<=theta<=pi-m
cond_v = cos_theta - self.threshold
cond_mask = cond_v <= 0
keep_val = (cos_theta - self.mm) # when theta not in [0,pi], use cosface instead
cos_theta_m[cond_mask] = keep_val[cond_mask]
output = cos_theta * 1.0 # a little bit hacky way to prevent in_place operation on cos_theta
idx_ = torch.arange(0, nB, dtype=torch.long)
output[idx_, label] = cos_theta_m[idx_, label]
output *= self.s # scale up in order to make softmax work, first introduced in normface
return output
################################## Cosface head #############################################################
class Am_softmax(Module):
# implementation of additive margin softmax loss in https://arxiv.org/abs/1801.05599
def __init__(self, embedding_size=512, classnum=51332):
super(Am_softmax, self).__init__()
self.classnum = classnum
self.kernel = Parameter(torch.Tensor(embedding_size, classnum))
# initial kernel
self.kernel.data.uniform_(-1, 1).renorm_(2, 1, 1e-5).mul_(1e5)
self.m = 0.35 # additive margin recommended by the paper
self.s = 30. # see normface https://arxiv.org/abs/1704.06369
def forward(self, embbedings, label):
kernel_norm = l2_norm(self.kernel, axis=0)
cos_theta = torch.mm(embbedings, kernel_norm)
cos_theta = cos_theta.clamp(-1, 1) # for numerical stability
phi = cos_theta - self.m
label = label.view(-1, 1) # size=(B,1)
index = cos_theta.data * 0.0 # size=(B,Classnum)
index.scatter_(1, label.data.view(-1, 1), 1)
index = index.byte()
output = cos_theta * 1.0
output[index] = phi[index] # only change the correct predicted output
output *= self.s # scale up in order to make softmax work, first introduced in normface
return output
+31
View File
@@ -0,0 +1,31 @@
from facelib import get_config
from facelib import FaceRecognizer
import argparse
# python train.py -net mobilefacenet -b 200 -w 4
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='for face verification')
parser.add_argument("-e", "--epochs", help="training epochs", default=20, type=int)
parser.add_argument("-net", "--net_mode", help="which network, [ir, ir_se, mobilefacenet]",default='ir_se', type=str)
parser.add_argument("-depth", "--net_depth", help="how many layers [50,100,152]", default=50, type=int)
parser.add_argument('-lr','--lr',help='learning rate',default=1e-3, type=float)
parser.add_argument("-b", "--batch_size", help="batch_size", default=96, type=int)
parser.add_argument("-w", "--num_workers", help="workers number", default=3, type=int)
parser.add_argument("-d", "--data_mode", help="use which database, [vgg, ms1m, emore, concat]",default='emore', type=str)
args = parser.parse_args()
conf = get_config()
if args.net_mode == 'mobilefacenet':
conf.use_mobilfacenet = True
else:
conf.net_mode = args.net_mode
conf.net_depth = args.net_depth
conf.lr = args.lr
conf.batch_size = args.batch_size
conf.num_workers = args.num_workers
conf.data_mode = args.data_mode
learner = FaceRecognizer(conf)
learner.train(conf, args.epochs)
+132
View File
@@ -0,0 +1,132 @@
from datetime import datetime
import numpy as np
import io, cv2, os
from .model import l2_norm
import torch
import matplotlib.pyplot as plt
plt.switch_backend('agg')
def faces_preprocessing(faces, device):
faces = faces.permute(0, 3, 1, 2).float()
faces = faces.div(255).to(device)
mu = torch.as_tensor([.5, .5, .5], dtype=faces.dtype, device=device)
faces[:].sub_(mu[:, None, None]).div_(mu[:, None, None])
return faces
def separate_bn_paras(modules):
if not isinstance(modules, list):
modules = [*modules.modules()]
paras_only_bn = []
paras_wo_bn = []
for layer in modules:
if 'model' in str(layer.__class__):
continue
if 'container' in str(layer.__class__):
continue
else:
if 'batchnorm' in str(layer.__class__):
paras_only_bn.extend([*layer.parameters()])
else:
paras_wo_bn.extend([*layer.parameters()])
return paras_only_bn, paras_wo_bn
def update_facebank(conf, model, detector, tta=True):
if os.path.exists(conf.facebank_path) == False:
raise Exception("you don't have facebank yet: create with add_from_webcam or add_from_folder function")
model.eval()
faces_embs = torch.empty(0).to(conf.device)
names = np.array(['Unknown'])
for path in conf.facebank_path.iterdir():
if path.is_file():
continue
faces = []
for img_path in path.iterdir():
face = cv2.imread(str(img_path))
if face.shape[:2] != (112, 112): # if img be not face
face = detector.detect_align(face)[0]
cv2.imwrite(img_path, face)
else:
face = torch.tensor(face).unsqueeze(0)
faces.append(face)
faces = torch.cat(faces)
if len(faces.shape) <= 3:
continue
with torch.no_grad():
faces = faces_preprocessing(faces, device=conf.device)
if tta:
face_emb = model(faces)
hflip_emb = model(faces.flip(-1)) # image horizontal flip
face_embs = l2_norm(face_emb + hflip_emb)
else:
face_embs = model(faces)
faces_embs = torch.cat((faces_embs, face_embs.mean(0, keepdim=True)))
names = np.append(names, path.name)
torch.save(faces_embs, conf.facebank_path/'facebank.pth')
np.save(conf.facebank_path/'names', names)
print('from FaceRecognizer: facebank updated')
return faces_embs, names
def load_facebank(conf):
if os.path.exists(conf.facebank_path) == False:
raise Exception("you don't have facebank yet: create with add_from_webcam or add_from_folder function")
embs = torch.load(conf.facebank_path/'facebank.pth')
names = np.load(conf.facebank_path/'names.npy')
print('from FaceRecognizer: facebank loaded')
return embs, names
def get_time():
return (str(datetime.now())[:-10]).replace(' ', '-').replace(':', '-')
def gen_plot(fpr, tpr):
"""Create a pyplot plot and save to buffer."""
plt.figure()
plt.xlabel("FPR", fontsize=14)
plt.ylabel("TPR", fontsize=14)
plt.title("ROC Curve", fontsize=14)
plt.plot(fpr, tpr, linewidth=2)
buf = io.BytesIO()
plt.savefig(buf, format='jpeg')
buf.seek(0)
plt.close()
return buf
def draw(bbox, name, frame):
frame = cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 0, 255), 6)
frame = cv2.putText(frame, name, (bbox[0], bbox[1]), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 4, cv2.LINE_AA)
return frame
def special_draw(img, box, landmarsk, name, score=100):
"""draw a bounding box on image"""
color = (148, 133, 0)
tl = round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line thickness
c1, c2 = (box[0], box[1]), (box[2], box[3])
# draw bounding box
cv2.rectangle(img, c1, c2, color, thickness=tl)
# draw landmark
for land in landmarsk:
cv2.circle(img, tuple(land.int().tolist()), 3, color, -1)
# draw score
score = 100-(score*100/1.4)
score = 0 if score < 0 else score
bar = (box[3] + 2) - (box[1] - 2)
score_final = bar - (score*bar/100)
cv2.rectangle(img, (box[2] + 1, box[1] - 2 + score_final), (box[2] + (tl+5), box[3] + 2), color, -1)
# draw label
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(name, 0, fontScale=tl / 3, thickness=tf)[0]
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
cv2.rectangle(img, c1, c2, color, -1) # filled
cv2.putText(img, name, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
+54
View File
@@ -0,0 +1,54 @@
import cv2
import torch
from ..InsightFace.models.data.config import get_config
from ..InsightFace.models.utils import update_facebank, load_facebank,special_draw
from ..Retinaface.Retinaface import FaceDetector
from ..InsightFace.models.Learner import FaceRecognizer
class WebcamVerify:
"""
WebcamVerify: face verfication with cv2
if you add new person in to facebank
pass update True
"""
def __init__(self, update=True, tta=True, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
print('loading ...')
self.tta = tta
self.detector = FaceDetector(device=device)
self.conf = get_config()
self.conf.device = device
recognizer = FaceRecognizer(self.conf)
recognizer.model.eval()
self.recognizer = recognizer
if update:
self.targets, self.names = update_facebank(self.conf, recognizer.model, self.detector, tta=self.tta)
else:
self.targets, self.names = load_facebank(self.conf)
def run(self, camera_index=0):
if len(self.targets) < 1:
raise Exception("you don't have any person in facebank: add new person with 'add_from_webcam' or 'add_from_folder' function")
cap = cv2.VideoCapture(camera_index)
cap.set(3, 1280)
cap.set(4, 720)
# frame rate 6 due to my laptop is quite slow...
print('type q for exit')
while cap.isOpened():
ret , frame = cap.read()
if ret == False:
raise Exception('the camera not recognized: change camera_index param to ' + str(0 if camera_index == 1 else 1))
faces, boxes, scores, landmarks = self.detector.detect_align(frame)
if len(faces.shape) > 1:
results, score = self.recognizer.infer(faces, self.targets, tta=self.tta)
for idx, bbox in enumerate(boxes):
special_draw(frame, bbox, landmarks[idx], self.names[results[idx] + 1], score[idx])
cv2.imshow('face Capture', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
+182
View File
@@ -0,0 +1,182 @@
import os
import torch
import cv2
import numpy as np
from skimage import transform
from .utils.alignment import get_reference_facial_points, FaceWarpException, alignment
from .utils.box_utils import decode, decode_landmark, prior_box, nms
from .utils.config import cfg_mnet, cfg_re50
from .models.retinaface import RetinaFace
from ..utils import download_weight
class FaceDetector:
def __init__(self, name='mobilenet', weight_path=None, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"), confidence_threshold=0.99,
top_k=5000, nms_threshold=0.4, keep_top_k=750, face_size=(112, 112), crop_size=(96, 112), verbose=True):
"""
RetinaFace Detector with 5points landmarks
Args:
name: name of backbone (resnet, mobilenet, slim, rfb)
weight_path: path of network weight
device: running device (cuda, cpu)
face_size: final face size
face_padding: padding for bounding boxes
"""
model, cfg = None, None
if name == 'mobilenet':
cfg = cfg_mnet
model = RetinaFace(cfg=cfg, phase='test')
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mobilenet.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/mobilenet.pth')
weight_path="F:/test/ComfyUI/models/stable_makeup/mobilenet0.25_Final.pth"
if os.path.isfile(weight_path) == False:
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://drive.google.com/uc?export=download&id=15zP8BP-5IvWXWZoYTNdvUJUiBqZ1hxu1',
file_name=file_name,
verbose=verbose)
os.rename(file_name, weight_path)
elif name == 'resnet':
cfg = cfg_re50
model = RetinaFace(cfg=cfg, phase='test')
file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resnet50.pth')
weight_path = os.path.join(os.path.dirname(file_name), 'weights/resnet50.pth')
if os.path.isfile(weight_path) == False:
os.makedirs(os.path.split(weight_path)[0], exist_ok=True)
download_weight(link='https://www.dropbox.com/s/8sxkgc9voel6ost/resnet50.pth?dl=1',
file_name=file_name,
verbose=verbose)
os.rename(file_name, weight_path)
else:
exit('FaceDetector Exit: model name can be either mobilenet or resnet')
# settings for model
model.load_state_dict(torch.load(weight_path, map_location=device))
model.to(device).eval()
self.model = model
self.device = device
self.cfg = cfg
# settings for face detection
self.thresh = confidence_threshold
self.top_k = top_k
self.nms_thresh = nms_threshold
self.keep_top_k = keep_top_k
# settings for face alignment
self.trans = transform.SimilarityTransform()
self.out_size = face_size
self.ref_pts = get_reference_facial_points(output_size=face_size, crop_size=crop_size)
def preprocessor(self, img_raw):
img = torch.tensor(img_raw, dtype=torch.float32).to(self.device)
scale = torch.Tensor([img.shape[1], img.shape[0], img.shape[1], img.shape[0]]).to(self.device)
img -= torch.tensor([104, 117, 123]).to(self.device)
img = img.permute(2, 0, 1).unsqueeze(0)
return img, scale
def detect_faces(self, img_raw):
"""
get a image from ndarray, detect faces in image
Args:
img_raw: original image from cv2(BGR) or PIL(RGB)
Notes:
coordinate is corresponding to original image
and type of return image is corresponding to input(cv2, PIL)
Returns:
boxes:
faces bounding box for each face
scores:
percentage of each face
landmarks:
faces landmarks for each face
"""
img, scale = self.preprocessor(img_raw)
# tic = time.time()
with torch.no_grad():
loc, conf, landmarks = self.model(img) # forward pass
# print('net forward time: {:.4f}'.format(time.time() - tic))
priors = prior_box(self.cfg, image_size=img.shape[2:]).to(self.device)
boxes = decode(loc.data.squeeze(0), priors, self.cfg['variance'])
boxes = boxes * scale
scores = conf.squeeze(0)[:, 1]
landmarks = decode_landmark(landmarks.squeeze(0), priors, self.cfg['variance'])
scale1 = torch.Tensor([img.shape[3], img.shape[2], img.shape[3], img.shape[2],
img.shape[3], img.shape[2], img.shape[3], img.shape[2],
img.shape[3], img.shape[2]]).to(self.device)
landmarks = landmarks * scale1
# ignore low scores
index = torch.where(scores > self.thresh)[0]
boxes = boxes[index]
landmarks = landmarks[index]
scores = scores[index]
# keep top-K before NMS
order = scores.argsort(dim=0, descending=True)[:self.top_k]
boxes = boxes[order]
landmarks = landmarks[order]
scores = scores[order]
# Do NMS
keep = nms(boxes, scores, self.nms_thresh)
boxes = torch.abs(boxes[keep, :])
scores = scores[:, None][keep, :]
landmarks = landmarks[keep, :].reshape(-1, 5, 2)
# # keep top-K faster NMS
landmarks = landmarks[:self.keep_top_k, :]
scores = scores[:self.keep_top_k, :]
boxes = boxes[:self.keep_top_k, :]
return boxes, scores, landmarks
def detect_align(self, img):
"""
get a image from ndarray, detect faces in image,
cropped face and align face
Args:
img: original image from cv2(BGR) or PIL(RGB)
Notes:
coordinate is corresponding to original image
and type of return image is corresponding to input(cv2, PIL)
Returns:
faces:
a tensor(n, 112, 112, 3) of faces that aligned
"""
boxes, scores, landmarks = self.detect_faces(img)
warped = []
for src_pts in landmarks:
if max(src_pts.shape) < 3 or min(src_pts.shape) != 2:
raise FaceWarpException('facial_pts.shape must be (K,2) or (2,K) and K>2')
if src_pts.shape[0] == 2:
src_pts = src_pts.T
if src_pts.shape != self.ref_pts.shape:
raise FaceWarpException('facial_pts and reference_pts must have the same shape')
self.trans.estimate(src_pts.cpu().numpy(), self.ref_pts)
face_img = cv2.warpAffine(img, self.trans.params[0:2, :], self.out_size)
warped.append(face_img)
faces = torch.tensor(np.array(warped)).to(self.device)
return faces, boxes, scores, landmarks
def detect(self, img, align=False):
boxes, scores, landmarks = self.detect_faces(img)
box = boxes[0].int()
face = img[box[1]: box[3], box[0]: box[2]]
face = cv2.resize(face, self.out_size)
if align:
landmark = landmarks[0].int()[:3].numpy()
return alignment(face, *landmark)
else:
return face
View File
+33
View File
@@ -0,0 +1,33 @@
import cv2
import torch
#from facelib import special_draw
#from facelib import FaceDetector
from ..InsightFace.models.utils import special_draw
from ..Retinaface.Retinaface import FaceDetector
class WebcamFaceDetector:
def __init__(self, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")):
print('loading ...')
self.detector = FaceDetector(face_size=(224, 224), device=device)
def run(self, camera_index=0):
cap = cv2.VideoCapture(camera_index)
cap.set(3, 1280)
cap.set(4, 720)
print('type q for exit')
while cap.isOpened():
ret , frame = cap.read()
if ret == False:
raise Exception('the camera not recognized: change camera_index param to ' + str(0 if camera_index == 1 else 1))
# boxes, scores, landmarks = detector.detect_faces(frame)
faces, boxes, scores, landmarks = self.detector.detect_align(frame)
if len(faces.shape) > 1:
for idx, bbox in enumerate(boxes):
special_draw(frame, bbox, landmarks[idx], name='face', score=scores[idx])
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
+137
View File
@@ -0,0 +1,137 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
def conv_bn(inp, oup, stride=1, leaky=0):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.LeakyReLU(negative_slope=leaky, inplace=True)
)
def conv_bn_no_relu(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
)
def conv_bn1X1(inp, oup, stride, leaky=0):
return nn.Sequential(
nn.Conv2d(inp, oup, 1, stride, padding=0, bias=False),
nn.BatchNorm2d(oup),
nn.LeakyReLU(negative_slope=leaky, inplace=True)
)
def conv_dw(inp, oup, stride, leaky=0.1):
return nn.Sequential(
nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.LeakyReLU(negative_slope=leaky, inplace=True),
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.LeakyReLU(negative_slope=leaky, inplace=True),
)
class SSH(nn.Module):
def __init__(self, in_channel, out_channel):
super(SSH, self).__init__()
assert out_channel % 4 == 0
leaky = 0
if (out_channel <= 64):
leaky = 0.1
self.conv3X3 = conv_bn_no_relu(in_channel, out_channel // 2, stride=1)
self.conv5X5_1 = conv_bn(in_channel, out_channel // 4, stride=1, leaky=leaky)
self.conv5X5_2 = conv_bn_no_relu(out_channel // 4, out_channel // 4, stride=1)
self.conv7X7_2 = conv_bn(out_channel // 4, out_channel // 4, stride=1, leaky=leaky)
self.conv7x7_3 = conv_bn_no_relu(out_channel // 4, out_channel // 4, stride=1)
def forward(self, input):
conv3X3 = self.conv3X3(input)
conv5X5_1 = self.conv5X5_1(input)
conv5X5 = self.conv5X5_2(conv5X5_1)
conv7X7_2 = self.conv7X7_2(conv5X5_1)
conv7X7 = self.conv7x7_3(conv7X7_2)
out = torch.cat([conv3X3, conv5X5, conv7X7], dim=1)
out = F.relu(out)
return out
class FPN(nn.Module):
def __init__(self, in_channels_list, out_channels):
super(FPN, self).__init__()
leaky = 0
if (out_channels <= 64):
leaky = 0.1
self.output1 = conv_bn1X1(in_channels_list[0], out_channels, stride=1, leaky=leaky)
self.output2 = conv_bn1X1(in_channels_list[1], out_channels, stride=1, leaky=leaky)
self.output3 = conv_bn1X1(in_channels_list[2], out_channels, stride=1, leaky=leaky)
self.merge1 = conv_bn(out_channels, out_channels, leaky=leaky)
self.merge2 = conv_bn(out_channels, out_channels, leaky=leaky)
def forward(self, input):
# names = list(input.keys())
input = list(input.values())
output1 = self.output1(input[0])
output2 = self.output2(input[1])
output3 = self.output3(input[2])
up3 = F.interpolate(output3, size=[output2.size(2), output2.size(3)], mode="nearest")
output2 = output2 + up3
output2 = self.merge2(output2)
up2 = F.interpolate(output2, size=[output1.size(2), output1.size(3)], mode="nearest")
output1 = output1 + up2
output1 = self.merge1(output1)
out = [output1, output2, output3]
return out
class MobileNetV1(nn.Module):
def __init__(self):
super(MobileNetV1, self).__init__()
self.stage1 = nn.Sequential(
conv_bn(3, 8, 2, leaky=0.1), # 3
conv_dw(8, 16, 1), # 7
conv_dw(16, 32, 2), # 11
conv_dw(32, 32, 1), # 19
conv_dw(32, 64, 2), # 27
conv_dw(64, 64, 1), # 43
)
self.stage2 = nn.Sequential(
conv_dw(64, 128, 2), # 43 + 16 = 59
conv_dw(128, 128, 1), # 59 + 32 = 91
conv_dw(128, 128, 1), # 91 + 32 = 123
conv_dw(128, 128, 1), # 123 + 32 = 155
conv_dw(128, 128, 1), # 155 + 32 = 187
conv_dw(128, 128, 1), # 187 + 32 = 219
)
self.stage3 = nn.Sequential(
conv_dw(128, 256, 2), # 219 +3 2 = 241
conv_dw(256, 256, 1), # 241 + 64 = 301
)
self.avg = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(256, 1000)
def forward(self, x):
x = self.stage1(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.avg(x)
# x = self.model(x)
x = x.view(-1, 256)
x = self.fc(x)
return x
+118
View File
@@ -0,0 +1,118 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models._utils as _utils
from ..models.net import FPN as FPN
from ..models.net import MobileNetV1 as MobileNetV1
from ..models.net import SSH as SSH
class ClassHead(nn.Module):
def __init__(self, inchannels=512, num_anchors=3):
super(ClassHead, self).__init__()
self.num_anchors = num_anchors
self.conv1x1 = nn.Conv2d(inchannels, self.num_anchors * 2, kernel_size=(1, 1), stride=1, padding=0)
def forward(self, x):
out = self.conv1x1(x)
out = out.permute(0, 2, 3, 1).contiguous()
return out.view(out.shape[0], -1, 2)
class BboxHead(nn.Module):
def __init__(self, inchannels=512, num_anchors=3):
super(BboxHead, self).__init__()
self.conv1x1 = nn.Conv2d(inchannels, num_anchors * 4, kernel_size=(1, 1), stride=1, padding=0)
def forward(self, x):
out = self.conv1x1(x)
out = out.permute(0, 2, 3, 1).contiguous()
return out.view(out.shape[0], -1, 4)
class LandmarkHead(nn.Module):
def __init__(self, inchannels=512, num_anchors=3):
super(LandmarkHead, self).__init__()
self.conv1x1 = nn.Conv2d(inchannels, num_anchors * 10, kernel_size=(1, 1), stride=1, padding=0)
def forward(self, x):
out = self.conv1x1(x)
out = out.permute(0, 2, 3, 1).contiguous()
return out.view(out.shape[0], -1, 10)
class RetinaFace(nn.Module):
def __init__(self, cfg=None, phase='train'):
"""
:param cfg: Network related settings.
:param phase: train or test.
"""
super(RetinaFace, self).__init__()
self.phase = phase
# backbone = MobileNetV1()
if cfg['name'] == 'mobilenet0.25':
backbone = MobileNetV1()
elif cfg['name'] == 'Resnet50':
import torchvision.models as models
backbone = models.resnet50(pretrained=cfg['pretrain'])
self.body = _utils.IntermediateLayerGetter(backbone, cfg['return_layers'])
in_channels_stage2 = cfg['in_channel']
in_channels_list = [
in_channels_stage2 * 2,
in_channels_stage2 * 4,
in_channels_stage2 * 8,
]
out_channels = cfg['out_channel']
self.fpn = FPN(in_channels_list, out_channels)
self.ssh1 = SSH(out_channels, out_channels)
self.ssh2 = SSH(out_channels, out_channels)
self.ssh3 = SSH(out_channels, out_channels)
self.ClassHead = self._make_class_head(fpn_num=3, inchannels=cfg['out_channel'])
self.BboxHead = self._make_bbox_head(fpn_num=3, inchannels=cfg['out_channel'])
self.LandmarkHead = self._make_landmark_head(fpn_num=3, inchannels=cfg['out_channel'])
def _make_class_head(self, fpn_num=3, inchannels=64, anchor_num=2):
classhead = nn.ModuleList()
for i in range(fpn_num):
classhead.append(ClassHead(inchannels, anchor_num))
return classhead
def _make_bbox_head(self, fpn_num=3, inchannels=64, anchor_num=2):
bboxhead = nn.ModuleList()
for i in range(fpn_num):
bboxhead.append(BboxHead(inchannels, anchor_num))
return bboxhead
def _make_landmark_head(self, fpn_num=3, inchannels=64, anchor_num=2):
landmarkhead = nn.ModuleList()
for i in range(fpn_num):
landmarkhead.append(LandmarkHead(inchannels, anchor_num))
return landmarkhead
def forward(self, inputs):
out = self.body(inputs)
# FPN
fpn = self.fpn(out)
# SSH
feature1 = self.ssh1(fpn[0])
feature2 = self.ssh2(fpn[1])
feature3 = self.ssh3(fpn[2])
features = [feature1, feature2, feature3]
bbox_regressions = torch.cat([self.BboxHead[i](feature) for i, feature in enumerate(features)], dim=1)
classifications = torch.cat([self.ClassHead[i](feature) for i, feature in enumerate(features)], dim=1)
ldm_regressions = torch.cat([self.LandmarkHead[i](feature) for i, feature in enumerate(features)], dim=1)
if self.phase == 'train':
output = (bbox_regressions, classifications, ldm_regressions)
else:
output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
return output
+197
View File
@@ -0,0 +1,197 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicConv(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, dilation=1, groups=1, relu=True, bn=True):
super(BasicConv, self).__init__()
self.out_channels = out_planes
if bn:
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=False)
self.bn = nn.BatchNorm2d(out_planes, eps=1e-5, momentum=0.01, affine=True)
self.relu = nn.ReLU(inplace=True) if relu else None
else:
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=True)
self.bn = None
self.relu = nn.ReLU(inplace=True) if relu else None
def forward(self, x):
x = self.conv(x)
if self.bn is not None:
x = self.bn(x)
if self.relu is not None:
x = self.relu(x)
return x
class BasicRFB(nn.Module):
def __init__(self, in_planes, out_planes, stride=1, scale=0.1, map_reduce=8, vision=1, groups=1):
super(BasicRFB, self).__init__()
self.scale = scale
self.out_channels = out_planes
inter_planes = in_planes // map_reduce
self.branch0 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1, groups=groups, relu=False),
BasicConv(inter_planes, 2 * inter_planes, kernel_size=(3, 3), stride=stride, padding=(1, 1), groups=groups),
BasicConv(2 * inter_planes, 2 * inter_planes, kernel_size=3, stride=1, padding=vision + 1, dilation=vision + 1, relu=False, groups=groups)
)
self.branch1 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1, groups=groups, relu=False),
BasicConv(inter_planes, 2 * inter_planes, kernel_size=(3, 3), stride=stride, padding=(1, 1), groups=groups),
BasicConv(2 * inter_planes, 2 * inter_planes, kernel_size=3, stride=1, padding=vision + 2, dilation=vision + 2, relu=False, groups=groups)
)
self.branch2 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1, groups=groups, relu=False),
BasicConv(inter_planes, (inter_planes // 2) * 3, kernel_size=3, stride=1, padding=1, groups=groups),
BasicConv((inter_planes // 2) * 3, 2 * inter_planes, kernel_size=3, stride=stride, padding=1, groups=groups),
BasicConv(2 * inter_planes, 2 * inter_planes, kernel_size=3, stride=1, padding=vision + 4, dilation=vision + 4, relu=False, groups=groups)
)
self.ConvLinear = BasicConv(6 * inter_planes, out_planes, kernel_size=1, stride=1, relu=False)
self.shortcut = BasicConv(in_planes, out_planes, kernel_size=1, stride=stride, relu=False)
self.relu = nn.ReLU(inplace=False)
def forward(self, x):
x0 = self.branch0(x)
x1 = self.branch1(x)
x2 = self.branch2(x)
out = torch.cat((x0, x1, x2), 1)
out = self.ConvLinear(out)
short = self.shortcut(x)
out = out * self.scale + short
out = self.relu(out)
return out
def conv_bn(inp, oup, stride = 1):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)
def depth_conv2d(inp, oup, kernel=1, stride=1, pad=0):
return nn.Sequential(
nn.Conv2d(inp, inp, kernel_size = kernel, stride = stride, padding=pad, groups=inp),
nn.ReLU(inplace=True),
nn.Conv2d(inp, oup, kernel_size=1)
)
def conv_dw(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.ReLU(inplace=True),
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)
class RFB(nn.Module):
def __init__(self, cfg = None, phase = 'train'):
"""
:param cfg: Network related settings.
:param phase: train or test.
"""
super(RFB,self).__init__()
self.phase = phase
self.num_classes = 2
self.conv1 = conv_bn(3, 16, 2)
self.conv2 = conv_dw(16, 32, 1)
self.conv3 = conv_dw(32, 32, 2)
self.conv4 = conv_dw(32, 32, 1)
self.conv5 = conv_dw(32, 64, 2)
self.conv6 = conv_dw(64, 64, 1)
self.conv7 = conv_dw(64, 64, 1)
self.conv8 = BasicRFB(64, 64, stride=1, scale=1.0)
self.conv9 = conv_dw(64, 128, 2)
self.conv10 = conv_dw(128, 128, 1)
self.conv11 = conv_dw(128, 128, 1)
self.conv12 = conv_dw(128, 256, 2)
self.conv13 = conv_dw(256, 256, 1)
self.conv14 = nn.Sequential(
nn.Conv2d(in_channels=256, out_channels=64, kernel_size=1),
nn.ReLU(inplace=True),
depth_conv2d(64, 256, kernel=3, stride=2, pad=1),
nn.ReLU(inplace=True)
)
self.loc, self.conf, self.landm = self.multibox(self.num_classes);
def multibox(self, num_classes):
loc_layers = []
conf_layers = []
landm_layers = []
loc_layers += [depth_conv2d(64, 3 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(64, 3 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(64, 3 * 10, kernel=3, pad=1)]
loc_layers += [depth_conv2d(128, 2 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(128, 2 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(128, 2 * 10, kernel=3, pad=1)]
loc_layers += [depth_conv2d(256, 2 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(256, 2 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(256, 2 * 10, kernel=3, pad=1)]
loc_layers += [nn.Conv2d(256, 3 * 4, kernel_size=3, padding=1)]
conf_layers += [nn.Conv2d(256, 3 * num_classes, kernel_size=3, padding=1)]
landm_layers += [nn.Conv2d(256, 3 * 10, kernel_size=3, padding=1)]
return nn.Sequential(*loc_layers), nn.Sequential(*conf_layers), nn.Sequential(*landm_layers)
def forward(self,inputs):
detections = list()
loc = list()
conf = list()
landm = list()
x1 = self.conv1(inputs)
x2 = self.conv2(x1)
x3 = self.conv3(x2)
x4 = self.conv4(x3)
x5 = self.conv5(x4)
x6 = self.conv6(x5)
x7 = self.conv7(x6)
x8 = self.conv8(x7)
detections.append(x8)
x9 = self.conv9(x8)
x10 = self.conv10(x9)
x11 = self.conv11(x10)
detections.append(x11)
x12 = self.conv12(x11)
x13 = self.conv13(x12)
detections.append(x13)
x14= self.conv14(x13)
detections.append(x14)
for (x, l, c, lam) in zip(detections, self.loc, self.conf, self.landm):
loc.append(l(x).permute(0, 2, 3, 1).contiguous())
conf.append(c(x).permute(0, 2, 3, 1).contiguous())
landm.append(lam(x).permute(0, 2, 3, 1).contiguous())
bbox_regressions = torch.cat([o.view(o.size(0), -1, 4) for o in loc], 1)
classifications = torch.cat([o.view(o.size(0), -1, 2) for o in conf], 1)
ldm_regressions = torch.cat([o.view(o.size(0), -1, 10) for o in landm], 1)
if self.phase == 'train':
output = (bbox_regressions, classifications, ldm_regressions)
else:
output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
return output
+129
View File
@@ -0,0 +1,129 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
def conv_bn(inp, oup, stride = 1):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)
def depth_conv2d(inp, oup, kernel=1, stride=1, pad=0):
return nn.Sequential(
nn.Conv2d(inp, inp, kernel_size = kernel, stride = stride, padding=pad, groups=inp),
nn.ReLU(inplace=True),
nn.Conv2d(inp, oup, kernel_size=1)
)
def conv_dw(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.ReLU(inplace=True),
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)
class Slim(nn.Module):
def __init__(self, cfg = None, phase = 'train'):
"""
:param cfg: Network related settings.
:param phase: train or test.
"""
super(Slim, self).__init__()
self.phase = phase
self.num_classes = 2
self.conv1 = conv_bn(3, 16, 2)
self.conv2 = conv_dw(16, 32, 1)
self.conv3 = conv_dw(32, 32, 2)
self.conv4 = conv_dw(32, 32, 1)
self.conv5 = conv_dw(32, 64, 2)
self.conv6 = conv_dw(64, 64, 1)
self.conv7 = conv_dw(64, 64, 1)
self.conv8 = conv_dw(64, 64, 1)
self.conv9 = conv_dw(64, 128, 2)
self.conv10 = conv_dw(128, 128, 1)
self.conv11 = conv_dw(128, 128, 1)
self.conv12 = conv_dw(128, 256, 2)
self.conv13 = conv_dw(256, 256, 1)
self.conv14 = nn.Sequential(
nn.Conv2d(in_channels=256, out_channels=64, kernel_size=1),
nn.ReLU(inplace=True),
depth_conv2d(64, 256, kernel=3, stride=2, pad=1),
nn.ReLU(inplace=True)
)
self.loc, self.conf, self.landm = self.multibox(self.num_classes);
def multibox(self, num_classes):
loc_layers = []
conf_layers = []
landm_layers = []
loc_layers += [depth_conv2d(64, 3 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(64, 3 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(64, 3 * 10, kernel=3, pad=1)]
loc_layers += [depth_conv2d(128, 2 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(128, 2 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(128, 2 * 10, kernel=3, pad=1)]
loc_layers += [depth_conv2d(256, 2 * 4, kernel=3, pad=1)]
conf_layers += [depth_conv2d(256, 2 * num_classes, kernel=3, pad=1)]
landm_layers += [depth_conv2d(256, 2 * 10, kernel=3, pad=1)]
loc_layers += [nn.Conv2d(256, 3 * 4, kernel_size=3, padding=1)]
conf_layers += [nn.Conv2d(256, 3 * num_classes, kernel_size=3, padding=1)]
landm_layers += [nn.Conv2d(256, 3 * 10, kernel_size=3, padding=1)]
return nn.Sequential(*loc_layers), nn.Sequential(*conf_layers), nn.Sequential(*landm_layers)
def forward(self,inputs):
detections = list()
loc = list()
conf = list()
landm = list()
x1 = self.conv1(inputs)
x2 = self.conv2(x1)
x3 = self.conv3(x2)
x4 = self.conv4(x3)
x5 = self.conv5(x4)
x6 = self.conv6(x5)
x7 = self.conv7(x6)
x8 = self.conv8(x7)
detections.append(x8)
x9 = self.conv9(x8)
x10 = self.conv10(x9)
x11 = self.conv11(x10)
detections.append(x11)
x12 = self.conv12(x11)
x13 = self.conv13(x12)
detections.append(x13)
x14= self.conv14(x13)
detections.append(x14)
for (x, l, c, lam) in zip(detections, self.loc, self.conf, self.landm):
loc.append(l(x).permute(0, 2, 3, 1).contiguous())
conf.append(c(x).permute(0, 2, 3, 1).contiguous())
landm.append(lam(x).permute(0, 2, 3, 1).contiguous())
bbox_regressions = torch.cat([o.view(o.size(0), -1, 4) for o in loc], 1)
classifications = torch.cat([o.view(o.size(0), -1, 2) for o in conf], 1)
ldm_regressions = torch.cat([o.view(o.size(0), -1, 10) for o in landm], 1)
if self.phase == 'train':
output = (bbox_regressions, classifications, ldm_regressions)
else:
output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
return output
+94
View File
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 1 15:43:29 2020
@author: Sajjad Ayobbi
"""
import cv2
import math
import numpy as np
from PIL import Image
from skimage import transform as trans
# reference facial points, a list of coordinates (x,y)
REFERENCE_FACIAL_POINTS = [
[30.29459953, 51.69630051],
[65.53179932, 51.50139999],
[48.02519989, 71.73660278],
[33.54930115, 87],
[62.72990036, 87]
]
class FaceWarpException(Exception):
def __str__(self):
return 'In File {}:{}'.format(
__file__, super.__str__(self))
def get_reference_facial_points(output_size=(112, 112), crop_size=(96, 112)):
tmp_5pts = np.array(REFERENCE_FACIAL_POINTS)
tmp_crop_size = np.array(crop_size)
# size_diff = max(tmp_crop_size) - tmp_crop_size
# tmp_5pts += size_diff / 2
# tmp_crop_size += size_diff
# return tmp_5pts
x_scale = output_size[0]/tmp_crop_size[0]
y_scale = output_size[1]/tmp_crop_size[1]
tmp_5pts[:, 0] *= x_scale
tmp_5pts[:, 1] *= y_scale
return tmp_5pts
####################################################################################
def findEuclideanDistance(source_representation, test_representation):
euclidean_distance = source_representation - test_representation
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
euclidean_distance = np.sqrt(euclidean_distance)
return euclidean_distance
def alignment(img, left_eye, right_eye, nose):
left_eye_x, left_eye_y = left_eye
right_eye_x, right_eye_y = right_eye
#-----------------------
upside_down = False
if nose[1] < left_eye[1] or nose[1] < right_eye[1]:
upside_down = True
#-----------------------
#find rotation direction
if left_eye_y > right_eye_y:
point_3rd = (right_eye_x, left_eye_y)
direction = -1 #rotate same direction to clock
else:
point_3rd = (left_eye_x, right_eye_y)
direction = 1 #rotate inverse direction of clock
#-----------------------
#find length of triangle edges
a = findEuclideanDistance(np.array(left_eye), np.array(point_3rd))
b = findEuclideanDistance(np.array(right_eye), np.array(point_3rd))
c = findEuclideanDistance(np.array(right_eye), np.array(left_eye))
#-----------------------
if b != 0 and c != 0: #this multiplication causes division by zero in cos_a calculation
cos_a = (b*b + c*c - a*a)/(2*b*c)
cos_a = min(1.0, max(-1.0, cos_a))
angle = np.arccos(cos_a) #angle in radian
angle = (angle * 180) / math.pi #radian to degree
#-----------------------
#rotate base image
if direction == -1:
angle = 90 - angle
if upside_down == True:
angle = angle + 90
img = Image.fromarray(img)
img = np.array(img.rotate(direction * angle))
return img
+102
View File
@@ -0,0 +1,102 @@
from itertools import product as product
from math import ceil
import torch
# Original author: Francisco Massa:
# https://github.com/fmassa/object-detection.torch
# Ported to PyTorch by Max deGroot (02/01/2017)
def prior_box(cfg, image_size=None):
steps = cfg['steps']
feature_maps = [[ceil(image_size[0] / step), ceil(image_size[1] / step)] for step in steps]
min_sizes_ = cfg['min_sizes']
anchors = []
for k, f in enumerate(feature_maps):
min_sizes = min_sizes_[k]
for i, j in product(range(f[0]), range(f[1])):
for min_size in min_sizes:
s_kx = min_size / image_size[1]
s_ky = min_size / image_size[0]
dense_cx = [x * steps[k] / image_size[1] for x in [j + 0.5]]
dense_cy = [y * steps[k] / image_size[0] for y in [i + 0.5]]
for cy, cx in product(dense_cy, dense_cx):
anchors += [cx, cy, s_kx, s_ky]
# back to torch land
output = torch.Tensor(anchors).view(-1, 4)
return output
# Adapted from https://github.com/Hakuyume/chainer-ssd
def decode(loc, priors, variances):
"""Decode locations from predictions using priors to undo
the encoding we did for offset regression at train time.
Args:
loc (tensor): location predictions for loc layers,
Shape: [num_priors,4]
priors (tensor): Prior boxes in center-offset form.
Shape: [num_priors,4].
variances: (list[float]) Variances of priorboxes
Return:
decoded bounding box predictions
"""
boxes = torch.cat((
priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
boxes[:, :2] -= boxes[:, 2:] / 2
boxes[:, 2:] += boxes[:, :2]
return boxes
def decode_landmark(pre, priors, variances):
"""Decode landm from predictions using priors to undo
the encoding we did for offset regression at train time.
Args:
pre (tensor): landm predictions for loc layers,
Shape: [num_priors,10]
priors (tensor): Prior boxes in center-offset form.
Shape: [num_priors,4].
variances: (list[float]) Variances of priorboxes
Return:
decoded landm predictions
"""
landms = torch.cat((priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 2:4] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 4:6] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 6:8] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 8:10] * variances[0] * priors[:, 2:],
), dim=1)
return landms
def nms(box, scores, thresh):
x1 = box[:, 0]
y1 = box[:, 1]
x2 = box[:, 2]
y2 = box[:, 3]
zero = torch.tensor([0.0]).to(scores.device)
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort(descending=True)
keep = []
while order.shape[0] > 0:
i = order[0]
keep.append(i)
xx1 = torch.max(x1[i], x1[order[1:]])
yy1 = torch.max(y1[i], y1[order[1:]])
xx2 = torch.min(x2[i], x2[order[1:]])
yy2 = torch.min(y2[i], y2[order[1:]])
w = torch.max(zero, xx2 - xx1 + 1)
h = torch.max(zero, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = torch.where(ovr <= thresh)[0]
order = order[inds + 1]
return keep
+74
View File
@@ -0,0 +1,74 @@
# config.py
cfg_mnet = {
'name': 'mobilenet0.25',
'min_sizes': [[16, 32], [64, 128], [256, 512]],
'steps': [8, 16, 32],
'variance': [0.1, 0.2],
'clip': False,
'loc_weight': 2.0,
'gpu_train': True,
'batch_size': 32,
'ngpu': 1,
'epoch': 250,
'decay1': 190,
'decay2': 220,
'image_size': 640,
'pretrain': True,
'return_layers': {'stage1': 1, 'stage2': 2, 'stage3': 3},
'in_channel': 32,
'out_channel': 64
}
cfg_re50 = {
'name': 'Resnet50',
'min_sizes': [[16, 32], [64, 128], [256, 512]],
'steps': [8, 16, 32],
'variance': [0.1, 0.2],
'clip': False,
'loc_weight': 2.0,
'gpu_train': True,
'batch_size': 24,
'ngpu': 4,
'epoch': 100,
'decay1': 70,
'decay2': 90,
'image_size': 840,
'pretrain': False,
'return_layers': {'layer2': 1, 'layer3': 2, 'layer4': 3},
'in_channel': 256,
'out_channel': 256
}
cfg_slim = {
'name': 'slim',
'min_sizes': [[10, 16, 24], [32, 48], [64, 96], [128, 192, 256]],
'steps': [8, 16, 32, 64],
'variance': [0.1, 0.2],
'clip': False,
'loc_weight': 2.0,
'gpu_train': True,
'batch_size': 32,
'ngpu': 1,
'epoch': 250,
'decay1': 190,
'decay2': 220,
'image_size': 300
}
cfg_rfb = {
'name': 'RFB',
'min_sizes': [[10, 16, 24], [32, 48], [64, 96], [128, 192, 256]],
'steps': [8, 16, 32, 64],
'variance': [0.1, 0.2],
'clip': False,
'loc_weight': 2.0,
'gpu_train': True,
'batch_size': 32,
'ngpu': 1,
'epoch': 250,
'decay1': 190,
'decay2': 220,
'image_size': 300
}
+16
View File
@@ -0,0 +1,16 @@
from .Retinaface.Retinaface import FaceDetector
from .AgeGender.Detector import AgeGenderEstimator
from .FacialExpression.FaceExpression import EmotionDetector
from .InsightFace.models.Learner import FaceRecognizer
from .InsightFace.models.utils import update_facebank, load_facebank, special_draw
from .InsightFace.models.data.config import get_config
from .InsightFace.add_face import add_from_webcam, add_from_folder
# webcame classes
from .InsightFace.verifier import WebcamVerify
from .AgeGender.from_camera import WebcamAgeGenderEstimator
from .Retinaface.from_camera import WebcamFaceDetector
from .FacialExpression.from_camera import WebcamEmotionDetector
__all__ = ['FaceDetector', 'AgeGenderEstimator', 'EmotionDetector', 'FaceRecognizer', 'WebcamVerify', 'WebcamAgeGenderEstimator', 'WebcamFaceDetector',
'get_config', 'update_facebank', 'load_facebank', 'special_draw', 'add_from_webcam', 'add_from_folder', 'WebcamEmotionDetector']
+20
View File
@@ -0,0 +1,20 @@
from tqdm import tqdm
import requests
import os
def download_weight(link, file_name, verbose=True):
response = requests.get(link, stream=True)
total_size_in_bytes= int(response.headers.get('content-length', 0))
block_size = 1024 #1 Kibibyte
progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True,
desc='downloading defualt weights',
disable=False if verbose else True)
with open(file_name, 'wb') as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
progress_bar.close()
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
exit("ERROR, something went wrong (check your connection)")
+91
View File
@@ -0,0 +1,91 @@
import os
import torch
from PIL import Image
from diffusers import UNet2DConditionModel as OriginalUNet2DConditionModel
from utils.pipeline_sd15 import StableDiffusionControlNetPipeline
from diffusers import DDIMScheduler, DPMSolverMultistepScheduler, ControlNetModel
from diffusers.utils import load_image
from detail_encoder.encoder_plus import detail_encoder
from spiga_draw import *
from PIL import Image
from spiga.inference.config import ModelConfig
from spiga.inference.framework import SPIGAFramework
from facelib import FaceDetector
processor = SPIGAFramework(ModelConfig("300wpublic"))
detector = FaceDetector(weight_path="./models/mobilenet0.25_Final.pth")
def get_draw(pil_img, size):
spigas = spiga_process(pil_img, detector)
if spigas == False:
width, height = pil_img.size
black_image_pil = Image.new('RGB', (width, height), color=(0, 0, 0))
return black_image_pil
else:
spigas_faces = spiga_segmentation(spigas, size=size)
return spigas_faces
def is_image_file(filename):
return any(filename.endswith(extension) for extension in ['.png', '.jpg', '.jpeg', '.PNG', '.JPG', '.JPEG'])
def concatenate_images(image_files, output_file):
images = image_files # list
max_height = max(img.height for img in images)
images = [img.resize((img.width, max_height)) for img in images]
total_width = sum(img.width for img in images)
combined = Image.new('RGB', (total_width, max_height))
x_offset = 0
for img in images:
combined.paste(img, (x_offset, 0))
x_offset += img.width
combined.save(output_file)
model_id = "sd_model_v1-5". # your sdv1-5 path
makeup_encoder_path = "./models/stablemakeup/pytorch_model.bin"
id_encoder_path = "./models/stablemakeup/pytorch_model_1.bin"
pose_encoder_path = "./models/stablemakeup/pytorch_model_2.bin"
Unet = OriginalUNet2DConditionModel.from_pretrained(model_id, subfolder="unet").to("cuda")
id_encoder = ControlNetModel.from_unet(Unet)
pose_encoder = ControlNetModel.from_unet(Unet)
makeup_encoder = detail_encoder(Unet, "./models/image_encoder_l", "cuda", dtype=torch.float32)
makeup_state_dict = torch.load(makeup_encoder_path)
id_state_dict = torch.load(id_encoder_path)
id_encoder.load_state_dict(id_state_dict, strict=False)
pose_state_dict = torch.load(pose_encoder_path)
pose_encoder.load_state_dict(pose_state_dict, strict=False)
makeup_encoder.load_state_dict(makeup_state_dict, strict=False)
id_encoder.to("cuda")
pose_encoder.to("cuda")
makeup_encoder.to("cuda")
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
safety_checker=None,
unet=Unet,
controlnet=[id_encoder, pose_encoder],
torch_dtype=torch.float32).to("cuda")
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
def infer():
id_folder = "./test_imgs/id"
makeup_folder = "./test_imgs/makeup"
out_folder = "./output"
os.makedirs(out_folder, exist_ok=True)
for name in os.listdir(id_folder):
if not is_image_file(name):
continue
id_image = load_image(os.path.join(id_folder, name)).resize((512, 512))
for mu in os.listdir(makeup_folder):
if not is_image_file(mu):
continue
makeup_image = load_image(os.path.join(makeup_folder, mu)).resize((512, 512))
pose_image = get_draw(id_image, size=512)
for k in range(3):
result_img = makeup_encoder.generate(id_image=[id_image, pose_image], makeup_image=makeup_image,
pipe=pipe, guidance_scale=1.6)
result_img.save(os.path.join(out_folder, name.split(".")[0] + "_" + mu.split(".")[0] + "_" + str(k) + ".jpg"))
if __name__ == '__main__':
infer()
+229
View File
@@ -0,0 +1,229 @@
accelerate==0.23.0
addict==2.4.0
aiofiles==23.2.1
aiohttp==3.8.6
aiosignal==1.3.1
albumentations==1.3.1
altair==5.2.0
annotated-types==0.6.0
antlr4-python3-runtime==4.9.3
anyio==4.2.0
appdirs==1.4.4
asgiref==3.7.2
asttokens==2.4.1
async-timeout==4.0.3
attrs==23.1.0
av==11.0.0
boto3==1.29.5
botocore==1.32.5
cairocffi==1.6.1
CairoSVG==2.7.1
certifi==2023.7.22
cffi==1.16.0
chardet==4.0.0
charset-normalizer==3.3.0
click==8.1.7
cmake==3.27.6
coloredlogs==15.0.1
comm==0.2.0
contourpy==1.1.1
cssselect2==0.7.0
cssutils==2.9.0
cycler==0.10.0
Cython==3.0.8
datasets==2.14.6
decorator==5.1.1
defusedxml==0.7.1
Deprecated==1.2.14
diffusers==0.25.1
diffvg==0.0.1
dill==0.3.7
Django==4.2.7
dlib==19.24.2
dnspython==2.4.2
docker-pycreds==0.4.0
docopt==0.6.2
easydict==1.11
einops==0.7.0
ete3==3.1.3
exceptiongroup==1.2.0
executing==2.0.1
facelib @ git+https://github.com/sajjjadayobi/FaceLib.git@382841efec823f059f49754e5d378abb12cfb551
fastapi==0.109.0
ffmpy==0.3.1
filelock==3.12.4
flatbuffers==23.5.26
fonttools==4.43.1
frozenlist==1.4.0
fsspec==2023.9.2
fvcore==0.1.5.post20221221
gitdb==4.0.10
GitPython==3.1.37
gradio==3.27.0
gradio_client==0.8.0
greenlet==3.0.1
-e git+https://github.com/IDEA-Research/GroundingDINO.git@57535c5a79791cb76e36fdb64975271354f10251#egg=groundingdino
h11==0.14.0
hjson==3.1.0
httpcore==1.0.2
httpx==0.26.0
huggingface-hub==0.17.3
humanfriendly==10.0
idna==2.10
imagecorruptions==1.1.2
imageio==2.31.5
imageio-ffmpeg==0.4.9
imgaug==0.4.0
importlib-metadata==6.8.0
insightface==0.7.3
iopath==0.1.10
ipdb==0.13.13
ipython==8.17.2
ipywidgets==8.1.1
jedi==0.19.1
Jinja2==3.1.2
jmespath==1.0.1
jmspath==0.0.1
joblib==1.3.2
jsonpatch==1.33
jsonpointer==2.4
jsonschema==4.20.0
jsonschema-specifications==2023.12.1
jupyterlab-widgets==3.0.9
kiwisolver==1.4.5
lazy_loader==0.3
lightning-utilities==0.9.0
linkify-it-py==2.0.2
lit==17.0.2
llvmlite==0.41.1
markdown-it-py==2.2.0
MarkupSafe==2.1.3
matplotlib==3.8.0
matplotlib-inline==0.1.6
mdit-py-plugins==0.3.3
mdurl==0.1.2
moviepy==1.0.3
mpmath==1.3.0
multidict==6.0.4
multiprocess==0.70.15
networkx==3.1
ninja==1.11.1.1
numba==0.58.1
numpy==1.26.0
nvidia-cublas-cu11==11.10.3.66
nvidia-cuda-cupti-cu11==11.7.101
nvidia-cuda-nvrtc-cu11==11.7.99
nvidia-cuda-runtime-cu11==11.7.99
nvidia-cudnn-cu11==8.5.0.96
nvidia-cufft-cu11==10.9.0.58
nvidia-curand-cu11==10.2.10.91
nvidia-cusolver-cu11==11.4.0.1
nvidia-cusparse-cu11==11.7.4.91
nvidia-nccl-cu11==2.14.3
nvidia-nvtx-cu11==11.7.91
omegaconf==2.3.0
onnx==1.15.0
onnxruntime==1.16.3
opencv-python==4.8.1.78
opencv-python-headless==4.8.0.74
orderedset==2.0.3
orjson==3.9.10
packaging==23.2
pandas==2.1.1
parso==0.8.3
pathtools==0.1.2
pexpect==4.8.0
Pillow==10.0.1
platformdirs==4.1.0
portalocker==2.8.2
prettytable==3.9.0
proglog==0.1.10
progressbar==2.5
prompt-toolkit==3.0.39
protobuf==4.24.3
psutil==5.9.5
ptyprocess==0.7.0
pure-eval==0.2.2
pyaml==23.9.7
pyarrow==14.0.0
pycocotools==2.0.7
pycparser==2.21
pydantic==2.5.3
pydantic_core==2.14.6
pydub==0.25.1
Pygments==2.17.2
pymongo==4.6.0
pyparsing==2.4.7
python-dateutil==2.8.2
python-dotenv==1.0.0
python-magic==0.4.27
python-multipart==0.0.6
pytorch-lightning==2.1.1
pytz==2023.3.post1
PyYAML==6.0.1
qudida==0.0.4
referencing==0.32.1
regex==2023.10.3
requests==2.31.0
requests-toolbelt==1.0.0
roboflow==1.1.12
rpds-py==0.17.1
s3transfer==0.7.0
safetensors==0.3.3
scikit-image==0.22.0
scikit-learn==1.4.0
scipy==1.11.3
seaborn==0.13.0
segment-anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588
semantic-version==2.10.0
sentry-sdk==1.31.0
setproctitle==1.3.3
shapely==2.0.2
simplejson==3.19.2
six==1.16.0
smmap==5.0.1
sniffio==1.3.0
SQLAlchemy==2.0.22
sqlparse==0.4.4
stack-data==0.6.3
starlette==0.35.1
supervision==0.6.0
svgpathtools==1.6.1
svgwrite==1.4.3
sympy==1.12
tabulate==0.9.0
termcolor==2.4.0
threadpoolctl==3.2.0
tifffile==2023.9.26
timm==0.9.12
tinycss2==1.2.1
tokenizers==0.14.0
tomli==2.0.1
toolz==0.12.0
torch==2.0.1
torch-tools==0.1.5
torchmetrics==1.2.0
torchsummary==1.5.1
torchvision==0.15.2
tornado==6.3.3
tqdm==4.66.1
traitlets==5.13.0
transformers==4.34.0
triton==2.0.0
typing_extensions==4.8.0
tzdata==2023.3
uc-micro-py==1.0.2
urllib3==2.0.6
uvicorn==0.25.0
visdom==0.2.4
wandb==0.15.11
wcwidth==0.2.12
webencodings==0.5.1
websocket-client==1.6.4
websockets==11.0.3
widgetsnbextension==4.0.9
xxhash==3.4.1
yacs==0.1.8
yapf==0.40.2
yarl==1.9.2
zipp==3.17.0
+1867
View File
File diff suppressed because it is too large Load Diff
View File
View File
@@ -0,0 +1,98 @@
{
"norm":
{
"lnd_bbox": 7,
"corners": 8,
"pupils": 8,
"bbox": 7,
"height": 4
},
"ldm_ids": [101, 102, 103, 104, 105, 106, 107, 108, 24, 110, 111, 112,
113, 114, 115, 116, 117, 1, 119, 2, 121, 3, 4, 124, 5, 126,
6, 128, 129, 130, 17, 16, 133, 134, 135, 18, 7, 138, 139, 8,
141, 142, 11, 144, 145, 12, 147, 148, 20, 150, 151, 22, 153,
154, 21, 156, 157, 23, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168],
"ldm_flip_order": [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 27,
28, 29, 30, 35, 34, 33, 32, 31, 45, 44, 43, 42, 47,
46, 39, 38, 37, 36, 41, 40, 54, 53, 52, 51, 50, 49,
48, 59, 58, 57, 56, 55, 64, 63, 62, 61, 60, 67, 66,
65],
"test_subsets":
{
"indoor": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299],
"outdoor": [300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599]
},
"ldm_edges_matrix": [ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,98 @@
{
"norm":
{
"corners": 8,
"pupils": 8,
"height": 4
},
"ldm_ids": [101, 102, 103, 104, 105, 106, 107, 108, 24, 110, 111, 112,
113, 114, 115, 116, 117, 1, 119, 2, 121, 3, 4, 124, 5, 126,
6, 128, 129, 130, 17, 16, 133, 134, 135, 18, 7, 138, 139, 8,
141, 142, 11, 144, 145, 12, 147, 148, 20, 150, 151, 22, 153,
154, 21, 156, 157, 23, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168],
"ldm_flip_order": [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 27,
28, 29, 30, 35, 34, 33, 32, 31, 45, 44, 43, 42, 47,
46, 39, 38, 37, 36, 41, 40, 54, 53, 52, 51, 50, 49,
48, 59, 58, 57, 56, 55, 64, 63, 62, 61, 60, 67, 66,
65],
"test_subsets":
{
"helen": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329],
"lfpw": [330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553],
"common": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553],
"ibug": [554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688]
},
"ldm_edges_matrix": [ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,92 @@
{
"norm":
{
"lnd_bbox": 7,
"corners": 8,
"pupils": 8
},
"ldm_ids": [101, 102, 103, 104, 105, 106, 107, 108, 24, 110, 111, 112,
113, 114, 115, 116, 117, 1, 119, 2, 121, 3, 4, 124, 5, 126,
6, 128, 129, 130, 17, 16, 133, 134, 135, 18, 7, 138, 139, 8,
141, 142, 11, 144, 145, 12, 147, 148, 20, 150, 151, 22, 153,
154, 21, 156, 157, 23, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168],
"ldm_flip_order": [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 27,
28, 29, 30, 35, 34, 33, 32, 31, 45, 44, 43, 42, 47,
46, 39, 38, 37, 36, 41, 40, 54, 53, 52, 51, 50, 49,
48, 59, 58, 57, 56, 55, 64, 63, 62, 61, 60, 67, 66,
65],
"test_subsets":{},
"ldm_edges_matrix": [ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+288
View File
@@ -0,0 +1,288 @@
{
"norm":
{
"corners": 10,
"pupils": 8
},
"ldm_ids": [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 24, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 132, 1, 134, 2, 136,
3, 138, 139, 140, 141, 4, 143, 5, 145, 6, 147, 148, 149, 150,
151, 152, 153, 17, 16, 156, 157, 158, 18, 7, 161, 9, 163, 8,
165, 10, 167, 11, 169, 13, 171, 12, 173, 14, 175, 20, 177, 178,
22, 180, 181, 21, 183, 184, 23, 186, 187, 188, 189, 190, 191,
192, 193, 194, 195, 196, 197],
"ldm_flip_order": [32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20,
19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6,
5, 4, 3, 2, 1, 0,
46, 45, 44, 43, 42, 50, 49, 48, 47,
37, 36, 35, 34, 33, 41, 40, 39, 38,
51, 52, 53, 54,
59, 58, 57, 56, 55,
72, 71, 70, 69, 68, 75, 74, 73,
64, 63, 62, 61, 60, 67, 66, 65,
82, 81, 80, 79, 78, 77, 76,
87, 86, 85, 84, 83,
92, 91, 90, 89, 88,
95, 94, 93,
97, 96],
"test_subsets":
{
"pose": [305, 310, 312, 328, 338, 392, 395, 403, 407, 415, 438, 478, 494, 498, 511, 529, 535, 564,
572, 577, 582, 609, 619, 650, 655, 662, 669, 671, 673, 680, 704, 733, 765, 768, 804, 826, 837, 838, 839, 844, 845,
878, 893, 935, 942, 966, 969, 972, 1007, 1009, 1015, 1018, 1023, 1100, 1121, 1125, 1148, 1172, 1180, 1186, 1221,
1311, 1335, 1378, 1379, 1403, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416, 1417, 1418, 1419, 1420, 1421,
1422, 1423, 1424, 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435, 1436, 1437, 1438, 1439, 1440,
1441, 1442, 1443, 1444, 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457, 1458, 1459,
1460, 1461, 1462, 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 1474, 1475, 1476, 1477, 1478,
1479, 1480, 1481, 1482, 1483, 1484, 1485, 1486, 1487, 1488, 1489, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1497,
1498, 1499, 1500, 1501, 1502, 1503, 1504, 1505, 1506, 1507, 1508, 1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516,
1517, 1518, 1519, 1520, 1521, 1522, 1523, 1524, 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, 1534, 1535,
1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1553, 1554,
1555, 1556, 1557, 1558, 1559, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1569, 1570, 1571, 1572, 1573,
1574, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 1591, 1592,
1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611,
1612, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628, 1629, 1630,
1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, 1639, 1640, 1641, 1668, 1711, 1719, 1723, 1822, 1907, 1912, 1933,
1962, 1964, 1974, 2010, 2043, 2089, 2094, 2108, 2170, 2181, 2187, 2220, 2317, 2335, 2389, 2391, 2459, 2489],
"expression": [310, 329, 357, 380, 396, 400, 408, 410, 417, 450, 452, 461, 469, 472, 501, 502, 505, 506, 516, 533,
541, 568, 585, 588, 597, 600, 624, 649, 652, 655, 701, 724, 751, 789, 817, 874, 884, 918, 1010, 1032, 1066, 1091,
1095, 1220, 1272, 1298, 1303, 1347, 1356, 1398, 1428, 1445, 1462, 1464, 1470, 1482, 1507, 1512, 1520, 1536, 1541,
1543, 1545, 1548, 1565, 1587, 1598, 1638, 1642, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653,
1654, 1655, 1656, 1657, 1658, 1659, 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, 1670, 1671, 1672,
1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681, 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1689, 1690, 1691,
1692, 1693, 1694, 1695, 1696, 1697, 1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710,
1711, 1712, 1713, 1714, 1715, 1716, 1717, 1718, 1719, 1720, 1721, 1722, 1723, 1724, 1725, 1726, 1727, 1728, 1729,
1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739, 1740, 1741, 1742, 1743, 1744, 1745, 1746, 1747, 1748,
1749, 1750, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1761, 1762, 1763, 1764, 1765, 1766, 1767,
1768, 1769, 1770, 1771, 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1785, 1786,
1787, 1788, 1789, 1790, 1791, 1792, 1793, 1794, 1795, 1796, 1797, 1798, 1799, 1800, 1801, 1802, 1803, 1804, 1805,
1806, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, 1822, 1823, 1824,
1825, 1826, 1827, 1828, 1829, 1830, 1831, 1832, 1833, 1834, 1835, 1836, 1837, 1838, 1839, 1840, 1841, 1842, 1843,
1844, 1845, 1846, 1847, 1848, 1849, 1850, 1851, 1852, 1853, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, 1862,
1863, 1864, 1865, 1866, 1867, 1868, 1869, 1870, 1871, 1872, 1873, 1910, 1930, 1965, 2013, 2024, 2069, 2263, 2272,
2276, 2315, 2360, 2375, 2406, 2412],
"illumination": [306, 309, 312, 314, 321, 325, 327, 330, 335, 336, 338, 351, 353, 360, 361, 364, 368, 370, 372, 373,
374, 375, 381, 384, 388, 394, 404, 405, 410, 411, 415, 417, 418, 434, 435, 438, 448, 450, 454, 458, 460, 468, 469,
473, 476, 479, 481, 485, 487, 490, 492, 497, 503, 504, 505, 508, 512, 513, 514, 516, 517, 519, 525, 532, 536, 541,
544, 549, 553, 555, 558, 561, 562, 568, 573, 580, 582, 588, 592, 593, 596, 607, 611, 615, 619, 622, 624, 626, 627,
628, 629, 631, 632, 635, 636, 643, 648, 661, 666, 671, 673, 676, 680, 686, 693, 695, 697, 701, 702, 706, 710, 715,
719, 721, 723, 725, 726, 731, 732, 734, 738, 745, 750, 765, 770, 774, 781, 784, 787, 790, 796, 804, 805, 807, 810,
821, 832, 834, 838, 841, 842, 843, 844, 849, 853, 856, 866, 874, 887, 899, 900, 901, 902, 906, 909, 910, 911, 919,
921, 924, 929, 931, 939, 941, 942, 957, 959, 962, 964, 965, 968, 971, 972, 974, 983, 984, 987, 990, 996, 998,
1001, 1003, 1004, 1021, 1022, 1036, 1048, 1053, 1056, 1057, 1058, 1064, 1068, 1072, 1079, 1094, 1095, 1096, 1102,
1104, 1105, 1109, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126,
1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145,
1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, 1160, 1161, 1162, 1163, 1164,
1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, 1180, 1181, 1182, 1183,
1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, 1200, 1201, 1202,
1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1218, 1219, 1220, 1221,
1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240,
1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259,
1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297,
1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316,
1317, 1318, 1319, 1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, 1334, 1335,
1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354,
1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373,
1374, 1375, 1376, 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392,
1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1418, 1441, 1448,
1450, 1454, 1469, 1484, 1495, 1497, 1510, 1521, 1523, 1533, 1535, 1555, 1573, 1574, 1577, 1585, 1604, 1608, 1609,
1613, 1616, 1619, 1621, 1622, 1635, 1637, 1649, 1661, 1664, 1668, 1671, 1691, 1698, 1721, 1731, 1738, 1740, 1741,
1745, 1760, 1767, 1768, 1783, 1787, 1788, 1793, 1796, 1798, 1801, 1813, 1826, 1835, 1836, 1849, 1850, 1851, 1852,
1853, 1865, 1869, 1872, 1877, 1886, 1904, 1907, 1908, 1910, 1914, 1921, 1923, 1942, 1947, 1953, 1954, 1955, 1956,
1958, 1966, 1968, 1969, 1970, 1980, 1989, 1992, 2000, 2018, 2019, 2029, 2036, 2038, 2039, 2040, 2064, 2070, 2078,
2080, 2081, 2084, 2088, 2090, 2098, 2106, 2112, 2123, 2128, 2135, 2145, 2151, 2163, 2171, 2172, 2176, 2180, 2183,
2184, 2186, 2192, 2196, 2201, 2204, 2208, 2209, 2213, 2215, 2218, 2223, 2226, 2236, 2240, 2242, 2245, 2246, 2253,
2264, 2270, 2273, 2280, 2281, 2285, 2287, 2288, 2289, 2290, 2301, 2313, 2314, 2321, 2323, 2331, 2334, 2336, 2337,
2343, 2354, 2355, 2357, 2358, 2361, 2367, 2368, 2371, 2374, 2375, 2377, 2381, 2384, 2385, 2386, 2394, 2395, 2397,
2407, 2409, 2413, 2414, 2425, 2429, 2434, 2435, 2439, 2444, 2445, 2448, 2449, 2451, 2454, 2466, 2467, 2480, 2481,
2484, 2487, 2489, 2491, 2497],
"makeup": [313, 425, 464, 468, 501, 551, 614, 621, 707, 769, 776, 846, 870, 878, 900, 910, 925, 952, 960, 961, 965,
981, 1237, 1242, 1380, 1395, 1503, 1574, 1633, 1668, 1708, 1764, 1791, 1837, 1860, 1874, 1875, 1876, 1877, 1878,
1879, 1880, 1881, 1882, 1883, 1884, 1885, 1886, 1887, 1888, 1889, 1890, 1891, 1892, 1893, 1894, 1895, 1896, 1897,
1898, 1899, 1900, 1901, 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, 1913, 1914, 1915, 1916,
1917, 1918, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, 1931, 1932, 1933, 1934, 1935,
1936, 1937, 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954,
1955, 1956, 1957, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973,
1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992,
1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030,
2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2265, 2290, 2293, 2381, 2486, 2490],
"occlusion": [300, 301, 310, 328, 329, 332, 333, 335, 336, 338, 339, 342, 343, 356, 362, 367, 370, 374, 377, 384,
401, 406, 408, 417, 421, 423, 426, 427, 434, 435, 440, 453, 457, 461, 465, 466, 472, 475, 476, 479, 489, 497, 500,
504, 506, 514, 515, 518, 520, 524, 525, 536, 543, 546, 558, 559, 563, 564, 567, 568, 574, 577, 584, 589, 590, 592,
594, 602, 605, 609, 610, 614, 618, 627, 638, 640, 643, 645, 646, 652, 658, 661, 663, 666, 672, 674, 677, 682, 685,
700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722,
723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745,
746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768,
769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791,
792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814,
815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837,
838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860,
861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883,
884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906,
907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929,
930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952,
953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975,
976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998,
999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017,
1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036,
1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055,
1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074,
1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093,
1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1112, 1121,
1127, 1129, 1135, 1136, 1143, 1146, 1147, 1148, 1151, 1160, 1161, 1166, 1170, 1177, 1180, 1190, 1191, 1195, 1201,
1218, 1223, 1231, 1233, 1239, 1240, 1241, 1245, 1248, 1260, 1275, 1277, 1278, 1280, 1283, 1292, 1298, 1299, 1313,
1320, 1328, 1333, 1338, 1339, 1342, 1347, 1355, 1363, 1368, 1373, 1377, 1382, 1383, 1384, 1389, 1390, 1391, 1399,
1401, 1404, 1405, 1412, 1415, 1422, 1426, 1427, 1431, 1438, 1450, 1479, 1485, 1489, 1493, 1497, 1499, 1506, 1516,
1524, 1526, 1531, 1533, 1534, 1547, 1555, 1560, 1561, 1592, 1595, 1603, 1616, 1622, 1625, 1632, 1647, 1650, 1651,
1655, 1657, 1658, 1661, 1665, 1667, 1672, 1683, 1684, 1694, 1702, 1708, 1711, 1713, 1722, 1723, 1728, 1734, 1748,
1750, 1752, 1753, 1757, 1764, 1775, 1793, 1794, 1801, 1809, 1813, 1820, 1832, 1838, 1841, 1856, 1864, 1866, 1869,
1878, 1882, 1895, 1898, 1921, 1930, 1931, 1936, 1941, 1946, 1947, 1953, 1975, 1979, 1982, 1983, 1985, 1989, 1999,
2013, 2021, 2026, 2042, 2044, 2047, 2048, 2059, 2069, 2077, 2083, 2085, 2086, 2087, 2094, 2110, 2112, 2122, 2128,
2131, 2134, 2136, 2139, 2144, 2151, 2155, 2163, 2175, 2190, 2197, 2199, 2208, 2209, 2211, 2213, 2238, 2246, 2279,
2301, 2305, 2316, 2321, 2324, 2328, 2329, 2331, 2335, 2339, 2340, 2348, 2351, 2354, 2364, 2367, 2370, 2371, 2372,
2376, 2379, 2381, 2391, 2402, 2409, 2410, 2411, 2412, 2413, 2430, 2433, 2435, 2449, 2450, 2466, 2471, 2473, 2476,
2478, 2484, 2485, 2487, 2491, 2494],
"blur": [300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320,
321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366,
367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389,
390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412,
413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435,
436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458,
459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481,
482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504,
505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527,
528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550,
551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596,
597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619,
620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642,
643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665,
666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688,
689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 702, 705, 714, 725, 731, 738, 740, 742, 745, 746, 748, 752,
753, 754, 756, 760, 765, 766, 773, 780, 784, 785, 787, 796, 798, 799, 800, 804, 808, 809, 812, 813, 816, 817, 823,
824, 827, 832, 834, 838, 865, 872, 874, 879, 882, 889, 891, 899, 901, 908, 911, 918, 924, 929, 931, 932, 938, 941,
951, 953, 954, 959, 960, 965, 968, 969, 971, 977, 985, 989, 991, 995, 999, 1001, 1004, 1018, 1021, 1026, 1029,
1031, 1033, 1040, 1050, 1056, 1060, 1061, 1069, 1074, 1076, 1084, 1085, 1099, 1103, 1109, 1115, 1117, 1118, 1120,
1124, 1127, 1133, 1142, 1146, 1148, 1149, 1151, 1171, 1177, 1178, 1181, 1184, 1185, 1187, 1191, 1193, 1194, 1201,
1202, 1206, 1217, 1224, 1226, 1227, 1231, 1232, 1236, 1237, 1246, 1250, 1254, 1255, 1258, 1260, 1262, 1263, 1266,
1269, 1270, 1278, 1280, 1283, 1286, 1288, 1293, 1300, 1301, 1315, 1317, 1320, 1324, 1325, 1333, 1363, 1365, 1370,
1373, 1380, 1382, 1386, 1392, 1396, 1400, 1402, 1403, 1413, 1421, 1422, 1424, 1434, 1442, 1450, 1451, 1459, 1464,
1468, 1475, 1481, 1483, 1495, 1506, 1510, 1518, 1519, 1522, 1546, 1554, 1559, 1570, 1573, 1577, 1578, 1589, 1593,
1601, 1603, 1608, 1609, 1612, 1613, 1614, 1616, 1618, 1619, 1633, 1642, 1644, 1647, 1649, 1652, 1653, 1658, 1660,
1663, 1664, 1670, 1672, 1673, 1689, 1691, 1695, 1705, 1710, 1712, 1714, 1716, 1718, 1720, 1727, 1731, 1735, 1737,
1739, 1742, 1750, 1761, 1765, 1768, 1770, 1776, 1779, 1782, 1785, 1788, 1796, 1811, 1813, 1815, 1826, 1828, 1831,
1838, 1853, 1854, 1860, 1864, 1875, 1878, 1883, 1885, 1887, 1891, 1915, 1919, 1921, 1923, 1926, 1936, 1940, 1941,
1942, 1951, 1954, 1957, 1975, 1978, 1981, 1997, 1998, 2005, 2015, 2021, 2028, 2031, 2036, 2040, 2042, 2046, 2047,
2054, 2060, 2066, 2069, 2076, 2077, 2078, 2083, 2085, 2090, 2093, 2099, 2106, 2112, 2117, 2132, 2135, 2136, 2148,
2151, 2158, 2162, 2163, 2167, 2169, 2172, 2173, 2184, 2186, 2194, 2201, 2203, 2225, 2229, 2235, 2237, 2242, 2259,
2260, 2269, 2270, 2279, 2281, 2284, 2290, 2298, 2301, 2305, 2309, 2311, 2312, 2313, 2317, 2318, 2325, 2328, 2333,
2335, 2336, 2337, 2341, 2344, 2357, 2371, 2372, 2374, 2375, 2380, 2390, 2396, 2397, 2410, 2418, 2419, 2423, 2428,
2444, 2457, 2458, 2464, 2472, 2473, 2476, 2488, 2489]
},
"ldm_edges_matrix": [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File
+158
View File
@@ -0,0 +1,158 @@
import os
import json
import cv2
import numpy as np
from PIL import Image
from torch.utils.data import Dataset
from torchvision import transforms
from .transforms import get_transformers
class AlignmentsDataset(Dataset):
'''Loads datasets of images with landmarks and bounding boxes.
'''
def __init__(self,
database,
json_file,
images_dir,
image_size=(128, 128),
transform=None,
indices=None,
debug=False):
"""
:param database: class DatabaseStruct containing all the specifics of the database
:param json_file: path to the json file which contains the names of the images, landmarks, bounding boxes, etc
:param images_dir: path of the directory containing the images.
:param image_size: tuple like e.g. (128, 128)
:param transform: composition of transformations that will be applied to the samples.
:param debug_mode: bool if True, loads a very reduced_version of the dataset for debugging purposes.
:param indices: If it is a list of indices, allows to work with the subset of
items specified by the list. If it is None, the whole set is used.
"""
self.database = database
self.images_dir = images_dir
self.transform = transform
self.image_size = image_size
self.indices = indices
self._imgs_dict = None
self.debug = debug
with open(json_file) as jsonfile:
self.data = json.load(jsonfile)
def __len__(self):
'''Returns the length of the dataset
'''
if self.indices is None:
return len(self.data)
else:
return len(self.indices)
def __getitem__(self, sample_idx):
'''Returns sample of the dataset of index idx'''
# To allow work with a subset
if self.indices is not None:
sample_idx = self.indices[sample_idx]
# Load sample image
img_name = os.path.join(self.images_dir, self.data[sample_idx]['imgpath'])
if not self._imgs_dict:
image_cv = cv2.imread(img_name)
else:
image_cv = self._imgs_dict[sample_idx]
# Some images are B&W. We make sure that any image has three channels.
if len(image_cv.shape) == 2:
image_cv = np.repeat(image_cv[:, :, np.newaxis], 3, axis=-1)
# Some images have alpha channel
image_cv = image_cv[:, :, :3]
image_cv = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image_cv)
# Load sample anns
ids = np.array(self.data[sample_idx]['ids'])
landmarks = np.array(self.data[sample_idx]['landmarks'])
bbox = np.array(self.data[sample_idx]['bbox'])
vis = np.array(self.data[sample_idx]['visible'])
headpose = self.data[sample_idx]['headpose']
# Generate bbox if need it
if bbox is None:
# Compute bbox using landmarks
aux = landmarks[vis == 1.0]
bbox = np.zeros(4)
bbox[0] = min(aux[:, 0])
bbox[1] = min(aux[:, 1])
bbox[2] = max(aux[:, 0]) - bbox[0]
bbox[3] = max(aux[:, 1]) - bbox[1]
# Clean and mask landmarks
mask_ldm = np.ones(self.database.num_landmarks)
if not self.database.ldm_ids == ids.tolist():
new_ldm = np.zeros((self.database.num_landmarks, 2))
new_vis = np.zeros(self.database.num_landmarks)
xyv = np.hstack((landmarks, vis[np.newaxis,:].T))
ids_dict = dict(zip(ids.astype(int).astype(str), xyv))
for pos, identifier in enumerate(self.database.ldm_ids):
if str(identifier) in ids_dict:
x, y, v = ids_dict[str(identifier)]
new_ldm[pos] = [x,y]
new_vis[pos] = v
else:
mask_ldm[pos] = 0
landmarks = new_ldm
vis = new_vis
sample = {'image': image,
'sample_idx': sample_idx,
'imgpath': img_name,
'ids_ldm': np.array(self.database.ldm_ids),
'bbox': bbox,
'bbox_raw': bbox,
'landmarks': landmarks,
'visible': vis.astype(np.float64),
'mask_ldm': mask_ldm,
'imgpath_local': self.data[sample_idx]['imgpath'],
}
if self.debug:
sample['landmarks_ori'] = landmarks
sample['visible_ori'] = vis.astype(np.float64)
sample['mask_ldm_ori'] = mask_ldm
if headpose is not None:
sample['headpose_ori'] = np.array(headpose)
if self.transform:
sample = self.transform(sample)
return sample
def get_dataset(data_config, pretreat=None, debug=False):
augmentors = get_transformers(data_config)
if pretreat is not None:
augmentors.append(pretreat)
dataset = AlignmentsDataset(data_config.database,
data_config.anns_file,
data_config.image_dir,
image_size=data_config.image_size,
transform=transforms.Compose(augmentors),
indices=data_config.ids,
debug=debug)
return dataset
+122
View File
@@ -0,0 +1,122 @@
import numpy as np
from scipy import interpolate
import cv2
class AddBoundary(object):
def __init__(self, num_landmarks=68, map_size=64, sigma=1, min_dpi=64):
self.num_landmarks = num_landmarks
self.sigma = sigma
if isinstance(map_size, (tuple, list)):
self.width = map_size[0]
self.height = map_size[1]
else:
self.width = map_size
self.height = map_size
if max(map_size) > min_dpi:
self.dpi = max(map_size)
else:
self.dpi = min_dpi
self.fig_size =[self.height/self.dpi, self.width/self.dpi]
def __call__(self, sample):
landmarks = sample['landmarks_float']
mask_lnd = sample['mask_ldm_float']
boundaries = self.get_dataset_boundaries(landmarks, mask_lnd)
functions = {}
for key, points in boundaries.items():
if len(points) != 0:
temp = points[0]
new_points = points[0:1, :]
for point in points[1:]:
if point[0] == temp[0] and point[1] == temp[1]:
continue
else:
new_points = np.concatenate((new_points, np.expand_dims(point, 0)), axis=0)
temp = point
points = new_points
if points.shape[0] == 1:
points = np.concatenate((points, points+0.001), axis=0)
k = min(4, points.shape[0])
functions[key] = interpolate.splprep([points[:, 0], points[:, 1]], k=k-1,s=0)
boundary_maps = np.zeros((len(boundaries), self.height, self.width))
for i_map, key in enumerate(functions.keys()):
boundary_map = np.zeros((self.height, self.width))
xnew = np.arange(0, 1, 1/self.dpi)
out = interpolate.splev(xnew, functions[key][0], der=0)
out = np.round(out).astype(int).transpose()
out = out[out[:, 0] < self.height]
out = out[out[:, 1] < self.width]
boundary_map[out[:,1], out[:,0]]= 255
# Smooth
sigma = self.sigma
temp = 255 - boundary_map.astype(np.uint8)
temp = cv2.distanceTransform(temp, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
temp = temp.astype(np.float32)
temp = np.where(temp < 3*sigma, np.exp(-(temp*temp)/(2*sigma*sigma)), 0 )
boundary_maps[i_map] = temp
sample['boundary'] = boundary_maps
return sample
def get_dataset_boundaries(self, landmarks, mask_lnd):
boundaries = {}
if self.num_landmarks == 68:
cheek = landmarks[0:17]
boundaries['cheek'] = cheek[mask_lnd[0:17] > 0]
left_eyebrow = landmarks[17:22]
boundaries['left_eyebrow'] = left_eyebrow[mask_lnd[17:22] > 0]
right_eyebrow = landmarks[22:27]
boundaries['right_eyebrow'] = right_eyebrow[mask_lnd[22:27] > 0]
nose = landmarks[27:31]
boundaries['nose'] = nose[mask_lnd[27:31] > 0]
nose_bot = landmarks[31:36]
boundaries['nose_bot'] = nose_bot[mask_lnd[31:36] > 0]
uper_left_eyelid = landmarks[36:40]
boundaries['upper_left_eyelid'] = uper_left_eyelid[mask_lnd[36:40] > 0]
lower_left_eyelid = np.array([landmarks[i] for i in [36, 41, 40, 39]])
lower_left_eyelid_mask = np.array([mask_lnd[i] for i in [36, 41, 40, 39]])
boundaries['lower_left_eyelid'] = lower_left_eyelid[lower_left_eyelid_mask > 0]
upper_right_eyelid = landmarks[42:46]
boundaries['upper_right_eyelid'] = upper_right_eyelid[mask_lnd[42:46] > 0]
lower_right_eyelid = np.array([landmarks[i] for i in [42, 47, 46, 45]])
lower_right_eyelid_mask = np.array([mask_lnd[i] for i in [42, 47, 46, 45]])
boundaries['lower_right_eyelid'] = lower_right_eyelid[lower_right_eyelid_mask > 0]
upper_outer_lip = landmarks[48:55]
boundaries['upper_outer_lip'] = upper_outer_lip[mask_lnd[48:55] > 0]
lower_outer_lip = np.array([landmarks[i] for i in [48, 59, 58, 57, 56, 55, 54]])
lower_outer_lip_mask = np.array([mask_lnd[i] for i in [48, 59, 58, 57, 56, 55, 54]])
boundaries['lower_outer_lip'] = lower_outer_lip[lower_outer_lip_mask > 0]
upper_inner_lip = np.array([landmarks[i] for i in [60, 61, 62, 63, 64]])
upper_inner_lip_mask = np.array([mask_lnd[i] for i in [60, 61, 62, 63, 64]])
boundaries['upper_inner_lip'] = upper_inner_lip[upper_inner_lip_mask > 0]
lower_inner_lip = np.array([landmarks[i] for i in [60, 67, 66, 65, 64]])
lower_inner_lip_mask = np.array([mask_lnd[i] for i in [60, 67, 66, 65, 64]])
boundaries['lower_inner_lip'] = lower_inner_lip[lower_inner_lip_mask > 0]
elif self.num_landmarks == 98:
boundaries['cheek'] = landmarks[0:33]
boundaries['upper_left_eyebrow'] = landmarks[33:38]
boundaries['lower_left_eyebrow'] = np.array([landmarks[i] for i in [33, 41, 40, 39, 38]])
boundaries['upper_right_eyebrow'] = landmarks[42:47]
boundaries['lower_right_eyebrow'] = landmarks[46:51]
boundaries['nose'] = landmarks[51:55]
boundaries['nose_bot'] = landmarks[55:60]
boundaries['upper_left_eyelid'] = landmarks[60:65]
boundaries['lower_left_eyelid'] = np.array([landmarks[i] for i in [60, 67, 66, 65, 64]])
boundaries['upper_right_eyelid'] = landmarks[68:73]
boundaries['lower_right_eyelid'] = np.array([landmarks[i] for i in [68, 75, 74, 73, 72]])
boundaries['upper_outer_lip'] = landmarks[76:83]
boundaries['lower_outer_lip'] = np.array([landmarks[i] for i in [76, 87, 86, 85, 84, 83, 82]])
boundaries['upper_inner_lip'] = np.array([landmarks[i] for i in [88, 89, 90, 91, 92]])
boundaries['lower_inner_lip'] = np.array([landmarks[i] for i in [88, 95, 94, 93, 92]])
return boundaries
+39
View File
@@ -0,0 +1,39 @@
import numpy as np
class Heatmaps:
def __init__(self, num_maps, map_size, sigma, stride=1, norm=True):
self.num_maps = num_maps
self.sigma = sigma
self.double_sigma_pw2 = 2*sigma*sigma
self.doublepi_sigma_pw2 = self.double_sigma_pw2 * np.pi
self.stride = stride
self.norm = norm
if isinstance(map_size, (tuple, list)):
self.width = map_size[0]
self.height = map_size[1]
else:
self.width = map_size
self.height = map_size
grid_x = np.arange(self.width) * stride + stride / 2 - 0.5
self.grid_x = np.repeat(grid_x.reshape(1, self.width), self.num_maps, axis=0)
grid_y = np.arange(self.height) * stride + stride / 2 - 0.5
self.grid_y = np.repeat(grid_y.reshape(1, self.height), self.num_maps, axis=0)
def __call__(self, sample):
landmarks = sample['landmarks']
landmarks = landmarks[-self.num_maps:]
# Heatmap generation
exp_x = np.exp(-(self.grid_x - landmarks[:, 0].reshape(-1, 1)) ** 2 / self.double_sigma_pw2)
exp_y = np.exp(-(self.grid_y - landmarks[:, 1].reshape(-1, 1)) ** 2 / self.double_sigma_pw2)
heatmaps = np.matmul(exp_y.reshape(self.num_maps, self.height, 1), exp_x.reshape(self.num_maps, 1, self.width))
if self.norm:
heatmaps = heatmaps/self.doublepi_sigma_pw2
sample['heatmap2D'] = heatmaps
return sample
+307
View File
@@ -0,0 +1,307 @@
import random
import cv2
import numpy as np
from PIL import Image
from torchvision import transforms
# My libs
from. import utils as dlu
class HorizontalFlipAug:
def __init__(self, ldm_flip_order, prob=0.5):
self.prob = prob
self.ldm_flip_order = ldm_flip_order
def __call__(self, sample):
img = sample['image']
landmarks = sample['landmarks']
mask = sample['mask_ldm']
vis = sample['visible']
bbox = sample['bbox']
if random.random() < self.prob:
new_img = transforms.functional.hflip(img)
lm_new_order = self.ldm_flip_order
new_landmarks = landmarks[lm_new_order]
new_landmarks = (new_landmarks - (img.size[0], 0)) * (-1, 1)
new_mask = mask[lm_new_order]
new_vis = vis[lm_new_order]
x, y, w, h = bbox
new_x = img.size[0] - x - w
new_bbox = np.array((new_x, y, w, h))
sample['image'] = new_img
sample['landmarks'] = new_landmarks
sample['mask_ldm'] = new_mask
sample['visible'] = new_vis
sample['bbox'] = new_bbox
return sample
class GeometryBaseAug:
def __call__(self, sample):
raise NotImplementedError('Inheritance __call__ not defined')
def map_affine_transformation(self, sample, affine_transf, new_size=None):
sample['image'] = self._image_affine_trans(sample['image'], affine_transf, new_size)
sample['bbox'] = self._bbox_affine_trans(sample['bbox'], affine_transf)
if 'landmarks' in sample.keys():
sample['landmarks'] = self._landmarks_affine_trans(sample['landmarks'], affine_transf)
return sample
def clean_outbbox_landmarks(self, shape, landmarks, mask):
filter_x1 = landmarks[:, 0] >= shape[0]
filter_x2 = landmarks[:, 0] < (shape[0] + shape[2])
filter_x = np.logical_and(filter_x1,filter_x2)
filter_y1 = landmarks[:, 1] >= shape[1]
filter_y2 = landmarks[:, 1] < (shape[1] + shape[3])
filter_y = np.logical_and(filter_y1, filter_y2)
filter_bbox = np.logical_and(filter_x, filter_y)
new_mask = mask*filter_bbox
new_landmarks = (landmarks.T * new_mask).T
new_landmarks = new_landmarks.astype(int).astype(float)
return new_mask, new_landmarks
def _image_affine_trans(self, image, affine_transf, new_size=None):
if not new_size:
new_size = image.size
inv_affine_transf = dlu.get_inverse_transf(affine_transf)
new_image = image.transform(new_size, Image.AFFINE, inv_affine_transf.flatten())
return new_image
def _bbox_affine_trans(self, bbox, affine_transf):
x, y, w, h = bbox
images_bb = []
for point in ([x, y, 1], [x + w, y, 1],
[x, y + h, 1], [x + w, y + h, 1]):
images_bb.append(affine_transf.dot(point))
images_bb = np.array(images_bb)
new_corner0 = np.min(images_bb, axis=0)
new_corner1 = np.max(images_bb, axis=0)
new_x, new_y = new_corner0
new_w, new_h = new_corner1 - new_corner0
new_bbox = np.array((new_x, new_y, new_w, new_h))
return new_bbox
def _landmarks_affine_trans(self, landmarks, affine_transf):
homog_landmarks = dlu.affine2homogeneous(landmarks)
new_landmarks = affine_transf.dot(homog_landmarks.T).T
return new_landmarks
class RSTAug(GeometryBaseAug):
def __init__(self, angle_range=45., scale_min=-0.15, scale_max=0.15, trl_ratio=0.05):
self.scale_max = scale_max
self.scale_min = scale_min
self.angle_range = angle_range
self.trl_ratio = trl_ratio
def __call__(self, sample):
x, y, w, h = sample['bbox']
x0, y0 = x + w/2, y + h/2 # center of the face, which will be the center of the rotation
# Bbox translation
rnd_Tx = np.random.uniform(-self.trl_ratio, self.trl_ratio) * w
rnd_Ty = np.random.uniform(-self.trl_ratio, self.trl_ratio) * h
sample['bbox'][0] += rnd_Tx
sample['bbox'][1] += rnd_Ty
scale = 1 + np.random.uniform(self.scale_min, self.scale_max)
angle = np.random.uniform(-self.angle_range, self.angle_range)
similarity = dlu.get_similarity_matrix(angle, scale, center=(x0, y0))
new_sample = self.map_affine_transformation(sample, similarity)
return new_sample
class TargetCropAug(GeometryBaseAug):
def __init__(self, img_new_size=128, map_new_size=128, target_dist=1.3):
self.target_dist = target_dist
self.new_size_x, self.new_size_y = self._convert_shapes(img_new_size)
self.map_size_x, self.map_size_y = self._convert_shapes(map_new_size)
self.img2map_scale = False
# Mismatch between img shape and featuremap shape
if self.map_size_x != self.new_size_x or self.map_size_y != self.new_size_y:
self.img2map_scale = True
self.map_scale_x = self.map_size_x / self.new_size_x
self.map_scale_y = self.map_size_y / self.new_size_y
self.map_scale_xx = self.map_scale_x * self.map_scale_x
self.map_scale_xy = self.map_scale_x * self.map_scale_y
self.map_scale_yy = self.map_scale_y * self.map_scale_y
def _convert_shapes(self, new_size):
if isinstance(new_size, (tuple, list)):
new_size_x = new_size[0]
new_size_y = new_size[1]
else:
new_size_x = new_size
new_size_y = new_size
return new_size_x, new_size_y
def __call__(self, sample):
x, y, w, h = sample['bbox']
# we enlarge the area taken around the bounding box
# it is neccesary to change the botton left point of the bounding box
# according to the previous enlargement. Note this will NOT be the new
# bounding box!
# We return square images, which is neccesary since
# all the images must have the same size in order to form batches
side = max(w, h) * self.target_dist
x -= (side - w) / 2
y -= (side - h) / 2
# center of the enlarged bounding box
x0, y0 = x + side/2, y + side/2
# homothety factor, chosen so the new horizontal dimension will
# coincide with new_size
mu_x = self.new_size_x / side
mu_y = self.new_size_y / side
# new_w, new_h = new_size, int(h * mu)
new_w = self.new_size_x
new_h = self.new_size_y
new_x0, new_y0 = new_w / 2, new_h / 2
# dilatation + translation
affine_transf = np.array([[mu_x, 0, new_x0 - mu_x * x0],
[0, mu_y, new_y0 - mu_y * y0]])
sample = self.map_affine_transformation(sample, affine_transf,(new_w, new_h))
if 'landmarks' in sample.keys():
img_shape = np.array([0, 0, self.new_size_x, self.new_size_y])
sample['landmarks_float'] = sample['landmarks']
sample['mask_ldm_float'] = sample['mask_ldm']
sample['landmarks'] = np.round(sample['landmarks'])
sample['mask_ldm'], sample['landmarks'] = self.clean_outbbox_landmarks(img_shape, sample['landmarks'],
sample['mask_ldm'])
if self.img2map_scale:
sample = self._rescale_map(sample)
return sample
def _rescale_map(self, sample):
# Rescale
lnd_float = sample['landmarks_float']
lnd_float[:, 0] = self.map_scale_x * lnd_float[:, 0]
lnd_float[:, 1] = self.map_scale_y * lnd_float[:, 1]
# Filter landmarks
lnd = np.round(lnd_float)
filter_x = lnd[:, 0] >= self.map_size_x
filter_y = lnd[:, 1] >= self.map_size_y
lnd[filter_x] = self.map_size_x - 1
lnd[filter_y] = self.map_size_y - 1
new_lnd = (lnd.T * sample['mask_ldm']).T
new_lnd = new_lnd.astype(int).astype(float)
sample['landmarks_float'] = lnd_float
sample['landmarks'] = new_lnd
sample['img2map_scale'] = [self.map_scale_x, self.map_scale_y]
return sample
class OcclusionAug:
def __init__(self, min_length=0.1, max_length=0.4, num_maps=1):
self.min_length = min_length
self.max_length = max_length
self.num_maps = num_maps
def __call__(self, sample):
x, y, w, h = sample['bbox']
image = sample['image']
landmarks = sample['landmarks']
vis = sample['visible']
min_ratio = self.min_length
max_ratio = self.max_length
rnd_width = np.random.randint(int(w * min_ratio), int(w * max_ratio))
rnd_height = np.random.randint(int(h * min_ratio), int(h * max_ratio))
# (xi, yi) and (xf, yf) are, respectively, the lower left points of the
# occlusion rectangle and the upper right point.
xi = int(x + np.random.randint(0, w - rnd_width))
xf = int(xi + rnd_width)
yi = int(y + np.random.randint(0, h - rnd_height))
yf = int(yi + rnd_height)
pixels = np.array(image)
pixels[yi:yf, xi:xf, :] = np.random.uniform(0, 255, size=3)
image = Image.fromarray(pixels)
sample['image'] = image
# Update visibilities
filter_x1 = landmarks[:, 0] >= xi
filter_x2 = landmarks[:, 0] < xf
filter_x = np.logical_and(filter_x1, filter_x2)
filter_y1 = landmarks[:, 1] >= yi
filter_y2 = landmarks[:, 1] < yf
filter_y = np.logical_and(filter_y1, filter_y2)
filter_novis = np.logical_and(filter_x, filter_y)
filter_vis = np.logical_not(filter_novis)
sample['visible'] = vis * filter_vis
return sample
class LightingAug:
def __init__(self, hsv_range_min=(-0.5, -0.5, -0.5), hsv_range_max=(0.5, 0.5, 0.5)):
self.hsv_range_min = hsv_range_min
self.hsv_range_max = hsv_range_max
def __call__(self, sample):
# Convert to HSV colorspace from RGB colorspace
image = np.array(sample['image'])
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
# Generate new random values
H = 1 + np.random.uniform(self.hsv_range_min[0], self.hsv_range_max[0])
S = 1 + np.random.uniform(self.hsv_range_min[1], self.hsv_range_max[1])
V = 1 + np.random.uniform(self.hsv_range_min[2], self.hsv_range_max[2])
hsv[:, :, 0] = np.clip(H*hsv[:, :, 0], 0, 179)
hsv[:, :, 1] = np.clip(S*hsv[:, :, 1], 0, 255)
hsv[:, :, 2] = np.clip(V*hsv[:, :, 2], 0, 255)
# Convert back to BGR colorspace
image = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
sample['image'] = Image.fromarray(image)
return sample
class BlurAug:
def __init__(self, blur_prob=0.5, blur_kernel_range=(0, 2)):
self.blur_prob = blur_prob
self.kernel_range = blur_kernel_range
def __call__(self, sample):
# Smooth image
image = np.array(sample['image'])
if np.random.uniform(0.0, 1.0) < self.blur_prob:
kernel = np.random.random_integers(self.kernel_range[0], self.kernel_range[1]) * 2 + 1
image = cv2.GaussianBlur(image, (kernel, kernel), 0, 0)
sample['image'] = Image.fromarray(image)
return sample
@@ -0,0 +1,199 @@
import os
import numpy as np
import cv2
# My libs
from .utils import rotation_matrix_to_euler
current_path = os.path.dirname(os.path.abspath(__file__))
current_path = os.path.dirname(current_path)
current_path = os.path.dirname(current_path)
# Model file nomenclature
model_file_dft = os.path.join(current_path,"models3D/mean_face_3D_{num_ldm}.txt")
class PositPose:
def __init__(self, ldm_ids, focal_ratio=1, selected_ids=None, max_iter=100,
fix_bbox=True, model_file=model_file_dft):
# Load 3D face model
model3d_world, model3d_ids = self._load_world_shape(ldm_ids, model_file)
# Generate id mask to pick only the robust landmarks for posit
if selected_ids is None:
model3d_mask = np.ones(len(ldm_ids))
else:
model3d_mask = np.zeros(len(ldm_ids))
for index, posit_id in enumerate(model3d_ids):
if posit_id in selected_ids:
model3d_mask[index] = 1
self.ldm_ids = ldm_ids # Ids from the database
self.model3d_world = model3d_world # Model data
self.model3d_ids = model3d_ids # Model ids
self.model3d_mask = model3d_mask # Model mask ids
self.max_iter = max_iter # Refinement iterations
self.focal_ratio = focal_ratio # Camera matrix focal length ratio
self.fix_bbox = fix_bbox # Camera matrix centered on image (False to centered on bbox)
def __call__(self, sample):
landmarks = sample['landmarks']
mask = sample['mask_ldm']
# Camera matrix
img_shape = np.array(sample['image'].shape)[0:2]
if 'img2map_scale' in sample.keys():
img_shape = img_shape * sample['img2map_scale']
if self.fix_bbox:
img_bbox = [0, 0, img_shape[1], img_shape[0]] # Shapes given are inverted (y,x)
cam_matrix = self._camera_matrix(img_bbox)
else:
bbox = sample['bbox'] # Scale error when ftshape and img_shape mismatch
cam_matrix = self._camera_matrix(bbox)
# Save intrinsic matrix and 3D model landmarks
sample['cam_matrix'] = cam_matrix
sample['model3d'] = self.model3d_world
world_pts, image_pts = self._set_correspondences(landmarks, mask)
if image_pts.shape[0] < 4:
print('POSIT does not work without landmarks')
rot_matrix, trl_matrix = np.eye(3, dtype=float), np.array([0, 0, 0])
else:
rot_matrix, trl_matrix = self._modern_posit(world_pts, image_pts, cam_matrix)
euler = rotation_matrix_to_euler(rot_matrix)
sample['pose'] = np.array([euler[0], euler[1], euler[2], trl_matrix[0], trl_matrix[1], trl_matrix[2]])
sample['model3d_proj'] = self._project_points(rot_matrix, trl_matrix, cam_matrix, norm=img_shape)
return sample
def _load_world_shape(self, ldm_ids, model_file):
return load_world_shape(ldm_ids, model_file=model_file)
def _camera_matrix(self, bbox):
focal_length_x = bbox[2] * self.focal_ratio
focal_length_y = bbox[3] * self.focal_ratio
face_center = (bbox[0] + (bbox[2] * 0.5)), (bbox[1] + (bbox[3] * 0.5))
cam_matrix = np.array([[focal_length_x, 0, face_center[0]],
[0, focal_length_y, face_center[1]],
[0, 0, 1]])
return cam_matrix
def _set_correspondences(self, landmarks, mask):
# Correspondences using labelled and robust landmarks
img_mask = np.logical_and(mask, self.model3d_mask)
img_mask = img_mask.astype(bool)
image_pts = landmarks[img_mask]
world_pts = self.model3d_world[img_mask]
return world_pts, image_pts
def _modern_posit(self, world_pts, image_pts, cam_matrix):
return modern_posit(world_pts, image_pts, cam_matrix, self.max_iter)
def _project_points(self, rot, trl, cam_matrix, norm=None):
# Perspective projection model
trl = np.expand_dims(trl, 1)
extrinsics = np.concatenate((rot, trl), 1)
proj_matrix = np.matmul(cam_matrix, extrinsics)
# Homogeneous landmarks
pts = self.model3d_world
ones = np.ones(pts.shape[0])
ones = np.expand_dims(ones, 1)
pts_hom = np.concatenate((pts, ones), 1)
# Project landmarks
pts_proj = np.matmul(proj_matrix, pts_hom.T).T
pts_proj = pts_proj / np.expand_dims(pts_proj[:, 2], 1) # Lambda = 1
if norm is not None:
pts_proj[:, 0] /= norm[0]
pts_proj[:, 1] /= norm[1]
return pts_proj[:, :-1]
def load_world_shape(db_landmarks, model_file=model_file_dft):
# Load 3D mean face coordinates
num_ldm = len(db_landmarks)
filename = model_file.format(num_ldm=num_ldm)
if not os.path.exists(filename):
raise ValueError('No 3D model find for %i landmarks' % num_ldm)
posit_landmarks = np.genfromtxt(filename, delimiter='|', dtype=int, usecols=0).tolist()
mean_face_3D = np.genfromtxt(filename, delimiter='|', dtype=(float, float, float), usecols=(1, 2, 3)).tolist()
world_all = len(mean_face_3D)*[None]
index_all = len(mean_face_3D)*[None]
for cont, elem in enumerate(mean_face_3D):
pt3d = [elem[2], -elem[0], -elem[1]]
lnd_idx = db_landmarks.index(posit_landmarks[cont])
world_all[lnd_idx] = pt3d
index_all[lnd_idx] = posit_landmarks[cont]
return np.array(world_all), np.array(index_all)
def modern_posit(world_pts, image_pts, cam_matrix, max_iters):
# Homogeneous world points
num_landmarks = image_pts.shape[0]
one = np.ones((num_landmarks, 1))
A = np.concatenate((world_pts, one), axis=1)
B = np.linalg.pinv(A)
# Normalize image points
focal_length = cam_matrix[0,0]
img_center = (cam_matrix[0,2], cam_matrix[1,2])
centered_pts = np.zeros((num_landmarks,2))
centered_pts[:,0] = (image_pts[:,0]-img_center[0])/focal_length
centered_pts[:,1] = (image_pts[:,1]-img_center[1])/focal_length
Ui = centered_pts[:,0]
Vi = centered_pts[:,1]
# POSIT loop
Tx, Ty, Tz = 0.0, 0.0, 0.0
r1, r2, r3 = [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]
for iter in range(0, max_iters):
I = np.dot(B,Ui)
J = np.dot(B,Vi)
# Estimate translation vector and rotation matrix
normI = 1.0 / np.sqrt(I[0] * I[0] + I[1] * I[1] + I[2] * I[2])
normJ = 1.0 / np.sqrt(J[0] * J[0] + J[1] * J[1] + J[2] * J[2])
Tz = np.sqrt(normI * normJ) # geometric average instead of arithmetic average of classicPosit
r1N = I*Tz
r2N = J*Tz
r1 = r1N[0:3]
r2 = r2N[0:3]
r1 = np.clip(r1, -1, 1)
r2 = np.clip(r2, -1, 1)
r3 = np.cross(r1,r2)
r3T = np.concatenate((r3, [Tz]), axis=0)
Tx = r1N[3]
Ty = r2N[3]
# Compute epsilon, update Ui and Vi and check convergence
eps = np.dot(A, r3T)/Tz
oldUi = Ui
oldVi = Vi
Ui = np.multiply(eps, centered_pts[:,0])
Vi = np.multiply(eps, centered_pts[:,1])
deltaUi = Ui - oldUi
deltaVi = Vi - oldVi
delta = focal_length * focal_length * (np.dot(np.transpose(deltaUi), deltaUi) + np.dot(np.transpose(deltaVi), deltaVi))
if iter > 0 and delta < 0.01: # converged
break
rot_matrix = np.array([np.transpose(r1), np.transpose(r2), np.transpose(r3)])
trl_matrix = np.array([Tx, Ty, Tz])
# Convert to the nearest orthogonal rotation matrix
w, u, vt = cv2.SVDecomp(rot_matrix) # R = U*D*Vt
rot_matrix = np.matmul(np.matmul(u, np.eye(3, dtype=float)), vt)
return rot_matrix, trl_matrix
+147
View File
@@ -0,0 +1,147 @@
import numpy as np
def affine2homogeneous(points):
'''Returns the points completed with a new last coordinate
equal to 1
Arguments
---------
points: np.array of shape (num_points, dim)
Returns
-------
hpoints: np.array of shape (num_points, dim + 1),
of the points completed with ones'''
num_points = points.shape[0]
hpoints = np.hstack(
(points, np.repeat(1, num_points).reshape(num_points, 1)))
return hpoints
def get_similarity_matrix(deg_angle, scale, center):
'''Similarity matrix.
Arguments:
---------
deg_angle: rotation angle in degrees
scale: factor scale
center: coordinates of the rotation center
Returns:
-------
matrix: (2, 3) numpy array representing the
similarity matrix.
'''
x0, y0 = center
angle = np.radians(deg_angle)
matrix = np.zeros((2, 3))
matrix[0:2, 0:2] = [[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]]
matrix[0: 2, 0: 2] *= scale
matrix[:, 2] = [(1 - scale * np.cos(angle)) * x0 +
scale * np.sin(angle) * y0,
-scale * np.sin(angle) * x0 +
(1 - scale * np.cos(angle)) * y0]
return matrix
def get_inverse_similarity_matrix(deg_angle, scale, center):
'''Returns the inverse of the affine similarity
Arguments
---------
deg_angle: angle in degrees of the rotation
center: iterable of two components (x0, y0),
center of the rotation
scale: float, scale factor
Returns
-------
matrix: np.array of shape (2, 3) with the coordinates of
the inverse of the similarity'''
x0, y0 = center
angle = np.radians(deg_angle)
inv_scale = 1 / scale
matrix = np.zeros((2, 3))
matrix[0:2, 0:2] = [[np.cos(angle), np.sin(angle)],
[-np.sin(angle), np.cos(angle)]]
matrix[0:2, 0:2] *= inv_scale
matrix[:, 2] = [(1 - inv_scale * np.cos(angle)) * x0 -
inv_scale * np.sin(angle) * y0,
inv_scale * np.sin(angle) * x0 +
(1 - inv_scale * np.cos(angle)) * y0]
return matrix
def get_inverse_transf(affine_transf):
A = affine_transf[0:2, 0:2]
b = affine_transf[:, 2]
inv_A = np.linalg.inv(A) # we assume A invertible!
inv_affine = np.zeros((2, 3))
inv_affine[0:2, 0:2] = inv_A
inv_affine[:, 2] = -inv_A.dot(b)
return inv_affine
def image2vect(image):
'''
Input:
image[batch_size, num_channels, im_size_x, im_size_y]
Output:
vect[batch_size, num_channels, im_size_x*im_size_y]
'''
vect = image.reshape(*image.shape[0:-2], -1)
return vect
def rotation_matrix_to_euler(rot_matrix):
# http://euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm
a00, a01, a02 = rot_matrix[0, 0], rot_matrix[0, 1], rot_matrix[0, 2]
a10, a11, a12 = rot_matrix[1, 0], rot_matrix[1, 1], rot_matrix[1, 2]
a20, a21, a22 = rot_matrix[2, 0], rot_matrix[2, 1], rot_matrix[2, 2]
if abs(1.0 - a10) <= np.finfo(float).eps: # singularity at north pole / special case a10 == 1
yaw = np.arctan2(a02, a22)
pitch = np.pi/2.0
roll = 0
elif abs(-1.0 - a10) <= np.finfo(float).eps: # singularity at south pole / special case a10 == -1
yaw = np.arctan2(a02, a22)
pitch = -np.pi/2.0
roll = 0
else: # standard case
yaw = np.arctan2(-a20, a00)
pitch = np.arcsin(a10)
roll = np.arctan2(-a12, a11)
# Convert to degrees
euler = np.array([yaw, pitch, roll])*(180.0/np.pi)
# Change coordinates system
euler = np.array([(-euler[0])+90, -euler[1], (-euler[2])-90])
if euler[0] > 180: euler[0] -= 360
elif euler[0] < -180: euler[0] += 360
if euler[1] > 180: euler[1] -= 360
elif euler[1] < -180: euler[1] += 360
if euler[2] > 180: euler[2] -= 360
elif euler[2] < -180: euler[2] += 360
return euler
def euler_to_rotation_matrix(headpose):
# http://euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm
# Change coordinates system
euler = np.array([-(headpose[0]-90), -headpose[1], -(headpose[2]+90)])
# Convert to radians
rad = euler*(np.pi/180.0)
cy = np.cos(rad[0])
sy = np.sin(rad[0])
cp = np.cos(rad[1])
sp = np.sin(rad[1])
cr = np.cos(rad[2])
sr = np.sin(rad[2])
Ry = np.array([[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]]) # yaw
Rp = np.array([[cp, -sp, 0.0], [sp, cp, 0.0], [0.0, 0.0, 1.0]]) # pitch
Rr = np.array([[1.0, 0.0, 0.0], [0.0, cr, -sr], [0.0, sr, cr]]) # roll
return np.matmul(np.matmul(Ry, Rp), Rr)
+41
View File
@@ -0,0 +1,41 @@
from torch.utils.data import DataLoader
from torch.utils.data.distributed import DistributedSampler
from .import alignments as zoo_alignments
zoos = [zoo_alignments]
def get_dataset(data_config, pretreat=None, debug=False):
for zoo in zoos:
dataset = zoo.get_dataset(data_config, pretreat=pretreat, debug=debug)
if dataset is not None:
return dataset
raise NotImplementedError('Dataset not available')
def get_dataloader(batch_size, data_config, pretreat=None, sampler_cfg=None, debug=False):
dataset = get_dataset(data_config, pretreat=pretreat, debug=debug)
if (len(dataset) % batch_size) == 1 and data_config.shuffle == True:
drop_last_batch = True
else:
drop_last_batch = False
shuffle = data_config.shuffle
sampler = None
if sampler_cfg is not None:
sampler = DistributedSampler(dataset, num_replicas=sampler_cfg.world_size, rank=sampler_cfg.rank)
shuffle = False
dataloader = DataLoader(dataset,
batch_size=batch_size,
shuffle=shuffle,
num_workers=data_config.num_workers,
pin_memory=True,
drop_last=drop_last_batch,
sampler=sampler)
return dataloader, dataset
+173
View File
@@ -0,0 +1,173 @@
import os
import json
from collections import OrderedDict
current_path = os.path.dirname(os.path.abspath(__file__))
current_path= os.path.dirname(current_path)
db_img_path =os.path.join(current_path,"databases")
db_anns_path=os.path.join(current_path,"annotations","{database}/{file_name}.json")
# Default data paths
#db_img_path = "spiga/data/annotations/300wpublic"
#db_anns_path = "spiga/data/annotations/300wpublic/db_info.json"
class AlignConfig:
def __init__(self, database_name, mode='train'):
# Dataset
self.database_name = database_name
self.working_mode = mode
self.database = None # Set at self._update_database()
self.anns_file = None # Set at self._update_database()
self.image_dir = None # Set at self._update_database()
self._update_database()
self.image_size = (256, 256)
self.ftmap_size = (256, 256)
# Dataloaders
self.ids = None # List of a subset if need it
self.shuffle = True # Shuffle samples
self.num_workers = 4 # Threads
# Posit
self.generate_pose = True # Generate pose parameters from landmarks
self.focal_ratio = 1.5 # Camera matrix focal length ratio
self.posit_max_iter = 100 # Refinement iterations
# Subset of robust ids in the 3D model to use in posit.
# 'None' to use all the available model landmarks.
self.posit_ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
# Data augmentation
# Control augmentations with the following list, crop to self.img_size is mandatory, check target_dist param.
if mode == 'train':
self.aug_names = ['flip', 'rotate_scale', 'occlusion', 'lighting', 'blur']
else:
self.aug_names = []
self.shuffle = False
# Flip
self.hflip_prob = 0.5
# Rotation
self.angle_range = 45.
# Scale
self.scale_max = 0.15
self.scale_min = -0.15
# Translation
self.trl_ratio = 0.05 # Translation augmentation
# Crop target rescale
self.target_dist = 1.6 # Target distance zoom in/out around face. Default: 1.
# Occlusion
self.occluded_max_len = 0.4
self.occluded_min_len = 0.1
self.occluded_covar_ratio = 2.25**0.5
# Lighting
self.hsv_range_min = [-0.5, -0.5, -0.5]
self.hsv_range_max = [0.5, 0.5, 0.5]
# Blur
self.blur_prob = 0.5
self.blur_kernel_range = [0, 2]
# Heatmaps 2D
self.sigma2D = 1.5
self.heatmap2D_norm = False
# Boundaries
self.sigmaBD = 1
def update(self, params_dict):
state_dict = self.state_dict()
for k, v in params_dict.items():
if k in state_dict or hasattr(self, k):
setattr(self, k, v)
else:
Warning('Unknown option: {}: {}'.format(k, v))
self._update_database()
def state_dict(self, tojson=False):
state_dict = OrderedDict()
for k in self.__dict__.keys():
if not k.startswith('_'):
if tojson and k in ['database']:
continue
state_dict[k] = getattr(self, k)
return state_dict
def _update_database(self):
self.database = DatabaseStruct(self.database_name)
self.anns_file = db_anns_path.format(database=self.database_name, file_name=self.working_mode)
self.image_dir = self._get_imgdb_path()
def _get_imgdb_path(self):
img_dir = None
if self.database_name in ['300wpublic', '300wprivate']:
img_dir = db_img_path + '/300w/'
elif self.database_name in ['aflw19', 'merlrav']:
img_dir = db_img_path + '/aflw/data/'
elif self.database_name in ['cofw', 'cofw68']:
img_dir = db_img_path + '/cofw/'
elif self.database_name in ['wflw']:
img_dir = db_img_path + '/wflw/'
return img_dir
def __str__(self):
state_dict = self.state_dict()
text = 'Dataloader {\n'
for k, v in state_dict.items():
if isinstance(v, DatabaseStruct):
text += '\t{}: {}'.format(k, str(v).expandtabs(12))
else:
text += '\t{}: {}\n'.format(k, v)
text += '\t}\n'
return text
class DatabaseStruct:
def __init__(self, database_name):
self.name = database_name
self.ldm_ids, self.ldm_flip_order, self.ldm_edges_matrix = self._get_database_specifics()
self.num_landmarks = len(self.ldm_ids)
self.num_edges = len(self.ldm_edges_matrix[0])-1
self.fields = ['imgpath', 'bbox', 'headpose', 'ids', 'landmarks', 'visible']
def _get_database_specifics(self):
'''Returns specifics ids and horizontal flip reorder'''
database_name = self.name
db_info_file = db_anns_path.format(database=database_name, file_name='db_info')
ldm_edges_matrix = None
if os.path.exists(db_info_file):
with open(db_info_file) as jsonfile:
db_info = json.load(jsonfile)
ldm_ids = db_info['ldm_ids']
ldm_flip_order = db_info['ldm_flip_order']
if 'ldm_edges_matrix' in db_info.keys():
ldm_edges_matrix = db_info['ldm_edges_matrix']
else:
raise ValueError('Database ' + database_name + 'specifics not defined. Missing db_info.json')
return ldm_ids, ldm_flip_order, ldm_edges_matrix
def state_dict(self):
state_dict = OrderedDict()
for k in self.__dict__.keys():
if not k.startswith('_'):
state_dict[k] = getattr(self, k)
return state_dict
def __str__(self):
state_dict = self.state_dict()
text = 'Database {\n'
for k, v in state_dict.items():
text += '\t{}: {}\n'.format(k, v)
text += '\t}\n'
return text
+84
View File
@@ -0,0 +1,84 @@
import cv2
import numpy as np
import torch
from .augmentors.modern_posit import PositPose
from .augmentors.heatmaps import Heatmaps
from .augmentors.boundary import AddBoundary
from .augmentors.landmarks import HorizontalFlipAug, RSTAug, OcclusionAug, \
LightingAug, BlurAug, TargetCropAug
def get_transformers(data_config):
# Data augmentation
aug_names = data_config.aug_names
augmentors = []
if 'flip' in aug_names:
augmentors.append(HorizontalFlipAug(data_config.database.ldm_flip_order, data_config.hflip_prob))
if 'rotate_scale' in aug_names:
augmentors.append(RSTAug(data_config.angle_range, data_config.scale_min,
data_config.scale_max, data_config.trl_ratio))
if 'occlusion' in aug_names:
augmentors.append(OcclusionAug(data_config.occluded_min_len,
data_config.occluded_max_len,
data_config.database.num_landmarks))
if 'lighting' in aug_names:
augmentors.append(LightingAug(data_config.hsv_range_min, data_config.hsv_range_max))
if 'blur' in aug_names:
augmentors.append(BlurAug(data_config.blur_prob, data_config.blur_kernel_range))
# Crop mandatory
augmentors.append(TargetCropAug(data_config.image_size, data_config.ftmap_size, data_config.target_dist))
# Opencv style
augmentors.append(ToOpencv())
# Gaussian heatmaps
if 'heatmaps2D' in aug_names:
augmentors.append(Heatmaps(data_config.database.num_landmarks, data_config.ftmap_size,
data_config.sigma2D, norm=data_config.heatmap2D_norm))
if 'boundaries' in aug_names:
augmentors.append(AddBoundary(num_landmarks=data_config.database.num_landmarks,
map_size=data_config.ftmap_size,
sigma=data_config.sigmaBD))
# Pose generator
if data_config.generate_pose:
augmentors.append(PositPose(data_config.database.ldm_ids,
focal_ratio=data_config.focal_ratio,
selected_ids=data_config.posit_ids,
max_iter=data_config.posit_max_iter))
return augmentors
class ToOpencv:
def __call__(self, sample):
# Convert in a numpy array and change to GBR
image = np.array(sample['image'])
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
sample['image'] = image
return sample
class TargetCrop(TargetCropAug):
def __init__(self, crop_size=256, target_dist=1.6):
super(TargetCrop, self).__init__(crop_size, crop_size, target_dist)
class AddModel3D(PositPose):
def __init__(self, ldm_ids, ftmap_size=(256, 256), focal_ratio=1.5, totensor=False):
super(AddModel3D, self).__init__(ldm_ids, focal_ratio=focal_ratio)
img_bbox = [0, 0, ftmap_size[1], ftmap_size[0]] # Shapes given are inverted (y,x)
self.cam_matrix = self._camera_matrix(img_bbox)
if totensor:
self.cam_matrix = torch.tensor(self.cam_matrix, dtype=torch.float)
self.model3d_world = torch.tensor(self.model3d_world, dtype=torch.float)
def __call__(self, sample={}):
# Save intrinsic matrix and 3D model landmarks
sample['cam_matrix'] = self.cam_matrix
sample['model3d'] = self.model3d_world
return sample
View File
+68
View File
@@ -0,0 +1,68 @@
101|-0.71046061493|0.39334543762|-0.853184236267
102|-0.685409656726|0.169750713576|-0.878623094747
103|-0.607672440834|-0.0597969416711|-0.922355056609
104|-0.545664962607|-0.262499453743|-0.920836599126
105|-0.446214526601|-0.439045175771|-0.863139209
106|-0.334435950087|-0.566049889292|-0.728913281016
107|-0.236168305748|-0.653698223145|-0.585459059996
108|-0.128307367184|-0.712045321067|-0.488221730877
24|-0.00354849146401|-0.73799619898|-0.461364289002
110|0.131199109787|-0.733135962679|-0.466262199442
111|0.257373771199|-0.666487934012|-0.560781626341
112|0.359823738861|-0.569594984479|-0.666664889106
113|0.455878939364|-0.475272786995|-0.762199581604
114|0.583151236654|-0.255441185628|-0.895445263128
115|0.635035270608|-0.076593055883|-0.925024981148
116|0.688562921266|0.127095701882|-0.90624669312
117|0.711883056933|0.332170073984|-0.901990426153
7|-0.406302776083|0.360389456414|-0.383328256737
138|-0.34477414508|0.384294131116|-0.310253684249
139|-0.261521910706|0.396020592391|-0.286645173372
8|-0.160787895282|0.371046071664|-0.330243237009
141|-0.245621576158|0.334421719594|-0.323105980143
142|-0.328092143154|0.331973054751|-0.339942994735
11|0.163880511996|0.34074733643|-0.314140711047
144|0.250844605212|0.392490504074|-0.279030578851
145|0.340770354713|0.378980645804|-0.285841921386
12|0.432352000574|0.348665008376|-0.377158168878
147|0.333467711264|0.328033806608|-0.318530865652
148|0.246621391603|0.335053488011|-0.313137199532
1|-0.552168877878|0.483792334207|-0.466456539274
119|-0.459810924979|0.561780416181|-0.308160933539
2|-0.360178576451|0.566290707558|-0.216753374881
121|-0.249770054109|0.530336745189|-0.148694799479
3|-0.101941089981|0.482471777751|-0.124216132105
128|0.00843526194116|0.416310824525|-0.139142361862
129|0.00731217835113|0.267155736618|-0.106471830332
130|0.00190174006432|0.142359799355|-0.0544539305898
17|0.0|0.0|0.0
16|-0.118903311783|-0.0792784542251|-0.23553779698
133|-0.0504300242939|-0.104665185108|-0.171210015652
134|0.00312657092554|-0.111845126622|-0.154139340027
135|0.0687543983424|-0.0995458563329|-0.173275169304
18|0.148988810614|-0.0737848514841|-0.23851346235
4|0.0979764884322|0.465935806416|-0.131673602172
124|0.214281256931|0.497130488239|-0.135680573937
5|0.373011222167|0.520379035113|-0.223000042464
126|0.501607216527|0.500371303274|-0.31925158046
6|0.57362522379|0.457842172776|-0.465084060802
20|-0.2229832011|-0.277627584817|-0.354306324758
150|-0.157062857603|-0.248737641674|-0.270218764688
151|-0.0816354856174|-0.23618498428|-0.225237980338
22|-0.00391577759471|-0.24499812323|-0.2126098208
153|0.0629421066443|-0.230774971512|-0.208501270489
154|0.135507933036|-0.240646328001|-0.235471195707
21|0.240381217797|-0.283709533358|-0.332281891845
156|0.143706630542|-0.329461104518|-0.283978333058
157|0.084139951946|-0.355354733249|-0.256464077311
23|0.00813704018925|-0.360357397837|-0.250423014186
159|-0.0778260690655|-0.349041413147|-0.26966136614
160|-0.157111645515|-0.311971743234|-0.322383820174
161|-0.182660017757|-0.278377925956|-0.341170846656
162|-0.075943475389|-0.283519721316|-0.270854173882
163|-0.0026701322545|-0.286917503123|-0.254428375696
164|0.0685062003389|-0.285630836758|-0.257845545665
165|0.194170296105|-0.279641061937|-0.314605542849
166|0.0689862474833|-0.288076969827|-0.258696545259
167|-0.00273913414372|-0.28915723747|-0.25622946708
168|-0.0766989697362|-0.286311968999|-0.273027820051
+98
View File
@@ -0,0 +1,98 @@
100|-0.710460614932|0.393345437621|-0.853184236268
101|-0.697935135807|0.281548075586|-0.865903665506
102|-0.685409656727|0.169750713576|-0.878623094748
103|-0.646541048786|0.0549768859803|-0.90048907572
104|-0.607672440835|-0.0597969416712|-0.92235505661
105|-0.576668701754|-0.16114819775|-0.921595827864
106|-0.545664962608|-0.262499453743|-0.920836599128
107|-0.495939744597|-0.350772314723|-0.891987904048
108|-0.446214526602|-0.439045175772|-0.863139209001
109|-0.390325238349|-0.502547532515|-0.796026245014
110|-0.334435950088|-0.566049889293|-0.728913281017
111|-0.285302127936|-0.60987405623|-0.657186170535
112|-0.236168305748|-0.653698223146|-0.585459059997
113|-0.182237836481|-0.682871772143|-0.536840395466
114|-0.128307367184|-0.712045321068|-0.488221730878
115|-0.065927929287|-0.725020759991|-0.474793009911
24|-0.00354849146405|-0.737996198981|-0.461364289002
117|0.0638253091608|-0.735566080866|-0.463813244263
118|0.131199109787|-0.73313596268|-0.466262199443
119|0.194286440465|-0.699811948342|-0.513521912927
120|0.257373771199|-0.666487934013|-0.560781626342
121|0.308598755049|-0.61804145929|-0.613723257723
122|0.359823738861|-0.569594984479|-0.666664889106
123|0.407851339087|-0.522433885778|-0.714432235344
124|0.455878939366|-0.475272786996|-0.762199581605
125|0.519515088045|-0.365356986341|-0.828822422342
126|0.583151236655|-0.255441185628|-0.89544526313
127|0.609093253652|-0.166017120718|-0.910235122162
128|0.635035270609|-0.0765930558831|-0.92502498115
129|0.661799095895|0.0252513229862|-0.915635837119
130|0.688562921268|0.127095701883|-0.906246693121
131|0.700222989101|0.229632887961|-0.904118559673
132|0.711883056935|0.332170073985|-0.901990426154
1|-0.552168877879|0.483792334207|-0.466456539275
134|-0.45981092498|0.561780416182|-0.30816093354
2|-0.360178576451|0.566290707559|-0.216753374882
136|-0.249770054109|0.53033674519|-0.148694799479
3|-0.101941089981|0.482471777751|-0.124216132105
138|-0.100790757462|0.429593539303|-0.13295377507
139|-0.24861972159|0.477458506742|-0.157432442443
140|-0.359028243932|0.513412469111|-0.225491017847
141|-0.458660592461|0.508902177734|-0.316898576504
4|0.0979764884324|0.465935806417|-0.131673602172
143|0.214281256932|0.49713048824|-0.135680573937
5|0.373011222167|0.520379035114|-0.223000042464
145|0.501607216528|0.500371303275|-0.31925158046
6|0.573625223791|0.457842172777|-0.465084060803
147|0.500456884008|0.447493064827|-0.310513937496
148|0.374161554686|0.467500796666|-0.231737685429
149|0.215431589451|0.444252249792|-0.144418216902
150|0.0991268209517|0.413057567969|-0.140411245136
151|0.00843526194114|0.416310824526|-0.139142361862
152|0.00731217835118|0.267155736618|-0.106471830333
153|0.00190174006432|0.142359799356|-0.0544539305899
17|0.0|0.0|0.0
16|-0.118903311783|-0.0792784542252|-0.23553779698
156|-0.0504300242939|-0.104665185108|-0.171210015652
157|0.00312657092558|-0.111845126622|-0.154139340027
158|0.0687543983424|-0.0995458563331|-0.173275169304
18|0.148988810614|-0.0737848514843|-0.23851346235
7|-0.406302776084|0.360389456414|-0.383328256738
161|-0.34477414508|0.384294131116|-0.310253684249
9|-0.303148027928|0.398970401478|-0.298449428843
163|-0.261521910707|0.396020592392|-0.286645173373
8|-0.160787895283|0.371046071665|-0.33024323701
165|-0.245621576158|0.334421719595|-0.323105980144
10|-0.286856859665|0.324384347457|-0.33152448747
167|-0.328092143155|0.331973054752|-0.339942994736
11|0.163880511996|0.340747336431|-0.314140711048
169|0.250844605212|0.392490504075|-0.279030578852
13|0.295807479997|0.39454861469|-0.282436250152
171|0.340770354713|0.378980645804|-0.285841921386
12|0.432352000574|0.348665008377|-0.377158168879
173|0.333467711265|0.328033806608|-0.318530865652
14|0.290044551442|0.322730607606|-0.315834032602
175|0.246621391603|0.335053488012|-0.313137199533
20|-0.2229832011|-0.277627584818|-0.354306324759
177|-0.157062857603|-0.248737641675|-0.270218764688
178|-0.0816354856176|-0.23618498428|-0.225237980339
22|-0.00391577759461|-0.24499812323|-0.212609820801
180|0.0629421066443|-0.230774971513|-0.20850127049
181|0.135507933036|-0.240646328002|-0.235471195707
21|0.240381217797|-0.283709533359|-0.332281891846
183|0.143706630542|-0.329461104519|-0.283978333058
184|0.0841399519463|-0.35535473325|-0.256464077312
23|0.00813704018926|-0.360357397838|-0.250423014186
186|-0.0778260690653|-0.349041413147|-0.269661366141
187|-0.157111645515|-0.311971743235|-0.322383820174
188|-0.182660017758|-0.278377925956|-0.341170846657
189|-0.0759434753887|-0.283519721316|-0.270854173882
190|-0.00267013225455|-0.286917503124|-0.254428375697
191|0.068506200339|-0.285630836758|-0.257845545665
192|0.194170296105|-0.279641061938|-0.31460554285
193|0.0689862474834|-0.288076969827|-0.25869654526
194|-0.00273913414368|-0.28915723747|-0.25622946708
195|-0.0766989697367|-0.286311968999|-0.273027820052
196|-0.291183407723|0.363024170951|-0.328919887677
197|0.294656095888|0.353995131537|-0.31463990759
+37
View File
@@ -0,0 +1,37 @@
import argparse
import numpy as np
import matplotlib.pyplot as plt
def main():
# Input arguments control
pars = argparse.ArgumentParser(description='3D model visualization')
pars.add_argument('file', type=str, help='File txt path')
args = pars.parse_args()
visualize_3Dmodel(args.file)
def visualize_3Dmodel(input_file):
with open(input_file) as f:
lines = f.readlines()
model = []
for line in lines:
line = line[:-1] # Remove \n
line_split = line.split('|')
values = np.array(line_split, dtype=float)
model.append(values)
model = np.array(model)
model_xyz = model[:, 1:]
# Show model
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(model_xyz[:, 0], model_xyz[:, 1], model_xyz[:, 2]+0.8)
plt.show()
if __name__ == '__main__':
main()
+72
View File
@@ -0,0 +1,72 @@
# SPIGA: Dataloaders
Currently, the repository provides a pytorch based image dataloader implementation for the task of:
* **Facial Landmarks estimation**.
* **Headpose estimation**.
* **Facial landmarks visibilities**.
The dataloader can be used for training or testing the networks and it includes general and
specifics data augmentation for each task, as it can be image partial occlusions
or headpose generation from facial landmarks.
In addition, the framework provides a wide benchmark software in order to evaluate the
different task along the following databases:
* **WFLW**.
* **MERLRAV (AFLW 68)**
* **COFW68**.
* **300W Public, Private**.
<details>
<summary> Coming soon... </summary>
* AFLW, AFLW19, AFLW2000 (test only).
* Menpo and 3D Menpo.
* COFW.
* 300WLP
* 300W Masked
</details>
***Note:*** All the callable files provide a detailed parser that describes the behaviour of
the program and their inputs. Please, check the operational modes by using the extension ```--help```.
## Training/Testing
The dataloader structure can be found in ```./data/loaders/aligments.py``` and it can be
manually controlled by instantiating the class ```AlignmentsDataset()``` or by using
the ```data_config``` structure available in ```./data/loaders/dl_config.py```.
Each image sample will follow the next configuration:
```
sample = {'image': Data augmented crop image,
'sample_idx': Image ID,
'imgpath': Absolute path to raw image,
'imgpath_local': Relative path to raw image,
'ids_ldm': Landmarks ids,
'bbox': Face bbox [x,y,w,h] (ref crop),
'bbox_raw': Face bbox [x,y,w,h] (ref image),
'landmarks': Augmented landmarks [[x1,y1], [x2,y2], ...] (ref crop)
'visible': Visibilities [0,1, ...] (1 == Visible)
'mask_ldm': Available landmarks anns [True, False, ...] <- len(ids_ldm)
'headpose': Augmented POSIT headpose [yaw, pithc, roll]
}
Extra features while debugging:
sample = { ...
'landmarks_ori' = Ground truth landmarks before augmentation (ref image)
'visible_ori' = Ground truth visibilities before augmentation
'mask_ldm_ori' = Ground truth mask before augmentation
'headpose_ori' = Ground truth headpose before augmentation (if available)
}
```
## Visualizers
The dataloader framework provides complementary visualizers to further understand the databases,
datasets and their difficulties:
* ```./data/visualize/inspect_dataset.py```
Focus on the database annotations and the data augmentations, which allows us to
understand the training, validation and test datasets.
* ```./data/visualize/inspect_heatmaps.py```
Extended visualizer version focus on understanding the heatmaps and boundaries features used for training.
* ```./data/model3D/visualization.py```
Visualize the rigid facial 3D models used by SPIGA to project the initial coordinates of the GAT regressor.
View File
+181
View File
@@ -0,0 +1,181 @@
import cv2
import random
import numpy as np
from ..loaders import dl_config as dl_cfg
from ..loaders import dataloader as dl
from ..visualize import plotting as plot
def inspect_parser():
import argparse
pars = argparse.ArgumentParser(description='Data augmentation and dataset visualization. '
'Press Q to quit,'
'N to visualize the next image'
' and any other key to visualize the next default data.')
pars.add_argument('database', type=str,
choices=['wflw', '300wpublic', '300wprivate', 'cofw68', 'merlrav'], help='Database name')
pars.add_argument('-a', '--anns', type=str, default='train', help='Annotation type: test, train or valid')
pars.add_argument('-np', '--nopose', action='store_false', default=True, help='Avoid pose generation')
pars.add_argument('-c', '--clean', action='store_true', help='Process without data augmentation for train')
pars.add_argument('--shape', nargs='+', type=int, default=[256, 256], help='Image cropped shape (W,H)')
pars.add_argument('--img', nargs='+', type=int, default=None, help='Select specific image ids')
return pars.parse_args()
class DatasetInspector:
def __init__(self, database, anns_type, data_aug=True, pose=True, image_shape=(256,256)):
data_config = dl_cfg.AlignConfig(database, anns_type)
data_config.image_size = image_shape
data_config.ftmap_size = image_shape
data_config.generate_pose = pose
if not data_aug:
data_config.aug_names = []
self.data_config = data_config
dataloader, dataset = dl.get_dataloader(1, data_config, debug=True)
self.dataset = dataset
self.dataloader = dataloader
self.colors_dft = {'lnd': (plot.GREEN, plot.RED), 'pose': (plot.BLUE, plot.GREEN, plot.RED)}
def show_dataset(self, ids_list=None):
if ids_list is None:
ids = self.get_idx(shuffle=self.data_config.shuffle)
else:
ids = ids_list
for img_id in ids:
data_dict = self.dataset[img_id]
crop_imgs, full_img = self.plot_features(data_dict)
# Plot crop
if 'merge' in crop_imgs.keys():
crop = crop_imgs['merge']
else:
crop = crop_imgs['lnd']
cv2.imshow('crop', crop)
# Plot full
cv2.imshow('image', full_img['lnd'])
key = cv2.waitKey()
if key == ord('q'):
break
def plot_features(self, data_dict, colors=None):
# Init variables
crop_imgs = {}
full_imgs = {}
if colors is None:
colors = self.colors_dft
# Cropped image
image = data_dict['image']
landmarks = data_dict['landmarks']
visible = data_dict['visible']
if np.any(np.isnan(visible)):
visible = None
mask = data_dict['mask_ldm']
# Full image
if 'image_ori' in data_dict.keys():
image_ori = data_dict['image_ori']
else:
image_ori = cv2.imread(data_dict['imgpath'])
landmarks_ori = data_dict['landmarks_ori']
visible_ori = data_dict['visible_ori']
if np.any(np.isnan(visible_ori)):
visible_ori = None
mask_ori = data_dict['mask_ldm_ori']
# Plot landmarks
crop_imgs['lnd'] = self._plot_lnd(image, landmarks, visible, mask, colors=colors['lnd'])
full_imgs['lnd'] = self._plot_lnd(image_ori, landmarks_ori, visible_ori, mask_ori, colors=colors['lnd'])
if self.data_config.generate_pose:
rot, trl, cam_matrix = self._extract_pose(data_dict)
# Plot pose
crop_imgs['pose'] = plot.draw_pose(image, rot, trl, cam_matrix, euler=True, colors=colors['pose'])
# Plot merge features
crop_imgs['merge'] = plot.draw_pose(crop_imgs['lnd'], rot, trl, cam_matrix, euler=True, colors=colors['pose'])
return crop_imgs, full_imgs
def get_idx(self, shuffle=False):
ids = list(range(len(self.dataset)))
if shuffle:
random.shuffle(ids)
return ids
def reload_dataset(self, data_config=None):
if data_config is None:
data_config = self.data_config
dataloader, dataset = dl.get_dataloader(1, data_config, debug=True)
self.dataset = dataset
self.dataloader = dataloader
def _extract_pose(self, data_dict):
# Rotation and translation matrix
pose = data_dict['pose']
rot = pose[:3]
trl = pose[3:]
# Camera matrix
cam_matrix = data_dict['cam_matrix']
# Check for ground truth anns
if 'headpose_ori' in data_dict.keys():
if len(self.data_config.aug_names) == 0:
print('Image headpose generated by ground truth data')
pose_ori = data_dict['headpose_ori']
rot = pose_ori
return rot, trl, cam_matrix
def _plot_lnd(self, image, landmarks, visible, mask, max_shape_thr=720, colors=None):
if colors is None:
colors = self.colors_dft['lnd']
# Full image plots
W, H, C = image.shape
# Original image resize if need it
if W > max_shape_thr or H > max_shape_thr:
max_shape = max(W, H)
scale_factor = max_shape_thr / max_shape
resize_shape = (int(H * scale_factor), int(W * scale_factor))
image_out = plot.draw_landmarks(image, landmarks, visible=visible, mask=mask,
thick_scale=1 / scale_factor, colors=colors)
image_out = cv2.resize(image_out, resize_shape)
else:
image_out = plot.draw_landmarks(image, landmarks, visible=visible, mask=mask, colors=colors)
return image_out
if __name__ == '__main__':
args = inspect_parser()
data_aug = True
database = args.database
anns_type = args.anns
pose = args.nopose
select_img = args.img
if args.clean:
data_aug = False
if len(args.shape) != 2:
raise ValueError('--shape requires two values: width and height. Ej: --shape 256 256')
else:
img_shape = tuple(args.shape)
visualizer = DatasetInspector(database, anns_type, data_aug=data_aug, pose=pose, image_shape=img_shape)
visualizer.show_dataset(ids_list=select_img)
+92
View File
@@ -0,0 +1,92 @@
import cv2
import numpy as np
from .inspect_dataset import DatasetInspector, inspect_parser
class HeatmapInspector(DatasetInspector):
def __init__(self, database, anns_type, data_aug=True, image_shape=(256,256)):
super().__init__(database, anns_type, data_aug=data_aug, pose=False, image_shape=image_shape)
self.data_config.aug_names.append('heatmaps2D')
self.data_config.heatmap2D_norm = False
self.data_config.aug_names.append('boundaries')
self.data_config.shuffle = False
self.reload_dataset()
def show_dataset(self, ids_list=None):
if ids_list is None:
ids = self.get_idx(shuffle=self.data_config.shuffle)
else:
ids = ids_list
for img_id in ids:
data_dict = self.dataset[img_id]
crop_imgs, _ = self.plot_features(data_dict)
# Plot landmark crop
cv2.imshow('crop', crop_imgs['lnd'])
# Plot landmarks 2D (group)
crop_allheats = self._plot_heatmaps2D(data_dict)
# Plot boundaries shape
cv2.imshow('boundary', np.max(data_dict['boundary'], axis=0))
for lnd_idx in range(self.data_config.database.num_landmarks):
# Heatmaps 2D
crop_heats = self._plot_heatmaps2D(data_dict, lnd_idx)
maps = cv2.hconcat([crop_allheats['heatmaps2D'], crop_heats['heatmaps2D']])
cv2.imshow('heatmaps', maps)
key = cv2.waitKey()
if key == ord('q'):
break
if key == ord('n'):
break
if key == ord('q'):
break
def _plot_heatmaps2D(self, data_dict, heatmap_id=None):
# Variables
heatmaps = {}
image = data_dict['image']
if heatmap_id is None:
heatmaps2D = data_dict['heatmap2D']
heatmaps2D = np.max(heatmaps2D, axis=0)
else:
heatmaps2D = data_dict['heatmap2D'][heatmap_id]
# Plot maps
heatmaps['heatmaps2D'] = self._merge_imgmap(image, heatmaps2D)
return heatmaps
def _merge_imgmap(self, image, maps):
crop_maps = cv2.applyColorMap(np.uint8(255 * maps), cv2.COLORMAP_JET)
return cv2.addWeighted(image, 0.7, crop_maps, 0.3, 0)
if __name__ == '__main__':
args = inspect_parser()
data_aug = True
database = args.database
anns_type = args.anns
select_img = args.img
if args.clean:
data_aug = False
if len(args.shape) != 2:
raise ValueError('--shape requires two values: width and height. Ej: --shape 256 256')
else:
img_shape = tuple(args.shape)
visualizer = HeatmapInspector(database, anns_type, data_aug, image_shape=img_shape)
visualizer.show_dataset(ids_list=select_img)
+97
View File
@@ -0,0 +1,97 @@
import matplotlib.pyplot as plt
import numpy as np
import cv2
from ..loaders.augmentors import utils as dlu
BLUE = (255, 0, 0)
GREEN = (0, 255, 0)
RED = (0, 0, 255)
PURPLE = (128, 0, 128)
def draw_landmarks(image, landmarks, visible=None, mask=None, thick_scale=1, colors=(GREEN, RED)):
# Fix variable
thick = int(2 * thick_scale + 0.5)
# Initialize variables if need it
if visible is None:
visible = np.ones(len(landmarks))
if mask is None:
mask = np.ones(len(landmarks))
mask = np.array(mask, dtype=bool)
visible = np.array(visible, dtype=bool)
# Clean and split landmarks
landmarks = landmarks[mask]
visible = visible[mask]
ldm_vis = landmarks[visible]
not_visible = np.logical_not(visible)
ldm_notvis = landmarks[not_visible]
# Plot landmarks
if image.shape[0] == 3:
image = image.transpose(1, 2, 0)
canvas = image.copy()
canvas = _write_circles(canvas, ldm_vis, color=colors[0], thick=thick)
canvas = _write_circles(canvas, ldm_notvis, color=colors[1], thick=thick)
return canvas
def _write_circles(canvas, landmarks, color=RED, thick=2):
for xy in landmarks:
xy = np.array(xy+0.5, dtype=int)
canvas = cv2.circle(canvas, (xy[0], xy[1]), thick, color, -1)
return canvas
def plot_landmarks_pil(image, landmarks, visible=None, mask=None):
# Initialize variables if need it
if visible is None:
visible = np.ones(len(landmarks))
if mask is None:
mask = np.ones(len(landmarks))
mask = np.array(mask, dtype=bool)
visible = np.array(visible, dtype=bool)
not_visible = np.logical_not(visible)
# Clean and split landmarks
landmarks = landmarks[mask]
ldm_vis = landmarks[visible]
ldm_notvis = landmarks[not_visible]
# Plot landmarks
if image.shape[0] == 3:
image = image.transpose(1, 2, 0)
plt.imshow(image / 255)
plt.scatter(ldm_vis[:, 0], ldm_vis[:, 1], s=10, marker='.', c='g')
plt.scatter(ldm_notvis[:, 0], ldm_notvis[:, 1], s=10, marker='.', c='r')
plt.show()
def draw_pose(img, rot, trl, K, euler=False, size=0.5, colors=(BLUE, GREEN, RED)):
if euler:
rot = dlu.euler_to_rotation_matrix(rot)
canvas = img.copy()
rotV, _ = cv2.Rodrigues(rot)
points = np.float32([[size, 0, 0], [0, -size, 0], [0, 0, -size], [0, 0, 0]]).reshape(-1, 3)
axisPoints, _ = cv2.projectPoints(points, rotV, trl, K, (0, 0, 0, 0))
axisPoints = axisPoints.astype(int)
canvas = cv2.line(canvas, tuple(axisPoints[3].ravel()), tuple(axisPoints[2].ravel()), colors[0], 3)
canvas = cv2.line(canvas, tuple(axisPoints[3].ravel()), tuple(axisPoints[1].ravel()), colors[1], 3)
canvas = cv2.line(canvas, tuple(axisPoints[3].ravel()), tuple(axisPoints[0].ravel()), colors[2], 3)
return canvas
def enhance_heatmap(heatmap):
map_aux = heatmap - heatmap.min()
map_aux = map_aux / map_aux.max()
map_img = cv2.applyColorMap((map_aux * 255).astype(np.uint8), cv2.COLORMAP_BONE)
return map_img

Some files were not shown because too many files have changed in this diff Show More