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() cv2.namedWindow("SmartSoft", cv2.WINDOW_NORMAL) 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()