leicam commited on
Commit
cb2bcf1
·
verified ·
1 Parent(s): 186e431

delete face_tracking.py

Browse files
Files changed (1) hide show
  1. face_tracking.py +0 -346
face_tracking.py DELETED
@@ -1,346 +0,0 @@
1
- """
2
- Módulo de rastreamento facial para crop inteligente de vídeos.
3
- Usa OpenCV e detecção de rostos para manter pessoas centralizadas ao redimensionar.
4
- """
5
-
6
- import cv2
7
- import numpy as np
8
- from typing import Tuple, Optional, List
9
- from dataclasses import dataclass
10
-
11
- @dataclass
12
- class FaceBox:
13
- """Representa uma detecção de rosto."""
14
- x: int
15
- y: int
16
- w: int
17
- h: int
18
- center_x: int
19
- center_y: int
20
- confidence: float = 1.0
21
-
22
- class FaceTracker:
23
- """Rastreador de rostos para crop inteligente de vídeos."""
24
-
25
- def __init__(self):
26
- """Inicializa o detector de rostos usando Haar Cascades do OpenCV."""
27
- # Tenta carregar diferentes cascades (frontal e perfil)
28
- cascade_paths = [
29
- cv2.data.haarcascades + 'haarcascade_frontalface_default.xml',
30
- cv2.data.haarcascades + 'haarcascade_frontalface_alt.xml',
31
- ]
32
-
33
- self.face_cascade = None
34
- for path in cascade_paths:
35
- try:
36
- self.face_cascade = cv2.CascadeClassifier(path)
37
- if not self.face_cascade.empty():
38
- break
39
- except:
40
- continue
41
-
42
- if self.face_cascade is None or self.face_cascade.empty():
43
- print("⚠️ Aviso: Não foi possível carregar detector de rostos. Crop será centralizado.")
44
- self.enabled = False
45
- else:
46
- self.enabled = True
47
- print("✓ Detector de rostos carregado com sucesso")
48
-
49
- def detect_faces(self, frame: np.ndarray) -> List[FaceBox]:
50
- """
51
- Detecta rostos em um frame.
52
-
53
- Args:
54
- frame: Frame do vídeo (BGR ou RGB)
55
-
56
- Returns:
57
- Lista de FaceBox com rostos detectados
58
- """
59
- if not self.enabled:
60
- return []
61
-
62
- # Converte para escala de cinza para detecção
63
- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
64
-
65
- # Detecta rostos
66
- faces = self.face_cascade.detectMultiScale(
67
- gray,
68
- scaleFactor=1.1,
69
- minNeighbors=5,
70
- minSize=(30, 30),
71
- flags=cv2.CASCADE_SCALE_IMAGE
72
- )
73
-
74
- # Converte para FaceBox
75
- face_boxes = []
76
- for (x, y, w, h) in faces:
77
- center_x = x + w // 2
78
- center_y = y + h // 2
79
- face_boxes.append(FaceBox(x, y, w, h, center_x, center_y))
80
-
81
- return face_boxes
82
-
83
- def get_primary_face(self, faces: List[FaceBox], frame_width: int, frame_height: int) -> Optional[FaceBox]:
84
- """
85
- Seleciona o rosto principal (mais central e maior).
86
-
87
- Args:
88
- faces: Lista de rostos detectados
89
- frame_width: Largura do frame
90
- frame_height: Altura do frame
91
-
92
- Returns:
93
- FaceBox do rosto principal ou None
94
- """
95
- if not faces:
96
- return None
97
-
98
- # Se só há um rosto, retorna ele
99
- if len(faces) == 1:
100
- return faces[0]
101
-
102
- # Calcula score para cada rosto (baseado em tamanho e centralização)
103
- frame_center_x = frame_width / 2
104
- frame_center_y = frame_height / 2
105
-
106
- scored_faces = []
107
- for face in faces:
108
- # Score por tamanho (normalizado)
109
- size_score = (face.w * face.h) / (frame_width * frame_height)
110
-
111
- # Score por distância ao centro (normalizado e invertido)
112
- dx = abs(face.center_x - frame_center_x) / frame_width
113
- dy = abs(face.center_y - frame_center_y) / frame_height
114
- center_score = 1 - (dx + dy) / 2
115
-
116
- # Score final (peso maior para centralização)
117
- total_score = (size_score * 0.3) + (center_score * 0.7)
118
- scored_faces.append((total_score, face))
119
-
120
- # Retorna o rosto com maior score
121
- scored_faces.sort(reverse=True, key=lambda x: x[0])
122
- return scored_faces[0][1]
123
-
124
- def calculate_smart_crop(
125
- self,
126
- frame: np.ndarray,
127
- target_width: int,
128
- target_height: int
129
- ) -> Tuple[int, int, int, int]:
130
- """
131
- Calcula coordenadas de crop inteligente baseado em detecção facial.
132
-
133
- Args:
134
- frame: Frame do vídeo
135
- target_width: Largura desejada
136
- target_height: Altura desejada
137
-
138
- Returns:
139
- Tupla (x, y, w, h) das coordenadas de crop
140
- """
141
- frame_h, frame_w = frame.shape[:2]
142
-
143
- # Detecta rostos
144
- faces = self.detect_faces(frame)
145
- primary_face = self.get_primary_face(faces, frame_w, frame_h)
146
-
147
- # Calcula aspect ratio alvo
148
- target_ar = target_width / target_height
149
- frame_ar = frame_w / frame_h
150
-
151
- if primary_face:
152
- # Crop baseado no rosto detectado
153
- face_center_x = primary_face.center_x
154
- face_center_y = primary_face.center_y
155
-
156
- # Ajusta centro baseado no rosto com margens de segurança
157
- if target_ar < frame_ar: # Crop vertical (9:16, 1:1, 4:5)
158
- crop_w = int(frame_h * target_ar)
159
- crop_h = frame_h
160
-
161
- # Centraliza horizontalmente no rosto
162
- crop_x = max(0, min(face_center_x - crop_w // 2, frame_w - crop_w))
163
- crop_y = 0
164
- else: # Crop horizontal ou quadrado
165
- crop_w = frame_w
166
- crop_h = int(frame_w / target_ar)
167
-
168
- # Centraliza verticalmente no rosto (com leve offset para cima)
169
- offset = int(crop_h * 0.1) # 10% offset para dar espaço acima da cabeça
170
- crop_x = 0
171
- crop_y = max(0, min(face_center_y - crop_h // 2 - offset, frame_h - crop_h))
172
- else:
173
- # Fallback: crop centralizado tradicional
174
- if target_ar < frame_ar: # Mais alto que largo
175
- crop_w = int(frame_h * target_ar)
176
- crop_h = frame_h
177
- crop_x = (frame_w - crop_w) // 2
178
- crop_y = 0
179
- else: # Mais largo que alto
180
- crop_w = frame_w
181
- crop_h = int(frame_w / target_ar)
182
- crop_x = 0
183
- crop_y = (frame_h - crop_h) // 2
184
-
185
- return (crop_x, crop_y, crop_w, crop_h)
186
-
187
-
188
- def apply_smart_crop_to_video(
189
- input_path: str,
190
- output_path: str,
191
- target_width: int,
192
- target_height: int,
193
- sample_frames: int = 10
194
- ) -> bool:
195
- """
196
- Aplica crop inteligente com rastreamento facial a um vídeo.
197
-
198
- Args:
199
- input_path: Caminho do vídeo de entrada
200
- output_path: Caminho do vídeo de saída
201
- target_width: Largura desejada
202
- target_height: Altura desejada
203
- sample_frames: Número de frames para amostragem (para calcular posição média)
204
-
205
- Returns:
206
- True se sucesso, False caso contrário
207
- """
208
- tracker = FaceTracker()
209
-
210
- # Abre vídeo de entrada
211
- cap = cv2.VideoCapture(input_path)
212
- if not cap.isOpened():
213
- print(f"❌ Erro ao abrir vídeo: {input_path}")
214
- return False
215
-
216
- # Propriedades do vídeo
217
- fps = int(cap.get(cv2.CAP_PROP_FPS))
218
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
219
-
220
- # Amostra alguns frames para determinar melhor posição de crop
221
- sample_positions = []
222
- frame_indices = np.linspace(0, frame_count - 1, min(sample_frames, frame_count), dtype=int)
223
-
224
- for idx in frame_indices:
225
- cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
226
- ret, frame = cap.read()
227
- if ret:
228
- crop_coords = tracker.calculate_smart_crop(frame, target_width, target_height)
229
- sample_positions.append(crop_coords)
230
-
231
- # Calcula posição média de crop (suaviza movimento)
232
- if sample_positions:
233
- avg_x = int(np.median([p[0] for p in sample_positions]))
234
- avg_y = int(np.median([p[1] for p in sample_positions]))
235
- crop_w = sample_positions[0][2]
236
- crop_h = sample_positions[0][3]
237
- final_crop = (avg_x, avg_y, crop_w, crop_h)
238
- else:
239
- # Fallback
240
- frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
241
- frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
242
- target_ar = target_width / target_height
243
- frame_ar = frame_w / frame_h
244
-
245
- if target_ar < frame_ar:
246
- crop_w = int(frame_h * target_ar)
247
- crop_h = frame_h
248
- final_crop = ((frame_w - crop_w) // 2, 0, crop_w, crop_h)
249
- else:
250
- crop_w = frame_w
251
- crop_h = int(frame_w / target_ar)
252
- final_crop = (0, (frame_h - crop_h) // 2, crop_w, crop_h)
253
-
254
- # Reseta para início do vídeo
255
- cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
256
-
257
- # Configura writer de saída
258
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
259
- out = cv2.VideoWriter(output_path, fourcc, fps, (target_width, target_height))
260
-
261
- if not out.isOpened():
262
- print(f"❌ Erro ao criar vídeo de saída: {output_path}")
263
- cap.release()
264
- return False
265
-
266
- # Processa cada frame
267
- print(f"🎬 Processando vídeo com crop inteligente: {final_crop}")
268
- frame_num = 0
269
- while True:
270
- ret, frame = cap.read()
271
- if not ret:
272
- break
273
-
274
- # Aplica crop
275
- x, y, w, h = final_crop
276
- cropped = frame[y:y+h, x:x+w]
277
-
278
- # Redimensiona para tamanho final
279
- resized = cv2.resize(cropped, (target_width, target_height), interpolation=cv2.INTER_LANCZOS4)
280
-
281
- # Escreve frame
282
- out.write(resized)
283
- frame_num += 1
284
-
285
- # Progress
286
- if frame_num % 30 == 0:
287
- progress = (frame_num / frame_count) * 100
288
- print(f" Progresso: {progress:.1f}% ({frame_num}/{frame_count} frames)")
289
-
290
- # Finaliza
291
- cap.release()
292
- out.release()
293
-
294
- print(f"✓ Vídeo processado com sucesso: {output_path}")
295
- return True
296
-
297
-
298
- def get_aspect_ratio_dimensions(ar_mode: str, base_height: int = 1080) -> Tuple[int, int]:
299
- """
300
- Retorna dimensões (width, height) baseado no modo de aspect ratio.
301
-
302
- Args:
303
- ar_mode: Modo do aspect ratio ("Original", "Vertical 9:16", "Quadrado 1:1", "Retrato 4:5")
304
- base_height: Altura base para cálculos (padrão: 1080p)
305
-
306
- Returns:
307
- Tupla (width, height)
308
- """
309
- ar_map = {
310
- "Original": None, # Mantém original
311
- "Vertical 9:16": (9, 16),
312
- "Quadrado 1:1": (1, 1),
313
- "Retrato 4:5": (4, 5),
314
- }
315
-
316
- if ar_mode not in ar_map or ar_map[ar_mode] is None:
317
- return None
318
-
319
- w_ratio, h_ratio = ar_map[ar_mode]
320
-
321
- # Calcula width baseado na altura
322
- width = int((base_height / h_ratio) * w_ratio)
323
-
324
- return (width, base_height)
325
-
326
-
327
- # Exemplo de uso:
328
- if __name__ == "__main__":
329
- # Teste básico
330
- tracker = FaceTracker()
331
-
332
- # Simula um frame de teste
333
- test_frame = np.zeros((1080, 1920, 3), dtype=np.uint8)
334
-
335
- # Detecta rostos
336
- faces = tracker.detect_faces(test_frame)
337
- print(f"Rostos detectados: {len(faces)}")
338
-
339
- # Calcula crop para 9:16
340
- crop_coords = tracker.calculate_smart_crop(test_frame, 1080, 1920)
341
- print(f"Coordenadas de crop (9:16): {crop_coords}")
342
-
343
- # Testa diferentes aspect ratios
344
- for ar_mode in ["Vertical 9:16", "Quadrado 1:1", "Retrato 4:5"]:
345
- dims = get_aspect_ratio_dimensions(ar_mode)
346
- print(f"{ar_mode}: {dims}")