Spaces:
Sleeping
Sleeping
# Author: aqeelanwar | |
# Created: 27 April,2020, 10:21 PM | |
# Email: [email protected] | |
from configparser import ConfigParser | |
import cv2, math, os | |
from PIL import Image, ImageDraw | |
from tqdm import tqdm | |
from read_cfg import read_cfg | |
from fit_ellipse import * | |
import random | |
from create_mask import texture_the_mask, color_the_mask | |
from imutils import face_utils | |
import requests | |
from zipfile import ZipFile | |
from tqdm import tqdm | |
import bz2, shutil | |
import numpy as np | |
def download_dlib_model(): | |
print_orderly("Get dlib model", 60) | |
dlib_model_link = "http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2" | |
print("Downloading dlib model...") | |
with requests.get(dlib_model_link, stream=True) as r: | |
print("Zip file size: ", np.round(len(r.content) / 1024 / 1024, 2), "MB") | |
destination = ( | |
"dlib_models" + os.path.sep + "shape_predictor_68_face_landmarks.dat.bz2" | |
) | |
if not os.path.exists(destination.rsplit(os.path.sep, 1)[0]): | |
os.mkdir(destination.rsplit(os.path.sep, 1)[0]) | |
print("Saving dlib model...") | |
with open(destination, "wb") as fd: | |
for chunk in r.iter_content(chunk_size=32678): | |
fd.write(chunk) | |
print("Extracting dlib model...") | |
with bz2.BZ2File(destination) as fr, open( | |
"dlib_models/shape_predictor_68_face_landmarks.dat", "wb" | |
) as fw: | |
shutil.copyfileobj(fr, fw) | |
print("Saved: ", destination) | |
print_orderly("done", 60) | |
os.remove(destination) | |
def get_line(face_landmark, image, type="eye", debug=False): | |
pil_image = Image.fromarray(image) | |
d = ImageDraw.Draw(pil_image) | |
left_eye = face_landmark["left_eye"] | |
right_eye = face_landmark["right_eye"] | |
left_eye_mid = np.mean(np.array(left_eye), axis=0) | |
right_eye_mid = np.mean(np.array(right_eye), axis=0) | |
eye_line_mid = (left_eye_mid + right_eye_mid) / 2 | |
if type == "eye": | |
left_point = left_eye_mid | |
right_point = right_eye_mid | |
mid_point = eye_line_mid | |
elif type == "nose_mid": | |
nose_length = ( | |
face_landmark["nose_bridge"][-1][1] - face_landmark["nose_bridge"][0][1] | |
) | |
left_point = [left_eye_mid[0], left_eye_mid[1] + nose_length / 2] | |
right_point = [right_eye_mid[0], right_eye_mid[1] + nose_length / 2] | |
# mid_point = ( | |
# face_landmark["nose_bridge"][-1][1] + face_landmark["nose_bridge"][0][1] | |
# ) / 2 | |
mid_pointY = ( | |
face_landmark["nose_bridge"][-1][1] + face_landmark["nose_bridge"][0][1] | |
) / 2 | |
mid_pointX = ( | |
face_landmark["nose_bridge"][-1][0] + face_landmark["nose_bridge"][0][0] | |
) / 2 | |
mid_point = (mid_pointX, mid_pointY) | |
elif type == "nose_tip": | |
nose_length = ( | |
face_landmark["nose_bridge"][-1][1] - face_landmark["nose_bridge"][0][1] | |
) | |
left_point = [left_eye_mid[0], left_eye_mid[1] + nose_length] | |
right_point = [right_eye_mid[0], right_eye_mid[1] + nose_length] | |
mid_point = ( | |
face_landmark["nose_bridge"][-1][1] + face_landmark["nose_bridge"][0][1] | |
) / 2 | |
elif type == "bottom_lip": | |
bottom_lip = face_landmark["bottom_lip"] | |
bottom_lip_mid = np.max(np.array(bottom_lip), axis=0) | |
shiftY = bottom_lip_mid[1] - eye_line_mid[1] | |
left_point = [left_eye_mid[0], left_eye_mid[1] + shiftY] | |
right_point = [right_eye_mid[0], right_eye_mid[1] + shiftY] | |
mid_point = bottom_lip_mid | |
elif type == "perp_line": | |
bottom_lip = face_landmark["bottom_lip"] | |
bottom_lip_mid = np.mean(np.array(bottom_lip), axis=0) | |
left_point = eye_line_mid | |
left_point = face_landmark["nose_bridge"][0] | |
right_point = bottom_lip_mid | |
mid_point = bottom_lip_mid | |
elif type == "nose_long": | |
nose_bridge = face_landmark["nose_bridge"] | |
left_point = [nose_bridge[0][0], nose_bridge[0][1]] | |
right_point = [nose_bridge[-1][0], nose_bridge[-1][1]] | |
mid_point = left_point | |
# d.line(eye_mid, width=5, fill='red') | |
y = [left_point[1], right_point[1]] | |
x = [left_point[0], right_point[0]] | |
# cv2.imshow('h', image) | |
# cv2.waitKey(0) | |
eye_line = fit_line(x, y, image) | |
d.line(eye_line, width=5, fill="blue") | |
# Perpendicular Line | |
# (midX, midY) and (midX - y2 + y1, midY + x2 - x1) | |
y = [ | |
(left_point[1] + right_point[1]) / 2, | |
(left_point[1] + right_point[1]) / 2 + right_point[0] - left_point[0], | |
] | |
x = [ | |
(left_point[0] + right_point[0]) / 2, | |
(left_point[0] + right_point[0]) / 2 - right_point[1] + left_point[1], | |
] | |
perp_line = fit_line(x, y, image) | |
if debug: | |
d.line(perp_line, width=5, fill="red") | |
pil_image.show() | |
return eye_line, perp_line, left_point, right_point, mid_point | |
def get_points_on_chin(line, face_landmark, chin_type="chin"): | |
chin = face_landmark[chin_type] | |
points_on_chin = [] | |
for i in range(len(chin) - 1): | |
chin_first_point = [chin[i][0], chin[i][1]] | |
chin_second_point = [chin[i + 1][0], chin[i + 1][1]] | |
flag, x, y = line_intersection(line, (chin_first_point, chin_second_point)) | |
if flag: | |
points_on_chin.append((x, y)) | |
return points_on_chin | |
def plot_lines(face_line, image, debug=False): | |
pil_image = Image.fromarray(image) | |
if debug: | |
d = ImageDraw.Draw(pil_image) | |
d.line(face_line, width=4, fill="white") | |
pil_image.show() | |
def line_intersection(line1, line2): | |
# mid = int(len(line1) / 2) | |
start = 0 | |
end = -1 | |
line1 = ([line1[start][0], line1[start][1]], [line1[end][0], line1[end][1]]) | |
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0]) | |
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1]) | |
x = [] | |
y = [] | |
flag = False | |
def det(a, b): | |
return a[0] * b[1] - a[1] * b[0] | |
div = det(xdiff, ydiff) | |
if div == 0: | |
return flag, x, y | |
d = (det(*line1), det(*line2)) | |
x = det(d, xdiff) / div | |
y = det(d, ydiff) / div | |
segment_minX = min(line2[0][0], line2[1][0]) | |
segment_maxX = max(line2[0][0], line2[1][0]) | |
segment_minY = min(line2[0][1], line2[1][1]) | |
segment_maxY = max(line2[0][1], line2[1][1]) | |
if ( | |
segment_maxX + 1 >= x >= segment_minX - 1 | |
and segment_maxY + 1 >= y >= segment_minY - 1 | |
): | |
flag = True | |
return flag, x, y | |
def fit_line(x, y, image): | |
if x[0] == x[1]: | |
x[0] += 0.1 | |
coefficients = np.polyfit(x, y, 1) | |
polynomial = np.poly1d(coefficients) | |
x_axis = np.linspace(0, image.shape[1], 50) | |
y_axis = polynomial(x_axis) | |
eye_line = [] | |
for i in range(len(x_axis)): | |
eye_line.append((x_axis[i], y_axis[i])) | |
return eye_line | |
def get_six_points(face_landmark, image): | |
_, perp_line1, _, _, m = get_line(face_landmark, image, type="nose_mid") | |
face_b = m | |
perp_line, _, _, _, _ = get_line(face_landmark, image, type="perp_line") | |
points1 = get_points_on_chin(perp_line1, face_landmark) | |
points = get_points_on_chin(perp_line, face_landmark) | |
if not points1: | |
face_e = tuple(np.asarray(points[0])) | |
elif not points: | |
face_e = tuple(np.asarray(points1[0])) | |
else: | |
face_e = tuple((np.asarray(points[0]) + np.asarray(points1[0])) / 2) | |
# face_e = points1[0] | |
nose_mid_line, _, _, _, _ = get_line(face_landmark, image, type="nose_long") | |
angle = get_angle(perp_line, nose_mid_line) | |
# print("angle: ", angle) | |
nose_mid_line, _, _, _, _ = get_line(face_landmark, image, type="nose_tip") | |
points = get_points_on_chin(nose_mid_line, face_landmark) | |
if len(points) < 2: | |
face_landmark = get_face_ellipse(face_landmark) | |
# print("extrapolating chin") | |
points = get_points_on_chin( | |
nose_mid_line, face_landmark, chin_type="chin_extrapolated" | |
) | |
if len(points) < 2: | |
points = [] | |
points.append(face_landmark["chin"][0]) | |
points.append(face_landmark["chin"][-1]) | |
face_a = points[0] | |
face_c = points[-1] | |
# cv2.imshow('j', image) | |
# cv2.waitKey(0) | |
nose_mid_line, _, _, _, _ = get_line(face_landmark, image, type="bottom_lip") | |
points = get_points_on_chin(nose_mid_line, face_landmark) | |
face_d = points[0] | |
face_f = points[-1] | |
six_points = np.float32([face_a, face_b, face_c, face_f, face_e, face_d]) | |
return six_points, angle | |
def get_angle(line1, line2): | |
delta_y = line1[-1][1] - line1[0][1] | |
delta_x = line1[-1][0] - line1[0][0] | |
perp_angle = math.degrees(math.atan2(delta_y, delta_x)) | |
if delta_x < 0: | |
perp_angle = perp_angle + 180 | |
if perp_angle < 0: | |
perp_angle += 360 | |
if perp_angle > 180: | |
perp_angle -= 180 | |
# print("perp", perp_angle) | |
delta_y = line2[-1][1] - line2[0][1] | |
delta_x = line2[-1][0] - line2[0][0] | |
nose_angle = math.degrees(math.atan2(delta_y, delta_x)) | |
if delta_x < 0: | |
nose_angle = nose_angle + 180 | |
if nose_angle < 0: | |
nose_angle += 360 | |
if nose_angle > 180: | |
nose_angle -= 180 | |
# print("nose", nose_angle) | |
angle = nose_angle - perp_angle | |
return angle | |
def mask_face(image, face_location, six_points, angle, args, type="surgical"): | |
debug = False | |
# Find the face angle | |
threshold = 13 | |
if angle < -threshold: | |
type += "_right" | |
elif angle > threshold: | |
type += "_left" | |
face_height = face_location[2] - face_location[0] | |
face_width = face_location[1] - face_location[3] | |
# image = image_raw[ | |
# face_location[0]-int(face_width/2): face_location[2]+int(face_width/2), | |
# face_location[3]-int(face_height/2): face_location[1]+int(face_height/2), | |
# :, | |
# ] | |
# cv2.imshow('win', image) | |
# cv2.waitKey(0) | |
# Read appropriate mask image | |
w = image.shape[0] | |
h = image.shape[1] | |
if not "empty" in type and not "inpaint" in type: | |
cfg = read_cfg(config_filename="masks.cfg", mask_type=type, verbose=False) | |
else: | |
if "left" in type: | |
str = "surgical_blue_left" | |
elif "right" in type: | |
str = "surgical_blue_right" | |
else: | |
str = "surgical_blue" | |
cfg = read_cfg(config_filename="masks.cfg", mask_type=str, verbose=False) | |
img = cv2.imread(cfg.template, cv2.IMREAD_UNCHANGED) | |
# Process the mask if necessary | |
if args.pattern: | |
# Apply pattern to mask | |
img = texture_the_mask(img, args.pattern, args.pattern_weight) | |
if args.color: | |
# Apply color to mask | |
img = color_the_mask(img, args.color, args.color_weight) | |
mask_line = np.float32( | |
[cfg.mask_a, cfg.mask_b, cfg.mask_c, cfg.mask_f, cfg.mask_e, cfg.mask_d] | |
) | |
# Warp the mask | |
M, mask = cv2.findHomography(mask_line, six_points) | |
dst_mask = cv2.warpPerspective(img, M, (h, w)) | |
dst_mask_points = cv2.perspectiveTransform(mask_line.reshape(-1, 1, 2), M) | |
mask = dst_mask[:, :, 3] | |
face_height = face_location[2] - face_location[0] | |
face_width = face_location[1] - face_location[3] | |
image_face = image[ | |
face_location[0] + int(face_height / 2) : face_location[2], | |
face_location[3] : face_location[1], | |
:, | |
] | |
image_face = image | |
# Adjust Brightness | |
mask_brightness = get_avg_brightness(img) | |
img_brightness = get_avg_brightness(image_face) | |
delta_b = 1 + (img_brightness - mask_brightness) / 255 | |
dst_mask = change_brightness(dst_mask, delta_b) | |
# Adjust Saturation | |
mask_saturation = get_avg_saturation(img) | |
img_saturation = get_avg_saturation(image_face) | |
delta_s = 1 - (img_saturation - mask_saturation) / 255 | |
dst_mask = change_saturation(dst_mask, delta_s) | |
# Apply mask | |
mask_inv = cv2.bitwise_not(mask) | |
img_bg = cv2.bitwise_and(image, image, mask=mask_inv) | |
img_fg = cv2.bitwise_and(dst_mask, dst_mask, mask=mask) | |
out_img = cv2.add(img_bg, img_fg[:, :, 0:3]) | |
if "empty" in type or "inpaint" in type: | |
out_img = img_bg | |
# Plot key points | |
if "inpaint" in type: | |
out_img = cv2.inpaint(out_img, mask, 3, cv2.INPAINT_TELEA) | |
# dst_NS = cv2.inpaint(img, mask, 3, cv2.INPAINT_NS) | |
if debug: | |
for i in six_points: | |
cv2.circle(out_img, (i[0], i[1]), radius=4, color=(0, 0, 255), thickness=-1) | |
for i in dst_mask_points: | |
cv2.circle( | |
out_img, (i[0][0], i[0][1]), radius=4, color=(0, 255, 0), thickness=-1 | |
) | |
return out_img, mask | |
def draw_landmarks(face_landmarks, image): | |
pil_image = Image.fromarray(image) | |
d = ImageDraw.Draw(pil_image) | |
for facial_feature in face_landmarks.keys(): | |
d.line(face_landmarks[facial_feature], width=5, fill="white") | |
pil_image.show() | |
def get_face_ellipse(face_landmark): | |
chin = face_landmark["chin"] | |
x = [] | |
y = [] | |
for point in chin: | |
x.append(point[0]) | |
y.append(point[1]) | |
x = np.asarray(x) | |
y = np.asarray(y) | |
a = fitEllipse(x, y) | |
center = ellipse_center(a) | |
phi = ellipse_angle_of_rotation(a) | |
axes = ellipse_axis_length(a) | |
a, b = axes | |
arc = 2.2 | |
R = np.arange(0, arc * np.pi, 0.2) | |
xx = center[0] + a * np.cos(R) * np.cos(phi) - b * np.sin(R) * np.sin(phi) | |
yy = center[1] + a * np.cos(R) * np.sin(phi) + b * np.sin(R) * np.cos(phi) | |
chin_extrapolated = [] | |
for i in range(len(R)): | |
chin_extrapolated.append((xx[i], yy[i])) | |
face_landmark["chin_extrapolated"] = chin_extrapolated | |
return face_landmark | |
def get_avg_brightness(img): | |
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
h, s, v = cv2.split(img_hsv) | |
return np.mean(v) | |
def get_avg_saturation(img): | |
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
h, s, v = cv2.split(img_hsv) | |
return np.mean(v) | |
def change_brightness(img, value=1.0): | |
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
h, s, v = cv2.split(img_hsv) | |
v = value * v | |
v[v > 255] = 255 | |
v = np.asarray(v, dtype=np.uint8) | |
final_hsv = cv2.merge((h, s, v)) | |
img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR) | |
return img | |
def change_saturation(img, value=1.0): | |
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
h, s, v = cv2.split(img_hsv) | |
s = value * s | |
s[s > 255] = 255 | |
s = np.asarray(s, dtype=np.uint8) | |
final_hsv = cv2.merge((h, s, v)) | |
img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR) | |
return img | |
def check_path(path): | |
is_directory = False | |
is_file = False | |
is_other = False | |
if os.path.isdir(path): | |
is_directory = True | |
elif os.path.isfile(path): | |
is_file = True | |
else: | |
is_other = True | |
return is_directory, is_file, is_other | |
def shape_to_landmarks(shape): | |
face_landmarks = {} | |
face_landmarks["left_eyebrow"] = [ | |
tuple(shape[17]), | |
tuple(shape[18]), | |
tuple(shape[19]), | |
tuple(shape[20]), | |
tuple(shape[21]), | |
] | |
face_landmarks["right_eyebrow"] = [ | |
tuple(shape[22]), | |
tuple(shape[23]), | |
tuple(shape[24]), | |
tuple(shape[25]), | |
tuple(shape[26]), | |
] | |
face_landmarks["nose_bridge"] = [ | |
tuple(shape[27]), | |
tuple(shape[28]), | |
tuple(shape[29]), | |
tuple(shape[30]), | |
] | |
face_landmarks["nose_tip"] = [ | |
tuple(shape[31]), | |
tuple(shape[32]), | |
tuple(shape[33]), | |
tuple(shape[34]), | |
tuple(shape[35]), | |
] | |
face_landmarks["left_eye"] = [ | |
tuple(shape[36]), | |
tuple(shape[37]), | |
tuple(shape[38]), | |
tuple(shape[39]), | |
tuple(shape[40]), | |
tuple(shape[41]), | |
] | |
face_landmarks["right_eye"] = [ | |
tuple(shape[42]), | |
tuple(shape[43]), | |
tuple(shape[44]), | |
tuple(shape[45]), | |
tuple(shape[46]), | |
tuple(shape[47]), | |
] | |
face_landmarks["top_lip"] = [ | |
tuple(shape[48]), | |
tuple(shape[49]), | |
tuple(shape[50]), | |
tuple(shape[51]), | |
tuple(shape[52]), | |
tuple(shape[53]), | |
tuple(shape[54]), | |
tuple(shape[60]), | |
tuple(shape[61]), | |
tuple(shape[62]), | |
tuple(shape[63]), | |
tuple(shape[64]), | |
] | |
face_landmarks["bottom_lip"] = [ | |
tuple(shape[54]), | |
tuple(shape[55]), | |
tuple(shape[56]), | |
tuple(shape[57]), | |
tuple(shape[58]), | |
tuple(shape[59]), | |
tuple(shape[48]), | |
tuple(shape[64]), | |
tuple(shape[65]), | |
tuple(shape[66]), | |
tuple(shape[67]), | |
tuple(shape[60]), | |
] | |
face_landmarks["chin"] = [ | |
tuple(shape[0]), | |
tuple(shape[1]), | |
tuple(shape[2]), | |
tuple(shape[3]), | |
tuple(shape[4]), | |
tuple(shape[5]), | |
tuple(shape[6]), | |
tuple(shape[7]), | |
tuple(shape[8]), | |
tuple(shape[9]), | |
tuple(shape[10]), | |
tuple(shape[11]), | |
tuple(shape[12]), | |
tuple(shape[13]), | |
tuple(shape[14]), | |
tuple(shape[15]), | |
tuple(shape[16]), | |
] | |
return face_landmarks | |
def rect_to_bb(rect): | |
x1 = rect.left() | |
x2 = rect.right() | |
y1 = rect.top() | |
y2 = rect.bottom() | |
return (x1, x2, y2, x1) | |
def mask_image(theImage, args): | |
# Read the image | |
image = theImage | |
original_image = image.copy() | |
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
gray = image | |
face_locations = args.detector(gray, 1) | |
mask_type = args.mask_type | |
verbose = args.verbose | |
if args.code: | |
ind = random.randint(0, len(args.code_count) - 1) | |
mask_dict = args.mask_dict_of_dict[ind] | |
mask_type = mask_dict["type"] | |
args.color = mask_dict["color"] | |
args.pattern = mask_dict["texture"] | |
args.code_count[ind] += 1 | |
elif mask_type == "random": | |
available_mask_types = get_available_mask_types() | |
mask_type = random.choice(available_mask_types) | |
if verbose: | |
tqdm.write("Faces found: {:2d}".format(len(face_locations))) | |
# Process each face in the image | |
masked_images = [] | |
mask_binary_array = [] | |
mask = [] | |
for (i, face_location) in enumerate(face_locations): | |
shape = args.predictor(gray, face_location) | |
shape = face_utils.shape_to_np(shape) | |
face_landmarks = shape_to_landmarks(shape) | |
face_location = rect_to_bb(face_location) | |
# draw_landmarks(face_landmarks, image) | |
six_points_on_face, angle = get_six_points(face_landmarks, image) | |
mask = [] | |
if mask_type != "all": | |
if len(masked_images) > 0: | |
image = masked_images.pop(0) | |
image, mask_binary = mask_face( | |
image, face_location, six_points_on_face, angle, args, type=mask_type | |
) | |
# compress to face tight | |
face_height = face_location[2] - face_location[0] | |
face_width = face_location[1] - face_location[3] | |
masked_images.append(image) | |
mask_binary_array.append(mask_binary) | |
mask.append(mask_type) | |
else: | |
available_mask_types = get_available_mask_types() | |
for m in range(len(available_mask_types)): | |
if len(masked_images) == len(available_mask_types): | |
image = masked_images.pop(m) | |
img, mask_binary = mask_face( | |
image, | |
face_location, | |
six_points_on_face, | |
angle, | |
args, | |
type=available_mask_types[m], | |
) | |
masked_images.insert(m, img) | |
mask_binary_array.insert(m, mask_binary) | |
mask = available_mask_types | |
cc = 1 | |
return masked_images, mask, mask_binary_array, original_image | |
def is_image(path): | |
try: | |
extensions = path[-4:] | |
image_extensions = ["png", "PNG", "jpg", "JPG"] | |
if extensions[1:] in image_extensions: | |
return True | |
else: | |
print("Please input image file. png / jpg") | |
return False | |
except: | |
return False | |
def get_available_mask_types(config_filename="masks.cfg"): | |
parser = ConfigParser() | |
parser.optionxform = str | |
parser.read(config_filename) | |
available_mask_types = parser.sections() | |
available_mask_types = [ | |
string for string in available_mask_types if "left" not in string | |
] | |
available_mask_types = [ | |
string for string in available_mask_types if "right" not in string | |
] | |
return available_mask_types | |
def print_orderly(str, n): | |
# print("") | |
hyphens = "-" * int((n - len(str)) / 2) | |
str_p = hyphens + " " + str + " " + hyphens | |
hyphens_bar = "-" * len(str_p) | |
print(hyphens_bar) | |
print(str_p) | |
print(hyphens_bar) | |
def display_MaskTheFace(): | |
with open("utils/display.txt", "r") as file: | |
for line in file: | |
cc = 1 | |
print(line, end="") | |