# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved. import os import cv2 import math import random import numpy as np def get_mask_boxes(mask): """ Return [x_min, y_min, x_max, y_max] or None if mask is empty. mask: [h, w] (bool, 0/1, or uint8) """ # Accept any truthy mask y_coords, x_coords = np.nonzero(mask) if x_coords.size == 0 or y_coords.size == 0: return None x_min = int(x_coords.min()) x_max = int(x_coords.max()) y_min = int(y_coords.min()) y_max = int(y_coords.max()) return np.array([x_min, y_min, x_max, y_max], dtype=np.int32) def get_aug_mask(body_mask, w_len=10, h_len=20): """ Fills small holes/stripes inside the detected bbox area. Safely handles empty masks and degenerate boxes. """ # Ensure binary 0/1 uint8 if body_mask.dtype != np.uint8: body_mask = (body_mask > 0).astype(np.uint8) body_bbox = get_mask_boxes(body_mask) if body_bbox is None: # Nothing to augment for this frame return body_mask x_min, y_min, x_max, y_max = map(int, body_bbox) if x_max <= x_min or y_max <= y_min: # Degenerate bbox; do nothing return body_mask bbox_w = x_max - x_min bbox_h = y_max - y_min # Prevent zero step sizes w_slice = max(1, int(bbox_w / max(1, w_len))) h_slice = max(1, int(bbox_h / max(1, h_len))) for each_w in range(x_min, x_max, w_slice): w_start = min(each_w, x_max) w_end = min(each_w + w_slice, x_max) for each_h in range(y_min, y_max, h_slice): h_start = min(each_h, y_max) h_end = min(each_h + h_slice, y_max) if body_mask[h_start:h_end, w_start:w_end].sum() > 0: body_mask[h_start:h_end, w_start:w_end] = 1 return body_mask def get_mask_body_img(img_copy, hand_mask, k=7, iterations=1): kernel = np.ones((k, k), np.uint8) dilation = cv2.dilate(hand_mask, kernel, iterations=iterations) mask_hand_img = img_copy * (1 - dilation[:, :, None]) return mask_hand_img, dilation def get_face_bboxes(kp2ds, scale, image_shape, ratio_aug): h, w = image_shape kp2ds_face = kp2ds.copy()[23:91, :2] min_x, min_y = np.min(kp2ds_face, axis=0) max_x, max_y = np.max(kp2ds_face, axis=0) initial_width = max_x - min_x initial_height = max_y - min_y initial_area = initial_width * initial_height expanded_area = initial_area * scale new_width = np.sqrt(expanded_area * (initial_width / initial_height)) new_height = np.sqrt(expanded_area * (initial_height / initial_width)) delta_width = (new_width - initial_width) / 2 delta_height = (new_height - initial_height) / 4 if ratio_aug: if random.random() > 0.5: delta_width += random.uniform(0, initial_width // 10) else: delta_height += random.uniform(0, initial_height // 10) expanded_min_x = max(min_x - delta_width, 0) expanded_max_x = min(max_x + delta_width, w) expanded_min_y = max(min_y - 3 * delta_height, 0) expanded_max_y = min(max_y + delta_height, h) return [int(expanded_min_x), int(expanded_max_x), int(expanded_min_y), int(expanded_max_y)] def calculate_new_size(orig_w, orig_h, target_area, divisor=64): target_ratio = orig_w / orig_h def check_valid(w, h): if w <= 0 or h <= 0: return False return (w * h <= target_area and w % divisor == 0 and h % divisor == 0) def get_ratio_diff(w, h): return abs(w / h - target_ratio) def round_to_64(value, round_up=False, divisor=64): if round_up: return divisor * ((value + (divisor - 1)) // divisor) return divisor * (value // divisor) possible_sizes = [] max_area_h = int(np.sqrt(target_area / target_ratio)) max_area_w = int(max_area_h * target_ratio) max_h = round_to_64(max_area_h, round_up=True, divisor=divisor) max_w = round_to_64(max_area_w, round_up=True, divisor=divisor) for h in range(divisor, max_h + divisor, divisor): ideal_w = h * target_ratio w_down = round_to_64(ideal_w) w_up = round_to_64(ideal_w, round_up=True) for w in [w_down, w_up]: if check_valid(w, h, divisor): possible_sizes.append((w, h, get_ratio_diff(w, h))) if not possible_sizes: raise ValueError("Can not find suitable size") possible_sizes.sort(key=lambda x: (-x[0] * x[1], x[2])) best_w, best_h, _ = possible_sizes[0] return int(best_w), int(best_h) def resize_by_area(image, target_area, keep_aspect_ratio=True, divisor=64, padding_color=(0, 0, 0)): h, w = image.shape[:2] try: new_w, new_h = calculate_new_size(w, h, target_area, divisor) except: aspect_ratio = w / h if keep_aspect_ratio: new_h = math.sqrt(target_area / aspect_ratio) new_w = target_area / new_h else: new_w = new_h = math.sqrt(target_area) new_w, new_h = int((new_w // divisor) * divisor), int((new_h // divisor) * divisor) interpolation = cv2.INTER_AREA if (new_w * new_h < w * h) else cv2.INTER_LINEAR resized_image = padding_resize(image, height=new_h, width=new_w, padding_color=padding_color, interpolation=interpolation) return resized_image def padding_resize(img_ori, height=512, width=512, padding_color=(0, 0, 0), interpolation=cv2.INTER_LINEAR): ori_height = img_ori.shape[0] ori_width = img_ori.shape[1] channel = img_ori.shape[2] img_pad = np.zeros((height, width, channel)) if channel == 1: img_pad[:, :, 0] = padding_color[0] else: img_pad[:, :, 0] = padding_color[0] img_pad[:, :, 1] = padding_color[1] img_pad[:, :, 2] = padding_color[2] if (ori_height / ori_width) > (height / width): new_width = int(height / ori_height * ori_width) img = cv2.resize(img_ori, (new_width, height), interpolation=interpolation) padding = int((width - new_width) / 2) if len(img.shape) == 2: img = img[:, :, np.newaxis] img_pad[:, padding: padding + new_width, :] = img else: new_height = int(width / ori_width * ori_height) img = cv2.resize(img_ori, (width, new_height), interpolation=interpolation) padding = int((height - new_height) / 2) if len(img.shape) == 2: img = img[:, :, np.newaxis] img_pad[padding: padding + new_height, :, :] = img img_pad = np.uint8(img_pad) return img_pad def get_frame_indices(frame_num, video_fps, clip_length, train_fps): start_frame = 0 times = np.arange(0, clip_length) / train_fps frame_indices = start_frame + np.round(times * video_fps).astype(int) frame_indices = np.clip(frame_indices, 0, frame_num - 1) return frame_indices.tolist() def get_face_bboxes(kp2ds, scale, image_shape): h, w = image_shape kp2ds_face = kp2ds.copy()[1:] * (w, h) min_x, min_y = np.min(kp2ds_face, axis=0) max_x, max_y = np.max(kp2ds_face, axis=0) initial_width = max_x - min_x initial_height = max_y - min_y initial_area = initial_width * initial_height expanded_area = initial_area * scale new_width = np.sqrt(expanded_area * (initial_width / initial_height)) new_height = np.sqrt(expanded_area * (initial_height / initial_width)) delta_width = (new_width - initial_width) / 2 delta_height = (new_height - initial_height) / 4 expanded_min_x = max(min_x - delta_width, 0) expanded_max_x = min(max_x + delta_width, w) expanded_min_y = max(min_y - 3 * delta_height, 0) expanded_max_y = min(max_y + delta_height, h) return [int(expanded_min_x), int(expanded_max_x), int(expanded_min_y), int(expanded_max_y)]