|
import os |
|
import json |
|
|
|
from glibvision.numpy_utils import bulge_polygon |
|
import numpy as np |
|
import math |
|
USE_CACHE = True |
|
|
|
|
|
|
|
|
|
|
|
TOP_LIP = "top_lip" |
|
BOTTOM_LIP = "bottom_lip" |
|
PARTS_CHIN ="chin" |
|
PARTS_LEFT_EYEBROW ="left_eyebrow" |
|
PARTS_RIGHT_EYEBROW ="right_eyebrow" |
|
PARTS_LEFT_EYE ="left_eye" |
|
PARTS_RIGHT_EYE ="right_eye" |
|
|
|
POINTS_TOP_LIP = "top_lip" |
|
POINTS_BOTTOM_LIP = "bottom_lip" |
|
POINTS_CHIN = "chin" |
|
|
|
COLOR_WHITE=(255,255,255) |
|
COLOR_BLACK=(0,0,0) |
|
COLOR_ALPHA=(0,0,0,0) |
|
|
|
DEBUG = False |
|
DEBUG_CHIN = False |
|
face_recognition = None |
|
|
|
def load_image_file(path): |
|
|
|
image = face_recognition.load_image_file(path) |
|
data_path=path+".json" |
|
if USE_CACHE and os.path.exists(data_path): |
|
with open(data_path, "r") as f: |
|
face_landmarks_list = json.loads(f.read()) |
|
else: |
|
face_landmarks_list = image_to_landmarks_list(image) |
|
if USE_CACHE: |
|
json_data = json.dumps(face_landmarks_list) |
|
with open(data_path, "w") as f: |
|
f.write(json_data) |
|
|
|
return image,face_landmarks_list |
|
|
|
def save_landmarks(face_landmarks,out_path): |
|
json_data = json.dumps(face_landmarks) |
|
with open(out_path, "w") as f: |
|
f.write(json_data) |
|
|
|
def load_landmarks(input_path): |
|
with open(input_path, "r") as f: |
|
face_landmarks_list = json.loads(f.read()) |
|
return face_landmarks_list |
|
|
|
|
|
|
|
def image_to_landmarks_list(image): |
|
face_landmarks_list = face_recognition.face_landmarks(image) |
|
return face_landmarks_list |
|
|
|
def fill_polygon(image,face_landmarks_list,key,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
points=get_landmark_points(face_landmarks_list,key) |
|
np_points = np.array(points,dtype=np.int32) |
|
cv2.fillPoly(image, [np_points], fill_color) |
|
cv2.polylines(image, [np_points], isClosed=True, color=line_color, thickness=thickness) |
|
|
|
def fill_lip(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
points1=get_landmark_points(face_landmarks_list,TOP_LIP)[0:7] |
|
points2=get_landmark_points(face_landmarks_list,BOTTOM_LIP)[0:7] |
|
|
|
np_points = np.array(points1+points2[::-1],dtype=np.int32) |
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
if thickness > 0: |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def fill_top(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
points1=get_landmark_points(face_landmarks_list,TOP_LIP)[0:7] |
|
|
|
np_points = np.array(points1,dtype=np.int32) |
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
if thickness > 0: |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def fill_top_lower(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
top_lip_points=get_landmark_points(face_landmarks_list,TOP_LIP) |
|
points1 = [lerp_points(top_lip_points[5],top_lip_points[7],0.7)]+ \ |
|
[mid_points(top_lip_points[7],top_lip_points[8])]+ \ |
|
list(top_lip_points[8:11]) +\ |
|
[mid_points(top_lip_points[10],top_lip_points[11])]+ \ |
|
[lerp_points(top_lip_points[1],top_lip_points[11],0.7)]+\ |
|
[mid_points(top_lip_points[2],top_lip_points[10])]+\ |
|
[mid_points(top_lip_points[3],top_lip_points[9])]+\ |
|
[mid_points(top_lip_points[4],top_lip_points[8])] |
|
|
|
np_points = np.array(points1,dtype=np.int32) |
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
if thickness > 0: |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def get_lip_mask_points(face_landmarks_list): |
|
points1=get_landmark_points(face_landmarks_list,TOP_LIP)[0:7] |
|
points2=get_landmark_points(face_landmarks_list,BOTTOM_LIP)[0:7] |
|
return points1+points2 |
|
|
|
|
|
|
|
from scipy.special import comb |
|
|
|
def bernstein_poly(i, n, t): |
|
""" |
|
n 次ベジェ曲線の i 番目の Bernstein 基底関数を計算する |
|
""" |
|
return comb(n, i) * (t**(n-i)) * (1 - t)**i |
|
|
|
def bezier_curve(points, num_points=100): |
|
""" |
|
与えられた点からベジェ曲線を計算する |
|
""" |
|
nPoints = len(points) |
|
xPoints = np.array([p[0] for p in points]) |
|
yPoints = np.array([p[1] for p in points]) |
|
|
|
t = np.linspace(0.0, 1.0, num_points) |
|
|
|
polynomial_array = np.array([bernstein_poly(i, nPoints-1, t) for i in range(0, nPoints)]) |
|
|
|
xvals = np.dot(xPoints, polynomial_array) |
|
yvals = np.dot(yPoints, polynomial_array) |
|
|
|
return np.array(list(zip(xvals, yvals))) |
|
import cv2 |
|
import numpy as np |
|
|
|
|
|
|
|
|
|
|
|
|
|
def fill_eyes(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
points1=get_landmark_points(face_landmarks_list,PARTS_LEFT_EYE) |
|
points2=get_landmark_points(face_landmarks_list,PARTS_RIGHT_EYE) |
|
|
|
for points in [points1,points2]: |
|
|
|
|
|
points = bulge_polygon(points, bulge_factor=0.2) |
|
|
|
np_points = np.array(points,dtype=np.int32) |
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
if thickness > 0: |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
|
|
|
|
|
|
|
|
def fill_face(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
points1=get_landmark_points(face_landmarks_list,PARTS_LEFT_EYEBROW) |
|
points2=get_landmark_points(face_landmarks_list,PARTS_RIGHT_EYEBROW) |
|
points3=get_landmark_points(face_landmarks_list,PARTS_CHIN) |
|
|
|
np_points = np.array(points1+points2+points3[::-1],dtype=np.int32) |
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def fill_face_inside(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
print("not support yet") |
|
return None |
|
points1=get_landmark_points(face_landmarks_list,PARTS_LEFT_EYEBROW) |
|
points2=get_landmark_points(face_landmarks_list,PARTS_RIGHT_EYEBROW) |
|
points3=get_landmark_points(face_landmarks_list,PARTS_CHIN) |
|
points3=get_landmark_points(face_landmarks_list,PARTS_CHIN) |
|
points3=get_landmark_points(face_landmarks_list,PARTS_CHIN) |
|
|
|
np_points = np.array(points1+points2+points3[::-1],dtype=np.int32) |
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def half_pt(point1,point2): |
|
return [sum(x) / 2 for x in zip(point1, point2)] |
|
|
|
|
|
def line_lip(image,face_landmarks_list,key,thickness=1,line_color=(255,255,255)): |
|
points=get_landmark_points(face_landmarks_list,key) |
|
print(len(points)) |
|
|
|
st = [sum(x) / 2 for x in zip(points[0], points[11])] |
|
|
|
|
|
et = [sum(x) / 2 for x in zip(points[6], points[7])] |
|
print(et) |
|
print(points) |
|
np_points = np.array([st]+points[1:6]+[et],dtype=np.int32) |
|
|
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def get_lip_hole_points(face_landmarks_list): |
|
top_points=get_landmark_points(face_landmarks_list,TOP_LIP) |
|
bottom_points=get_landmark_points(face_landmarks_list,BOTTOM_LIP) |
|
return top_points[7:]+bottom_points[7:] |
|
|
|
|
|
def get_lip_hole_top_points(face_landmarks_list): |
|
top_points=get_landmark_points(face_landmarks_list,TOP_LIP) |
|
|
|
return top_points[7:] |
|
|
|
def get_lip_hole_bottom_points(face_landmarks_list): |
|
|
|
bottom_points=get_landmark_points(face_landmarks_list,BOTTOM_LIP) |
|
|
|
return bottom_points[7:][::-1] |
|
|
|
|
|
def get_lip_hole_bottom_half_points(face_landmarks_list): |
|
|
|
bottom_points=get_landmark_points(face_landmarks_list,BOTTOM_LIP) |
|
|
|
st = [sum(x) / 2 for x in zip(bottom_points[7], bottom_points[8])] |
|
et = [sum(x) / 2 for x in zip(bottom_points[10], bottom_points[11])] |
|
points = [st]+bottom_points[8:11]+[et] |
|
|
|
return points[::-1] |
|
|
|
def fill_points(points,image,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
np_points = np.array(points,dtype=np.int32) |
|
|
|
|
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
if thickness>0: |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
def fill_lip_hole_top(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
np_points = np.array(get_lip_hole_top_points(face_landmarks_list),dtype=np.int32) |
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
|
|
|
|
def fill_lip_hole(image,face_landmarks_list,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)): |
|
np_points = np.array(get_lip_hole_points(face_landmarks_list),dtype=np.int32) |
|
|
|
cv2.fillPoly(image, [np_points], fill_color) |
|
cv2.polylines(image, [np_points], isClosed=False, color=line_color, thickness=thickness) |
|
|
|
|
|
|
|
def get_landmark_points(face_landmarks_list,key): |
|
matching_landmark_points = [] |
|
for face_landmarks in face_landmarks_list: |
|
for landmark_name, landmark_points in face_landmarks.items(): |
|
|
|
if landmark_name ==key: |
|
for value in landmark_points: |
|
matching_landmark_points.append([value[0],value[1]]) |
|
return tuple(matching_landmark_points) |
|
|
|
def get_image_size(cv2_image): |
|
return cv2_image.shape[:2] |
|
|
|
def get_top_lip_box(face_landmarks_list,margin = 0): |
|
print(f"get_top_lip_box margin = {margin}") |
|
points = get_landmark_points(face_landmarks_list,TOP_LIP) |
|
box= points_to_box(points) |
|
if margin>0: |
|
return ((box[0][0] - margin,box[0][1] - margin),(box[1][0] + margin, box[1][1] + margin)) |
|
else: |
|
return box |
|
|
|
def get_points_box(face_landmarks_list,key,margin = 0): |
|
print(f"margin = {margin}") |
|
points = get_landmark_points(face_landmarks_list,key) |
|
box= points_to_box(points) |
|
if margin>0: |
|
return ((box[0][0] - margin,box[0][1] - margin),(box[1][0] + margin, box[1][1] + margin)) |
|
else: |
|
return box |
|
|
|
|
|
|
|
def create_moved_image(image,src_points,dst_points,force_size=None): |
|
|
|
|
|
|
|
src_pts=np.array([src_points],dtype=np.float32) |
|
dst_pts=np.array([dst_points],dtype=np.float32) |
|
|
|
return warp_with_auto_resize(image, src_pts, dst_pts,cv2.BORDER_REPLICATE,force_size) |
|
|
|
|
|
""" |
|
1 2 3 4 5 |
|
0 6 |
|
11 10 9 8 7 |
|
""" |
|
def get_top_lip_align_points(face_landmarks_list): |
|
landmark=get_landmark_points(face_landmarks_list,TOP_LIP) |
|
index_center = 3 |
|
index_right= 0 |
|
|
|
|
|
index_left = 6 |
|
|
|
|
|
x1 = landmark[index_right][0] |
|
y1 = landmark[index_right][1] |
|
|
|
x2 = landmark[index_left][0] |
|
y2 = landmark[index_left][1] |
|
|
|
|
|
|
|
|
|
|
|
|
|
cx = (x1+x2)/2 |
|
cy = (y1+y2)/2 |
|
|
|
|
|
diffx=(landmark[index_center][0]-cx) |
|
diffy=(landmark[index_center][1]-cy) |
|
|
|
|
|
|
|
|
|
return ((int(x1+diffx),int(y1+diffy)),(int(x2+diffx),int(y2+diffy)),(x1,y1),(x2,y2)) |
|
|
|
|
|
def calculate_new_point(start_point, distance, angle): |
|
x1, y1 = start_point |
|
angle_rad = math.radians(angle) |
|
|
|
|
|
new_x = x1 + distance * math.cos(angle_rad) |
|
new_y = y1 + distance * math.sin(angle_rad) |
|
|
|
return (new_x, new_y) |
|
|
|
def calculate_clockwise_angle(point1, point2): |
|
x1, y1 = point1 |
|
x2, y2 = point2 |
|
|
|
|
|
angle_rad = math.atan2(y2 - y1, x2 - x1) |
|
|
|
|
|
if angle_rad < 0: |
|
angle_rad += 2 * math.pi |
|
|
|
|
|
angle_deg = math.degrees(angle_rad) |
|
|
|
return angle_deg |
|
|
|
def get_bottom_lip_align_points(landmarks_list): |
|
points = get_landmark_points(landmarks_list,POINTS_BOTTOM_LIP) |
|
return (points[0],points[3],points[6],points[9]) |
|
|
|
def get_bottom_lip_width_height(landmarks_list): |
|
points = get_landmark_points(landmarks_list,POINTS_BOTTOM_LIP) |
|
return (points[0][0] -points[6][0],points[3][1] -points[9][1]) |
|
|
|
|
|
def crop_image(image,x1,y1,x2,y2): |
|
return image[y1:y2, x1:x2] |
|
|
|
def crop_cv2_image_by_box(image,box): |
|
return crop_image_by_box(image,box) |
|
|
|
def crop_image_by_box(image,box): |
|
print(f"crop_cv2_image_by_box yy2 xx2 {box[0][1]}:{box[1][1]},{box[0][0]}:{box[1][0]}") |
|
return image[box[0][1]:box[1][1], box[0][0]:box[1][0]] |
|
|
|
def get_top_lip_datas(img,margin=4): |
|
landmarks_list=image_to_landmarks_list(img) |
|
box = get_top_lip_box(landmarks_list,margin) |
|
cropped_img = crop_cv2_image_by_box(img,box) |
|
points = get_top_lip_points(landmarks_list) |
|
return landmarks_list,cropped_img,points,box |
|
|
|
def get_bottom_lip_datas(img,margin=4): |
|
landmarks_list=image_to_landmarks_list(img) |
|
box = get_points_box(landmarks_list,POINTS_BOTTOM_LIP,margin) |
|
cropped_img = crop_cv2_image_by_box(img,box) |
|
points = get_bottom_lip_align_points(landmarks_list) |
|
return landmarks_list,cropped_img,points,box |
|
|
|
def offset_points(points,offset): |
|
new_points = [] |
|
for point in points: |
|
new_points.append((point[0]-offset[0],point[1]-offset[1])) |
|
return new_points |
|
|
|
|
|
def points_to_box(points): |
|
min_x = 0 |
|
min_y = 0 |
|
min_x = float('inf') |
|
min_y = float('inf') |
|
max_x= 0 |
|
max_y= 0 |
|
for point in points: |
|
if point[0]>max_x: |
|
max_x=int(point[0]) |
|
if point[1]>max_y: |
|
max_y=int(point[1]) |
|
if point[0]<min_x: |
|
min_x=int(point[0]) |
|
if point[1]<min_y: |
|
min_y=int(point[1]) |
|
return ((min_x,min_y),(max_x,max_y)) |
|
|
|
|
|
|
|
import cv2 |
|
import numpy as np |
|
|
|
def warp_with_auto_resize(img, src_pts, dst_pts, borderMode=cv2.BORDER_TRANSPARENT, force_size=None): |
|
""" |
|
画像を WRAP 変換し、はみ出した場合は自動的にサイズを調整します。 |
|
|
|
Args: |
|
img: 変換対象の画像 (numpy array) |
|
src_pts: 変換元の四角形の頂点 (numpy array) |
|
dst_pts: 変換先の四角形の頂点 (numpy array) |
|
|
|
Returns: |
|
変換後の画像 (numpy array) |
|
""" |
|
|
|
mat = cv2.getPerspectiveTransform(src_pts, dst_pts) |
|
|
|
|
|
h, w = img.shape[:2] |
|
corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]) |
|
warped_corners = cv2.perspectiveTransform(corners.reshape(-1, 1, 2), mat).reshape(-1, 2) |
|
|
|
|
|
min_x = np.min(warped_corners[:, 0]) |
|
min_y = np.min(warped_corners[:, 1]) |
|
max_x = np.max(warped_corners[:, 0]) |
|
max_y = np.max(warped_corners[:, 1]) |
|
new_w, new_h = int(max_x - min_x), int(max_y - min_y) |
|
|
|
|
|
mat[0, 2] += -min_x |
|
mat[1, 2] += -min_y |
|
|
|
if force_size: |
|
new_w = force_size[0] |
|
new_h = force_size[1] |
|
|
|
warped_img = cv2.warpPerspective(img, mat, (new_w, new_h), flags=cv2.INTER_LANCZOS4, borderMode=borderMode) |
|
|
|
return warped_img |
|
|
|
def warp_with_auto_resize1(img, src_pts, dst_pts,borderMode= cv2.BORDER_TRANSPARENT,force_size=None): |
|
""" |
|
画像を WRAP 変換し、はみ出した場合は自動的にサイズを調整します。 |
|
|
|
Args: |
|
img: 変換対象の画像 (numpy array) |
|
src_pts: 変換元の四角形の頂点 (numpy array) |
|
dst_pts: 変換先の四角形の頂点 (numpy array) |
|
|
|
Returns: |
|
変換後の画像 (numpy array) |
|
""" |
|
|
|
mat = cv2.getPerspectiveTransform(src_pts, dst_pts) |
|
|
|
|
|
h, w = img.shape[:2] |
|
|
|
corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]) |
|
warped_corners = cv2.perspectiveTransform(corners.reshape(-1, 1, 2), mat).reshape(-1, 2) |
|
|
|
|
|
min_x, min_y = np.min(warped_corners, axis=0) |
|
|
|
max_x, max_y = np.max(warped_corners, axis=0) |
|
new_w, new_h = int(max_x - min_x), int(max_y - min_y) |
|
|
|
|
|
|
|
mat[0, 2] += -min_x |
|
mat[1, 2] += -min_y |
|
|
|
|
|
|
|
if force_size: |
|
new_w = force_size[0] |
|
new_h = force_size[1] |
|
|
|
warped_img = cv2.warpPerspective(img, mat, (new_w, new_h),flags=cv2.INTER_LANCZOS4, borderMode=borderMode) |
|
|
|
return warped_img |
|
|
|
def get_channel(np_array): |
|
return np_array.shape[2] if np_array.ndim == 3 else 1 |
|
|
|
def print_numpy(np_array,key=""): |
|
channel = get_channel(np_array) |
|
print(f"{key} shape = {np_array.shape} channel = {channel} ndim = {np_array.ndim} size = {np_array.size}") |
|
|
|
def create_color_image(img,color=(255,255,255)): |
|
mask = np.zeros_like(img) |
|
h, w = img.shape[:2] |
|
cv2.rectangle(mask, (0, 0), (w, h), color, -1) |
|
return mask |
|
|
|
def create_mask(img,color=(255,255,255)): |
|
mask = np.zeros_like(img) |
|
h, w = img.shape[:2] |
|
cv2.rectangle(mask, (0, 0), (w, h), color, -1) |
|
return mask |
|
|
|
def create_rgba(width,height): |
|
return np.zeros((height, width, 4), dtype=np.uint8) |
|
|
|
def create_rgb(width,height): |
|
return np.zeros((height, width, 3), dtype=np.uint8) |
|
|
|
def create_gray(width,height): |
|
return np.zeros((height, width), dtype=np.uint8) |
|
|
|
|
|
def copy_image(img1, img2, x, y): |
|
""" |
|
Paste img2 onto img1 at position (x, y). |
|
If img2 extends beyond the bounds of img1, only the overlapping part is pasted. |
|
|
|
Parameters: |
|
img1 (numpy.ndarray): The base image to modify (H, W, C). |
|
img2 (numpy.ndarray): The image to paste onto img1 (h, w, C). |
|
x (int): The x-coordinate where img2 will be placed. |
|
y (int): The y-coordinate where img2 will be placed. |
|
|
|
Raises: |
|
TypeError: If img1 or img2 are not NumPy arrays. |
|
ValueError: If x or y are out of bounds of img1. |
|
ValueError: If img1 and img2 do not have the same number of channels or are not 3-dimensional arrays. |
|
""" |
|
|
|
if not isinstance(img1, np.ndarray) or not isinstance(img2, np.ndarray): |
|
raise TypeError("img1 and img2 must be NumPy arrays.") |
|
|
|
|
|
if img1.ndim != 3 or img2.ndim != 3 or img1.shape[2] != img2.shape[2]: |
|
raise ValueError("img1 and img2 must have the same number of channels and be 3-dimensional arrays.") |
|
|
|
|
|
max_y, max_x, _ = img1.shape |
|
if not (0 <= y < max_y and 0 <= x < max_x): |
|
raise ValueError(f"x ({x}) and y ({y}) must be within the bounds of img1 ({max_x}, {max_y}).") |
|
|
|
|
|
h = min(img2.shape[0], max_y - y) |
|
w = min(img2.shape[1], max_x - x) |
|
|
|
|
|
img1[y:y+h, x:x+w] = img2[:h, :w] |
|
|
|
|
|
def copy_color(img1,x,y,x2,y2,color): |
|
color_img = np.full((y2-y, x2-x, 4), color, dtype=np.uint8) |
|
img1[y:y2, x:x2] = color_img |
|
|
|
|
|
|
|
def multiply_point(point,multiply): |
|
return int(point[0]*multiply),int(point[1]*multiply) |
|
|
|
def get_resized_top_pos(points,multiply=0.5): |
|
diff_left = multiply_point((points[0][0]-points[2][0],points[0][1]-points[2][1]),multiply) |
|
diff_right = multiply_point((points[1][0]-points[3][0],points[1][1]-points[3][1]),multiply) |
|
return (diff_right,diff_left) |
|
|
|
|
|
def get_alpha_image(base_image,landmarks_list,key,margin = 0,dilation_size = 2,gaussian_size = 2): |
|
box = get_points_box(landmarks_list,key,margin) |
|
|
|
cropped_img = crop_cv2_image_by_box(base_image,box) |
|
|
|
if cropped_img.shape[2] == 3: |
|
image_rgba = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2BGRA) |
|
mask = np.zeros(cropped_img.shape[:2], dtype="uint8") |
|
else: |
|
print("already alpha skipped") |
|
image_rgba = cropped_img |
|
mask = np.zeros(cropped_img.shape[:2], dtype="uint8") |
|
|
|
|
|
global_points = get_landmark_points(landmarks_list,key) |
|
|
|
local_points = offset_points(global_points,box[0]) |
|
print(local_points) |
|
|
|
np_points = np.array(local_points,dtype=np.int32) |
|
|
|
cv2.fillPoly(mask, [np_points], 255) |
|
|
|
kernel = np.ones((dilation_size, dilation_size), np.uint8) |
|
|
|
dilated_mask = cv2.dilate(mask, kernel, iterations=1) |
|
|
|
|
|
|
|
if gaussian_size > 0: |
|
smooth_mask = cv2.GaussianBlur(dilated_mask, (0,0 ), sigmaX=gaussian_size, sigmaY=gaussian_size) |
|
expanded_mask = np.expand_dims(smooth_mask, axis=-1) |
|
else: |
|
expanded_mask = np.expand_dims(dilated_mask, axis=-1) |
|
|
|
|
|
|
|
|
|
|
|
image_rgba[..., 3] = expanded_mask[..., 0] |
|
|
|
|
|
return image_rgba,box |
|
|
|
def apply_mask(image,mask): |
|
if len(mask.shape) == 3: |
|
expanded_mask = mask |
|
else: |
|
expanded_mask = np.expand_dims(mask, axis=-1) |
|
|
|
if len(mask.shape)!=3: |
|
error = f"image must be shape 3 {image.shape}" |
|
raise ValueError(error) |
|
|
|
if get_channel(image)!=4: |
|
image_rgba = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA) |
|
else: |
|
image_rgba = image |
|
image_rgba[..., 3] = expanded_mask[..., 0] |
|
return image_rgba |
|
|
|
def apply_mask_alpha(image,mask,invert=False): |
|
if len(mask.shape) == 3: |
|
expanded_mask = mask |
|
else: |
|
expanded_mask = np.expand_dims(mask, axis=-1) |
|
|
|
image_rgba = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA) |
|
if invert: |
|
image_rgba[..., 3] = expanded_mask[..., 0] |
|
else: |
|
image_rgba[..., 3] = 255 - expanded_mask[..., 0] |
|
return image_rgba |
|
|
|
def print_width_height(image,label): |
|
new_h,new_w = get_image_size(image) |
|
print(f"{label}:width = {new_w} height = {new_h}") |
|
|
|
|
|
def create_mask_from_points(img,points,dilation_size=4,gaussian_size=4): |
|
np_points = np.array(points,dtype=np.int32) |
|
mask = np.zeros(img.shape[:2], dtype="uint8") |
|
cv2.fillPoly(mask, [np_points], 255) |
|
|
|
kernel = np.ones((abs(dilation_size),abs(dilation_size) ), np.uint8) |
|
if dilation_size > 0: |
|
dilated_mask = cv2.dilate(mask, kernel, iterations=1) |
|
else: |
|
dilated_mask = cv2.erode(mask, kernel, iterations=1) |
|
|
|
if gaussian_size > 0: |
|
smooth_mask = cv2.GaussianBlur(dilated_mask, (0,0 ), sigmaX=gaussian_size, sigmaY=gaussian_size) |
|
expanded_mask = np.expand_dims(smooth_mask, axis=-1) |
|
else: |
|
expanded_mask = np.expand_dims(dilated_mask, axis=-1) |
|
return expanded_mask |
|
|
|
|
|
|
|
|
|
def mid_points(point1,point2): |
|
return [sum(x) / 2 for x in zip(point1,point2)] |
|
|
|
def lerp_points(point1, point2, lerp): |
|
return [(1.0 - lerp) * p1 + lerp * p2 for p1, p2 in zip(point1, point2)] |
|
|
|
def get_jaw_points(face_landmarks_list): |
|
chin_points = get_landmark_points(face_landmarks_list,POINTS_CHIN) |
|
bottom_lip_points = get_landmark_points(face_landmarks_list,POINTS_BOTTOM_LIP) |
|
|
|
points =[] |
|
|
|
points.extend(chin_points[4:13]) |
|
points.append(mid_points(chin_points[12],bottom_lip_points[0])) |
|
points.append(mid_points(chin_points[8],bottom_lip_points[3])) |
|
points.append(mid_points(chin_points[4],bottom_lip_points[6])) |
|
|
|
return points |
|
|
|
def get_bottom_mid_drop_size(open_size_y,lip_height): |
|
|
|
mid_lip_move_ratio = open_size_y/80.0 if open_size_y>0 else 0 |
|
return mid_lip_move_ratio*lip_height |
|
|
|
|
|
def fade_in_x(img,size): |
|
if size==0: |
|
return |
|
per_pixel = 1.0/size |
|
for y in range(img.shape[0]): |
|
for x in range(img.shape[1]): |
|
|
|
if x <size: |
|
alpha_base = per_pixel * x |
|
|
|
|
|
img[y, x, 3] = img[y,x,3] * alpha_base |
|
def fade_out_x(img,size): |
|
if size==0: |
|
return |
|
per_pixel = 1.0/size |
|
w = img.shape[1] |
|
|
|
for y in range(img.shape[0]): |
|
for x in range(img.shape[1]): |
|
|
|
if x >w: |
|
diff = x - w |
|
alpha_base = 1.0 - (per_pixel * x) |
|
|
|
|
|
img[y, x, 3] = img[y,x,3] * alpha_base |
|
|
|
|
|
def alpha_blend_with_image2_alpha(image1, image2): |
|
return cv2.addWeighted(image1, 1, image2, 1, 0) |
|
def numpy_alpha_blend_with_image2_alpha(image1, image2,invert=False): |
|
""" |
|
image1をimage2のアルファチャンネルを使用してアルファブレンディングします。 |
|
""" |
|
|
|
if image1.shape[:2] != image2.shape[:2]: |
|
image1 = cv2.resize(image1, (image2.shape[1], image2.shape[0])) |
|
|
|
src1 = np.array(image1) |
|
src2 = np.array(image2) |
|
mask1 = np.array(image2[:, :, 3]) |
|
mask1 = mask1 / 255 |
|
mask1 = np.expand_dims(mask1, axis=-1) |
|
if invert: |
|
dst = src1 * (1-mask1) + src2 * mask1 |
|
else: |
|
dst = src1 * mask1 + src2 * (1 - mask1) |
|
|
|
|
|
dst = dst.astype(np.uint8) |
|
return dst |
|
|
|
def distance_2d(point1, point2): |
|
return math.sqrt((point2[0] - point1[0])**2 + (point2[1] - point1[1])**2) |
|
|
|
|
|
def get_top_lip_thicks(landmarks_list,is_distance_base=False): |
|
points = get_landmark_points(landmarks_list,POINTS_TOP_LIP) |
|
if is_distance_base: |
|
return (distance_2d(points[10],points[2]),distance_2d(points[9],points[3]),distance_2d(points[8],points[4])) |
|
return (points[10][1] -points[2][1],points[9][1] -points[3][1],points[8][1] -points[4][1]) |
|
|
|
|
|
def scale_down_values(data, scale_factor=0.25): |
|
""" |
|
Scales down the values in a list of dictionaries by a given scale factor. |
|
|
|
Parameters: |
|
- data: A list of dictionaries where each dictionary represents facial landmarks. |
|
- scale_factor: The factor by which to scale down the values. Default is 0.25 (1/4). |
|
|
|
Returns: |
|
- A new list of dictionaries with scaled down values. |
|
""" |
|
scaled_data = [] |
|
for item in data: |
|
scaled_item = {} |
|
for key, values in item.items(): |
|
scaled_values = [(int(x * scale_factor), int(y * scale_factor)) for x, y in values] |
|
scaled_item[key] = scaled_values |
|
scaled_data.append(scaled_item) |
|
return scaled_data |
|
|
|
def save_landmarks(face_landmarks,out_path): |
|
json_data = json.dumps(face_landmarks) |
|
with open(out_path, "w") as f: |
|
f.write(json_data) |
|
|
|
def load_landmarks(input_path): |
|
with open(input_path, "r") as f: |
|
face_landmarks_list = json.loads(f.read()) |
|
return face_landmarks_list |
|
|
|
|
|
|
|
|
|
|