2026-04-08 16:05:57 +00:00
|
|
|
import os
|
|
|
|
|
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
|
|
|
|
|
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
|
|
|
|
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;tcp|stimeout;3000000"
|
|
|
|
|
|
|
|
|
|
# ⚡ NUEVOS CANDADOS: Silenciamos la burocracia de OpenCV y Qt
|
|
|
|
|
os.environ['QT_LOGGING_RULES'] = '*=false'
|
|
|
|
|
os.environ['OPENCV_LOG_LEVEL'] = 'ERROR'
|
|
|
|
|
import cv2
|
|
|
|
|
import numpy as np
|
|
|
|
|
import time
|
|
|
|
|
import threading
|
|
|
|
|
import queue
|
|
|
|
|
from queue import Queue
|
|
|
|
|
from ultralytics import YOLO
|
|
|
|
|
import warnings
|
|
|
|
|
|
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# 1. IMPORTAMOS NUESTROS MÓDULOS
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Del motor matemático y tracking
|
|
|
|
|
from seguimiento2 import GlobalMemory, CamManager, SECUENCIA, URLS, FUENTE, similitud_hibrida
|
|
|
|
|
|
|
|
|
|
# ⚡ Del motor de reconocimiento facial
|
|
|
|
|
from reconocimiento import (
|
|
|
|
|
app,
|
|
|
|
|
gestionar_vectores,
|
|
|
|
|
buscar_mejor_match,
|
|
|
|
|
hilo_bienvenida,
|
|
|
|
|
UMBRAL_SIM,
|
|
|
|
|
COOLDOWN_TIME
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# 2. PROTECCIONES MULTIHILO E INICIALIZACIÓN
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
COLA_ROSTROS = Queue(maxsize=4)
|
|
|
|
|
IA_LOCK = threading.Lock()
|
|
|
|
|
|
|
|
|
|
print("\nIniciando carga de base de datos...")
|
|
|
|
|
BASE_DATOS_ROSTROS = gestionar_vectores(actualizar=True)
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# 3. MOTOR ASÍNCRONO CON INSIGHTFACE
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
def procesar_rostro_async(roi_cabeza, gid, cam_id, global_mem, trk):
|
|
|
|
|
try:
|
|
|
|
|
if not BASE_DATOS_ROSTROS or roi_cabeza.size == 0: return
|
|
|
|
|
|
|
|
|
|
with IA_LOCK:
|
|
|
|
|
faces = app.get(roi_cabeza)
|
|
|
|
|
|
|
|
|
|
if len(faces) == 0:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))
|
|
|
|
|
|
|
|
|
|
w_rostro = face.bbox[2] - face.bbox[0]
|
|
|
|
|
h_rostro = face.bbox[3] - face.bbox[1]
|
|
|
|
|
|
|
|
|
|
# ⚡ Límite bajo (20px) para reconocer desde más lejos
|
|
|
|
|
if w_rostro < 20 or h_rostro < 20 or (w_rostro / h_rostro) < 0.35:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
emb = np.array(face.normed_embedding, dtype=np.float32)
|
|
|
|
|
genero_detectado = "Man" if face.sex == "M" else "Woman"
|
|
|
|
|
|
|
|
|
|
mejor_match, max_sim = buscar_mejor_match(emb, BASE_DATOS_ROSTROS)
|
|
|
|
|
print(f"[DEBUG CAM {cam_id}] InsightFace: {mejor_match} al {max_sim:.2f}")
|
|
|
|
|
|
|
|
|
|
votos_finales = 0
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
|
|
|
# ⚡ LÓGICA DE VOTACIÓN PONDERADA INYECTADA
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
|
|
|
# Aceptamos rostros de CCTV desde 0.38 (ajustado según tu código a 0.35)
|
|
|
|
|
if max_sim >= 0.35 and mejor_match:
|
|
|
|
|
nombre_limpio = mejor_match.split('_')[0]
|
|
|
|
|
|
|
|
|
|
# Variable para rastrear quién es el ID definitivo al final del proceso
|
|
|
|
|
id_definitivo = gid
|
|
|
|
|
|
|
|
|
|
with global_mem.lock:
|
|
|
|
|
datos_id = global_mem.db.get(gid)
|
|
|
|
|
if not datos_id: return
|
|
|
|
|
|
|
|
|
|
if datos_id.get('candidato_nombre') == nombre_limpio:
|
|
|
|
|
datos_id['votos_nombre'] = datos_id.get('votos_nombre', 0) + 1
|
|
|
|
|
else:
|
|
|
|
|
datos_id['candidato_nombre'] = nombre_limpio
|
|
|
|
|
datos_id['votos_nombre'] = 1
|
|
|
|
|
|
|
|
|
|
# Sistema de Puntos
|
|
|
|
|
if max_sim >= 0.50:
|
|
|
|
|
datos_id['votos_nombre'] += 3 # Pase VIP por buena foto
|
|
|
|
|
elif max_sim >= 0.40:
|
|
|
|
|
datos_id['votos_nombre'] += 2 # Voto extra por foto decente
|
|
|
|
|
|
|
|
|
|
votos_finales = datos_id['votos_nombre']
|
|
|
|
|
|
|
|
|
|
# ⚡ Exigimos 3 votos (según tu condicional)
|
|
|
|
|
if votos_finales >= 3:
|
|
|
|
|
nombre_actual = datos_id.get('nombre')
|
|
|
|
|
|
|
|
|
|
# 1. Buscamos si ese nombre ya lo tiene un veterano en la memoria
|
|
|
|
|
id_veterano = None
|
|
|
|
|
for gid_mem, data_mem in global_mem.db.items():
|
|
|
|
|
if data_mem.get('nombre') == nombre_limpio and gid_mem != gid:
|
|
|
|
|
id_veterano = gid_mem
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 2. FUSIÓN MÁGICA: Si ya existe, lo fusionamos
|
|
|
|
|
if id_veterano is not None:
|
|
|
|
|
print(f"[FUSIÓN FACIAL] ID {gid} es el clon de la espalda. Fusionando con el veterano ID {id_veterano} ({nombre_limpio}).")
|
|
|
|
|
|
|
|
|
|
# Pasamos la memoria de ropa al veterano
|
|
|
|
|
global_mem.db[id_veterano]['firmas'].extend(datos_id.get('firmas', []))
|
|
|
|
|
if len(global_mem.db[id_veterano]['firmas']) > 15:
|
|
|
|
|
global_mem.db[id_veterano]['firmas'] = global_mem.db[id_veterano]['firmas'][-15:]
|
|
|
|
|
|
|
|
|
|
# Vaciamos al perdedor y redireccionamos
|
|
|
|
|
datos_id['firmas'] = []
|
|
|
|
|
datos_id['fusionado_con'] = id_veterano
|
|
|
|
|
|
|
|
|
|
# Actualizamos el tracker y el ID definitivo
|
|
|
|
|
trk.gid = id_veterano
|
|
|
|
|
id_definitivo = id_veterano
|
|
|
|
|
|
|
|
|
|
# 3. BAUTIZO NORMAL: Si no hay clones, procedemos con tu lógica de protección VIP
|
|
|
|
|
else:
|
|
|
|
|
if nombre_actual is not None and nombre_actual != nombre_limpio:
|
|
|
|
|
# Si ya tiene nombre y quieren robárselo, exigimos 0.56 mínimo
|
|
|
|
|
if max_sim < 0.56:
|
|
|
|
|
print(f"[RECHAZO VIP] {nombre_actual} protegido de {nombre_limpio} ({max_sim:.2f})")
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
print(f" [CORRECCIÓN VIP] Renombrando a {nombre_limpio} ({max_sim:.2f})")
|
|
|
|
|
|
|
|
|
|
if nombre_actual != nombre_limpio:
|
|
|
|
|
datos_id['nombre'] = nombre_limpio
|
|
|
|
|
print(f"[BAUTIZO] ID {gid} confirmado como {nombre_limpio}")
|
|
|
|
|
|
|
|
|
|
# Actualizamos el tiempo de vida del ID ganador
|
|
|
|
|
global_mem.db[id_definitivo]['ts'] = time.time()
|
|
|
|
|
|
|
|
|
|
# 🔊 AUDIO DE BIENVENIDA (Funciona tanto para bautizos como para fusiones)
|
|
|
|
|
if str(cam_id) == "7":
|
|
|
|
|
if not hasattr(global_mem, 'ultimos_saludos'):
|
|
|
|
|
global_mem.ultimos_saludos = {}
|
|
|
|
|
ultimo = global_mem.ultimos_saludos.get(nombre_limpio, 0)
|
|
|
|
|
|
|
|
|
|
# Aquí asumo que tienes COOLDOWN_TIME definido globalmente en tu archivo
|
|
|
|
|
if (time.time() - ultimo) > COOLDOWN_TIME:
|
|
|
|
|
global_mem.ultimos_saludos[nombre_limpio] = time.time()
|
|
|
|
|
threading.Thread(target=hilo_bienvenida, args=(nombre_limpio, genero_detectado), daemon=True).start()
|
|
|
|
|
|
|
|
|
|
# Blindaje OSNet fuera del lock (Usando el id_definitivo por si hubo fusión)
|
|
|
|
|
if max_sim > 0.50 and votos_finales >= 3:
|
|
|
|
|
global_mem.confirmar_firma_vip(id_definitivo, time.time())
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error en InsightFace asíncrono: {e}")
|
|
|
|
|
finally:
|
|
|
|
|
# ⚡ EL LIBERADOR (Vital para que el tracker no se quede bloqueado)
|
|
|
|
|
trk.procesando_rostro = False
|
|
|
|
|
|
|
|
|
|
def worker_rostros(global_mem):
|
|
|
|
|
while True:
|
|
|
|
|
roi_cabeza, gid, cam_id, trk = COLA_ROSTROS.get()
|
|
|
|
|
procesar_rostro_async(roi_cabeza, gid, cam_id, global_mem, trk)
|
|
|
|
|
COLA_ROSTROS.task_done()
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# 4. LOOP PRINCIPAL DE FUSIÓN
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
class CamStream:
|
|
|
|
|
def __init__(self, url):
|
|
|
|
|
self.url = url
|
|
|
|
|
self.cap = cv2.VideoCapture(url)
|
|
|
|
|
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
|
|
|
|
self.frame = None
|
|
|
|
|
threading.Thread(target=self._run, daemon=True).start()
|
|
|
|
|
|
|
|
|
|
def _run(self):
|
|
|
|
|
while True:
|
|
|
|
|
ret, f = self.cap.read()
|
|
|
|
|
if ret:
|
|
|
|
|
self.frame = f
|
|
|
|
|
time.sleep(0.01)
|
|
|
|
|
else:
|
|
|
|
|
time.sleep(2)
|
|
|
|
|
self.cap.open(self.url)
|
|
|
|
|
|
|
|
|
|
def dibujar_track_fusion(frame_show, trk, global_mem):
|
|
|
|
|
try: x1, y1, x2, y2 = map(int, trk.box)
|
|
|
|
|
except Exception: return
|
|
|
|
|
|
|
|
|
|
nombre_str = ""
|
|
|
|
|
if trk.gid is not None:
|
|
|
|
|
with global_mem.lock:
|
|
|
|
|
nombre = global_mem.db.get(trk.gid, {}).get('nombre')
|
|
|
|
|
if nombre: nombre_str = f" [{nombre}]"
|
|
|
|
|
|
|
|
|
|
if trk.gid is None: color, label = (150, 150, 150), f"?{trk.local_id}"
|
|
|
|
|
elif nombre_str: color, label = (255, 0, 255), f"ID:{trk.gid}{nombre_str}"
|
|
|
|
|
elif getattr(trk, 'en_grupo', False): color, label = (0, 0, 255), f"ID:{trk.gid} [grp]"
|
|
|
|
|
elif getattr(trk, 'aprendiendo', False): color, label = (255, 255, 0), f"ID:{trk.gid} [++]"
|
|
|
|
|
elif getattr(trk, 'origen_global', False): color, label = (0, 165, 255), f"ID:{trk.gid} [re-id]"
|
|
|
|
|
else: color, label = (0, 255, 0), f"ID:{trk.gid}"
|
|
|
|
|
|
|
|
|
|
cv2.rectangle(frame_show, (x1, y1), (x2, y2), color, 2)
|
|
|
|
|
(tw, th), _ = cv2.getTextSize(label, FUENTE, 0.55, 1)
|
|
|
|
|
cv2.rectangle(frame_show, (x1, y1-th-6), (x1+tw+2, y1), color, -1)
|
|
|
|
|
cv2.putText(frame_show, label, (x1+1, y1-4), FUENTE, 0.55, (0,0,0), 1)
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
print("\nIniciando Sistema")
|
|
|
|
|
model = YOLO("yolov8n.pt")
|
|
|
|
|
global_mem = GlobalMemory()
|
|
|
|
|
managers = {str(c): CamManager(c, global_mem) for c in SECUENCIA}
|
|
|
|
|
cams = [CamStream(u) for u in URLS]
|
|
|
|
|
|
|
|
|
|
threading.Thread(target=worker_rostros, args=(global_mem,), daemon=True).start()
|
|
|
|
|
|
2026-04-10 21:33:40 +00:00
|
|
|
cv2.namedWindow("SmartSoft", cv2.WINDOW_NORMAL)
|
2026-04-08 16:05:57 +00:00
|
|
|
idx = 0
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
now = time.time()
|
|
|
|
|
tiles = []
|
|
|
|
|
cam_ia = idx % len(cams)
|
|
|
|
|
|
|
|
|
|
for i, cam_obj in enumerate(cams):
|
|
|
|
|
frame = cam_obj.frame; cid = str(SECUENCIA[i])
|
|
|
|
|
if frame is None:
|
|
|
|
|
tiles.append(np.zeros((270, 480, 3), np.uint8))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
frame_show = cv2.resize(frame.copy(), (480, 270))
|
|
|
|
|
boxes = []
|
|
|
|
|
turno_activo = (i == cam_ia)
|
|
|
|
|
|
|
|
|
|
if turno_activo:
|
|
|
|
|
# ⚡ 1. Subimos la confianza de YOLO de 0.50 a 0.60 para matar siluetas dudosas
|
|
|
|
|
res = model.predict(frame_show, conf=0.60, iou=0.50, classes=[0], verbose=False, imgsz=480)
|
|
|
|
|
|
|
|
|
|
if res[0].boxes:
|
|
|
|
|
cajas_crudas = res[0].boxes.xyxy.cpu().numpy().tolist()
|
|
|
|
|
|
|
|
|
|
# ⚡ 2. FILTRO BIOMECÁNICO (Anti-Sillas)
|
|
|
|
|
for b in cajas_crudas:
|
|
|
|
|
w_caja = b[2] - b[0]
|
|
|
|
|
h_caja = b[3] - b[1]
|
|
|
|
|
|
|
|
|
|
# Un humano real es al menos 10% más alto que ancho (ratio > 1.1)
|
|
|
|
|
# Si la caja es muy cuadrada o ancha, la ignoramos y no se la pasamos a Kalman
|
|
|
|
|
if h_caja > (w_caja * 1.1):
|
|
|
|
|
boxes.append(b)
|
|
|
|
|
|
|
|
|
|
# ⚡ UPDATE LIMPIO (Sin IDs Globales)
|
|
|
|
|
tracks = managers[cid].update(boxes, frame_show, frame, now, turno_activo)
|
|
|
|
|
|
|
|
|
|
for trk in tracks:
|
|
|
|
|
if getattr(trk, 'time_since_update', 1) <= 2:
|
|
|
|
|
dibujar_track_fusion(frame_show, trk, global_mem)
|
|
|
|
|
|
|
|
|
|
if turno_activo and trk.gid is not None and not getattr(trk, 'procesando_rostro', False):
|
|
|
|
|
|
|
|
|
|
with global_mem.lock:
|
|
|
|
|
votos_actuales = global_mem.db.get(trk.gid, {}).get('votos_nombre', 0)
|
|
|
|
|
if votos_actuales >= 2:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
x1, y1, x2, y2 = trk.box
|
|
|
|
|
h_real, w_real = frame.shape[:2]
|
|
|
|
|
escala_x = w_real / 480.0
|
|
|
|
|
escala_y = h_real / 270.0
|
|
|
|
|
h_box = y2 - y1
|
|
|
|
|
|
|
|
|
|
y_exp = max(0, y1 - h_box * 0.15)
|
|
|
|
|
y_cab = min(270, y1 + h_box * 0.50)
|
|
|
|
|
|
|
|
|
|
roi_recortado = frame[
|
|
|
|
|
int(y_exp * escala_y) : int(y_cab * escala_y),
|
|
|
|
|
int(max(0, x1) * escala_x) : int(min(480, x2) * escala_x)
|
|
|
|
|
].copy()
|
|
|
|
|
|
|
|
|
|
# ⚡ COMPUERTA ÓPTICA (Filtro Anti-Tartamudeo 25x25)
|
|
|
|
|
if roi_recortado.size > 0 and roi_recortado.shape[0] >= 25 and roi_recortado.shape[1] >= 25:
|
|
|
|
|
gray = cv2.cvtColor(roi_recortado, cv2.COLOR_BGR2GRAY)
|
|
|
|
|
nitidez = cv2.Laplacian(gray, cv2.CV_64F).var()
|
|
|
|
|
|
|
|
|
|
# ⚡ BAJAMOS DE 12.0 a 5.0.
|
|
|
|
|
# Deja pasar rostros un poco borrosos por la velocidad de caminar,
|
|
|
|
|
# pero sigue bloqueando espaldas lisas.
|
|
|
|
|
if nitidez > 8.0:
|
|
|
|
|
if not COLA_ROSTROS.full():
|
|
|
|
|
ultimo_envio = getattr(trk, 'ultimo_rostro_enviado', 0)
|
|
|
|
|
|
|
|
|
|
# Solo mandamos la cara si ha pasado medio segundo desde la última vez que lo intentamos
|
|
|
|
|
if (now - ultimo_envio) > 0.5:
|
|
|
|
|
|
|
|
|
|
# Tu escudo anti-lag previo
|
|
|
|
|
if COLA_ROSTROS.qsize() < 2:
|
|
|
|
|
trk.ultimo_rostro_enviado = now # Registramos la hora del envío
|
|
|
|
|
trk.procesando_rostro = True # Bloqueamos el tracker
|
|
|
|
|
|
|
|
|
|
# Aquí pones TU línea original de la cola:
|
|
|
|
|
COLA_ROSTROS.put((roi_recortado, trk.gid, cid, trk), block=False)
|
|
|
|
|
else:
|
|
|
|
|
trk.ultimo_intento_cara = time.time() - 0.5
|
|
|
|
|
|
|
|
|
|
"""if nitidez > 8.0:
|
|
|
|
|
|
|
|
|
|
if not COLA_ROSTROS.full():
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
|
|
if COLA_ROSTROS.qsize() < 2:
|
|
|
|
|
|
|
|
|
|
COLA_ROSTROS.put((roi_recortado, trk.gid, cid, trk), block=False)
|
|
|
|
|
|
|
|
|
|
trk.procesando_rostro = True
|
|
|
|
|
|
|
|
|
|
trk.ultimo_intento_cara = time.time()
|
|
|
|
|
|
|
|
|
|
except queue.Full:
|
|
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
|
|
trk.ultimo_intento_cara = time.time() - 0.5"""
|
|
|
|
|
|
|
|
|
|
if turno_activo: cv2.circle(frame_show, (460, 20), 6, (0, 0, 255), -1)
|
|
|
|
|
|
|
|
|
|
con_id = sum(1 for t in tracks if t.gid and getattr(t, 'time_since_update', 1) == 0)
|
|
|
|
|
cv2.putText(frame_show, f"CAM {cid} [{con_id} ID]", (10, 28), FUENTE, 0.7, (255, 255, 255), 2)
|
|
|
|
|
tiles.append(frame_show)
|
|
|
|
|
|
|
|
|
|
if len(tiles) == 6:
|
|
|
|
|
cv2.imshow("SmartSoft Fusion", np.vstack([np.hstack(tiles[0:3]), np.hstack(tiles[3:6])]))
|
|
|
|
|
|
|
|
|
|
idx += 1
|
|
|
|
|
key = cv2.waitKey(1) & 0xFF
|
|
|
|
|
|
|
|
|
|
if key == ord('q'):
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
elif key == ord('r'):
|
|
|
|
|
print("\n[MODO REGISTRO] Escaneando mosaico para registrar...")
|
|
|
|
|
mejor_roi = None
|
|
|
|
|
max_area = 0
|
|
|
|
|
face_metadata = None
|
|
|
|
|
|
|
|
|
|
for i, cam_obj in enumerate(cams):
|
|
|
|
|
if cam_obj.frame is None: continue
|
|
|
|
|
|
|
|
|
|
faces = app.get(cam_obj.frame)
|
|
|
|
|
for face in faces:
|
|
|
|
|
fw = face.bbox[2] - face.bbox[0]
|
|
|
|
|
fh = face.bbox[3] - face.bbox[1]
|
|
|
|
|
area = fw * fh
|
|
|
|
|
if area > max_area:
|
|
|
|
|
max_area = area
|
|
|
|
|
h_frame, w_frame = cam_obj.frame.shape[:2]
|
|
|
|
|
|
|
|
|
|
m_x, m_y = int(fw * 0.30), int(fh * 0.30)
|
|
|
|
|
y1 = max(0, int(face.bbox[1]) - m_y)
|
|
|
|
|
y2 = min(h_frame, int(face.bbox[3]) + m_y)
|
|
|
|
|
x1 = max(0, int(face.bbox[0]) - m_x)
|
|
|
|
|
x2 = min(w_frame, int(face.bbox[2]) + m_x)
|
|
|
|
|
|
|
|
|
|
mejor_roi = cam_obj.frame[y1:y2, x1:x2]
|
|
|
|
|
face_metadata = face
|
|
|
|
|
|
|
|
|
|
if mejor_roi is not None and mejor_roi.size > 0:
|
|
|
|
|
cv2.imshow("Nueva Persona", mejor_roi)
|
|
|
|
|
cv2.waitKey(1)
|
|
|
|
|
|
|
|
|
|
nom = input("Escribe el nombre de la persona: ").strip()
|
|
|
|
|
cv2.destroyWindow("Nueva Persona")
|
|
|
|
|
|
|
|
|
|
if nom:
|
|
|
|
|
import json
|
|
|
|
|
genero_guardado = "Man" if face_metadata.sex == "M" else "Woman"
|
|
|
|
|
|
|
|
|
|
ruta_generos = os.path.join("cache_nombres", "generos.json")
|
|
|
|
|
os.makedirs("cache_nombres", exist_ok=True)
|
|
|
|
|
dic_generos = {}
|
|
|
|
|
|
|
|
|
|
if os.path.exists(ruta_generos):
|
|
|
|
|
try:
|
|
|
|
|
with open(ruta_generos, 'r') as f:
|
|
|
|
|
dic_generos = json.load(f)
|
|
|
|
|
except Exception: pass
|
|
|
|
|
|
|
|
|
|
dic_generos[nom] = genero_guardado
|
|
|
|
|
with open(ruta_generos, 'w') as f:
|
|
|
|
|
json.dump(dic_generos, f)
|
|
|
|
|
|
|
|
|
|
ruta_db = "db_institucion"
|
|
|
|
|
os.makedirs(ruta_db, exist_ok=True)
|
|
|
|
|
cv2.imwrite(os.path.join(ruta_db, f"{nom}.jpg"), mejor_roi)
|
|
|
|
|
|
|
|
|
|
print(f"[OK] Rostro guardado (Género autodetectado: {genero_guardado})")
|
|
|
|
|
print(" Sincronizando base de datos en caliente...")
|
|
|
|
|
|
|
|
|
|
nuevos_vectores = gestionar_vectores(actualizar=True)
|
|
|
|
|
with IA_LOCK:
|
|
|
|
|
BASE_DATOS_ROSTROS.clear()
|
|
|
|
|
BASE_DATOS_ROSTROS.update(nuevos_vectores)
|
|
|
|
|
print(" Sistema listo.")
|
|
|
|
|
else:
|
|
|
|
|
print("[!] Registro cancelado.")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|