!apt update
!apt install ffmpeg libsm6 libxext6 build-essential cmake -y
!pip install opencv-contrib-python==4.1.0.25
!pip install dlib==19.21.1
import cv2
import sys
import dlib
import time
import math
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import faceBlendCommon as fbc
import matplotlib
matplotlib.rcParams['figure.figsize'] = (8.0,8.0)
matplotlib.rcParams['image.cmap'] = 'gray'
matplotlib.rcParams['image.interpolation'] = 'bilinear'
# Landmark model location
PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
# Get the face detector
faceDetector = dlib.get_frontal_face_detector()
# The landmark detector is implemented in the shape_predictor class
landmarkDetector = dlib.shape_predictor(PREDICTOR_PATH)
im = cv2.imread("girl-no-makeup.jpg")
imDlib = cv2.cvtColor(im,cv2.COLOR_BGR2RGB)
plt.imshow(imDlib)
points = fbc.getLandmarks(faceDetector, landmarkDetector, imDlib)
print(points)
def alphaBlend(alpha, foreground, background):
fore = np.zeros(foreground.shape, dtype=foreground.dtype)
fore = cv2.multiply(alpha, foreground, fore, 1 / 255.0)
alphaPrime = np.ones(alpha.shape, dtype=alpha.dtype) * 255 - alpha
back = np.zeros(background.shape, dtype=background.dtype)
back = cv2.multiply(alphaPrime, background, back, 1 / 255.0)
outImage = cv2.add(fore, back)
return outImage
def get_angle(p1, p2):
if p2[1] <= p1[1]:
y = np.abs(p1[1] - p2[1])
else:
y = p1[1] - p2[1]
x = np.abs(p1[0] - p2[0])
return np.rad2deg(np.arctan2(y, x))
## Test with face rotation changing degrees
angle_deg = 0
center_x = int(im.shape[1]/2)
center_y = int(im.shape[0]/2)
M = cv2.getRotationMatrix2D((center_x, center_y), angle_deg, 1.0)
im = cv2.warpAffine(im, M, (im.shape[1], im.shape[0]))
## Left Eye Center
left_eye = ((np.asarray(points[37]) + np.asarray(points[38]) + np.asarray(points[40]) + np.asarray(
points[41])) / 4).astype(int)
## Right Eye Center
right_eye = ((np.asarray(points[43]) + np.asarray(points[44]) + np.asarray(points[46]) + np.asarray(
points[47])) / 4).astype(int)
## Draw Eye's centers points in red color
clone_im = im.copy()
## Draw landmarks points in green color
for point in points:
cv2.circle(clone_im, point, 1, (0, 255, 0), -1)
cv2.circle(clone_im, tuple(left_eye), 2, (0, 0, 255), -1)
cv2.circle(clone_im, tuple(right_eye), 2, (0, 0, 255), -1)
clone_im = cv2.cvtColor(clone_im,cv2.COLOR_BGR2RGB)
plt.imshow(clone_im)
## Load glasses image with alpha channel
glasses = cv2.imread("lentes/lente_alpha_1.png", cv2.IMREAD_UNCHANGED)
left_eyeglass = np.asarray([200, 115])
right_eyeglass = np.asarray([615, 115])
## Draw center of eyeglass per eye
clone_glasses = glasses.copy()
cv2.circle(clone_glasses, tuple(left_eyeglass), 2, (0, 0, 255), -1)
cv2.circle(clone_glasses, tuple(right_eyeglass), 2, (0, 0, 255), -1)
clone_glasses = cv2.cvtColor(clone_glasses,cv2.COLOR_BGR2RGB)
plt.imshow(clone_glasses)
## Angle Between eyes
# Know this angle, allow to rotate the glasses image if the eyes are not aligned horizontally
# and then rotate the glasses image to adjust to face angle
rotation_angle = round(get_angle(left_eye, right_eye), 3)
print("[INFO] Rotation Angle: {}°".format(rotation_angle))
## Get the euclidean distance between centers of eyes
# This allow to properly scale the glasses size, that depend of face size
inter_eye_distance = np.linalg.norm(left_eye - right_eye)
print("[INFO] Inter Eyes Distance: {}".format(round(inter_eye_distance, 3)))
## Get the euclidean distance between eyeglasses centers
inter_lens_distance = np.linalg.norm(left_eyeglass - right_eyeglass)
print("[INFO] Inter Eyeglasses Distance: {}".format(round(inter_lens_distance, 3)))
## Compute the ratio and scale the glasses image
ratio = inter_eye_distance / inter_lens_distance
print("[INFO] Scale Factor: {}".format(round(ratio, 3)))
w_final = int(glasses.shape[1] * ratio)
h_final = int(glasses.shape[0] * ratio)
resized_glasses = cv2.resize(glasses, (w_final, h_final), interpolation=cv2.INTER_AREA)
## Get center of eyeglasses in the scaled size
left_lens_resized = (left_eyeglass * ratio).astype(int)
right_lens_resized = (right_eyeglass * ratio).astype(int)
clone_resized_glasses = resized_glasses.copy()
cv2.circle(clone_resized_glasses, tuple(left_lens_resized), 2, (0, 0, 255), -1)
cv2.circle(clone_resized_glasses, tuple(right_lens_resized), 2, (0, 0, 255), -1)
clone_resized_glasses = cv2.cvtColor(clone_resized_glasses,cv2.COLOR_BGR2RGB)
plt.imshow(clone_resized_glasses)
### Create canvas to overlap glasses over face
## Create canvas for glasses
canvas = np.zeros((im.shape[0], im.shape[1], 4), dtype=im.dtype)
x = left_eye[0] - left_lens_resized[0]
y = left_eye[1] - left_lens_resized[1]
w = resized_glasses.shape[1]
h = resized_glasses.shape[0]
## "Paste" the glasses image on the canvas and rotate the canvas to adjuts to the eyes angle
canvas[y:y + h, x:x + w, :] = resized_glasses
M = cv2.getRotationMatrix2D(tuple(left_eye), rotation_angle, 1.0)
rotated_canvas = cv2.warpAffine(canvas, M, (canvas.shape[1], canvas.shape[0]))
rotated_canvas_rbg = cv2.cvtColor(rotated_canvas,cv2.COLOR_BGR2RGB)
plt.imshow(rotated_canvas_rbg)
## Separate the mask from the bgra rotated_canvas
b, g, r, a = cv2.split(rotated_canvas)
bgr_rotated_canvas = cv2.merge([b, g, r])
mask_rotated_canvas = cv2.merge([a, a, a])
# Blurring face mask to alpha blend to hide seams
# In this case the blurring is minimum, mainly to smooth uneven edges
# Also, I omit the step of erode the mask, because the edges between
# the face and the glasses in reality are not "seamless".
maskHeight, maskWidth = mask_rotated_canvas.shape[0:2]
maskSmall = cv2.resize(mask_rotated_canvas, (400, int(maskHeight * 400.0 / maskWidth)))
maskSmall = cv2.GaussianBlur(maskSmall, (3, 3), 0, 0)
mask = cv2.resize(maskSmall, (maskWidth, maskHeight))
bgr = cv2.bitwise_and(bgr_rotated_canvas, bgr_rotated_canvas, mask=a)
final = alphaBlend(mask, bgr, im)
final_image = np.hstack((im, final))
final_image = cv2.cvtColor(final_image,cv2.COLOR_BGR2RGB)
plt.imshow(final_image)
import random
lipstick_colors = {"vamptastic_plum": (97, 45, 130),
"red_dahlia": (51, 30, 136),
"flamenco_red": (42, 31, 192),
"chery_red": (63, 45, 222),
"caramel_nude": (120, 131, 201),
"mango_tango": (103, 92, 223),
"neon_red": (79, 32, 223),
"electric_orchid": (139, 64, 243),
"forbbiden_fuchsia": (105, 39, 184),
"sweet_marsala": (93, 67, 164)}
def getLipsMask(size, lips):
# Find Convex hull of all points
hullIndex = cv2.convexHull(np.array(lips), returnPoints=False)
# Convert hull index to list of points
hullInt = []
for hIndex in hullIndex:
hullInt.append(lips[hIndex[0]])
# Create mask such that convex hull is white
mask = np.zeros((size[0], size[1], 3), dtype=np.uint8)
cv2.fillConvexPoly(mask, np.int32(hullInt), (255, 255, 255))
return mask
def apply_color_to_mask(mask):
# Get random lipstick color
color_name, color = random.choice(list(lipstick_colors.items()))
print("[INFO] Color Name: {}".format(color_name))
b, g, r = cv2.split(mask)
b = np.where(b > 0, color[0], 0).astype('uint8')
g = np.where(g > 0, color[1], 0).astype('uint8')
r = np.where(r > 0, color[2], 0).astype('uint8')
return cv2.merge((b, g, r)), color_name
## Select points of lips and create "lips mask"
lips = [points[x] for x in range(48, 68)]
mouth = [points[x] for x in range(60, 68)]
clone_lips = im.copy()
for point in lips:
cv2.circle(clone_lips, point, 1, (0, 255, 0), -1)
for point in mouth:
cv2.circle(clone_lips, point, 1, (0, 0, 255), -1)
clone_lips = cv2.cvtColor(clone_lips,cv2.COLOR_BGR2RGB)
plt.imshow(clone_lips)
contours = [np.asarray(lips, dtype=np.int32)]
(x, y, w, h) = cv2.boundingRect(contours[0])
center = (int(x+w/2), int(y+h/2))
mask = getLipsMask(im.shape, lips)
mouth_mask = getLipsMask(im.shape, mouth)
mouth_mask = cv2.bitwise_not(mouth_mask)
#mask = cv2.bitwise_and(mask, mask, mask=mouth_mask[:, :, 0])
## Dilate lips mask to include some skin around the mouth
maskHeight, maskWidth = mask.shape[0:2]
maskSmall = cv2.resize(mask, (600, int(maskHeight * 600.0 / maskWidth)))
maskSmall = cv2.dilate(maskSmall, (3, 3))
maskSmall = cv2.GaussianBlur(maskSmall, (5, 5), 0, 0)
mask = cv2.resize(maskSmall, (maskWidth, maskHeight))
mask_rgb = cv2.cvtColor(mask,cv2.COLOR_BGR2RGB)
plt.imshow(mask_rgb)
## Apply color to mask
color_mask, color_name = apply_color_to_mask(mask)
color_mask_rgb = cv2.cvtColor(color_mask,cv2.COLOR_BGR2RGB)
plt.imshow(color_mask_rgb)
## Seamless cloning the mask with the lips. In this case MIXED_CLONE allow to maintain the lips texture
masked_lips = cv2.bitwise_and(im, im, mask=mask[:, :, 0])
output = cv2.seamlessClone(masked_lips, color_mask, mask[:, :, 0], center, cv2.MIXED_CLONE)
output_rgb = cv2.cvtColor(output,cv2.COLOR_BGR2RGB)
plt.imshow(output_rgb)
## Alpha Blending
final = alphaBlend(mask, output, im)
cv2.putText(final, "Lipstick Color: {}".format(color_name), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
final_image = np.hstack((im, final))
final_image = cv2.cvtColor(final_image,cv2.COLOR_BGR2RGB)
plt.imshow(final_image)