Spaces:
Running
Running
| 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 | |