import os import json #from glibvision.cv2_utils import get_numpy_text from glibvision.numpy_utils import bulge_polygon import numpy as np import math USE_CACHE = True # face structures are same # MIT LICENSED # https://github.com/ageitgey/face_recognition 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) # 5 to 7 ,1 t- 11 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 = bezier_curve(points, num_points=10) #print(points) points = bulge_polygon(points, bulge_factor=0.2) #print(points) 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=[(points[0]+points[11])/2] st = [sum(x) / 2 for x in zip(points[0], points[11])] #et=[(points[6]+points[7])/2] 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) #if key == TOP_LIP: 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:]#[::-1] #np_points = np.array(top_points[7:]+bottom_points[7:][::-1],dtype=np.int32) def get_lip_hole_top_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:] def get_lip_hole_bottom_points(face_landmarks_list): #top_points=get_landmark_points(face_landmarks_list,TOP_LIP) bottom_points=get_landmark_points(face_landmarks_list,BOTTOM_LIP) #inverted for connect top return bottom_points[7:][::-1] #for hide too long tooth def get_lip_hole_bottom_half_points(face_landmarks_list): #top_points=get_landmark_points(face_landmarks_list,TOP_LIP) bottom_points=get_landmark_points(face_landmarks_list,BOTTOM_LIP) #inverted for connect top 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] #print(points) 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) #print(np_points) 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(): #matching_landmark_points = landmark_points.copy() 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 #for size up def create_moved_image(image,src_points,dst_points,force_size=None): # keep top of lip stable but affing must be 4 point #print(f"src = {src_points}") #print(f"dst = {dst_points}") src_pts=np.array([src_points],dtype=np.float32) dst_pts=np.array([dst_points],dtype=np.float32) #BORDER_REPLICATE return warp_with_auto_resize(image, src_pts, dst_pts,cv2.BORDER_REPLICATE,force_size) # lip-index """ 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 #mirror #index_ritht_top= 2 #mirror #index_left_top= 4 #mirror index_left = 6 #if landmark_name ==key: # 0 is right edge x1 = landmark[index_right][0] y1 = landmark[index_right][1] # 6 is left edge x2 = landmark[index_left][0] y2 = landmark[index_left][1] #left_top = landmark[index_left_top][1] #right_top = landmark[index_ritht_top][1] #top = left_top if left_topmax_x: max_x=int(point[0]) if point[1]>max_y: max_y=int(point[1]) if point[0] 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) #lip_utils.print_numpy(image_rgba,"rgba") #lip_utils.print_numpy(smooth_mask,"smooth") #lip_utils.print_numpy(expanded_mask,"expanded_mask") 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) #why rgb to gray? 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) # TODO support dilation_size # Gaussian Blur 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 #lip_utils.print_numpy(image_rgba,"rgba") #lip_utils.print_numpy(smooth_mask,"smooth") #lip_utils.print_numpy(expanded_mask,"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): # when full open case open_size_y 40 lip become half 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 w: diff = x - w alpha_base = 1.0 - (per_pixel * x) # アルファ値を変更し、ピクセルに設定 #print(f"before x ={x} = {img[y,x,3]} after = {img[y,x,3] * alpha_base}") 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) # アルファブレンディングを行います。 #blended = cv2.cvtColor(dst, cv2.COLOR_BGRA2BGRA) 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) # points[index][x=0 y=1] index is see landmark image by plot2.py 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