ViralCutterPRO / scripts /two_face.py
RafaG's picture
Upload 24 files
1496c35 verified
import cv2
import mediapipe as mp
import numpy as np
def crop_and_maintain_ar(frame, face_box, target_w, target_h, zoom_out_factor=2.2):
"""
Recorta uma região baseada no rosto mantendo o aspect ratio do target.
Previne deformação (esticar/espremer).
"""
img_h, img_w, _ = frame.shape
x, y, w, h = face_box
# Centro do rosto
cx = x + w // 2
cy = y + h // 2
# Dimensão base do rosto (maior lado para garantir cobertura)
face_size = max(w, h)
# Altura desejada do crop (altura do rosto * fator de zoom/afastamento)
# zoom_out_factor: quanto maior, mais afastado (mais cenário)
req_h = face_size * zoom_out_factor
# Aspect Ratio alvo (1080 / 960 = 1.125)
target_ar = target_w / target_h
# Calcular largura e altura do crop mantendo AR
crop_h = req_h
crop_w = crop_h * target_ar
# Verificar limitações da imagem original (não podemos cortar mais que existe)
# Se a largura necessária for maior que a imagem, limitamos pela largura
if crop_w > img_w:
crop_w = float(img_w)
crop_h = crop_w / target_ar
# Se a altura necessária for maior que a imagem, limitamos pela altura
if crop_h > img_h:
crop_h = float(img_h)
crop_w = crop_h * target_ar
# Converter para inteiros
crop_w = int(crop_w)
crop_h = int(crop_h)
# Calcular coordenadas top-left do crop centralizado no rosto
x1 = int(cx - crop_w // 2)
y1 = int(cy - crop_h // 2)
# Ajuste de bordas (Clamp) deslisando a janela se possível
# Se sair pela esquerda, encosta na esquerda
if x1 < 0:
x1 = 0
# Se sair pela direita, encosta na direita
elif x1 + crop_w > img_w:
x1 = img_w - crop_w
# Se sair por cima
if y1 < 0:
y1 = 0
# Se sair por baixo
elif y1 + crop_h > img_h:
y1 = img_h - crop_h
# Verificação de segurança final se a imagem for menor que o crop (embora lógica acima evite)
x2 = x1 + crop_w
y2 = y1 + crop_h
# Crop
cropped = frame[y1:y2, x1:x2]
# Se o crop falhar (tamanho 0), retorna preto
if cropped.size == 0 or cropped.shape[0] == 0 or cropped.shape[1] == 0:
return np.zeros((target_h, target_w, 3), dtype=np.uint8)
# Redimensionar para o tamanho alvo final (1080x960)
# Como garantimos o AR, o resize mantém a proporção correta
resized = cv2.resize(cropped, (target_w, target_h), interpolation=cv2.INTER_LINEAR)
return resized
def crop_and_resize_two_faces(frame, face_positions, zoom_out_factor=2.2):
"""
Recorta e redimensiona dois rostos detectados no frame, ajustando para uma composição vertical
1080x1920 onde cada rosto ocupa metade da tela (1080x960).
"""
# Target dimensoes para cada metade
target_w = 1080
target_h = 960
# Se não temos 2 faces, fallback (segurança)
if len(face_positions) < 2:
return np.zeros((1920, 1080, 3), dtype=np.uint8)
# Primeiro rosto (Topo)
face1_img = crop_and_maintain_ar(frame, face_positions[0], target_w, target_h, zoom_out_factor)
# Segundo rosto (Embaixo)
face2_img = crop_and_maintain_ar(frame, face_positions[1], target_w, target_h, zoom_out_factor)
# Compor imagem final (Stack Vertical)
result_frame = np.vstack((face1_img, face2_img))
return result_frame
def detect_face_or_body_two_faces(frame, face_detection, face_mesh, pose):
# Converter a imagem para RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Processar a detecção de rosto
results_face_detection = face_detection.process(frame_rgb)
results_face_mesh = face_mesh.process(frame_rgb)
results_pose = pose.process(frame_rgb)
face_positions_detection = []
if results_face_detection.detections:
for detection in results_face_detection.detections[:2]:
bbox = detection.location_data.relative_bounding_box
x_min = int(bbox.xmin * frame.shape[1])
y_min = int(bbox.ymin * frame.shape[0])
width = int(bbox.width * frame.shape[1])
height = int(bbox.height * frame.shape[0])
face_positions_detection.append((x_min, y_min, width, height))
if len(face_positions_detection) == 2:
return face_positions_detection
face_positions_mesh = []
if results_face_mesh.multi_face_landmarks:
for landmarks in results_face_mesh.multi_face_landmarks[:2]:
x_coords = [int(landmark.x * frame.shape[1]) for landmark in landmarks.landmark]
y_coords = [int(landmark.y * frame.shape[0]) for landmark in landmarks.landmark]
x_min, x_max = min(x_coords), max(x_coords)
y_min, y_max = min(y_coords), max(y_coords)
width = x_max - x_min
height = y_max - y_min
face_positions_mesh.append((x_min, y_min, width, height))
if len(face_positions_mesh) == 2:
return face_positions_mesh
# If neither found 2, return what we found (prefer detection as it is bounding box optimized)
if face_positions_detection:
return face_positions_detection
if face_positions_mesh:
return face_positions_mesh
# Se nenhum rosto for detectado, usar a pose para estimar o corpo
if results_pose.pose_landmarks:
x_coords = [lmk.x for lmk in results_pose.pose_landmarks.landmark]
y_coords = [lmk.y for lmk in results_pose.pose_landmarks.landmark]
x_min = int(min(x_coords) * frame.shape[1])
x_max = int(max(x_coords) * frame.shape[1])
y_min = int(min(y_coords) * frame.shape[0])
y_max = int(max(y_coords) * frame.shape[0])
width = x_max - x_min
height = y_max - y_min
return [(x_min, y_min, width, height)]
return None