Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import cv2 | |
| from matplotlib import pyplot as plt | |
| import torch | |
| # In the below line,remove '.' while working on your local system. However Make sure that '.' is present before face_recognition_model while uploading to the server, Do not remove it. | |
| from .face_recognition_model import * | |
| from PIL import Image | |
| import base64 | |
| import io | |
| import os | |
| import joblib | |
| import pickle | |
| from sklearn.preprocessing import StandardScaler, normalize | |
| # Add more imports if required | |
| ########################################################################################################################################### | |
| # Caution: Don't change any of the filenames, function names and definitions # | |
| # Always use the current_path + file_name for refering any files, without it we cannot access files on the server # | |
| ########################################################################################################################################### | |
| # Current_path stores absolute path of the file from where it runs. | |
| current_path = os.path.dirname(os.path.abspath(__file__)) | |
| # Simple error handling for missing models | |
| def handle_missing_model(error_msg): | |
| """Helper function to handle missing model files""" | |
| print(f"ERROR: {error_msg}") | |
| return "Unknown" | |
| #1) The below function is used to detect faces in the given image. | |
| #2) It returns only one image which has maximum area out of all the detected faces in the photo. | |
| #3) If no face is detected,then it returns zero(0). | |
| def detected_face(image): | |
| eye_haar = current_path + '/haarcascade_eye.xml' | |
| face_haar = current_path + '/haarcascade_frontalface_default.xml' | |
| face_cascade = cv2.CascadeClassifier(face_haar) | |
| eye_cascade = cv2.CascadeClassifier(eye_haar) | |
| gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| faces = face_cascade.detectMultiScale(gray, 1.3, 5) | |
| face_areas=[] | |
| images = [] | |
| required_image = None | |
| if len(faces) == 0: | |
| return None | |
| for i, (x,y,w,h) in enumerate(faces): | |
| face_cropped = gray[y:y+h, x:x+w] | |
| face_areas.append(w*h) | |
| images.append(face_cropped) | |
| if face_areas: | |
| max_area_idx = np.argmax(face_areas) | |
| required_image = images[max_area_idx] | |
| required_image = Image.fromarray(required_image) | |
| return required_image | |
| return None | |
| #1) Images captured from mobile is passed as parameter to the below function in the API call. It returns the similarity measure between given images. | |
| #2) The image is passed to the function in base64 encoding, Code for decoding the image is provided within the function. | |
| #3) Define an object to your siamese network here in the function and load the weight from the trained network, set it in evaluation mode. | |
| #4) Get the features for both the faces from the network and return the similarity measure, Euclidean,cosine etc can be it. But choose the Relevant measure. | |
| #5) For loading your model use the current_path+'your model file name', anyhow detailed example is given in comments to the function | |
| #Caution: Don't change the definition or function name; for loading the model use the current_path for path example is given in comments to the function | |
| def get_similarity(img1, img2): | |
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |
| det_img1 = detected_face(img1) | |
| det_img2 = detected_face(img2) | |
| if(det_img1 is None or det_img2 is None): | |
| det_img1 = Image.fromarray(cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)) | |
| det_img2 = Image.fromarray(cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)) | |
| face1 = trnscm(det_img1).unsqueeze(0) | |
| face2 = trnscm(det_img2).unsqueeze(0) | |
| try: | |
| # Load the Siamese model | |
| model = SiameseNetwork().to(device) | |
| model_path = current_path + '/siamese_model.t7' | |
| if os.path.exists(model_path): | |
| ckpt = torch.load(model_path, map_location=device) | |
| model.load_state_dict(ckpt['net_dict']) | |
| model.eval() | |
| # Move tensors to device | |
| face1 = face1.to(device) | |
| face2 = face2.to(device) | |
| # Get dissimilarity using Euclidean distance (same as notebook) | |
| with torch.no_grad(): | |
| output1, output2 = model(face1, face2) | |
| euclidean_distance = torch.nn.functional.pairwise_distance(output1, output2) | |
| # Return raw distance as dissimilarity (lower = more similar) | |
| dissimilarity = euclidean_distance.item() | |
| return dissimilarity | |
| else: | |
| print(f"Model file not found: {model_path}") | |
| return 0.0 | |
| except Exception as e: | |
| print(f"Error in similarity calculation: {str(e)}") | |
| return 0.0 | |
| #1) Image captured from mobile is passed as parameter to this function in the API call, It returns the face class in the string form ex: "Person1" | |
| #2) The image is passed to the function in base64 encoding, Code to decode the image provided within the function | |
| #3) Define an object to your network here in the function and load the weight from the trained network, set it in evaluation mode | |
| #4) Perform necessary transformations to the input(detected face using the above function). | |
| #5) Along with the siamese, you need the classifier as well, which is to be finetuned with the faces that you are training | |
| ##Caution: Don't change the definition or function name; for loading the model use the current_path for path example is given in comments to the function | |
| def get_face_class(img1): | |
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |
| print(f"LOG: Using device: {device}") | |
| det_img1 = detected_face(img1) | |
| if(det_img1 is None): | |
| det_img1 = Image.fromarray(cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)) | |
| print("LOG: No face detected, using full image") | |
| else: | |
| print("LOG: Face detected successfully") | |
| try: | |
| # Load the Siamese model for feature extraction | |
| model = SiameseNetwork().to(device) | |
| model_path = current_path + '/siamese_model.t7' | |
| print(f"LOG: Looking for model at: {model_path}") | |
| print(f"LOG: Model file exists: {os.path.exists(model_path)}") | |
| if os.path.exists(model_path): | |
| ckpt = torch.load(model_path, map_location=device) | |
| model.load_state_dict(ckpt['net_dict']) | |
| model.eval() | |
| print("LOG: Siamese model loaded successfully") | |
| # Preprocess the image | |
| face_tensor = trnscm(det_img1).unsqueeze(0).to(device) | |
| print(f"LOG: Face tensor shape: {face_tensor.shape}") | |
| # Extract features using Siamese network | |
| with torch.no_grad(): | |
| features = model.forward_once(face_tensor) | |
| features_np = features.cpu().numpy() | |
| print(f"LOG: Extracted features shape: {features_np.shape}") | |
| # Load the SVM classifier, scaler, and normalization flag | |
| classifier_path = current_path + '/face_recognition_classifier.pkl' | |
| scaler_path = current_path + '/face_recognition_scaler.pkl' | |
| norm_flag_path = current_path + '/normalization_flag.pkl' | |
| print(f"LOG: Looking for classifier files:") | |
| print(f"LOG: Classifier: {os.path.exists(classifier_path)}") | |
| print(f"LOG: Scaler: {os.path.exists(scaler_path)}") | |
| print(f"LOG: Normalization flag: {os.path.exists(norm_flag_path)}") | |
| # Load normalization flag (default to False if not found) | |
| needs_normalization = False | |
| if os.path.exists(norm_flag_path): | |
| try: | |
| with open(norm_flag_path, 'rb') as f: | |
| needs_normalization = pickle.load(f) | |
| print(f"LOG: Normalization flag loaded: {needs_normalization}") | |
| except Exception as e: | |
| print(f"WARN: Could not load normalization flag: {e}") | |
| # Try to load the classifier | |
| classifier = None | |
| try: | |
| if os.path.exists(classifier_path): | |
| print("LOG: Loading SVM classifier...") | |
| classifier = joblib.load(classifier_path) | |
| print(f"LOG: SVM classifier loaded successfully") | |
| print(f"LOG: Classifier type: {type(classifier).__name__}") | |
| # Debug: Check the actual number of classes in the SVM | |
| if hasattr(classifier, 'classes_'): | |
| print(f"LOG: SVM trained classes: {classifier.classes_}") | |
| print(f"LOG: Number of SVM classes: {len(classifier.classes_)}") | |
| print(f"LOG: Class labels: {list(classifier.classes_)}") | |
| else: | |
| print("WARN: Classifier doesn't have classes_ attribute") | |
| else: | |
| print("ERROR: No classifier file found!") | |
| return "Unknown" | |
| except Exception as classifier_error: | |
| print(f"ERROR: Failed to load classifier: {classifier_error}") | |
| return handle_missing_model("Failed to load classifier") | |
| if classifier is not None: | |
| # Apply L2 normalization if needed | |
| if needs_normalization: | |
| print("LOG: Applying L2 normalization to features") | |
| features_normalized = normalize(features_np, norm='l2') | |
| else: | |
| print("LOG: Using features without L2 normalization") | |
| features_normalized = features_np | |
| # Load and apply the scaler (only if it exists and is not None) | |
| if os.path.exists(scaler_path): | |
| scaler = joblib.load(scaler_path) | |
| # Check if scaler is None (indicates it wasn't used during training) | |
| if scaler is not None: | |
| features_scaled = scaler.transform(features_normalized) | |
| print("LOG: Features scaled using saved StandardScaler") | |
| else: | |
| features_scaled = features_normalized | |
| print("LOG: StandardScaler not used (embeddings are L2 normalized)") | |
| else: | |
| print("WARN: Scaler file not found. Using unscaled features.") | |
| features_scaled = features_normalized | |
| # Make prediction (predict returns the class label, not the index into classes_) | |
| predicted_label = int(classifier.predict(features_scaled)[0]) | |
| print(f"LOG: Predicted label: {predicted_label}") | |
| # Get confidence scores if available; probabilities are aligned with classes_ indices | |
| confidence = None | |
| if hasattr(classifier, 'predict_proba'): | |
| probabilities = classifier.predict_proba(features_scaled)[0] | |
| if hasattr(classifier, 'classes_'): | |
| try: | |
| class_index = int(np.where(classifier.classes_ == predicted_label)[0][0]) | |
| except Exception as e: | |
| print(f"WARN: Could not map label to classes_ index ({e}); using argmax as fallback") | |
| class_index = int(np.argmax(probabilities)) | |
| else: | |
| # Assume labels are 0..N-1 aligned with probability order | |
| class_index = predicted_label if 0 <= predicted_label < len(probabilities) else int(np.argmax(probabilities)) | |
| confidence = float(probabilities[class_index]) | |
| print(f"LOG: Prediction confidence: {confidence:.3f} ({confidence*100:.1f}%)") | |
| # Map predicted label to team member name | |
| from .face_recognition_model import classes | |
| predicted_class = classes[predicted_label] | |
| if confidence is not None: | |
| return f"{predicted_class} (confidence: {confidence:.3f})" | |
| else: | |
| return predicted_class | |
| else: | |
| print("ERROR: Failed to load classifier!") | |
| return "Unknown" | |
| else: | |
| print(f"ERROR: Model file not found: {model_path}") | |
| return "Unknown" | |
| except Exception as e: | |
| print(f"ERROR: Exception in face classification: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| return "Unknown" | |