Subh775's picture
Update recognizer.py
15940e7 verified
import cv2
import os
import numpy as np
from deepface import DeepFace
import logging
from typing import Dict, List, Tuple, Optional
import sqlite3
from datetime import datetime
import pytz
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class EnhancedFaceRecognizer:
"""Enhanced face recognition system using DeepFace with optimizations"""
def __init__(self, known_faces_dir: str = 'static/known_faces', db_path: str = 'attendance.db'):
self.known_faces_dir = known_faces_dir
self.db_path = db_path
self.known_faces = {}
self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
self.models = ['VGG-Face', 'Facenet', 'OpenFace'] # Multiple models for better accuracy
self.current_model = 'VGG-Face'
self.recognition_threshold = 0.4 # Cosine distance threshold
self.confidence_threshold = 65 # Minimum confidence percentage
# Create directories if they don't exist
os.makedirs(self.known_faces_dir, exist_ok=True)
# Load known faces
self.load_known_faces()
def load_known_faces(self) -> None:
"""Load known faces from database and file system"""
try:
self.known_faces = {}
# Connect to database
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Get all users with face images
cursor.execute('SELECT id, name, face_encoding_path FROM users WHERE face_encoding_path IS NOT NULL')
users = cursor.fetchall()
conn.close()
for user_id, name, face_path in users:
full_path = os.path.join(self.known_faces_dir, face_path)
if os.path.exists(full_path):
# Validate image file
if self._validate_image(full_path):
self.known_faces[name] = {
'user_id': user_id,
'image_path': full_path,
'embeddings': {} # Cache for embeddings
}
else:
logger.warning(f"Invalid image file for user {name}: {full_path}")
else:
logger.warning(f"Image file not found for user {name}: {full_path}")
logger.info(f"Loaded {len(self.known_faces)} known faces")
except Exception as e:
logger.error(f"Error loading known faces: {e}")
self.known_faces = {}
def _validate_image(self, image_path: str) -> bool:
"""Validate if image file is readable and contains a face"""
try:
image = cv2.imread(image_path)
if image is None:
return False
# Check if image contains at least one face
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)
return len(faces) > 0
except Exception as e:
logger.error(f"Error validating image {image_path}: {e}")
return False
def preprocess_image(self, image: np.ndarray) -> np.ndarray:
"""Preprocess image for better recognition"""
try:
# Convert to RGB if needed
if len(image.shape) == 3 and image.shape[2] == 3:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Enhance image quality
# 1. Histogram equalization for better contrast
if len(image.shape) == 3:
# Convert to LAB color space
lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)
l, a, b = cv2.split(lab)
# Apply CLAHE to L channel
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
l = clahe.apply(l)
# Merge channels
enhanced = cv2.merge([l, a, b])
image = cv2.cvtColor(enhanced, cv2.COLOR_LAB2RGB)
else:
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
image = clahe.apply(image)
# 2. Gaussian blur to reduce noise
image = cv2.GaussianBlur(image, (1, 1), 0)
return image
except Exception as e:
logger.error(f"Error preprocessing image: {e}")
return image
def detect_faces(self, image: np.ndarray) -> List[Tuple[int, int, int, int]]:
"""Detect faces in image using Haar cascade"""
try:
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) if len(image.shape) == 3 else image
faces = self.face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE
)
return faces.tolist()
except Exception as e:
logger.error(f"Error detecting faces: {e}")
return []
def extract_face_region(self, image: np.ndarray, face_coords: Tuple[int, int, int, int]) -> np.ndarray:
"""Extract face region from image with padding"""
try:
x, y, w, h = face_coords
# Add padding around face
padding = int(min(w, h) * 0.2)
# Calculate padded coordinates
x1 = max(0, x - padding)
y1 = max(0, y - padding)
x2 = min(image.shape[1], x + w + padding)
y2 = min(image.shape[0], y + h + padding)
# Extract face region
face_region = image[y1:y2, x1:x2]
return face_region
except Exception as e:
logger.error(f"Error extracting face region: {e}")
return image
def get_face_embedding(self, image_path: str, model_name: str = None) -> Optional[np.ndarray]:
"""Get face embedding using DeepFace"""
try:
if model_name is None:
model_name = self.current_model
# Use DeepFace to get embedding
embedding = DeepFace.represent(
img_path=image_path,
model_name=model_name,
enforce_detection=False,
detector_backend='opencv'
)
if isinstance(embedding, list) and len(embedding) > 0:
return np.array(embedding[0]['embedding'])
elif isinstance(embedding, dict):
return np.array(embedding['embedding'])
else:
return None
except Exception as e:
logger.debug(f"Error getting embedding for {image_path} with {model_name}: {e}")
return None
def compare_faces(self, img1_path: str, img2_path: str, model_name: str = None) -> Dict:
"""Compare two faces using DeepFace"""
try:
if model_name is None:
model_name = self.current_model
result = DeepFace.verify(
img1_path=img1_path,
img2_path=img2_path,
model_name=model_name,
distance_metric='cosine',
enforce_detection=False,
detector_backend='opencv'
)
return result
except Exception as e:
logger.debug(f"Error comparing faces: {e}")
return {'verified': False, 'distance': 1.0}
def recognize_face_advanced(self, frame: np.ndarray, use_multiple_models: bool = True) -> Tuple[Optional[Dict], float]:
"""Advanced face recognition with multiple models and preprocessing"""
try:
if not self.known_faces:
return None, 0
# Preprocess the frame
processed_frame = self.preprocess_image(frame.copy())
# Detect faces in the frame
faces = self.detect_faces(processed_frame)
if not faces:
return None, 0
# Use the largest detected face
largest_face = max(faces, key=lambda f: f[2] * f[3])
# Extract face region
face_region = self.extract_face_region(processed_frame, largest_face)
# Save temporary frame for DeepFace
temp_path = 'temp_recognition_frame.jpg'
# Convert back to BGR for saving
if len(face_region.shape) == 3:
face_bgr = cv2.cvtColor(face_region, cv2.COLOR_RGB2BGR)
else:
face_bgr = face_region
cv2.imwrite(temp_path, face_bgr)
best_match = None
highest_confidence = 0
# Models to try
models_to_use = self.models if use_multiple_models else [self.current_model]
for name, face_data in self.known_faces.items():
best_model_result = None
best_model_confidence = 0
# Try multiple models for this face
for model in models_to_use:
try:
result = self.compare_faces(temp_path, face_data['image_path'], model)
if result['verified'] and result['distance'] < self.recognition_threshold:
confidence = (1 - result['distance']) * 100
if confidence > best_model_confidence:
best_model_confidence = confidence
best_model_result = {
'name': name,
'user_id': face_data['user_id'],
'confidence': confidence,
'model_used': model,
'distance': result['distance']
}
except Exception as e:
logger.debug(f"Model {model} failed for {name}: {e}")
continue
# Check if this is the best match overall
if best_model_result and best_model_confidence > highest_confidence and best_model_confidence > self.confidence_threshold:
highest_confidence = best_model_confidence
best_match = best_model_result
# Clean up temp file
if os.path.exists(temp_path):
os.remove(temp_path)
return best_match, highest_confidence
except Exception as e:
logger.error(f"Advanced face recognition error: {e}")
return None, 0
def recognize_face(self, frame: np.ndarray) -> Tuple[Optional[Dict], float]:
"""Main face recognition method (backward compatibility)"""
return self.recognize_face_advanced(frame, use_multiple_models=False)
def add_known_face(self, name: str, image_path: str) -> bool:
"""Add a new known face"""
try:
if not os.path.exists(image_path):
logger.error(f"Image file not found: {image_path}")
return False
if not self._validate_image(image_path):
logger.error(f"Invalid image file: {image_path}")
return False
# Add to database (assuming it's already added)
# Just update our known_faces dictionary
self.load_known_faces()
return name in self.known_faces
except Exception as e:
logger.error(f"Error adding known face: {e}")
return False
def update_model_settings(self, model_name: str = None, threshold: float = None, confidence_threshold: float = None):
"""Update recognition settings"""
if model_name and model_name in self.models:
self.current_model = model_name
logger.info(f"Model changed to: {model_name}")
if threshold is not None:
self.recognition_threshold = threshold
logger.info(f"Recognition threshold changed to: {threshold}")
if confidence_threshold is not None:
self.confidence_threshold = confidence_threshold
logger.info(f"Confidence threshold changed to: {confidence_threshold}")
def get_recognition_stats(self) -> Dict:
"""Get recognition system statistics"""
return {
'total_known_faces': len(self.known_faces),
'current_model': self.current_model,
'available_models': self.models,
'recognition_threshold': self.recognition_threshold,
'confidence_threshold': self.confidence_threshold,
'known_faces_dir': self.known_faces_dir
}
# Utility functions for standalone usage
def recognize_from_webcam(recognizer: EnhancedFaceRecognizer, camera_index: int = 1): # changed camera index to 1
"""Recognize faces from webcam feed"""
cap = cv2.VideoCapture(camera_index)
if not cap.isOpened():
logger.error("Could not open webcam")
return
logger.info("Starting webcam recognition. Press 'q' to quit.")
while True:
ret, frame = cap.read()
if not ret:
break
# Recognize face
result, confidence = recognizer.recognize_face_advanced(frame)
# Draw results on frame
if result:
# Draw bounding box and name
faces = recognizer.detect_faces(frame)
if faces:
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
label = f"{result['name']} ({confidence:.1f}%)"
cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# Show frame
cv2.imshow('Face Recognition', frame)
# Break on 'q' key
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# Test the recognizer
recognizer = EnhancedFaceRecognizer()
print("Enhanced Face Recognizer Test")
print(f"Stats: {recognizer.get_recognition_stats()}")
# Uncomment to test with webcam
# recognize_from_webcam(recognizer)