forked from PulseFocusPlatform/PulseFocusPlatform
283 lines
9.5 KiB
Python
283 lines
9.5 KiB
Python
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import os
|
|
import cv2
|
|
import math
|
|
import numpy as np
|
|
|
|
HAT_SCALES = {
|
|
'1.png': [3.0, 0.9, .0],
|
|
'2.png': [3.0, 1.3, .5],
|
|
'3.png': [2.2, 1.5, .8],
|
|
'4.png': [2.2, 1.8, .0],
|
|
'5.png': [1.8, 1.2, .0],
|
|
}
|
|
|
|
GLASSES_SCALES = {
|
|
'1.png': [0.65, 2.5],
|
|
'2.png': [0.65, 2.5],
|
|
}
|
|
|
|
BEARD_SCALES = {'1.png': [700, 0.3], '2.png': [220, 0.2]}
|
|
|
|
|
|
def rotate(image, angle):
|
|
"""
|
|
angle is degree, not radian
|
|
"""
|
|
(h, w) = image.shape[:2]
|
|
(cx, cy) = (w / 2, h / 2)
|
|
M = cv2.getRotationMatrix2D((cx, cy), -angle, 1.0)
|
|
cos = np.abs(M[0, 0])
|
|
sin = np.abs(M[0, 1])
|
|
nW = int((h * sin) + (w * cos))
|
|
nH = int((h * cos) + (w * sin))
|
|
M[0, 2] += (nW / 2) - cx
|
|
M[1, 2] += (nH / 2) - cy
|
|
return cv2.warpAffine(image, M, (nW, nH))
|
|
|
|
|
|
def n_rotate_coord(angle, x, y):
|
|
"""
|
|
angle is radian, not degree
|
|
"""
|
|
rotatex = math.cos(angle) * x - math.sin(angle) * y
|
|
rotatey = math.cos(angle) * y + math.sin(angle) * x
|
|
return rotatex, rotatey
|
|
|
|
|
|
def r_rotate_coord(angle, x, y):
|
|
"""
|
|
angle is radian, not degree
|
|
"""
|
|
rotatex = math.cos(angle) * x + math.sin(angle) * y
|
|
rotatey = math.cos(angle) * y - math.sin(angle) * x
|
|
return rotatex, rotatey
|
|
|
|
|
|
def add_beard(person, kypoint, element_path):
|
|
beard_file_name = os.path.split(element_path)[1]
|
|
# element_len: top width of beard
|
|
# loc_offset_scale: scale relative to nose
|
|
element_len, loc_offset_scale = BEARD_SCALES[beard_file_name][:]
|
|
|
|
x1, y1, x2, y2, x3, y3, x4, y4, x5, y5 = kypoint[:]
|
|
mouth_len = np.sqrt(np.square(np.abs(y4 - y5)) + np.square(x4 - x5))
|
|
|
|
element = cv2.imread(element_path)
|
|
h, w, _ = element.shape
|
|
resize_scale = mouth_len / float(element_len)
|
|
h, w = round(h * resize_scale + 0.5), round(w * resize_scale + 0.5)
|
|
resized_element = cv2.resize(element, (w, h))
|
|
resized_ele_h, resized_ele_w, _ = resized_element.shape
|
|
|
|
# First find the keypoint of mouth in front face
|
|
m_center_x = (x4 + x5) / 2.
|
|
m_center_y = (y4 + y5) / 2.
|
|
# cal degree only according mouth coordinates
|
|
degree = np.arccos((x4 - x5) / mouth_len)
|
|
|
|
# coordinate of RoI in front face
|
|
half_w = int(resized_ele_w // 2)
|
|
scale = loc_offset_scale
|
|
roi_top_left_y = int(y3 + (((y5 + y4) // 2) - y3) * scale)
|
|
roi_top_left_x = int(x3 - half_w)
|
|
roi_top_right_y = roi_top_left_y
|
|
roi_top_right_x = int(x3 + half_w)
|
|
roi_bottom_left_y = roi_top_left_y + resized_ele_h
|
|
roi_bottom_left_x = roi_top_left_x
|
|
roi_bottom_right_y = roi_bottom_left_y
|
|
roi_bottom_right_x = roi_top_right_x
|
|
|
|
r_x11, r_y11 = roi_top_left_x - x3, roi_top_left_y - y3
|
|
r_x12, r_y12 = roi_top_right_x - x3, roi_top_right_y - y3
|
|
r_x21, r_y21 = roi_bottom_left_x - x3, roi_bottom_left_y - y3
|
|
r_x22, r_y22 = roi_bottom_right_x - x3, roi_bottom_right_y - y3
|
|
|
|
# coordinate of RoI in raw face
|
|
if m_center_x > x3:
|
|
x11, y11 = r_rotate_coord(degree, r_x11, r_y11)
|
|
x12, y12 = r_rotate_coord(degree, r_x12, r_y12)
|
|
x21, y21 = r_rotate_coord(degree, r_x21, r_y21)
|
|
x22, y22 = r_rotate_coord(degree, r_x22, r_y22)
|
|
else:
|
|
x11, y11 = n_rotate_coord(degree, r_x11, r_y11)
|
|
x12, y12 = n_rotate_coord(degree, r_x12, r_y12)
|
|
x21, y21 = n_rotate_coord(degree, r_x21, r_y21)
|
|
x22, y22 = n_rotate_coord(degree, r_x22, r_y22)
|
|
|
|
x11, y11 = x11 + x3, y11 + y3
|
|
x12, y12 = x12 + x3, y12 + y3
|
|
x21, y21 = x21 + x3, y21 + y3
|
|
x22, y22 = x22 + x3, y22 + y3
|
|
|
|
min_x = int(min(x11, x12, x21, x22))
|
|
max_x = int(max(x11, x12, x21, x22))
|
|
min_y = int(min(y11, y12, y21, y22))
|
|
max_y = int(max(y11, y12, y21, y22))
|
|
|
|
angle = np.degrees(degree)
|
|
|
|
if y4 < y5:
|
|
angle = -angle
|
|
|
|
rotated_element = rotate(resized_element, angle)
|
|
|
|
rotated_ele_h, rotated_ele_w, _ = rotated_element.shape
|
|
|
|
max_x = min_x + int(rotated_ele_w)
|
|
max_y = min_y + int(rotated_ele_h)
|
|
|
|
e2gray = cv2.cvtColor(rotated_element, cv2.COLOR_BGR2GRAY)
|
|
ret, mask = cv2.threshold(e2gray, 238, 255, cv2.THRESH_BINARY_INV)
|
|
mask_inv = cv2.bitwise_not(mask)
|
|
|
|
roi = person[min_y:max_y, min_x:max_x]
|
|
person_bg = cv2.bitwise_and(roi, roi, mask=mask)
|
|
element_fg = cv2.bitwise_and(
|
|
rotated_element, rotated_element, mask=mask_inv)
|
|
|
|
dst = cv2.add(person_bg, element_fg)
|
|
person[min_y:max_y, min_x:max_x] = dst
|
|
return person
|
|
|
|
|
|
def add_hat(person, kypoint, element_path):
|
|
x1, y1, x2, y2, x3, y3, x4, y4, x5, y5 = kypoint[:]
|
|
eye_len = np.sqrt(np.square(np.abs(y1 - y2)) + np.square(np.abs(x1 - x2)))
|
|
# cal degree only according eye coordinates
|
|
degree = np.arccos((x2 - x1) / eye_len)
|
|
|
|
angle = np.degrees(degree)
|
|
if y2 < y1:
|
|
angle = -angle
|
|
|
|
element = cv2.imread(element_path)
|
|
hat_file_name = os.path.split(element_path)[1]
|
|
# head_scale: size scale of hat
|
|
# high_scale: height scale above the eyes
|
|
# offect_scale: width offect of hat in face
|
|
head_scale, high_scale, offect_scale = HAT_SCALES[hat_file_name][:]
|
|
h, w, _ = element.shape
|
|
|
|
element_len = w
|
|
resize_scale = eye_len * head_scale / float(w)
|
|
h, w = round(h * resize_scale + 0.5), round(w * resize_scale + 0.5)
|
|
resized_element = cv2.resize(element, (w, h))
|
|
resized_ele_h, resized_ele_w, _ = resized_element.shape
|
|
|
|
m_center_x = (x1 + x2) / 2.
|
|
m_center_y = (y1 + y2) / 2.
|
|
|
|
head_len = int(eye_len * high_scale)
|
|
|
|
if angle > 0:
|
|
head_center_x = int(m_center_x + head_len * math.sin(degree))
|
|
head_center_y = int(m_center_y - head_len * math.cos(degree))
|
|
else:
|
|
head_center_x = int(m_center_x + head_len * math.sin(degree))
|
|
head_center_y = int(m_center_y - head_len * math.cos(degree))
|
|
|
|
rotated_element = rotate(resized_element, angle)
|
|
|
|
rotated_ele_h, rotated_ele_w, _ = rotated_element.shape
|
|
max_x = int(head_center_x + (resized_ele_w // 2) * math.cos(degree)) + int(
|
|
angle * head_scale) + int(eye_len * offect_scale)
|
|
min_y = int(head_center_y - (resized_ele_w // 2) * math.cos(degree))
|
|
|
|
pad_ele_x0 = 0 if (max_x - int(rotated_ele_w)) > 0 else -(
|
|
max_x - int(rotated_ele_w))
|
|
pad_ele_y0 = 0 if min_y > 0 else -(min_y)
|
|
|
|
min_x = int(max(max_x - int(rotated_ele_w), 0))
|
|
min_y = int(max(min_y, 0))
|
|
max_y = min_y + int(rotated_ele_h)
|
|
|
|
pad_y1 = max(max_y - int(person.shape[0]), 0)
|
|
pad_x1 = max(max_x - int(person.shape[1]), 0)
|
|
pad_w = pad_ele_x0 + pad_x1
|
|
pad_h = pad_ele_y0 + pad_y1
|
|
max_x += pad_w
|
|
|
|
pad_person = np.zeros(
|
|
(person.shape[0] + pad_h, person.shape[1] + pad_w, 3)).astype(np.uint8)
|
|
|
|
pad_person[pad_ele_y0:pad_ele_y0 + person.shape[0], pad_ele_x0:pad_ele_x0 +
|
|
person.shape[1], :] = person
|
|
|
|
e2gray = cv2.cvtColor(rotated_element, cv2.COLOR_BGR2GRAY)
|
|
ret, mask = cv2.threshold(e2gray, 1, 255, cv2.THRESH_BINARY_INV)
|
|
mask_inv = cv2.bitwise_not(mask)
|
|
|
|
roi = pad_person[min_y:max_y, min_x:max_x]
|
|
|
|
person_bg = cv2.bitwise_and(roi, roi, mask=mask)
|
|
element_fg = cv2.bitwise_and(
|
|
rotated_element, rotated_element, mask=mask_inv)
|
|
|
|
dst = cv2.add(person_bg, element_fg)
|
|
pad_person[min_y:max_y, min_x:max_x] = dst
|
|
|
|
return pad_person, pad_ele_x0, pad_x1, pad_ele_y0, pad_y1, min_x, min_y, max_x, max_y
|
|
|
|
|
|
def add_glasses(person, kypoint, element_path):
|
|
x1, y1, x2, y2, x3, y3, x4, y4, x5, y5 = kypoint[:]
|
|
eye_len = np.sqrt(np.square(np.abs(y1 - y2)) + np.square(np.abs(x1 - x2)))
|
|
# cal degree only according eye coordinates
|
|
degree = np.arccos((x2 - x1) / eye_len)
|
|
angle = np.degrees(degree)
|
|
if y2 < y1:
|
|
angle = -angle
|
|
|
|
element = cv2.imread(element_path)
|
|
glasses_file_name = os.path.split(element_path)[1]
|
|
# height_scale: height scale above the eyes
|
|
# glasses_scale: size ratio of glasses
|
|
height_scale, glasses_scale = GLASSES_SCALES[glasses_file_name][:]
|
|
h, w, _ = element.shape
|
|
|
|
element_len = w
|
|
resize_scale = eye_len * glasses_scale / float(element_len)
|
|
h, w = round(h * resize_scale + 0.5), round(w * resize_scale + 0.5)
|
|
resized_element = cv2.resize(element, (w, h))
|
|
resized_ele_h, resized_ele_w, _ = resized_element.shape
|
|
|
|
rotated_element = rotate(resized_element, angle)
|
|
|
|
rotated_ele_h, rotated_ele_w, _ = rotated_element.shape
|
|
|
|
eye_center_x = (x1 + x2) / 2.
|
|
eye_center_y = (y1 + y2) / 2.
|
|
|
|
min_x = int(eye_center_x) - int(rotated_ele_w * 0.5) + int(
|
|
angle * glasses_scale * person.shape[1] / 2000)
|
|
min_y = int(eye_center_y) - int(rotated_ele_h * height_scale)
|
|
max_x = min_x + rotated_ele_w
|
|
max_y = min_y + rotated_ele_h
|
|
|
|
e2gray = cv2.cvtColor(rotated_element, cv2.COLOR_BGR2GRAY)
|
|
ret, mask = cv2.threshold(e2gray, 1, 255, cv2.THRESH_BINARY_INV)
|
|
mask_inv = cv2.bitwise_not(mask)
|
|
|
|
roi = person[min_y:max_y, min_x:max_x]
|
|
|
|
person_bg = cv2.bitwise_and(roi, roi, mask=mask)
|
|
element_fg = cv2.bitwise_and(
|
|
rotated_element, rotated_element, mask=mask_inv)
|
|
|
|
dst = cv2.add(person_bg, element_fg)
|
|
person[min_y:max_y, min_x:max_x] = dst
|
|
return person
|