!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
Hit:1 http://deb.debian.org/debian buster InRelease
Hit:2 http://deb.debian.org/debian buster-updates InRelease
Hit:3 http://security.debian.org/debian-security buster/updates InRelease
11 packages can be upgraded. Run 'apt list --upgradable' to see them.
build-essential is already the newest version (12.6).
cmake is already the newest version (3.13.4-1).
ffmpeg is already the newest version (7:4.1.6-1~deb10u1).
libsm6 is already the newest version (2:1.2.3-1).
libxext6 is already the newest version (2:1.3.3-1+b2).
0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.
Collecting opencv-contrib-python==4.1.0.25
Downloading opencv_contrib_python-4.1.0.25-cp37-cp37m-manylinux1_x86_64.whl (32.6 MB)
|████████████████████████████████| 32.6 MB 20.2 MB/s
Requirement already satisfied: numpy>=1.14.5 in /shared-libs/python3.7/py/lib/python3.7/site-packages (from opencv-contrib-python==4.1.0.25) (1.19.5)
Installing collected packages: opencv-contrib-python
Successfully installed opencv-contrib-python-4.1.0.25
Requirement already satisfied: dlib==19.21.1 in /root/venv/lib/python3.7/site-packages (19.21.1)
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)
[(216, 375), (217, 411), (224, 446), (233, 480), (242, 515), (258, 547), (283, 574), (313, 594), (350, 600), (390, 595), (430, 579), (464, 557), (488, 524), (501, 487), (508, 447), (513, 408), (514, 369), (223, 343), (241, 326), (268, 325), (293, 333), (317, 346), (362, 345), (389, 331), (418, 321), (449, 321), (474, 336), (338, 374), (337, 397), (335, 419), (333, 442), (315, 465), (326, 468), (337, 471), (351, 468), (364, 465), (252, 378), (268, 368), (289, 369), (307, 385), (287, 390), (266, 389), (382, 384), (401, 369), (423, 367), (441, 375), (426, 387), (404, 389), (293, 517), (310, 503), (327, 496), (340, 500), (353, 496), (376, 502), (401, 514), (377, 528), (356, 535), (341, 536), (326, 535), (310, 530), (303, 516), (327, 512), (340, 512), (354, 511), (391, 513), (354, 512), (341, 513), (327, 513)]
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))
[INFO] Rotation Angle: 0.421°
## 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)))
[INFO] Inter Eyes Distance: 136.004
[INFO] Inter Eyeglasses Distance: 415.0
## 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)
[INFO] Scale Factor: 0.328
### 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)
[INFO] Color Name: sweet_marsala
## 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)