| |
|
|
| import cv2 |
| import numpy as np |
| import math |
| import copy |
|
|
| eps = 0.01 |
|
|
| DROP_FACE_POINTS = {0, 14, 15, 16, 17} |
| DROP_UPPER_POINTS = {0, 14, 15, 16, 17, 2, 1, 5, 3, 6} |
| DROP_LOWER_POINTS = {8, 9, 10, 11, 12, 13} |
|
|
| def scale_and_translate_pose(tgt_pose, ref_pose, conf_th=0.9, return_ratio=False): |
| aligned_pose = copy.deepcopy(tgt_pose) |
| th = 1e-6 |
| ref_kpt = ref_pose['bodies']['candidate'].astype(np.float32) |
| tgt_kpt = aligned_pose['bodies']['candidate'].astype(np.float32) |
|
|
| ref_sc = ref_pose['bodies'].get('score', np.ones(ref_kpt.shape[0])).astype(np.float32).reshape(-1) |
| tgt_sc = tgt_pose['bodies'].get('score', np.ones(tgt_kpt.shape[0])).astype(np.float32).reshape(-1) |
|
|
| ref_shoulder_valid = (ref_sc[2] >= conf_th) and (ref_sc[5] >= conf_th) |
| tgt_shoulder_valid = (tgt_sc[2] >= conf_th) and (tgt_sc[5] >= conf_th) |
| shoulder_ok = ref_shoulder_valid and tgt_shoulder_valid |
|
|
| ref_hip_valid = (ref_sc[8] >= conf_th) and (ref_sc[11] >= conf_th) |
| tgt_hip_valid = (tgt_sc[8] >= conf_th) and (tgt_sc[11] >= conf_th) |
| hip_ok = ref_hip_valid and tgt_hip_valid |
|
|
| if shoulder_ok and hip_ok: |
| ref_shoulder_w = abs(ref_kpt[5, 0] - ref_kpt[2, 0]) |
| tgt_shoulder_w = abs(tgt_kpt[5, 0] - tgt_kpt[2, 0]) |
| x_ratio = ref_shoulder_w / tgt_shoulder_w if tgt_shoulder_w > th else 1.0 |
|
|
| ref_torso_h = abs(np.mean(ref_kpt[[8, 11], 1]) - np.mean(ref_kpt[[2, 5], 1])) |
| tgt_torso_h = abs(np.mean(tgt_kpt[[8, 11], 1]) - np.mean(tgt_kpt[[2, 5], 1])) |
| y_ratio = ref_torso_h / tgt_torso_h if tgt_torso_h > th else 1.0 |
| scale_ratio = (x_ratio + y_ratio) / 2 |
|
|
| elif shoulder_ok: |
| ref_sh_dist = np.linalg.norm(ref_kpt[2] - ref_kpt[5]) |
| tgt_sh_dist = np.linalg.norm(tgt_kpt[2] - tgt_kpt[5]) |
| scale_ratio = ref_sh_dist / tgt_sh_dist if tgt_sh_dist > th else 1.0 |
|
|
| else: |
| ref_ear_dist = np.linalg.norm(ref_kpt[16] - ref_kpt[17]) |
| tgt_ear_dist = np.linalg.norm(tgt_kpt[16] - tgt_kpt[17]) |
| scale_ratio = ref_ear_dist / tgt_ear_dist if tgt_ear_dist > th else 1.0 |
|
|
| if return_ratio: |
| return scale_ratio |
|
|
| |
| anchor_idx = 1 |
| anchor_pt_before_scale = tgt_kpt[anchor_idx].copy() |
| def scale(arr): |
| if arr is not None and arr.size > 0: |
| arr[..., 0] = anchor_pt_before_scale[0] + (arr[..., 0] - anchor_pt_before_scale[0]) * scale_ratio |
| arr[..., 1] = anchor_pt_before_scale[1] + (arr[..., 1] - anchor_pt_before_scale[1]) * scale_ratio |
| scale(tgt_kpt) |
| scale(aligned_pose.get('faces')) |
| scale(aligned_pose.get('hands')) |
|
|
| |
| offset = ref_kpt[anchor_idx] - tgt_kpt[anchor_idx] |
| def translate(arr): |
| if arr is not None and arr.size > 0: |
| arr += offset |
| translate(tgt_kpt) |
| translate(aligned_pose.get('faces')) |
| translate(aligned_pose.get('hands')) |
| aligned_pose['bodies']['candidate'] = tgt_kpt |
|
|
| return aligned_pose, shoulder_ok, hip_ok |
|
|
|
|
| def warp_ref_to_pose(tgt_img, |
| ref_pose: dict, |
| tgt_pose: dict, |
| bg_val=(0, 0, 0), |
| conf_th=0.9, |
| align_center=False): |
|
|
| H, W = tgt_img.shape[:2] |
| img_tgt_pose = draw_pose_aligned(tgt_pose, H, W, without_face=True) |
|
|
| tgt_kpt = tgt_pose['bodies']['candidate'].astype(np.float32) |
| ref_kpt = ref_pose['bodies']['candidate'].astype(np.float32) |
|
|
| scale_ratio = scale_and_translate_pose(tgt_pose, ref_pose, conf_th=conf_th, return_ratio=True) |
|
|
| anchor_idx = 1 |
| x0 = tgt_kpt[anchor_idx][0] * W |
| y0 = tgt_kpt[anchor_idx][1] * H |
|
|
| ref_x = ref_kpt[anchor_idx][0] * W if not align_center else W/2 |
| ref_y = ref_kpt[anchor_idx][1] * H |
|
|
| dx = ref_x - x0 |
| dy = ref_y - y0 |
|
|
| |
| M = np.array([[scale_ratio, 0, (1-scale_ratio)*x0 + dx], |
| [0, scale_ratio, (1-scale_ratio)*y0 + dy]], |
| dtype=np.float32) |
| img_warp = cv2.warpAffine(tgt_img, M, (W, H), |
| flags=cv2.INTER_LINEAR, |
| borderValue=bg_val) |
| img_tgt_pose_warp = cv2.warpAffine(img_tgt_pose, M, (W, H), |
| flags=cv2.INTER_LINEAR, |
| borderValue=bg_val) |
| zeros = np.zeros((H, W), dtype=np.uint8) |
| mask_warp = cv2.warpAffine(zeros, M, (W, H), |
| flags=cv2.INTER_NEAREST, |
| borderValue=255) |
| return img_warp, img_tgt_pose_warp, mask_warp |
|
|
| def hsv_to_rgb(hsv): |
| hsv = np.asarray(hsv, dtype=np.float32) |
| in_shape = hsv.shape |
| hsv = hsv.reshape(-1, 3) |
|
|
| h, s, v = hsv[:, 0], hsv[:, 1], hsv[:, 2] |
|
|
| i = (h * 6.0).astype(int) |
| f = (h * 6.0) - i |
| i = i % 6 |
|
|
| p = v * (1.0 - s) |
| q = v * (1.0 - s * f) |
| t = v * (1.0 - s * (1.0 - f)) |
|
|
| rgb = np.zeros_like(hsv) |
| rgb[i == 0] = np.stack([v[i == 0], t[i == 0], p[i == 0]], axis=1) |
| rgb[i == 1] = np.stack([q[i == 1], v[i == 1], p[i == 1]], axis=1) |
| rgb[i == 2] = np.stack([p[i == 2], v[i == 2], t[i == 2]], axis=1) |
| rgb[i == 3] = np.stack([p[i == 3], q[i == 3], v[i == 3]], axis=1) |
| rgb[i == 4] = np.stack([t[i == 4], p[i == 4], v[i == 4]], axis=1) |
| rgb[i == 5] = np.stack([v[i == 5], p[i == 5], q[i == 5]], axis=1) |
|
|
| gray_mask = s == 0 |
| rgb[gray_mask] = np.stack([v[gray_mask]] * 3, axis=1) |
|
|
| return (rgb.reshape(in_shape) * 255) |
|
|
| def get_stickwidth(W, H, stickwidth=4): |
| if max(W, H) < 512: |
| ratio = 1.0 |
| elif max(W, H) < 1080: |
| ratio = 1.5 |
| elif max(W, H) < 2160: |
| ratio = 2.0 |
| elif max(W, H) < 3240: |
| ratio = 2.5 |
| elif max(W, H) < 4320: |
| ratio = 3.5 |
| elif max(W, H) < 5400: |
| ratio = 4.5 |
| else: |
| ratio = 4.0 |
| return int(stickwidth * ratio) |
|
|
|
|
| def alpha_blend_color(color, alpha): |
| return [int(c * alpha) for c in color] |
|
|
|
|
| def draw_bodypose_aligned(canvas, candidate, subset, score, plan=None): |
| H, W, C = canvas.shape |
| candidate = np.array(candidate) |
| subset = np.array(subset) |
| stickwidth = get_stickwidth(W, H, stickwidth=3) |
|
|
| limbSeq = [ |
| [2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], |
| [2, 9], [9, 10], [10, 11], [2, 12], [12, 13], [13, 14], |
| [2, 1], [1, 15], [15, 17], [1, 16], [16, 18], [3, 17], [6, 18]] |
| colors = [ |
| [255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], |
| [85, 255, 0], [0, 255, 0], [0, 255, 85], [0, 255, 170], [0, 255, 255], |
| [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], |
| [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] |
|
|
| HIDE_JOINTS = set() |
| stretch_limb_idx = None |
| stretch_scale = None |
| if plan: |
| if plan["mode"] == "drop_point": |
| HIDE_JOINTS.add(plan["point_idx"]) |
| elif plan["mode"] == "drop_region": |
| HIDE_JOINTS |= set(plan["points"]) |
| elif plan["mode"] == "stretch_limb": |
| stretch_limb_idx = plan["limb_idx"] |
| stretch_scale = plan["stretch_scale"] |
|
|
| hide_joint = np.zeros_like(subset, dtype=bool) |
|
|
| for i in range(17): |
| for n in range(len(subset)): |
| idx_pair = limbSeq[i] |
|
|
| if any(j in HIDE_JOINTS for j in idx_pair): |
| continue |
|
|
| index = subset[n][np.array(idx_pair) - 1] |
| conf = score[n][np.array(idx_pair) - 1] |
| if -1 in index: |
| continue |
| |
| alpha = max(conf[0] * conf[1], 0) if conf[0]>0 and conf[1]>0 else 0.35 |
| if conf[0] == 0 or conf[1] == 0: |
| alpha = 0 |
|
|
| Y = candidate[index.astype(int), 0] * float(W) |
| X = candidate[index.astype(int), 1] * float(H) |
|
|
| if stretch_limb_idx == i: |
| vec_x = X[1] - X[0] |
| vec_y = Y[1] - Y[0] |
| X[1] = X[0] + vec_x * stretch_scale |
| Y[1] = Y[0] + vec_y * stretch_scale |
| hide_joint[n, idx_pair[1]-1] = True |
|
|
| mX = np.mean(X) |
| mY = np.mean(Y) |
| length = ((X[0]-X[1])**2 + (Y[0]-Y[1])**2) ** 0.5 |
| angle = math.degrees(math.atan2(X[0]-X[1], Y[0]-Y[1])) |
| polygon = cv2.ellipse2Poly((int(mY), int(mX)), |
| (int(length/2), stickwidth), int(angle), 0, 360, 1) |
| cv2.fillConvexPoly(canvas, polygon, alpha_blend_color(colors[i], alpha)) |
|
|
| canvas = (canvas * 0.6).astype(np.uint8) |
|
|
| for i in range(18): |
| if i in HIDE_JOINTS: |
| continue |
| for n in range(len(subset)): |
| if hide_joint[n, i]: |
| continue |
| index = int(subset[n][i]) |
| if index == -1: |
| continue |
| x, y = candidate[index][0:2] |
| conf = score[n][i] |
|
|
| alpha = 0 if conf==-2 else max(conf, 0) |
| x = int(x * W) |
| y = int(y * H) |
| cv2.circle(canvas, (x, y), stickwidth, alpha_blend_color(colors[i], alpha), thickness=-1) |
|
|
| return canvas |
|
|
|
|
| def draw_handpose_aligned(canvas, all_hand_peaks, all_hand_scores, draw_th=0.3): |
| H, W, C = canvas.shape |
| stickwidth = get_stickwidth(W, H, stickwidth=2) |
| line_thickness = get_stickwidth(W, H, stickwidth=2) |
|
|
| edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ |
| [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] |
|
|
| for peaks, scores in zip(all_hand_peaks, all_hand_scores): |
| for ie, e in enumerate(edges): |
| if scores[e[0]] < draw_th or scores[e[1]] < draw_th: |
| continue |
| x1, y1 = peaks[e[0]] |
| x2, y2 = peaks[e[1]] |
| x1 = int(x1 * W) |
| y1 = int(y1 * H) |
| x2 = int(x2 * W) |
| y2 = int(y2 * H) |
|
|
| score = int(scores[e[0]] * scores[e[1]] * 255) |
| if x1 > eps and y1 > eps and x2 > eps and y2 > eps: |
| color = hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]).flatten() |
| color = tuple(int(c * score / 255) for c in color) |
| cv2.line(canvas, (x1, y1), (x2, y2), color, thickness=line_thickness) |
|
|
| for i, keyponit in enumerate(peaks): |
| if scores[i] < draw_th: |
| continue |
|
|
| x, y = keyponit |
| x = int(x * W) |
| y = int(y * H) |
| score = int(scores[i] * 255) |
| if x > eps and y > eps: |
| cv2.circle(canvas, (x, y), stickwidth, (0, 0, score), thickness=-1) |
| return canvas |
|
|
|
|
| def draw_facepose_aligned(canvas, all_lmks, all_scores, draw_th=0.3,face_change=False): |
| H, W, C = canvas.shape |
| stickwidth = get_stickwidth(W, H, stickwidth=2) |
| SKIP_IDX = set(range(0, 17)) |
| SKIP_IDX |= set(range(27, 36)) |
|
|
| for lmks, scores in zip(all_lmks, all_scores): |
| for idx, (lmk, score) in enumerate(zip(lmks, scores)): |
| |
| if idx in SKIP_IDX: |
| continue |
| if score < draw_th: |
| continue |
| x, y = lmk |
| x = int(x * W) |
| y = int(y * H) |
| conf = int(score * 255) |
| |
| if face_change: |
| conf = int(conf * 0.35) |
|
|
| if x > eps and y > eps: |
| cv2.circle(canvas, (x, y), stickwidth, (conf, conf, conf), thickness=-1) |
| return canvas |
|
|
|
|
| def draw_pose_aligned(pose, H, W, ref_w=2160, without_face=False, pose_plan=None, head_strength="full", face_change=False): |
| bodies = pose['bodies'] |
| faces = pose['faces'] |
| hands = pose['hands'] |
| candidate = bodies['candidate'] |
| subset = bodies['subset'] |
| body_score = bodies['score'].copy() |
| |
| if head_strength == "weak": |
| target_joints = [0, 14, 15, 16, 17] |
| body_score[:, target_joints] = -2 |
| elif head_strength == "none": |
| target_joints = [0, 14, 15, 16, 17] |
| body_score[:, target_joints] = 0 |
|
|
| sz = min(H, W) |
| sr = (ref_w / sz) if sz != ref_w else 1 |
| canvas = np.zeros(shape=(int(H*sr), int(W*sr), 3), dtype=np.uint8) |
|
|
| canvas = draw_bodypose_aligned(canvas, candidate, subset, |
| score=body_score, |
| plan=pose_plan,) |
|
|
| canvas = draw_handpose_aligned(canvas, hands, pose['hands_score']) |
|
|
| if not without_face: |
| canvas = draw_facepose_aligned(canvas, faces, pose['faces_score'],face_change=face_change) |
|
|
| return cv2.resize(canvas, (W, H)) |
|
|