""" Module de reconnaissance faciale simplifiée pour ENSIM Utilise OpenCV avec fallback console si GUI non disponible """ import os import pickle import hashlib import time from pathlib import Path # Gestion d'import sécurisée pour OpenCV try: import cv2 import numpy as np OPENCV_AVAILABLE = True # Vérifier si la GUI est disponible (imshow fonctionne) try: # Test rapide pour voir si imshow est implémenté test_window = cv2.namedWindow("test", cv2.WINDOW_NORMAL) cv2.destroyWindow("test") GUI_AVAILABLE = True print("OpenCV disponible (mode graphique)") except: GUI_AVAILABLE = False print("Interface graphique OpenCV non disponible (mode console)") except ImportError as e: print(f"OpenCV non disponible: {e}") print(" Pour l'activer: pip install opencv-python") OPENCV_AVAILABLE = False GUI_AVAILABLE = False cv2 = None np = None # Version simplifiée sans face_recognition FACE_SIMPLIFIED = True # Dossier pour stocker les données des visages FACE_DATA_DIR = Path("face_data") try: FACE_DATA_DIR.mkdir(exist_ok=True) except: print(f"Impossible de créer le dossier {FACE_DATA_DIR}") def get_face_hash_from_image(frame): """ Crée un hash simple d'un visage à partir d'une image Version simplifiée sans face_recognition """ if not OPENCV_AVAILABLE: return None try: # Convertir en gris gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Redimensionner à une taille fixe resized = cv2.resize(gray, (100, 100)) # Convertir en bytes et créer un hash img_bytes = resized.tobytes() face_hash = hashlib.sha256(img_bytes).hexdigest() return face_hash except Exception as e: print(f"Erreur création hash: {e}") return None def detect_face(frame): """ Détecte un visage dans l'image en utilisant le classificateur Haar Cascade d'OpenCV """ if not OPENCV_AVAILABLE: return None try: # Charger le classificateur Haar Cascade pour la détection de visages face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') # Convertir en gris gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Détecter les visages faces = face_cascade.detectMultiScale(gray, 1.1, 4) if len(faces) == 0: return None # Prendre le premier visage (x, y, w, h) = faces[0] face_roi = frame[y:y+h, x:x+w] return face_roi except Exception as e: print(f"Erreur détection visage: {e}") return None """ Capture un visage depuis la webcam (version avec fallback console) """ if not OPENCV_AVAILABLE: print("OpenCV non disponible") return None try: # Initialiser la webcam video_capture = cv2.VideoCapture(0) if not video_capture.isOpened(): print("Impossible d'ouvrir la webcam") return None print("\n" + "="*50) print("CAPTURE FACIALE - ENSIM") print("="*50) if GUI_AVAILABLE: print("Fenêtre de capture va s'ouvrir...") print(" [ESPACE] pour capturer") print(" [Q] pour annuler") else: print("Mode console - regarde la caméra") print(" Capture automatique dans 3 secondes...") time.sleep(1) print(" 3...") time.sleep(1) print(" 2...") time.sleep(1) print(" 1...") time.sleep(1) face_image = None face_hash = None captured = False # Variables pour le mode console console_attempts = 0 max_console_attempts = 5 while not captured: ret, frame = video_capture.read() if not ret: break # Détecter le visage face_roi = detect_face(frame) # Préparer l'image d'affichage display_frame = frame.copy() if face_roi is not None: face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 4) for (x, y, w, h) in faces: cv2.rectangle(display_frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText(display_frame, "VISAGE", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # Mode GUI if GUI_AVAILABLE: cv2.putText(display_frame, "ESPACE: capturer | Q: quitter", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if face_roi is None: cv2.putText(display_frame, "AUCUN VISAGE DETECTE", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow('Capture Visage - ENSIM', display_frame) key = cv2.waitKey(1) & 0xFF if key == ord(' '): if face_roi is not None: face_hash = get_face_hash_from_image(face_roi) face_image = face_roi captured = True print("\nVisage capturé avec succès!") else: print("\nAucun visage détecté, réessaie") elif key == ord('q'): print("\nCapture annulée") break # Mode console (sans GUI) else: console_attempts += 1 print(f"\rTentative {console_attempts}/{max_console_attempts}... ", end="", flush=True) if face_roi is not None: face_hash = get_face_hash_from_image(face_roi) face_image = face_roi captured = True print("\nVisage capturé avec succès!") elif console_attempts >= max_console_attempts: print("\nÉchec capture - aucun visage détecté") break else: time.sleep(0.5) # Pause entre les tentatives # Nettoyer video_capture.release() if GUI_AVAILABLE: cv2.destroyAllWindows() if face_hash: print("Données faciales enregistrées") return {"hash": face_hash, "image": face_image} print("Aucune donnée faciale capturée") return None except Exception as e: print(f"\nErreur capture: {e}") return None def capture_face_from_webcam(): """ Capture un visage depuis la webcam (version avec fallback console) """ if not OPENCV_AVAILABLE: print("❌ OpenCV non disponible") return None try: # Essayer différents backends sur Windows video_capture = None # Liste des backends à essayer backends = [] if hasattr(cv2, 'CAP_DSHOW'): backends.append(cv2.CAP_DSHOW) # DirectShow (meilleur sur Windows) if hasattr(cv2, 'CAP_MSMF'): backends.append(cv2.CAP_MSMF) # Media Foundation (défaut) backends.append(0) # Backend par défaut for backend in backends: try: print(f"🔄 Tentative avec backend: {backend}") if backend != 0: video_capture = cv2.VideoCapture(0, backend) else: video_capture = cv2.VideoCapture(0) if video_capture.isOpened(): print(f"✅ Webcam ouverte avec backend: {backend}") break else: video_capture.release() except: continue if not video_capture or not video_capture.isOpened(): print("❌ Impossible d'ouvrir la webcam avec aucun backend") return None # Configurer la résolution pour de meilleures performances video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) video_capture.set(cv2.CAP_PROP_FPS, 30) print("\n" + "="*50) print("🔵 CAPTURE FACIALE - ENSIM") print("="*50) if GUI_AVAILABLE: print("📸 Fenêtre de capture va s'ouvrir...") print(" [ESPACE] pour capturer") print(" [Q] pour annuler") print(" Appuie sur ESPACE quand ton visage est dans le cadre vert") face_image = None face_hash = None captured = False # Variables pour le mode console console_attempts = 0 max_console_attempts = 5 # Créer une fenêtre window_name = 'Capture Visage - ENSIM' if GUI_AVAILABLE: cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) cv2.resizeWindow(window_name, 800, 600) while not captured: ret, frame = video_capture.read() if not ret: print("❌ Erreur lecture frame") break # Détecter le visage face_roi = detect_face(frame) # Préparer l'image d'affichage display_frame = frame.copy() # Dessiner les rectangles et instructions if face_roi is not None: face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 4) for (x, y, w, h) in faces: # Rectangle vert pour le visage cv2.rectangle(display_frame, (x, y), (x+w, y+h), (0, 255, 0), 3) cv2.putText(display_frame, "VISAGE DETECTE", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # Ajouter un effet de glow cv2.rectangle(display_frame, (x-2, y-2), (x+w+2, y+h+2), (0, 255, 0), 1) # Mode GUI if GUI_AVAILABLE: # Instructions en haut cv2.putText(display_frame, "ESPACE: capturer | Q: quitter", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if face_roi is None: cv2.putText(display_frame, "⚠️ AUCUN VISAGE DETECTE - Regarde la camera", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) else: cv2.putText(display_frame, "✅ VISAGE DETECTE - Appuie sur ESPACE", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # Ajouter un guide au centre h, w = display_frame.shape[:2] center_x, center_y = w//2, h//2 cv2.circle(display_frame, (center_x, center_y), 5, (0, 255, 255), -1) cv2.rectangle(display_frame, (center_x-150, center_y-150), (center_x+150, center_y+150), (255, 255, 0), 2) cv2.putText(display_frame, "Place ton visage ici", (center_x-120, center_y-160), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2) cv2.imshow(window_name, display_frame) key = cv2.waitKey(1) & 0xFF if key == ord(' '): if face_roi is not None: face_hash = get_face_hash_from_image(face_roi) face_image = face_roi captured = True print("\n✅ Visage capturé avec succès!") # Afficher un message de confirmation cv2.putText(display_frame, "CAPTURE REUSSIE !", (center_x-150, center_y), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) cv2.imshow(window_name, display_frame) cv2.waitKey(1000) # Afficher 1 seconde else: print("\n❌ Aucun visage détecté, réessaie") elif key == ord('q'): print("\n❌ Capture annulée") break # Mode console (sans GUI) else: console_attempts += 1 print(f"\r📸 Tentative {console_attempts}/{max_console_attempts}... ", end="", flush=True) if face_roi is not None: face_hash = get_face_hash_from_image(face_roi) face_image = face_roi captured = True print("\n✅ Visage capturé avec succès!") elif console_attempts >= max_console_attempts: print("\n❌ Échec capture - aucun visage détecté") break else: time.sleep(0.5) # Pause entre les tentatives # Nettoyer video_capture.release() if GUI_AVAILABLE: cv2.destroyAllWindows() cv2.waitKey(1) # Petit délai pour que les fenêtres se ferment if face_hash: print("✅ Données faciales enregistrées") return {"hash": face_hash, "image": face_image} print("❌ Aucune donnée faciale capturée") return None except Exception as e: print(f"\n❌ Erreur capture: {e}") import traceback traceback.print_exc() return None def save_face_encoding(email, face_data): """Sauvegarde les données du visage""" if not OPENCV_AVAILABLE or face_data is None: return False try: # Nettoyer l'email pour en faire un nom de fichier valide safe_email = email.replace('@', '_at_').replace('.', '_') filename = FACE_DATA_DIR / f"{safe_email}.pkl" with open(filename, 'wb') as f: pickle.dump(face_data, f) print(f"✅ Visage sauvegardé pour {email}") return True except Exception as e: print(f"❌ Erreur sauvegarde: {e}") return False def load_face_encoding(email): """Charge les données du visage""" if not OPENCV_AVAILABLE: return None try: safe_email = email.replace('@', '_at_').replace('.', '_') filename = FACE_DATA_DIR / f"{safe_email}.pkl" if not filename.exists(): return None with open(filename, 'rb') as f: return pickle.load(f) except Exception as e: print(f"❌ Erreur chargement: {e}") return None def verify_face(email, tolerance=0.7): """Vérifie le visage (version avec fallback console)""" if not OPENCV_AVAILABLE: return False, "OpenCV non disponible" try: # Charger les données sauvegardées saved_data = load_face_encoding(email) if saved_data is None: return False, "Aucun visage enregistré pour cet email" saved_hash = saved_data.get("hash") # Initialiser la webcam video_capture = cv2.VideoCapture(0) if not video_capture.isOpened(): return False, "Webcam non disponible" print("\n" + "="*50) print("🔵 VÉRIFICATION FACIALE - ENSIM") print("="*50) if GUI_AVAILABLE: print("📸 Regarde la caméra...") else: print("📸 Mode console - regarde la caméra") print(" Vérification automatique dans 3 secondes...") time.sleep(1) print(" 3...") time.sleep(1) print(" 2...") time.sleep(1) print(" 1...") time.sleep(1) result = False message = "Vérification échouée" max_frames = 45 # ~1.5 secondes frame_count = 0 matched = False while frame_count < max_frames and not matched: ret, frame = video_capture.read() if not ret: break # Détecter le visage face_roi = detect_face(frame) # Préparer l'image d'affichage display_frame = frame.copy() if face_roi is not None: # Dessiner un rectangle face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 4) for (x, y, w, h) in faces: cv2.rectangle(display_frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # Calculer le hash du visage actuel current_hash = get_face_hash_from_image(face_roi) # Comparer les hash if current_hash and current_hash == saved_hash: result = True message = "✅ Visage reconnu !" matched = True if GUI_AVAILABLE: cv2.putText(display_frame, "RECONNU !", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) cv2.putText(display_frame, message, (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) # Mode GUI if GUI_AVAILABLE: # Afficher le compteur progress = f"Scan... {frame_count+1}/{max_frames}" cv2.putText(display_frame, progress, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if face_roi is None: cv2.putText(display_frame, "⚠️ AUCUN VISAGE", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow('Verification Faciale - ENSIM', display_frame) cv2.waitKey(1) # Mode console else: if face_roi is not None: print(f"\r📸 Visage détecté - analyse... {frame_count+1}/{max_frames}", end="", flush=True) else: print(f"\r📸 Aucun visage détecté - {frame_count+1}/{max_frames}", end="", flush=True) time.sleep(0.1) frame_count += 1 # Nettoyer print() # Nouvelle ligne video_capture.release() if GUI_AVAILABLE: cv2.destroyAllWindows() if matched: print(" Vérification réussie !") else: print(" Échec de la vérification") return result, message except Exception as e: print(f"\n Erreur vérification: {e}") return False, f"Erreur: {str(e)}" def delete_face_encoding(email): """Supprime le visage""" try: safe_email = email.replace('@', '_at_').replace('.', '_') filename = FACE_DATA_DIR / f"{safe_email}.pkl" if filename.exists(): filename.unlink() print(f" Visage supprimé pour {email}") return True else: print(f" Aucun visage trouvé pour {email}") except Exception as e: print(f" Erreur suppression: {e}") return False def user_has_face(email): """Vérifie si l'utilisateur a un visage""" return load_face_encoding(email) is not None def list_all_users_with_faces(): """Liste tous les utilisateurs ayant un visage enregistré""" try: files = list(FACE_DATA_DIR.glob("*.pkl")) users = [] for f in files: # Convertir le nom de fichier en email approximatif name = f.stem.replace('_at_', '@').replace('_', '.') users.append(name) return users except: return []