import cv2 import numpy as np import streamlit as st from sklearn.metrics.pairwise import cosine_similarity def compare_faces(image1, bboxes1, image2, bboxes2): """ Compare faces using HOG features """ # Convert images to grayscale gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY) # Initialize list to store comparison results comparison_results = [] # Calculate HOG parameters based on face size win_size = (64, 64) block_size = (16, 16) block_stride = (8, 8) cell_size = (8, 8) nbins = 9 # Iterate over each face in the first image for bbox1 in bboxes1: x1_1, y1_1, x2_1, y2_1, _ = bbox1 # Check if the face region is valid if x1_1 >= x2_1 or y1_1 >= y2_1: continue # Resize face to a standard size for HOG face1_roi = image1[y1_1:y2_1, x1_1:x2_1] face1_resized = cv2.resize(face1_roi, win_size) face1_gray = cv2.cvtColor(face1_resized, cv2.COLOR_BGR2GRAY) # Calculate HOG features hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, nbins) h1 = hog.compute(face1_gray) # Normalize the feature vector h1_norm = h1 / np.linalg.norm(h1) # Store results for this face face_comparisons = [] # Compare with each face in the second image for bbox2 in bboxes2: x1_2, y1_2, x2_2, y2_2, _ = bbox2 # Check if the face region is valid if x1_2 >= x2_2 or y1_2 >= y2_2: continue # Resize face to a standard size for HOG face2_roi = image2[y1_2:y2_2, x1_2:x2_2] face2_resized = cv2.resize(face2_roi, win_size) face2_gray = cv2.cvtColor(face2_resized, cv2.COLOR_BGR2GRAY) # Calculate HOG features h2 = hog.compute(face2_gray) # Normalize the feature vector h2_norm = h2 / np.linalg.norm(h2) # Calculate cosine similarity similarity = np.dot(h1_norm.flatten(), h2_norm.flatten()) * 100 # Add result face_comparisons.append({ "similarity": similarity }) comparison_results.append(face_comparisons) return comparison_results def compare_faces_embeddings(image1, bboxes1, image2, bboxes2, model_name="VGG-Face"): """ Compare faces using facial embeddings from DeepFace """ try: from deepface import DeepFace import numpy as np except ImportError: # Fallback to HOG if DeepFace is not available return compare_faces(image1, bboxes1, image2, bboxes2) # Initialize list to store comparison results comparison_results = [] # Iterate over each face in the first image for bbox1 in bboxes1: x1_1, y1_1, x2_1, y2_1, _ = bbox1 # Check if the face region is valid if x1_1 >= x2_1 or y1_1 >= y2_1: continue # Extract face region face1_roi = image1[y1_1:y2_1, x1_1:x2_1] # Get embedding for the face try: embedding1 = DeepFace.represent(face1_roi, model_name=model_name, enforce_detection=False)[0]["embedding"] except Exception as e: st.warning(f"Error extracting embedding from face 1: {str(e)}") # Try with a fallback model try: embedding1 = DeepFace.represent(face1_roi, model_name="OpenFace", enforce_detection=False)[0]["embedding"] except: # If still fails, use HOG face_comparisons = [] for bbox2 in bboxes2: face_comparisons.append({"similarity": 0}) comparison_results.append(face_comparisons) continue # Store results for this face face_comparisons = [] # Compare with each face in the second image for bbox2 in bboxes2: x1_2, y1_2, x2_2, y2_2, _ = bbox2 # Check if the face region is valid if x1_2 >= x2_2 or y1_2 >= y2_2: continue # Extract face region face2_roi = image2[y1_2:y2_2, x1_2:x2_2] # Get embedding for the face try: embedding2 = DeepFace.represent(face2_roi, model_name=model_name, enforce_detection=False)[0]["embedding"] except Exception as e: st.warning(f"Error extracting embedding from face 2: {str(e)}") # Try with a fallback model try: embedding2 = DeepFace.represent(face2_roi, model_name="OpenFace", enforce_detection=False)[0]["embedding"] except: # If still fails, add a 0 similarity face_comparisons.append({"similarity": 0}) continue # Calculate cosine similarity between embeddings embedding1_array = np.array(embedding1).reshape(1, -1) embedding2_array = np.array(embedding2).reshape(1, -1) similarity = cosine_similarity(embedding1_array, embedding2_array)[0][0] * 100 # Add result face_comparisons.append({ "similarity": similarity }) comparison_results.append(face_comparisons) return comparison_results def generate_comparison_report_english(comparison_results, bboxes1, bboxes2, threshold=50.0): """ Generate a text report of the face comparison results """ # Skip if no comparison results if not comparison_results: return "No face comparisons were performed." # Add header report = ["Face Comparison Report:"] # Add comparison results for i, face_comparisons in enumerate(comparison_results): report.append(f"\nFace {i+1} from Image 1:") # Skip if no comparisons for this face if not face_comparisons: report.append(" No comparisons available for this face.") continue # Find best match best_match_idx = max(range(len(face_comparisons)), key=lambda j: face_comparisons[j]["similarity"]) best_match_similarity = face_comparisons[best_match_idx]["similarity"] # Add best match info if best_match_similarity >= threshold: report.append(f" Best match: Face {best_match_idx+1} from Image 2 (Similarity: {best_match_similarity:.2f}%)") else: report.append(f" No strong matches found. Best similarity is with Face {best_match_idx+1} ({best_match_similarity:.2f}%)") # Add all comparisons report.append(" All comparisons:") for j, comp in enumerate(face_comparisons): report.append(f" Face {j+1}: Similarity {comp['similarity']:.2f}%") # Join the list into a single string with line breaks return "\n".join(report) def draw_face_matches(image1, bboxes1, image2, bboxes2, comparison_results, threshold=50.0): """ Create a combined image showing the two input images side by side with lines connecting matching faces """ # Get dimensions h1, w1 = image1.shape[:2] h2, w2 = image2.shape[:2] # Create a combined image combined_h = max(h1, h2) combined_w = w1 + w2 combined_img = np.zeros((combined_h, combined_w, 3), dtype=np.uint8) # Copy images combined_img[:h1, :w1] = image1 combined_img[:h2, w1:w1+w2] = image2 # Draw lines between matching faces for i, face_comparisons in enumerate(comparison_results): # Skip if no comparisons for this face if not face_comparisons: continue # Get bbox for this face x1_1, y1_1, x2_1, y2_1, _ = bboxes1[i] center1_x = (x1_1 + x2_1) // 2 center1_y = (y1_1 + y2_1) // 2 # For each comparison for j, comp in enumerate(face_comparisons): similarity = comp["similarity"] # Only draw lines for matches above threshold if similarity >= threshold: # Get bbox for the other face x1_2, y1_2, x2_2, y2_2, _ = bboxes2[j] center2_x = (x1_2 + x2_2) // 2 + w1 # Adjust for offset center2_y = (y1_2 + y2_2) // 2 # Calculate color based on similarity (green for high, red for low) # Map 50-100% to color scale color_val = min(255, max(0, int((similarity - threshold) * 255 / (100 - threshold)))) line_color = (0, 0, 255) # Red for all matches # Draw line cv2.line(combined_img, (center1_x, center1_y), (center2_x, center2_y), line_color, 2) # Add similarity text text_x = (center1_x + center2_x) // 2 - 20 text_y = (center1_y + center2_y) // 2 - 10 cv2.putText(combined_img, f"{similarity:.1f}%", (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2) return combined_img def extract_face_embeddings(image, bbox, model_name="VGG-Face"): """ Extract facial embeddings from a face using DeepFace """ try: from deepface import DeepFace except ImportError: st.error("DeepFace library is not available. Please install with 'pip install deepface' to use embeddings.") return None # Extract bbox coordinates x1, y1, x2, y2, _ = bbox # Check if the face region is valid if x1 >= x2 or y1 >= y2: return None # Extract face region face_roi = image[y1:y2, x1:x2] # Get embedding for the face try: embedding_info = DeepFace.represent(face_roi, model_name=model_name, enforce_detection=False)[0] return { "embedding": embedding_info["embedding"], "model": model_name } except Exception as e: st.warning(f"Error extracting embedding with {model_name}: {str(e)}") # Try with a fallback model try: fallback_model = "OpenFace" embedding_info = DeepFace.represent(face_roi, model_name=fallback_model, enforce_detection=False)[0] return { "embedding": embedding_info["embedding"], "model": fallback_model } except Exception as e: st.error(f"Failed to extract embeddings: {str(e)}") return None def extract_face_embeddings_all_models(image, bbox): """ Extract facial embeddings using multiple models (VGG-Face, Facenet, OpenFace, ArcFace) """ models = ["VGG-Face", "Facenet", "OpenFace", "ArcFace"] embeddings = [] for model_name in models: embedding = extract_face_embeddings(image, bbox, model_name=model_name) if embedding: embeddings.append(embedding) return embeddings if embeddings else None