-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff35b20
Showing
4 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
""" | ||
brief: face alignment with FFHQ method (https://github.com/NVlabs/ffhq-dataset) | ||
author: lzhbrian (https://lzhbrian.me) | ||
date: 2020.1.5 | ||
note: code is heavily borrowed from | ||
https://github.com/NVlabs/ffhq-dataset | ||
http://dlib.net/face_landmark_detection.py.html | ||
requirements: | ||
apt install cmake | ||
conda install Pillow numpy scipy | ||
pip install dlib | ||
# download face landmark model from: | ||
# http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 | ||
""" | ||
|
||
import numpy as np | ||
import PIL | ||
import PIL.Image | ||
import sys | ||
import os | ||
import glob | ||
import scipy | ||
import scipy.ndimage | ||
import dlib | ||
|
||
|
||
# download model from: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 | ||
predictor = dlib.shape_predictor('./shape_predictor_68_face_landmarks.dat') | ||
|
||
def get_landmark(filepath): | ||
"""get landmark with dlib | ||
:return: np.array shape=(68, 2) | ||
""" | ||
detector = dlib.get_frontal_face_detector() | ||
|
||
img = dlib.load_rgb_image(filepath) | ||
dets = detector(img, 1) | ||
|
||
print("Number of faces detected: {}".format(len(dets))) | ||
for k, d in enumerate(dets): | ||
print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format( | ||
k, d.left(), d.top(), d.right(), d.bottom())) | ||
# Get the landmarks/parts for the face in box d. | ||
shape = predictor(img, d) | ||
print("Part 0: {}, Part 1: {} ...".format(shape.part(0), shape.part(1))) | ||
|
||
|
||
t = list(shape.parts()) | ||
a = [] | ||
for tt in t: | ||
a.append([tt.x, tt.y]) | ||
lm = np.array(a) | ||
# lm is a shape=(68,2) np.array | ||
return lm | ||
|
||
|
||
def align_face(filepath): | ||
""" | ||
:param filepath: str | ||
:return: PIL Image | ||
""" | ||
|
||
lm = get_landmark(filepath) | ||
|
||
lm_chin = lm[0 : 17] # left-right | ||
lm_eyebrow_left = lm[17 : 22] # left-right | ||
lm_eyebrow_right = lm[22 : 27] # left-right | ||
lm_nose = lm[27 : 31] # top-down | ||
lm_nostrils = lm[31 : 36] # top-down | ||
lm_eye_left = lm[36 : 42] # left-clockwise | ||
lm_eye_right = lm[42 : 48] # left-clockwise | ||
lm_mouth_outer = lm[48 : 60] # left-clockwise | ||
lm_mouth_inner = lm[60 : 68] # left-clockwise | ||
|
||
# Calculate auxiliary vectors. | ||
eye_left = np.mean(lm_eye_left, axis=0) | ||
eye_right = np.mean(lm_eye_right, axis=0) | ||
eye_avg = (eye_left + eye_right) * 0.5 | ||
eye_to_eye = eye_right - eye_left | ||
mouth_left = lm_mouth_outer[0] | ||
mouth_right = lm_mouth_outer[6] | ||
mouth_avg = (mouth_left + mouth_right) * 0.5 | ||
eye_to_mouth = mouth_avg - eye_avg | ||
|
||
# Choose oriented crop rectangle. | ||
x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1] | ||
x /= np.hypot(*x) | ||
x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8) | ||
y = np.flipud(x) * [-1, 1] | ||
c = eye_avg + eye_to_mouth * 0.1 | ||
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y]) | ||
qsize = np.hypot(*x) * 2 | ||
|
||
|
||
# read image | ||
img = PIL.Image.open(filepath) | ||
|
||
output_size=1024 | ||
transform_size=4096 | ||
enable_padding=True | ||
|
||
# Shrink. | ||
shrink = int(np.floor(qsize / output_size * 0.5)) | ||
if shrink > 1: | ||
rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink))) | ||
img = img.resize(rsize, PIL.Image.ANTIALIAS) | ||
quad /= shrink | ||
qsize /= shrink | ||
|
||
# Crop. | ||
border = max(int(np.rint(qsize * 0.1)), 3) | ||
crop = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1])))) | ||
crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), min(crop[3] + border, img.size[1])) | ||
if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]: | ||
img = img.crop(crop) | ||
quad -= crop[0:2] | ||
|
||
# Pad. | ||
pad = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1])))) | ||
pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), max(pad[3] - img.size[1] + border, 0)) | ||
if enable_padding and max(pad) > border - 4: | ||
pad = np.maximum(pad, int(np.rint(qsize * 0.3))) | ||
img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect') | ||
h, w, _ = img.shape | ||
y, x, _ = np.ogrid[:h, :w, :1] | ||
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w-1-x) / pad[2]), 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h-1-y) / pad[3])) | ||
blur = qsize * 0.02 | ||
img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0) | ||
img += (np.median(img, axis=(0,1)) - img) * np.clip(mask, 0.0, 1.0) | ||
img = PIL.Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB') | ||
quad += pad[:2] | ||
|
||
# Transform. | ||
img = img.transform((transform_size, transform_size), PIL.Image.QUAD, (quad + 0.5).flatten(), PIL.Image.BILINEAR) | ||
if output_size < transform_size: | ||
img = img.resize((output_size, output_size), PIL.Image.ANTIALIAS) | ||
|
||
# Save aligned image. | ||
return img |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
""" | ||
brief: face alignment with FFHQ method (https://github.com/NVlabs/ffhq-dataset) | ||
author: lzhbrian (https://lzhbrian.me) | ||
date: 2020.1.5 | ||
note: code is heavily borrowed from | ||
https://github.com/NVlabs/ffhq-dataset | ||
http://dlib.net/face_landmark_detection.py.html | ||
requirements: | ||
apt install cmake | ||
conda install Pillow numpy scipy | ||
pip install dlib | ||
# download face landmark model from: | ||
# http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 | ||
""" | ||
|
||
import numpy as np | ||
import PIL | ||
import PIL.Image | ||
import sys | ||
import os | ||
import glob | ||
import scipy | ||
import scipy.ndimage | ||
import dlib | ||
import argparse | ||
import multiprocessing | ||
|
||
|
||
# download model from: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 | ||
# predictor = dlib.shape_predictor('./shape_predictor_68_face_landmarks.dat') | ||
|
||
def get_landmark(filepath): | ||
"""get landmark with dlib | ||
:return: np.array shape=(68, 2) | ||
""" | ||
detector = dlib.get_frontal_face_detector() | ||
|
||
img = dlib.load_rgb_image(filepath) | ||
dets = detector(img, 1) | ||
|
||
print("Number of faces detected: {}".format(len(dets))) | ||
for k, d in enumerate(dets): | ||
print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format( | ||
k, d.left(), d.top(), d.right(), d.bottom())) | ||
# Get the landmarks/parts for the face in box d. | ||
shape = predictor(img, d) | ||
print("Part 0: {}, Part 1: {} ...".format(shape.part(0), shape.part(1))) | ||
|
||
|
||
t = list(shape.parts()) | ||
a = [] | ||
for tt in t: | ||
a.append([tt.x, tt.y]) | ||
lm = np.array(a) | ||
# lm is a shape=(68,2) np.array | ||
return lm | ||
|
||
|
||
def align_face(filepath): | ||
""" | ||
:param filepath: str | ||
:return: PIL Image | ||
""" | ||
|
||
lm = get_landmark(filepath) | ||
|
||
lm_chin = lm[0 : 17] # left-right | ||
lm_eyebrow_left = lm[17 : 22] # left-right | ||
lm_eyebrow_right = lm[22 : 27] # left-right | ||
lm_nose = lm[27 : 31] # top-down | ||
lm_nostrils = lm[31 : 36] # top-down | ||
lm_eye_left = lm[36 : 42] # left-clockwise | ||
lm_eye_right = lm[42 : 48] # left-clockwise | ||
lm_mouth_outer = lm[48 : 60] # left-clockwise | ||
lm_mouth_inner = lm[60 : 68] # left-clockwise | ||
|
||
# Calculate auxiliary vectors. | ||
eye_left = np.mean(lm_eye_left, axis=0) | ||
eye_right = np.mean(lm_eye_right, axis=0) | ||
eye_avg = (eye_left + eye_right) * 0.5 | ||
eye_to_eye = eye_right - eye_left | ||
mouth_left = lm_mouth_outer[0] | ||
mouth_right = lm_mouth_outer[6] | ||
mouth_avg = (mouth_left + mouth_right) * 0.5 | ||
eye_to_mouth = mouth_avg - eye_avg | ||
|
||
# Choose oriented crop rectangle. | ||
x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1] | ||
x /= np.hypot(*x) | ||
x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8) | ||
y = np.flipud(x) * [-1, 1] | ||
c = eye_avg + eye_to_mouth * 0.1 | ||
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y]) | ||
qsize = np.hypot(*x) * 2 | ||
|
||
|
||
# read image | ||
img = PIL.Image.open(filepath) | ||
|
||
output_size=1024 | ||
transform_size=4096 | ||
enable_padding=True | ||
|
||
# Shrink. | ||
shrink = int(np.floor(qsize / output_size * 0.5)) | ||
if shrink > 1: | ||
rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink))) | ||
img = img.resize(rsize, PIL.Image.LANCZOS) | ||
quad /= shrink | ||
qsize /= shrink | ||
|
||
# Crop. | ||
border = max(int(np.rint(qsize * 0.1)), 3) | ||
crop = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1])))) | ||
crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), min(crop[3] + border, img.size[1])) | ||
if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]: | ||
img = img.crop(crop) | ||
quad -= crop[0:2] | ||
|
||
# Pad. | ||
pad = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1])))) | ||
pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), max(pad[3] - img.size[1] + border, 0)) | ||
if enable_padding and max(pad) > border - 4: | ||
pad = np.maximum(pad, int(np.rint(qsize * 0.3))) | ||
img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect') | ||
h, w, _ = img.shape | ||
y, x, _ = np.ogrid[:h, :w, :1] | ||
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w-1-x) / pad[2]), 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h-1-y) / pad[3])) | ||
blur = qsize * 0.02 | ||
img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0) | ||
img += (np.median(img, axis=(0,1)) - img) * np.clip(mask, 0.0, 1.0) | ||
img = PIL.Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB') | ||
quad += pad[:2] | ||
|
||
# Transform. | ||
img = img.transform((transform_size, transform_size), PIL.Image.QUAD, (quad + 0.5).flatten(), PIL.Image.BILINEAR) | ||
if output_size < transform_size: | ||
img = img.resize((output_size, output_size), PIL.Image.LANCZOS) | ||
|
||
# Save aligned image. | ||
return img | ||
|
||
def arg_parser(): | ||
parser = argparse.ArgumentParser(description='align face') | ||
parser.add_argument('--input_dir', type=str, required=True, help='input directory') | ||
parser.add_argument('--output_dir', type=str, default=None, help='output directory') | ||
parser.add_argument('--num_workers', type=int, default=4, help='number of workers') | ||
parser.add_argument('--predictor_path', type=str, default='./shape_predictor_68_face_landmarks.dat', help='path to dlib predictor') | ||
return parser | ||
|
||
if __name__ == "__main__": | ||
IMG_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.JPG', '.JPEG', '.PNG'] | ||
parser = arg_parser() | ||
args = parser.parse_args() | ||
input_dir = args.input_dir | ||
output_dir = args.output_dir if args.output_dir else os.path.join(input_dir, 'aligned') | ||
os.makedirs(output_dir, exist_ok=True) | ||
num_workers = args.num_workers | ||
predictor_path = args.predictor_path | ||
predictor = dlib.shape_predictor(predictor_path) | ||
|
||
input_images = [] | ||
# walk through input_dir | ||
for root, _, fnames in sorted(os.walk(input_dir)): | ||
for fname in sorted(fnames): | ||
if any(fname.endswith(extension) for extension in IMG_EXTENSIONS): | ||
path = os.path.join(root, fname) | ||
input_images.append(path) | ||
|
||
# align face | ||
def align_face_worker(filepath): | ||
# print(filepath) | ||
img = align_face(filepath) | ||
img.save(os.path.join(output_dir, os.path.basename(filepath))) | ||
|
||
with multiprocessing.Pool(num_workers) as p: | ||
p.map(align_face_worker, input_images) | ||
|
||
print('done') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
name: ffhq_align_face | ||
channels: | ||
- defaults | ||
dependencies: | ||
- _libgcc_mutex=0.1=main | ||
- _openmp_mutex=5.1=1_gnu | ||
- blas=1.0=mkl | ||
- bzip2=1.0.8=h7b6447c_0 | ||
- ca-certificates=2023.12.12=h06a4308_0 | ||
- freetype=2.12.1=h4a9f257_0 | ||
- giflib=5.2.1=h5eee18b_3 | ||
- intel-openmp=2023.1.0=hdb19cb5_46306 | ||
- jpeg=9e=h5eee18b_1 | ||
- lcms2=2.12=h3be6417_0 | ||
- ld_impl_linux-64=2.38=h1181459_1 | ||
- lerc=3.0=h295c915_0 | ||
- libdeflate=1.17=h5eee18b_1 | ||
- libffi=3.4.4=h6a678d5_0 | ||
- libgcc-ng=11.2.0=h1234567_1 | ||
- libgfortran-ng=11.2.0=h00389a5_1 | ||
- libgfortran5=11.2.0=h1234567_1 | ||
- libgomp=11.2.0=h1234567_1 | ||
- libpng=1.6.39=h5eee18b_0 | ||
- libstdcxx-ng=11.2.0=h1234567_1 | ||
- libtiff=4.5.1=h6a678d5_0 | ||
- libuuid=1.41.5=h5eee18b_0 | ||
- libwebp=1.3.2=h11a3e52_0 | ||
- libwebp-base=1.3.2=h5eee18b_0 | ||
- lz4-c=1.9.4=h6a678d5_0 | ||
- mkl=2023.1.0=h213fc3f_46344 | ||
- mkl-service=2.4.0=py310h5eee18b_1 | ||
- mkl_fft=1.3.8=py310h5eee18b_0 | ||
- mkl_random=1.2.4=py310hdb19cb5_0 | ||
- ncurses=6.4=h6a678d5_0 | ||
- numpy=1.26.3=py310h5f9d8c6_0 | ||
- numpy-base=1.26.3=py310hb5e798b_0 | ||
- openjpeg=2.4.0=h3ad879b_0 | ||
- openssl=3.0.13=h7f8727e_0 | ||
- pillow=10.0.1=py310ha6cbd5a_0 | ||
- pip=23.3.1=py310h06a4308_0 | ||
- python=3.10.13=h955ad1f_0 | ||
- readline=8.2=h5eee18b_0 | ||
- scipy=1.11.4=py310h5f9d8c6_0 | ||
- setuptools=68.2.2=py310h06a4308_0 | ||
- sqlite=3.41.2=h5eee18b_0 | ||
- tbb=2021.8.0=hdb19cb5_0 | ||
- tk=8.6.12=h1ccaba5_0 | ||
- tzdata=2023d=h04d1e81_0 | ||
- wheel=0.41.2=py310h06a4308_0 | ||
- xz=5.4.5=h5eee18b_0 | ||
- zlib=1.2.13=h5eee18b_0 | ||
- zstd=1.5.5=hc292b87_0 | ||
- pip: | ||
- dlib==19.24.2 | ||
prefix: /playpen-nas-ssd/luchao/software/miniconda3/envs/ffhq_align_face |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/playpen-nas-ssd/luchao/pretrained_weights/dlib/shape_predictor_68_face_landmarks.dat |