File size: 6,029 Bytes
80b326d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1496c35
80b326d
 
1496c35
80b326d
 
1496c35
 
80b326d
 
 
1496c35
80b326d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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