Files

319 lines
14 KiB
Python

import cv2
import numpy as np
from typing import Tuple, Dict
from .logging_utils import logger
class ImageProcessor:
@staticmethod
def smart_scaling(image, mask, target_h, head_ratio, face_size, face_center, face_info) -> Tuple[np.ndarray, np.ndarray, Tuple[int, int]]:
"""智能缩放算法 - 根据人脸比例调整,保持人脸中心不变"""
# 移除全局缩放系数,使用用户提供的face_size参数
global_scale_factor = 1.0 # 默认不额外缩放
# 应用用户指定的缩放系数
adjusted_face_size = face_size * global_scale_factor
logger.info(f"调整人脸大小系数为 {adjusted_face_size:.2f}")
# 优先使用检测到的人脸区域进行缩放
if face_info["face_height"] is not None:
try:
# 使用人脸高度计算缩放比例
face_height = face_info["face_height"]
target_face_height = target_h * head_ratio
scale_factor = (target_face_height / face_height) * adjusted_face_size
# 强制限制缩放比例在合理范围内
min_scale = 0.1
max_scale = 2.0
scale_factor = max(min(scale_factor, max_scale), min_scale)
# 记录缩放比例和目标人脸高度,用于后续处理
face_info["scale_factor"] = scale_factor
face_info["target_face_height"] = target_face_height
# 应用缩放
new_h = int(image.shape[0] * scale_factor)
new_w = int(image.shape[1] * scale_factor)
# 确保尺寸至少为1像素
new_h = max(1, new_h)
new_w = max(1, new_w)
# 使用高质量插值方法进行缩放
scaled_img = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
scaled_mask = cv2.resize(mask, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
# 计算缩放后的人脸中心点
if face_info["eyes_center"] is not None:
# 使用眼睛中心点作为参考
eyes_x, eyes_y = face_info["eyes_center"]
scaled_eyes_x = int(eyes_x * scale_factor)
scaled_eyes_y = int(eyes_y * scale_factor)
return scaled_img, scaled_mask, (scaled_eyes_x, scaled_eyes_y)
else:
# 使用人脸中心点作为参考
cx, cy = face_center
scaled_cx = int(cx * scale_factor)
scaled_cy = int(cy * scale_factor)
return scaled_img, scaled_mask, (scaled_cx, scaled_cy)
except Exception as e:
logger.warning(f"基于人脸的缩放失败: {str(e)}")
# 如果人脸检测失败,使用整个遮罩进行缩放
try:
if np.max(mask) > 0:
# 计算遮罩的边界框
non_zero_points = np.argwhere(mask > 0)
y_min, x_min = non_zero_points.min(axis=0)
y_max, x_max = non_zero_points.max(axis=0)
mask_height = y_max - y_min
# 使用遮罩高度计算缩放比例
# 假设遮罩高度的40%是人脸高度
estimated_face_height = mask_height * 0.4
target_face_height = target_h * head_ratio
scale_factor = (target_face_height / estimated_face_height) * adjusted_face_size
# 强制限制缩放比例在合理范围内
min_scale = 0.1
max_scale = 2.0
scale_factor = max(min(scale_factor, max_scale), min_scale)
# 应用缩放
new_h = int(image.shape[0] * scale_factor)
new_w = int(image.shape[1] * scale_factor)
# 确保尺寸至少为1像素
new_h = max(1, new_h)
new_w = max(1, new_w)
# 使用高质量插值方法进行缩放
scaled_img = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
scaled_mask = cv2.resize(mask, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
# 计算缩放后的中心点
cx, cy = face_center
scaled_cx = int(cx * scale_factor)
scaled_cy = int(cy * scale_factor)
return scaled_img, scaled_mask, (scaled_cx, scaled_cy)
except Exception as e:
logger.warning(f"基于遮罩的缩放失败: {str(e)}")
# 如果所有方法都失败,使用默认缩放
try:
# 默认缩放 - 使整个图像高度为目标高度的70%
scale_factor = (target_h * 0.7) / image.shape[0] * adjusted_face_size
# 强制限制缩放比例在合理范围内
min_scale = 0.1
max_scale = 2.0
scale_factor = max(min(scale_factor, max_scale), min_scale)
# 应用缩放
new_h = int(image.shape[0] * scale_factor)
new_w = int(image.shape[1] * scale_factor)
# 确保尺寸至少为1像素
new_h = max(1, new_h)
new_w = max(1, new_w)
# 使用高质量插值方法进行缩放
scaled_img = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
scaled_mask = cv2.resize(mask, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
# 计算缩放后的中心点
h, w = image.shape[:2]
cx, cy = w//2, h//2
scaled_cx = int(cx * scale_factor)
scaled_cy = int(cy * scale_factor)
return scaled_img, scaled_mask, (scaled_cx, scaled_cy)
except Exception as e:
logger.error(f"默认缩放失败: {str(e)}")
return image, mask, face_center
@staticmethod
def rotate_image(image, mask, angle, bg_color, center) -> Tuple[np.ndarray, np.ndarray, Tuple[int, int]]:
"""改进的旋转函数 - 支持扩展画布以避免裁剪"""
if angle == 0:
return image, mask, center
h, w = image.shape[:2]
cx, cy = center
# 计算旋转后需要的画布大小,确保旋转后的图像完全显示
angle_rad = abs(angle) * np.pi / 180
cos_a = np.cos(angle_rad)
sin_a = np.sin(angle_rad)
# 计算旋转后的新宽度和高度
new_w = int(h * sin_a + w * cos_a)
new_h = int(h * cos_a + w * sin_a)
# 创建一个更大的画布
padded_img = np.full((new_h, new_w, 3), bg_color, dtype=np.uint8)
padded_mask = np.zeros((new_h, new_w), dtype=np.uint8)
# 计算原图在新画布中的位置,使得旋转中心保持在相同的相对位置
pad_x = (new_w - w) // 2
pad_y = (new_h - h) // 2
# 将原图放在新画布中
padded_img[pad_y:pad_y+h, pad_x:pad_x+w] = image
padded_mask[pad_y:pad_y+h, pad_x:pad_x+w] = mask
# 更新旋转中心点在新画布中的坐标
new_cx = cx + pad_x
new_cy = cy + pad_y
# 创建旋转矩阵,以新的中心点为旋转中心
matrix = cv2.getRotationMatrix2D((new_cx, new_cy), -angle, 1)
# 旋转时填充背景色
rotated_img = cv2.warpAffine(padded_img, matrix, (new_w, new_h),
flags=cv2.INTER_LANCZOS4,
borderValue=bg_color)
rotated_mask = cv2.warpAffine(padded_mask, matrix, (new_w, new_h),
flags=cv2.INTER_NEAREST,
borderValue=0)
# 旋转后的中心点坐标保持不变,因为我们是以该点为中心进行的旋转
rotated_center = (new_cx, new_cy)
return rotated_img, rotated_mask, rotated_center
@staticmethod
def apply_positioning(image, mask, target_w, target_h, bg_color, move_x, move_y,
face_center, eye_position_ratio, gender, position="center"):
"""精确定位算法 - 根据人脸中心和眼睛位置比例放置人物"""
# 创建目标画布
canvas = np.ones((target_h, target_w, 3), dtype=np.uint8) * bg_color
canvas_mask = np.zeros((target_h, target_w), dtype=np.uint8)
# 计算眼睛应该在画布中的垂直位置
target_eye_y = int(target_h * eye_position_ratio)
# 计算水平位置 - 调整左右人脸的间距
if position == "left":
# 左侧人物 - 放在画布左侧约35%处
target_eye_x = int(target_w * 0.35)
elif position == "right":
# 右侧人物 - 放在画布右侧约70%处
target_eye_x = int(target_w * 0.70)
else:
# 居中
target_eye_x = target_w // 2
# 修正移动逻辑:X为正向右移动,Y为正向上移动
target_eye_x += int(move_x) # 向右移动
target_eye_y -= int(move_y) # 向上移动(注意这里是减法,因为图像坐标系Y轴向下)
# 计算图像应该放置的位置
face_x, face_y = face_center
x_offset = target_eye_x - face_x
y_offset = target_eye_y - face_y
# 计算图像在画布上的位置
x1 = x_offset
y1 = y_offset
x2 = x1 + image.shape[1]
y2 = y1 + image.shape[0]
# 计算有效的粘贴区域(处理图像超出画布的情况)
src_x1 = max(0, -x1)
src_y1 = max(0, -y1)
src_x2 = min(image.shape[1], target_w - x1)
src_y2 = min(image.shape[0], target_h - y1)
dst_x1 = max(0, x1)
dst_y1 = max(0, y1)
dst_x2 = min(target_w, x2)
dst_y2 = min(target_h, y2)
# 检查是否有有效的粘贴区域
if src_x1 < src_x2 and src_y1 < src_y2 and dst_x1 < dst_x2 and dst_y1 < dst_y2:
# 粘贴图像到画布
canvas[dst_y1:dst_y2, dst_x1:dst_x2] = image[src_y1:src_y2, src_x1:src_x2]
canvas_mask[dst_y1:dst_y2, dst_x1:dst_x2] = mask[src_y1:src_y2, src_x1:src_x2]
# 计算最终的人脸中心点
final_face_center = (target_eye_x, target_eye_y)
return canvas, canvas_mask, final_face_center
@staticmethod
def combine_images(image_L, mask_L, image_R, mask_R, target_w, target_h, bg_color):
"""合成最终图像 - 将左右两个人物合成到一个画布上"""
# 创建最终画布
final_image = np.ones((target_h, target_w, 3), dtype=np.uint8) * bg_color
combined_mask = np.zeros((target_h, target_w), dtype=np.uint8)
# 先绘制左侧人物(后层)
mask_L_bool = mask_L > 0
final_image[mask_L_bool] = image_L[mask_L_bool]
combined_mask[mask_L_bool] = 255
# 再绘制右侧人物(前层)
mask_R_bool = mask_R > 0
final_image[mask_R_bool] = image_R[mask_R_bool]
combined_mask[mask_R_bool] = 255
# 返回最终图像和各个遮罩
return final_image, mask_L, mask_R, combined_mask
@staticmethod
def check_boundary(mask, name):
"""检查人物是否脱离画布边界"""
if np.max(mask) == 0:
return
h, w = mask.shape
# 检查上下左右边界是否有非零像素
top_row = mask[0, :]
bottom_row = mask[h-1, :]
left_col = mask[:, 0]
right_col = mask[:, w-1]
warnings = []
if np.max(top_row) > 0:
warnings.append("上")
if np.max(bottom_row) > 0:
warnings.append("下")
if np.max(left_col) > 0:
warnings.append("左")
if np.max(right_col) > 0:
warnings.append("右")
if warnings:
logger.warning(f"{name}在画布{', '.join(warnings)}边界处被裁剪,请调整位置或缩小尺寸")
@staticmethod
def get_background_color(bg_type) -> Tuple[int, int, int]:
"""背景颜色管理系统"""
return (255, 255, 255) if bg_type == "white" else (192, 192, 192)
@staticmethod
def prepare_inputs(image_tensor, mask_tensor) -> Tuple[np.ndarray, np.ndarray]:
"""输入预处理"""
image = (image_tensor[0].cpu().numpy() * 255).astype(np.uint8)[:, :, :3]
mask = (mask_tensor[0].cpu().numpy() * 255).astype(np.uint8)
return image, mask
@staticmethod
def wrap_outputs(image, mask_L=None, mask_R=None, mask_combined=None) -> Tuple:
"""输出格式转换"""
image_tensor = torch.from_numpy(image.astype(np.float32)/255.0).unsqueeze(0)
if mask_L is not None and mask_R is not None and mask_combined is not None:
# 双人模式输出
mask_L_tensor = torch.from_numpy(mask_L.astype(np.float32)/255.0).unsqueeze(0)
mask_R_tensor = torch.from_numpy(mask_R.astype(np.float32)/255.0).unsqueeze(0)
mask_combined_tensor = torch.from_numpy(mask_combined.astype(np.float32)/255.0).unsqueeze(0)
return (image_tensor, mask_L_tensor, mask_R_tensor, mask_combined_tensor)
else:
# 单人模式输出
mask_tensor = torch.from_numpy((mask_L if mask_L is not None else mask_combined).astype(np.float32)/255.0).unsqueeze(0)
return (image_tensor, mask_tensor)