PEAR / utils /draw.py
BestWJH's picture
Upload 205 files
2c68f56 verified
import cv2
import numpy as np
import mediapipe as mp
from typing import Mapping
try:
from PIL.Image import Resampling
RESAMPLING_METHOD = Resampling.BICUBIC
except ImportError:
from PIL.Image import BICUBIC
RESAMPLING_METHOD = BICUBIC
import torch
def number_to_rgb(n):
n = int(n)
r = (n >> 16) & 0xFF
g = (n >> 8) & 0xFF
b = n & 0xFF
return (r, g, b)
# cv2.imwrite(os.path.join(save_path,f"smplx_stp_{iter_idx}_{im_idx}.png"), cv2.cvtColor(_img.copy(), cv2.COLOR_RGB2BGR))
def draw_landmarks(landmarks, image, color=(0, 255, 0), radius=4, thickness=-1, viz_index=False):
ret_img = image.copy()
for (idx, lmk) in enumerate(landmarks):
if len(lmk) > 2:
color = number_to_rgb(lmk[2]*1500)
ret_img = cv2.circle(ret_img, (int(lmk[0]), int(lmk[1])), radius, color, thickness) # 像素点坐标从 0-1024, x 为横坐标 w,y 为h坐标
if viz_index:
cv2.putText(ret_img, f'{idx}', (int(lmk[0]), int(lmk[1])), cv2.FONT_HERSHEY_COMPLEX_SMALL, 2, ((idx*2)%255, 145, (idx*3)%255), 1)
return ret_img
def _draw_mp_kps(image, landmarks, connections, landmark_drawing_spec, connection_drawing_spec):
t_landmarks = np.int32(landmarks[:, :2])
for connection in connections:
start_idx = connection[0]
end_idx = connection[1]
drawing_spec = connection_drawing_spec[connection] if isinstance(
connection_drawing_spec, Mapping) else connection_drawing_spec
cv2.line(image, t_landmarks[start_idx],
t_landmarks[end_idx], drawing_spec.color,
drawing_spec.thickness)
if landmark_drawing_spec:
for idx in range(t_landmarks.shape[0]):
landmark_px = t_landmarks[idx]
drawing_spec = landmark_drawing_spec[idx] if isinstance(
landmark_drawing_spec, Mapping) else landmark_drawing_spec
# White circle border
circle_border_radius = max(drawing_spec.circle_radius + 1,
int(drawing_spec.circle_radius * 1.2))
cv2.circle(image, landmark_px, circle_border_radius, (224, 224, 224),
drawing_spec.thickness)
# Fill color into the circle
cv2.circle(image, landmark_px, drawing_spec.circle_radius,
drawing_spec.color, drawing_spec.thickness)
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_connections = mp.solutions.face_mesh_connections
def draw_mediapipe_kps(mp_kps, canvas):
ret_img = canvas.copy()
# draw FACEMESH_TESSELATION
_draw_mp_kps(ret_img, mp_kps,
mp.solutions.face_mesh.FACEMESH_TESSELATION,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style(),
landmark_drawing_spec=None)
# draw FACEMESH_CONTOURS
_draw_mp_kps(ret_img, mp_kps,
mp.solutions.face_mesh.FACEMESH_CONTOURS,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_contours_style(),
landmark_drawing_spec=None)
# draw FACEMESH_IRISES
_draw_mp_kps(ret_img, mp_kps,
mp.solutions.face_mesh.FACEMESH_IRISES,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_iris_connections_style(),
landmark_drawing_spec=None)
return ret_img
def get_semantic_indices():
semantic_connections = {
'Contours': mp_connections.FACEMESH_CONTOURS,
'FaceOval': mp_connections.FACEMESH_FACE_OVAL,
'LeftIris': mp_connections.FACEMESH_LEFT_IRIS,
'LeftEye': mp_connections.FACEMESH_LEFT_EYE,
'LeftEyebrow': mp_connections.FACEMESH_LEFT_EYEBROW,
'RightIris': mp_connections.FACEMESH_RIGHT_IRIS,
'RightEye': mp_connections.FACEMESH_RIGHT_EYE,
'RightEyebrow': mp_connections.FACEMESH_RIGHT_EYEBROW,
'Lips': mp_connections.FACEMESH_LIPS,
'Tesselation': mp_connections.FACEMESH_TESSELATION
}
def get_compact_idx(connections):
ret = []
for conn in connections:
ret.append(conn[0])
ret.append(conn[1])
return sorted(tuple(set(ret)))
semantic_indexes = {k: get_compact_idx(v) for k, v in semantic_connections.items()}
return semantic_indexes
mp_lip_indices = [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,
76, 184, 74, 73, 72, 11, 302, 303, 304, 408, 306, 307, 320, 404, 315, 16, 85, 180, 90, 77,
62, 183, 42, 41, 38, 12, 268, 271, 272, 407, 292, 325, 319, 403, 316, 15, 86, 179, 89, 96,
78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95]
mp_lowerface_indices = [177, 132, 215, 58, 172, 136, 150, 149, 176, 148, 152, 377, 400, 378, 379, 365, 397, 288, 435, 361, 401,
147, 213, 192, 138, 135, 169, 170, 140, 171, 175, 396, 369, 395, 394, 364, 367, 433, 416, 376,
57, 186, 92, 165, 167, 164, 393, 391, 322, 410, 287, 273, 335, 406, 313, 18, 83, 182, 106, 43,
212, 216, 206, 203, 423, 426, 436, 432, 422, 424, 418, 421, 200, 201, 194, 204, 202,
214, 207, 205, 425, 427, 434, 430, 431, 262, 428, 199, 208, 32, 211, 210] # todo: need further check indices
chain_indices = [93, 137, 123, 50, 205, 206, 165, 167, 164, 393, 391, 426, 425, 280, 352, 366, 323]
teeth_indices = [62, 183, 42, 41, 38, 12, 268, 271, 272, 407, 292, 325, 319, 403, 316, 15, 86, 179, 89, 96]
nose_indices = [142, 129, 98, 97, 2, 326, 327, 358, 371, 355, 437, 399, 419, 197, 196, 174, 217, 126,
102, 64, 240, 99, 60, 75, 59, 235, 166, 219, 48, 49, 209,
331, 294, 460, 328, 290, 305, 289, 392, 439, 278, 279, 360,
198, 131, 115, 218, 79, 20, 242, 141, 94, 370, 462, 250, 309, 438, 344, 360, 420,
236, 3, 195, 248, 456, 363, 440, 457, 459, 458, 461, 354, 19, 125, 241, 238, 239, 237, 220, 134,
51, 5, 281, 275, 274, 354, 19, 125, 44, 45, 1, 4]
kp68_lowerface_indices = [x for x in range(3, 14)] + [52, 50, 3]
kp68_mouth_indices = [x for x in range(48, 61)]
def _merge_with_weight(p1, p2, w):
return p1 * (1 - w) + p2 * w
def merge_lower_face_pints(pred_lst, pred_ff_lst, ff_only=False):
if ff_only is True:
w1, w2 = 0.95, 0.95
else:
w1, w2 = 0.8, 0.2
pred_lst[mp_lip_indices] = _merge_with_weight(pred_lst[mp_lip_indices], pred_ff_lst[:len(mp_lip_indices)], w1)
pred_lst[mp_lowerface_indices] = _merge_with_weight(pred_lst[mp_lowerface_indices], pred_ff_lst[len(mp_lip_indices):], w2)
return pred_lst
def mask_from_points(size, points, radius=0, is_converx=True, mean_y=-1, ratio=0.025):
""" Create a mask of supplied size from supplied points
:param size: tuple of output mask size
:param points: array of [x, y] points
:returns: mask of values 0 and 255 where
255 indicates the convex hull containing the points
"""
if radius == 0:
radius = int(size[0] * ratio)
kernel = np.ones((int(abs(radius)), int(abs(radius))), np.uint8)
if mean_y > 0:
v_ids = points[:, :, 1] > mean_y
points[v_ids, 1] = mean_y + (points[v_ids, 1] - mean_y) * 1.3
mask = np.zeros(size, np.uint8)
if is_converx:
p = cv2.convexHull(points)
cv2.fillConvexPoly(mask, np.squeeze(p).astype(np.int32), 255)
else:
cv2.fillPoly(mask, [points.astype(np.int32)], 255)
if radius < 0:
mask = cv2.erode(mask, kernel)
else:
mask = cv2.dilate(mask, kernel)
return mask
def draw_teeth_mask(ver, canvas):
teeth_mask = mask_from_points(canvas.shape[:2], ver[:, teeth_indices, ...], radius=3)
teeth_mask = cv2.merge([np.zeros_like(teeth_mask), teeth_mask, teeth_mask])
canvas = np.clip(canvas / 1.0 + teeth_mask / 2.0, 0, 225)
return canvas.astype(np.uint8)
def draw_nose_mask(size, ver):
nose_mask = mask_from_points(size, ver[:, nose_indices, ...])
return nose_mask
def draw_lowerface_mask(size, ver, mean_y, is_kp68=False):
if not is_kp68:
lf_idx = mp_lowerface_indices + mp_lip_indices + chain_indices
lf_mask = mask_from_points(size, ver[:, lf_idx, ...], mean_y=mean_y)
else:
lf_idx = kp68_lowerface_indices
lf_mask = mask_from_points(size, ver[:, lf_idx, ...], is_converx=False, mean_y=mean_y)
return lf_mask
def draw_mouth_mask(size, ver, is_kp68=False):
if not is_kp68:
lf_idx = mp_lip_indices
lf_mask = mask_from_points(size, ver[:, lf_idx, ...])
else:
lf_idx = kp68_mouth_indices
lf_mask = mask_from_points(size, ver[:, lf_idx, ...], radius=27, is_converx=False)
return lf_mask
def draw_flame_lowerface_condition(img_fg:np.ndarray, img_fg_alpha:np.ndarray, img_bg:np.ndarray, lmk203:np.ndarray):
"""draw flame lower face condition
Args:
img_fg (np.ndarray): foreground image in 0~1, HxWxC
img_fg_alpha (np.ndarray): foreground image alpha in 0~1, HxW
img_bg (np.ndarray): background image in 0~1, HxWxC
lmk203 (np.ndarray): landmark in 203x2
"""
## 1. draw lower mask
lower_pts = np.concatenate([lmk203[114: 139], ((lmk203[57] + lmk203[202])/2)[None, ...]], axis=0)
# # enlarge lower y pts
lower_pts[:, 1] = (lower_pts[:, 1] - lmk203[201, 1]) * 1.2 + lmk203[201, 1]
mask1 = mask_from_points(img_bg.shape[:2], lower_pts, ratio=0.03, is_converx=False) / 255 # todo: check this and regenerate condition images
mouth_region = mask_from_points(img_bg.shape[:2], lmk203[84: 108], ratio=0.015, is_converx=False) / 255
mask1[mouth_region>0.05] = img_fg_alpha[mouth_region>0.05]
final_mask = np.clip(img_fg_alpha + mask1, 0, 1)
final_mask = cv2.merge([final_mask]*3)
## 2. draw inner mouth region
bg_img = img_bg * 255
bg_img[mouth_region>0.05] = (225, 225, 0)
## 3. overlay them
ret_img = final_mask * img_fg + (1 - final_mask) * bg_img / 255
return np.clip(ret_img, 0, 1)
def draw_fullface_mask(size, ver, mean_y):
lf_idx = mp_lowerface_indices + mp_lip_indices + chain_indices
lf_mask = mask_from_points(size, ver[:, lf_idx, ...], mean_y=mean_y)
ff_mask = mask_from_points(size, ver)
ff_mask = np.uint8(((lf_mask / 1.0 + ff_mask / 1.0) > 0) * 255.)
return ff_mask
def get_bbox_from_vert(vert):
rect = [vert[:,0].min(), vert[:,1].min(), vert[:,0].max(), vert[:,1].max()]
cx = rect[0] + (rect[2] - rect[0]) / 2
cy = rect[1] + (rect[3] - rect[1]) / 2
size = max(rect[2]- rect[0], rect[3]- rect[1])
size = size * 1.3
return [int(cx - size/2), int(cy-size/2), int(cx + size/2), int(cy + size/2)]
def alpha_feathering(src_img, dest_img, img_mask, use_blur=True):
if use_blur:
blur_radius = int(src_img.shape[0]/30)
mask = cv2.blur(img_mask, (blur_radius, blur_radius))
else:
mask = img_mask
mask = mask / 255.0
result_img = np.empty(src_img.shape, np.uint8)
for i in range(3):
result_img[..., i] = src_img[..., i] * mask + dest_img[..., i] * (1-mask)
return result_img