VeuReu commited on
Commit
80442f7
·
verified ·
1 Parent(s): ea7f392

Upload api.py

Browse files
Files changed (1) hide show
  1. api.py +150 -18
api.py CHANGED
@@ -181,26 +181,158 @@ def process_video_job(job_id: str):
181
 
182
  print(f"[{job_id}] Directorio base: {base}")
183
 
184
- # Detección real de personajes usando el código de Ana
185
  try:
186
- print(f"[{job_id}] Iniciando detección de personajes...")
187
- result = detect_characters_from_video(
188
- video_path=video_path,
189
- output_base=str(base),
190
- epsilon=epsilon,
191
- min_cluster_size=min_cluster_size,
192
- video_name=video_name,
193
- start_offset_sec=0.5,
194
- extract_every_sec=0.25
195
- )
196
-
197
- print(f"[{job_id}] DEBUG - result completo: {result}")
198
-
199
- characters = result.get("characters", [])
200
- analysis_path = result.get("analysis_path", "")
201
- face_labels = result.get("face_labels", [])
202
- num_face_embeddings = int(result.get("num_face_embeddings", 0))
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  print(f"[{job_id}] Personajes detectados: {len(characters)}")
205
  for char in characters:
206
  print(f"[{job_id}] - {char['name']}: {char['num_faces']} caras")
 
181
 
182
  print(f"[{job_id}] Directorio base: {base}")
183
 
184
+ # Detección de caras y embeddings (CPU), alineado con 'originales'
185
  try:
186
+ print(f"[{job_id}] Iniciando detección de personajes (CPU, originales)...")
187
+ import cv2
188
+ import numpy as np
189
+ try:
190
+ import face_recognition # CPU
191
+ _use_fr = True
192
+ print(f"[{job_id}] face_recognition disponible: CPU")
193
+ except Exception:
194
+ face_recognition = None # type: ignore
195
+ _use_fr = False
196
+ print(f"[{job_id}] face_recognition no disponible. Intentando DeepFace fallback.")
197
+ try:
198
+ from deepface import DeepFace # type: ignore
199
+ except Exception:
200
+ DeepFace = None # type: ignore
 
 
201
 
202
+ cap = cv2.VideoCapture(video_path)
203
+ if not cap.isOpened():
204
+ raise RuntimeError("No se pudo abrir el vídeo para extracción de caras")
205
+ fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
206
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
207
+ step = max(1, int(3)) # cada ~3 frames para CPU
208
+ print(f"[{job_id}] Total frames: {total_frames}, FPS: {fps:.2f}, Procesando cada {step} frames")
209
+
210
+ # Salidas
211
+ faces_root = base / "faces_raw"
212
+ faces_root.mkdir(parents=True, exist_ok=True)
213
+ embeddings: list[list[float]] = []
214
+ crops_meta: list[dict] = []
215
+
216
+ frame_idx = 0
217
+ saved_count = 0
218
+ while True:
219
+ ret = cap.grab()
220
+ if not ret:
221
+ break
222
+ if frame_idx % step == 0:
223
+ ret2, frame = cap.retrieve()
224
+ if not ret2:
225
+ break
226
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
227
+
228
+ if _use_fr and face_recognition is not None:
229
+ boxes = face_recognition.face_locations(rgb, model="hog") # CPU HOG
230
+ encs = face_recognition.face_encodings(rgb, boxes)
231
+ for (top, right, bottom, left), e in zip(boxes, encs):
232
+ crop = frame[top:bottom, left:right]
233
+ if crop.size == 0:
234
+ continue
235
+ fn = f"face_{frame_idx:06d}_{saved_count:03d}.jpg"
236
+ cv2.imwrite(str(faces_root / fn), crop)
237
+ # Normalizar embedding
238
+ e = np.array(e, dtype=float)
239
+ e = e / (np.linalg.norm(e) + 1e-9)
240
+ embeddings.append(e.astype(float).tolist())
241
+ crops_meta.append({
242
+ "file": fn,
243
+ "frame": frame_idx,
244
+ "box": [int(top), int(right), int(bottom), int(left)],
245
+ })
246
+ saved_count += 1
247
+ else:
248
+ # DeepFace fallback: no siempre devuelve boxes fácilmente; intentamos representaciones
249
+ if DeepFace is None:
250
+ pass
251
+ else:
252
+ try:
253
+ tmp_path = faces_root / f"frame_{frame_idx:06d}.jpg"
254
+ cv2.imwrite(str(tmp_path), frame)
255
+ reps = DeepFace.represent(img_path=str(tmp_path), model_name="Facenet512", enforce_detection=False)
256
+ # reps puede ser lista de embeddings; no tenemos boxes -> guardamos frame completo como proxy
257
+ for k, r in enumerate(reps or []):
258
+ emb = r.get("embedding") if isinstance(r, dict) else r
259
+ if emb is None:
260
+ continue
261
+ emb = np.array(emb, dtype=float)
262
+ emb = emb / (np.linalg.norm(emb) + 1e-9)
263
+ embeddings.append(emb.astype(float).tolist())
264
+ crops_meta.append({"file": tmp_path.name, "frame": frame_idx, "box": None})
265
+ saved_count += 1
266
+ except Exception as _e_df:
267
+ print(f"[{job_id}] DeepFace fallback error: {_e_df}")
268
+ frame_idx += 1
269
+ cap.release()
270
+
271
+ print(f"[{job_id}] ✓ Caras detectadas (embeddings): {len(embeddings)}")
272
+
273
+ # Clustering DBSCAN de caras como en 'originales'
274
+ from sklearn.cluster import DBSCAN
275
+ if embeddings:
276
+ Xf = np.array(embeddings)
277
+ f_eps = float(epsilon)
278
+ f_min = max(1, int(min_cluster_size))
279
+ labels = DBSCAN(eps=f_eps, min_samples=f_min, metric='euclidean').fit(Xf).labels_.tolist()
280
+ else:
281
+ labels = []
282
+
283
+ # Construir carpetas por clúster y representative
284
+ characters = []
285
+ cluster_map: dict[int, list[int]] = {}
286
+ for i, lbl in enumerate(labels):
287
+ if isinstance(lbl, int) and lbl >= 0:
288
+ cluster_map.setdefault(lbl, []).append(i)
289
+
290
+ chars_dir = base / "characters"
291
+ chars_dir.mkdir(parents=True, exist_ok=True)
292
+ import shutil as _sh
293
+ for ci, idxs in sorted(cluster_map.items(), key=lambda x: x[0]):
294
+ char_id = f"char_{ci:02d}"
295
+ out_dir = chars_dir / char_id
296
+ out_dir.mkdir(parents=True, exist_ok=True)
297
+ files = []
298
+ for k, j in enumerate(idxs[:24]): # limitar a 24
299
+ fname = crops_meta[j]["file"]
300
+ src = faces_root / fname
301
+ dst = out_dir / fname
302
+ try:
303
+ _sh.copy2(src, dst)
304
+ files.append(fname)
305
+ except Exception:
306
+ pass
307
+ rep = files[0] if files else None
308
+ if rep:
309
+ rep_src = out_dir / rep
310
+ rep_dst = out_dir / "representative.jpg"
311
+ try:
312
+ _sh.copy2(rep_src, rep_dst)
313
+ except Exception:
314
+ pass
315
+ characters.append({
316
+ "id": char_id,
317
+ "name": f"Personatge {ci+1}",
318
+ "folder": str(out_dir),
319
+ "num_faces": len(files),
320
+ "image_url": f"/files/{video_name}/{char_id}/representative.jpg" if rep else "",
321
+ })
322
+
323
+ # Escribir analysis.json compatible con 'originales'
324
+ analysis = {
325
+ "caras": [{"embeddings": e} for e in embeddings],
326
+ "voices": [],
327
+ "escenas": [],
328
+ }
329
+ analysis_path = str(base / "analysis.json")
330
+ with open(analysis_path, "w", encoding="utf-8") as f:
331
+ json.dump(analysis, f, ensure_ascii=False)
332
+
333
+ face_labels = labels
334
+ num_face_embeddings = len(embeddings)
335
+
336
  print(f"[{job_id}] Personajes detectados: {len(characters)}")
337
  for char in characters:
338
  print(f"[{job_id}] - {char['name']}: {char['num_faces']} caras")