chore: Add configuration and documentation files for project structure and guidelines

This commit introduces several new files to enhance project organization and developer onboarding. The `.cursorignore` and `.cursorrules` files provide guidelines for Cursor AI, while `CONTRIBUTING.md` outlines contribution procedures. Additionally, `IMPORT_FIX_SUMMARY.md`, `RESTRUCTURE_SUMMARY.md`, and `STATUS.md` summarize recent changes and project status. The `README.md` has been updated to reflect the new project focus and structure, ensuring clarity for contributors and users. These additions aim to improve maintainability and facilitate collaboration within the PunimTag project.
This commit is contained in:
tanyar09
2025-10-15 14:43:18 -04:00
parent e49b567afa
commit d300eb1122
61 changed files with 10717 additions and 1100 deletions
+4
View File
@@ -0,0 +1,4 @@
"""
Test suite for PunimTag
"""
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Debug face detection to see what's happening
"""
import os
from pathlib import Path
from PIL import Image
import numpy as np
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
def debug_face_detection():
from deepface import DeepFace
# Test with the reference image
test_image = "demo_photos/testdeepface/2019-11-22_0011.jpg"
if not os.path.exists(test_image):
print(f"Test image not found: {test_image}")
return
print(f"Testing face detection on: {test_image}")
# Load and display image info
img = Image.open(test_image)
print(f"Image size: {img.size}")
# Try different detection methods
detectors = ['opencv', 'mtcnn', 'retinaface', 'ssd']
for detector in detectors:
print(f"\n--- Testing {detector} detector ---")
try:
# Try extract_faces first
faces = DeepFace.extract_faces(
img_path=test_image,
detector_backend=detector,
enforce_detection=False,
align=True
)
print(f"extract_faces found {len(faces)} faces")
# Try represent
results = DeepFace.represent(
img_path=test_image,
model_name='ArcFace',
detector_backend=detector,
enforce_detection=False,
align=True
)
print(f"represent found {len(results)} results")
if results:
for i, result in enumerate(results):
region = result.get('region', {})
print(f" Result {i}: region={region}")
except Exception as e:
print(f"Error with {detector}: {e}")
if __name__ == "__main__":
debug_face_detection()
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
Show large thumbnails directly from the test images
"""
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os
from pathlib import Path
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
def show_large_thumbnails():
root = tk.Tk()
root.title("Large Thumbnails Demo")
root.geometry("1600x1000")
frame = ttk.Frame(root, padding="20")
frame.pack(fill=tk.BOTH, expand=True)
# Get test images
test_folder = Path("demo_photos/testdeepface/")
if not test_folder.exists():
ttk.Label(frame, text="Test folder not found: demo_photos/testdeepface/",
font=("Arial", 16, "bold"), foreground="red").pack(pady=20)
root.mainloop()
return
image_files = list(test_folder.glob("*.jpg"))
if not image_files:
ttk.Label(frame, text="No images found in test folder",
font=("Arial", 16, "bold"), foreground="red").pack(pady=20)
root.mainloop()
return
# Show first few images with large thumbnails
ttk.Label(frame, text="Large Thumbnails Demo (400x400 pixels)",
font=("Arial", 18, "bold")).pack(pady=10)
for i, img_path in enumerate(image_files[:4]): # Show first 4 images
try:
# Load and resize image
image = Image.open(img_path)
image.thumbnail((400, 400), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(image)
# Create frame for this image
img_frame = ttk.Frame(frame)
img_frame.pack(pady=10)
# Image label
img_label = ttk.Label(img_frame, image=photo)
img_label.image = photo # Keep a reference
img_label.pack()
# Text label
text_label = ttk.Label(img_frame, text=f"{img_path.name} (400x400)",
font=("Arial", 12, "bold"))
text_label.pack()
except Exception as e:
ttk.Label(frame, text=f"Error loading {img_path.name}: {e}",
font=("Arial", 12), foreground="red").pack()
# Add instruction
instruction = ttk.Label(frame, text="These are 400x400 pixel thumbnails - the same size the GUI will use!",
font=("Arial", 14, "bold"), foreground="green")
instruction.pack(pady=20)
root.mainloop()
if __name__ == "__main__":
show_large_thumbnails()
+716
View File
@@ -0,0 +1,716 @@
#!/usr/bin/env python3
"""
DeepFace GUI Test Application
GUI version of test_deepface_only.py that shows face comparison results
with left panel for reference faces and right panel for comparison faces with confidence scores.
"""
import os
import sys
import time
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pathlib import Path
from typing import List, Dict, Tuple, Optional
import numpy as np
from PIL import Image, ImageTk
# Suppress TensorFlow warnings and CUDA errors
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import warnings
warnings.filterwarnings('ignore')
# DeepFace library
from deepface import DeepFace
# Face recognition library
import face_recognition
# Supported image formats
SUPPORTED_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
class FaceComparisonGUI:
"""GUI application for DeepFace face comparison testing"""
def __init__(self):
self.root = tk.Tk()
self.root.title("Face Comparison Test - DeepFace vs face_recognition")
self.root.geometry("2000x1000")
self.root.minsize(1200, 800)
# Data storage
self.deepface_faces = [] # DeepFace faces from all images
self.facerec_faces = [] # face_recognition faces from all images
self.deepface_similarities = [] # DeepFace similarity results
self.facerec_similarities = [] # face_recognition similarity results
self.processing_times = {} # Timing information for each photo
# GUI components
self.setup_gui()
def setup_gui(self):
"""Set up the GUI layout"""
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(2, weight=1) # Make the content area expandable
# Title
title_label = ttk.Label(main_frame, text="Face Comparison Test - DeepFace vs face_recognition",
font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 10))
# Control panel
control_frame = ttk.Frame(main_frame)
control_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 5))
# Folder selection
ttk.Label(control_frame, text="Test Folder:").grid(row=0, column=0, padx=(0, 5))
self.folder_var = tk.StringVar(value="demo_photos/testdeepface/")
folder_entry = ttk.Entry(control_frame, textvariable=self.folder_var, width=40)
folder_entry.grid(row=0, column=1, padx=(0, 5))
browse_btn = ttk.Button(control_frame, text="Browse", command=self.browse_folder)
browse_btn.grid(row=0, column=2, padx=(0, 10))
# Reference image selection
ttk.Label(control_frame, text="Reference Image:").grid(row=0, column=3, padx=(10, 5))
self.reference_var = tk.StringVar(value="2019-11-22_0011.JPG")
reference_entry = ttk.Entry(control_frame, textvariable=self.reference_var, width=20)
reference_entry.grid(row=0, column=4, padx=(0, 5))
# Face detector selection
ttk.Label(control_frame, text="Detector:").grid(row=0, column=5, padx=(10, 5))
self.detector_var = tk.StringVar(value="retinaface")
detector_combo = ttk.Combobox(control_frame, textvariable=self.detector_var,
values=["retinaface", "mtcnn", "opencv", "ssd"],
state="readonly", width=10)
detector_combo.grid(row=0, column=6, padx=(0, 5))
# Similarity threshold
ttk.Label(control_frame, text="Threshold:").grid(row=0, column=7, padx=(10, 5))
self.threshold_var = tk.StringVar(value="60")
threshold_entry = ttk.Entry(control_frame, textvariable=self.threshold_var, width=8)
threshold_entry.grid(row=0, column=8, padx=(0, 5))
# Process button
process_btn = ttk.Button(control_frame, text="Process Images",
command=self.process_images, style="Accent.TButton")
process_btn.grid(row=0, column=9, padx=(10, 0))
# Progress bar
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(control_frame, variable=self.progress_var,
maximum=100, length=200)
self.progress_bar.grid(row=1, column=0, columnspan=10, sticky=(tk.W, tk.E), pady=(5, 0))
# Status label
self.status_var = tk.StringVar(value="Ready to process images")
status_label = ttk.Label(control_frame, textvariable=self.status_var)
status_label.grid(row=2, column=0, columnspan=10, pady=(5, 0))
# Main content area with three panels
content_frame = ttk.Frame(main_frame)
content_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(10, 0))
content_frame.columnconfigure(0, weight=1)
content_frame.columnconfigure(1, weight=1)
content_frame.columnconfigure(2, weight=1)
content_frame.rowconfigure(0, weight=1)
# Left panel - DeepFace results
left_frame = ttk.LabelFrame(content_frame, text="DeepFace Results", padding="5")
left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
left_frame.columnconfigure(0, weight=1)
left_frame.rowconfigure(0, weight=1)
# Left panel scrollable area
left_canvas = tk.Canvas(left_frame, bg="white")
left_scrollbar = ttk.Scrollbar(left_frame, orient="vertical", command=left_canvas.yview)
self.left_scrollable_frame = ttk.Frame(left_canvas)
self.left_scrollable_frame.bind(
"<Configure>",
lambda e: left_canvas.configure(scrollregion=left_canvas.bbox("all"))
)
left_canvas.create_window((0, 0), window=self.left_scrollable_frame, anchor="nw")
left_canvas.configure(yscrollcommand=left_scrollbar.set)
left_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
left_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# Middle panel - face_recognition results
middle_frame = ttk.LabelFrame(content_frame, text="face_recognition Results", padding="5")
middle_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 5))
middle_frame.columnconfigure(0, weight=1)
middle_frame.rowconfigure(0, weight=1)
# Right panel - Comparison Results
right_frame = ttk.LabelFrame(content_frame, text="Comparison Results", padding="5")
right_frame.grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(0, weight=1)
# Middle panel scrollable area
middle_canvas = tk.Canvas(middle_frame, bg="white")
middle_scrollbar = ttk.Scrollbar(middle_frame, orient="vertical", command=middle_canvas.yview)
self.middle_scrollable_frame = ttk.Frame(middle_canvas)
self.middle_scrollable_frame.bind(
"<Configure>",
lambda e: middle_canvas.configure(scrollregion=middle_canvas.bbox("all"))
)
middle_canvas.create_window((0, 0), window=self.middle_scrollable_frame, anchor="nw")
middle_canvas.configure(yscrollcommand=middle_scrollbar.set)
middle_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
middle_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# Right panel scrollable area
right_canvas = tk.Canvas(right_frame, bg="white")
right_scrollbar = ttk.Scrollbar(right_frame, orient="vertical", command=right_canvas.yview)
self.right_scrollable_frame = ttk.Frame(right_canvas)
self.right_scrollable_frame.bind(
"<Configure>",
lambda e: right_canvas.configure(scrollregion=right_canvas.bbox("all"))
)
right_canvas.create_window((0, 0), window=self.right_scrollable_frame, anchor="nw")
right_canvas.configure(yscrollcommand=right_scrollbar.set)
right_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
right_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# Bind mousewheel to all canvases
def _on_mousewheel(event):
left_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
middle_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
right_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
left_canvas.bind("<MouseWheel>", _on_mousewheel)
middle_canvas.bind("<MouseWheel>", _on_mousewheel)
right_canvas.bind("<MouseWheel>", _on_mousewheel)
def browse_folder(self):
"""Browse for folder containing test images"""
folder = filedialog.askdirectory(initialdir="demo_photos/")
if folder:
self.folder_var.set(folder)
def update_status(self, message: str):
"""Update status message"""
self.status_var.set(message)
self.root.update_idletasks()
def update_progress(self, value: float):
"""Update progress bar"""
self.progress_var.set(value)
self.root.update_idletasks()
def get_image_files(self, folder_path: str) -> List[str]:
"""Get all supported image files from folder"""
folder = Path(folder_path)
if not folder.exists():
raise FileNotFoundError(f"Folder not found: {folder_path}")
image_files = []
for file_path in folder.rglob("*"):
if file_path.is_file() and file_path.suffix.lower() in SUPPORTED_FORMATS:
image_files.append(str(file_path))
return sorted(image_files)
def process_with_deepface(self, image_path: str, detector: str = "retinaface") -> Dict:
"""Process image with DeepFace library"""
try:
# Use DeepFace.represent() to get proper face detection with regions
# Using selected detector for face detection
results = DeepFace.represent(
img_path=image_path,
model_name='ArcFace', # Best accuracy model
detector_backend=detector, # User-selected detector
enforce_detection=False, # Don't fail if no faces
align=True # Face alignment for better accuracy
)
if not results:
print(f"No faces found in {Path(image_path).name}")
return {'faces': [], 'encodings': []}
print(f"Found {len(results)} faces in {Path(image_path).name}")
# Convert to our format
faces = []
encodings = []
for i, result in enumerate(results):
try:
# Extract face region info from DeepFace result
# DeepFace uses 'facial_area' instead of 'region'
facial_area = result.get('facial_area', {})
face_confidence = result.get('face_confidence', 0.0)
# Create face data with proper bounding box
face_data = {
'image_path': image_path,
'face_id': f"df_{Path(image_path).stem}_{i}",
'location': (facial_area.get('y', 0), facial_area.get('x', 0) + facial_area.get('w', 0),
facial_area.get('y', 0) + facial_area.get('h', 0), facial_area.get('x', 0)),
'bbox': facial_area,
'encoding': np.array(result['embedding']),
'confidence': face_confidence
}
faces.append(face_data)
encodings.append(np.array(result['embedding']))
print(f"Face {i}: facial_area={facial_area}, confidence={face_confidence:.2f}, embedding shape={np.array(result['embedding']).shape}")
except Exception as e:
print(f"Error processing face {i}: {e}")
continue
return {
'faces': faces,
'encodings': encodings
}
except Exception as e:
print(f"DeepFace error on {image_path}: {e}")
return {'faces': [], 'encodings': []}
def process_with_face_recognition(self, image_path: str) -> Dict:
"""Process image with face_recognition library"""
try:
# Load image
image = face_recognition.load_image_file(image_path)
# Find face locations
face_locations = face_recognition.face_locations(image, model="hog") # Use HOG model for speed
if not face_locations:
print(f"No faces found in {Path(image_path).name} (face_recognition)")
return {'faces': [], 'encodings': []}
print(f"Found {len(face_locations)} faces in {Path(image_path).name} (face_recognition)")
# Get face encodings
face_encodings = face_recognition.face_encodings(image, face_locations)
# Convert to our format
faces = []
encodings = []
for i, (face_location, face_encoding) in enumerate(zip(face_locations, face_encodings)):
try:
# face_recognition returns (top, right, bottom, left)
top, right, bottom, left = face_location
# Create face data with proper bounding box
face_data = {
'image_path': image_path,
'face_id': f"fr_{Path(image_path).stem}_{i}",
'location': face_location,
'bbox': {'x': left, 'y': top, 'w': right - left, 'h': bottom - top},
'encoding': np.array(face_encoding),
'confidence': 1.0 # face_recognition doesn't provide confidence scores
}
faces.append(face_data)
encodings.append(np.array(face_encoding))
print(f"Face {i}: location={face_location}, encoding shape={np.array(face_encoding).shape}")
except Exception as e:
print(f"Error processing face {i}: {e}")
continue
return {
'faces': faces,
'encodings': encodings
}
except Exception as e:
print(f"face_recognition error on {image_path}: {e}")
return {'faces': [], 'encodings': []}
def extract_face_thumbnail(self, face_data: Dict, size: Tuple[int, int] = (150, 150)) -> ImageTk.PhotoImage:
"""Extract face thumbnail from image"""
try:
# Load original image
image = Image.open(face_data['image_path'])
# Extract face region
bbox = face_data['bbox']
left = bbox.get('x', 0)
top = bbox.get('y', 0)
right = left + bbox.get('w', 0)
bottom = top + bbox.get('h', 0)
# Add padding
padding = 20
left = max(0, left - padding)
top = max(0, top - padding)
right = min(image.width, right + padding)
bottom = min(image.height, bottom + padding)
# Crop face
face_crop = image.crop((left, top, right, bottom))
# FORCE resize to exact size (don't use thumbnail which maintains aspect ratio)
face_crop = face_crop.resize(size, Image.Resampling.LANCZOS)
print(f"DEBUG: Created thumbnail of size {face_crop.size} for {face_data['face_id']}")
# Convert to PhotoImage
return ImageTk.PhotoImage(face_crop)
except Exception as e:
print(f"Error extracting thumbnail for {face_data['face_id']}: {e}")
# Return a placeholder image
placeholder = Image.new('RGB', size, color='lightgray')
return ImageTk.PhotoImage(placeholder)
def calculate_face_similarity(self, encoding1: np.ndarray, encoding2: np.ndarray) -> float:
"""Calculate similarity between two face encodings using cosine similarity"""
try:
# Ensure encodings are numpy arrays
enc1 = np.array(encoding1).flatten()
enc2 = np.array(encoding2).flatten()
# Check if encodings have the same length
if len(enc1) != len(enc2):
print(f"Warning: Encoding length mismatch: {len(enc1)} vs {len(enc2)}")
return 0.0
# Normalize encodings
enc1_norm = enc1 / (np.linalg.norm(enc1) + 1e-8) # Add small epsilon to avoid division by zero
enc2_norm = enc2 / (np.linalg.norm(enc2) + 1e-8)
# Calculate cosine similarity
cosine_sim = np.dot(enc1_norm, enc2_norm)
# Clamp cosine similarity to valid range [-1, 1]
cosine_sim = np.clip(cosine_sim, -1.0, 1.0)
# Convert to confidence percentage (0-100)
# For face recognition, we typically want values between 0-100%
# where higher values mean more similar faces
confidence = max(0, min(100, (cosine_sim + 1) * 50)) # Scale from [-1,1] to [0,100]
return confidence
except Exception as e:
print(f"Error calculating similarity: {e}")
return 0.0
def process_images(self):
"""Process all images and perform face comparison"""
try:
# Clear previous results
self.deepface_faces = []
self.facerec_faces = []
self.deepface_similarities = []
self.facerec_similarities = []
self.processing_times = {}
# Clear GUI panels
for widget in self.left_scrollable_frame.winfo_children():
widget.destroy()
for widget in self.middle_scrollable_frame.winfo_children():
widget.destroy()
for widget in self.right_scrollable_frame.winfo_children():
widget.destroy()
folder_path = self.folder_var.get()
threshold = float(self.threshold_var.get())
if not folder_path:
messagebox.showerror("Error", "Please specify folder path")
return
self.update_status("Getting image files...")
self.update_progress(10)
# Get all image files
image_files = self.get_image_files(folder_path)
if not image_files:
messagebox.showerror("Error", "No image files found in the specified folder")
return
# Get selected detector
detector = self.detector_var.get()
self.update_status(f"Processing all images with both DeepFace and face_recognition...")
self.update_progress(20)
# Process all images with both libraries
for i, image_path in enumerate(image_files):
filename = Path(image_path).name
self.update_status(f"Processing {filename}...")
progress = 20 + (i / len(image_files)) * 50
self.update_progress(progress)
# Process with DeepFace
start_time = time.time()
deepface_result = self.process_with_deepface(image_path, detector)
deepface_time = time.time() - start_time
# Process with face_recognition
start_time = time.time()
facerec_result = self.process_with_face_recognition(image_path)
facerec_time = time.time() - start_time
# Store timing information
self.processing_times[filename] = {
'deepface_time': deepface_time,
'facerec_time': facerec_time,
'total_time': deepface_time + facerec_time
}
# Store results
self.deepface_faces.extend(deepface_result['faces'])
self.facerec_faces.extend(facerec_result['faces'])
print(f"Processed {filename}: DeepFace={deepface_time:.2f}s, face_recognition={facerec_time:.2f}s")
if not self.deepface_faces and not self.facerec_faces:
messagebox.showwarning("Warning", "No faces found in any images")
return
self.update_status("Calculating face similarities...")
self.update_progress(75)
# Calculate similarities for DeepFace
for i, face1 in enumerate(self.deepface_faces):
similarities = []
for j, face2 in enumerate(self.deepface_faces):
if i != j: # Don't compare face with itself
confidence = self.calculate_face_similarity(
face1['encoding'], face2['encoding']
)
if confidence >= threshold: # Only include faces above threshold
similarities.append({
'face': face2,
'confidence': confidence
})
# Sort by confidence (highest first)
similarities.sort(key=lambda x: x['confidence'], reverse=True)
self.deepface_similarities.append({
'face': face1,
'similarities': similarities
})
# Calculate similarities for face_recognition
for i, face1 in enumerate(self.facerec_faces):
similarities = []
for j, face2 in enumerate(self.facerec_faces):
if i != j: # Don't compare face with itself
confidence = self.calculate_face_similarity(
face1['encoding'], face2['encoding']
)
if confidence >= threshold: # Only include faces above threshold
similarities.append({
'face': face2,
'confidence': confidence
})
# Sort by confidence (highest first)
similarities.sort(key=lambda x: x['confidence'], reverse=True)
self.facerec_similarities.append({
'face': face1,
'similarities': similarities
})
self.update_status("Displaying results...")
self.update_progress(95)
# Display results in GUI
self.display_results()
total_deepface_faces = len(self.deepface_faces)
total_facerec_faces = len(self.facerec_faces)
avg_deepface_time = sum(t['deepface_time'] for t in self.processing_times.values()) / len(self.processing_times)
avg_facerec_time = sum(t['facerec_time'] for t in self.processing_times.values()) / len(self.processing_times)
self.update_status(f"Complete! DeepFace: {total_deepface_faces} faces ({avg_deepface_time:.2f}s avg), face_recognition: {total_facerec_faces} faces ({avg_facerec_time:.2f}s avg)")
self.update_progress(100)
except Exception as e:
messagebox.showerror("Error", f"Processing failed: {str(e)}")
self.update_status("Error occurred during processing")
print(f"Error: {e}")
import traceback
traceback.print_exc()
def display_results(self):
"""Display the face comparison results in the GUI panels"""
# Display DeepFace results in left panel
self.display_library_results(self.deepface_similarities, self.left_scrollable_frame, "DeepFace")
# Display face_recognition results in middle panel
self.display_library_results(self.facerec_similarities, self.middle_scrollable_frame, "face_recognition")
# Display timing comparison in right panel
self.display_timing_comparison()
def display_library_results(self, similarities_list: List[Dict], parent_frame, library_name: str):
"""Display results for a specific library"""
for i, result in enumerate(similarities_list):
face = result['face']
# Create frame for this face
face_frame = ttk.Frame(parent_frame)
face_frame.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=5, padx=5)
# Face thumbnail
thumbnail = self.extract_face_thumbnail(face, size=(80, 80))
thumbnail_label = ttk.Label(face_frame, image=thumbnail)
thumbnail_label.image = thumbnail # Keep a reference
thumbnail_label.grid(row=0, column=0, padx=5, pady=5)
# Face info
info_frame = ttk.Frame(face_frame)
info_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
ttk.Label(info_frame, text=f"Face {i+1}", font=("Arial", 10, "bold")).grid(row=0, column=0, sticky=tk.W, pady=1)
ttk.Label(info_frame, text=f"ID: {face['face_id']}", font=("Arial", 8)).grid(row=1, column=0, sticky=tk.W, pady=1)
ttk.Label(info_frame, text=f"Image: {Path(face['image_path']).name}", font=("Arial", 8)).grid(row=2, column=0, sticky=tk.W, pady=1)
# Show number of similar faces
similar_count = len(result['similarities'])
ttk.Label(info_frame, text=f"Similar: {similar_count}", font=("Arial", 8, "bold")).grid(row=3, column=0, sticky=tk.W, pady=1)
def display_timing_comparison(self):
"""Display timing comparison between libraries"""
if not self.processing_times:
return
# Create summary frame
summary_frame = ttk.LabelFrame(self.right_scrollable_frame, text="Processing Times Summary")
summary_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5, padx=5)
# Calculate averages
total_deepface_time = sum(t['deepface_time'] for t in self.processing_times.values())
total_facerec_time = sum(t['facerec_time'] for t in self.processing_times.values())
avg_deepface_time = total_deepface_time / len(self.processing_times)
avg_facerec_time = total_facerec_time / len(self.processing_times)
# Summary statistics
ttk.Label(summary_frame, text=f"Total Images: {len(self.processing_times)}", font=("Arial", 10, "bold")).grid(row=0, column=0, sticky=tk.W, pady=2)
ttk.Label(summary_frame, text=f"DeepFace Avg: {avg_deepface_time:.2f}s", font=("Arial", 9)).grid(row=1, column=0, sticky=tk.W, pady=1)
ttk.Label(summary_frame, text=f"face_recognition Avg: {avg_facerec_time:.2f}s", font=("Arial", 9)).grid(row=2, column=0, sticky=tk.W, pady=1)
speed_ratio = avg_deepface_time / avg_facerec_time if avg_facerec_time > 0 else 0
if speed_ratio > 1:
faster_lib = "face_recognition"
speed_text = f"{speed_ratio:.1f}x faster"
else:
faster_lib = "DeepFace"
speed_text = f"{1/speed_ratio:.1f}x faster"
ttk.Label(summary_frame, text=f"{faster_lib} is {speed_text}", font=("Arial", 9, "bold"), foreground="green").grid(row=3, column=0, sticky=tk.W, pady=2)
# Individual photo timings
timing_frame = ttk.LabelFrame(self.right_scrollable_frame, text="Per-Photo Timing")
timing_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5, padx=5)
row = 0
for filename, times in sorted(self.processing_times.items()):
ttk.Label(timing_frame, text=f"{filename[:20]}...", font=("Arial", 8)).grid(row=row, column=0, sticky=tk.W, pady=1)
ttk.Label(timing_frame, text=f"DF: {times['deepface_time']:.2f}s", font=("Arial", 8)).grid(row=row, column=1, sticky=tk.W, pady=1, padx=(5,0))
ttk.Label(timing_frame, text=f"FR: {times['facerec_time']:.2f}s", font=("Arial", 8)).grid(row=row, column=2, sticky=tk.W, pady=1, padx=(5,0))
row += 1
def display_comparison_faces(self, ref_index: int, similarities: List[Dict]):
"""Display comparison faces for a specific reference face"""
# Create frame for this reference face's comparisons
comp_frame = ttk.LabelFrame(self.right_scrollable_frame,
text=f"Matches for Reference Face {ref_index + 1}")
comp_frame.grid(row=ref_index, column=0, sticky=(tk.W, tk.E), pady=10, padx=10)
# Display top matches (limit to avoid too much clutter)
max_matches = min(8, len(similarities))
for i in range(max_matches):
sim_data = similarities[i]
face = sim_data['face']
confidence = sim_data['confidence']
# Create frame for this comparison face
face_frame = ttk.Frame(comp_frame)
face_frame.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=5, padx=10)
# Face thumbnail
thumbnail = self.extract_face_thumbnail(face, size=(120, 120))
thumbnail_label = ttk.Label(face_frame, image=thumbnail)
thumbnail_label.image = thumbnail # Keep a reference
thumbnail_label.grid(row=0, column=0, padx=10, pady=5)
# Face info with confidence
info_frame = ttk.Frame(face_frame)
info_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=10)
# Confidence with color coding
confidence_text = f"{confidence:.1f}%"
if confidence >= 80:
confidence_color = "green"
elif confidence >= 60:
confidence_color = "orange"
else:
confidence_color = "red"
ttk.Label(info_frame, text=confidence_text,
font=("Arial", 14, "bold"), foreground=confidence_color).grid(row=0, column=0, sticky=tk.W, pady=2)
ttk.Label(info_frame, text=f"ID: {face['face_id']}", font=("Arial", 10)).grid(row=1, column=0, sticky=tk.W, pady=2)
ttk.Label(info_frame, text=f"Image: {Path(face['image_path']).name}", font=("Arial", 10)).grid(row=2, column=0, sticky=tk.W, pady=2)
def run(self):
"""Start the GUI application"""
self.root.mainloop()
def main():
"""Main entry point"""
# Check dependencies
try:
from deepface import DeepFace
except ImportError as e:
print(f"Error: Missing required dependency: {e}")
print("Please install with: pip install deepface")
sys.exit(1)
try:
import face_recognition
except ImportError as e:
print(f"Error: Missing required dependency: {e}")
print("Please install with: pip install face_recognition")
sys.exit(1)
# Suppress TensorFlow warnings and errors
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # Suppress TensorFlow warnings
import warnings
warnings.filterwarnings('ignore')
try:
# Create and run GUI
app = FaceComparisonGUI()
app.run()
except Exception as e:
print(f"GUI Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
+399
View File
@@ -0,0 +1,399 @@
#!/usr/bin/env python3
"""
DeepFace Only Test Script
Tests only DeepFace on a folder of photos for faster testing.
Usage:
python test_deepface_only.py /path/to/photos [--save-crops] [--verbose]
Example:
python test_deepface_only.py demo_photos/ --save-crops --verbose
"""
import os
import sys
import time
import argparse
from pathlib import Path
from typing import List, Dict, Tuple, Optional
import numpy as np
import pandas as pd
from PIL import Image
# DeepFace library only
from deepface import DeepFace
# Supported image formats
SUPPORTED_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
class DeepFaceTester:
"""Test DeepFace face recognition"""
def __init__(self, verbose: bool = False):
self.verbose = verbose
self.results = {'faces': [], 'times': [], 'encodings': []}
def log(self, message: str, level: str = "INFO"):
"""Print log message with timestamp"""
if self.verbose or level == "ERROR":
timestamp = time.strftime("%H:%M:%S")
print(f"[{timestamp}] {level}: {message}")
def get_image_files(self, folder_path: str) -> List[str]:
"""Get all supported image files from folder"""
folder = Path(folder_path)
if not folder.exists():
raise FileNotFoundError(f"Folder not found: {folder_path}")
image_files = []
for file_path in folder.rglob("*"):
if file_path.is_file() and file_path.suffix.lower() in SUPPORTED_FORMATS:
image_files.append(str(file_path))
self.log(f"Found {len(image_files)} image files")
return sorted(image_files)
def process_with_deepface(self, image_path: str) -> Dict:
"""Process image with deepface library"""
start_time = time.time()
try:
# Use DeepFace to detect and encode faces
results = DeepFace.represent(
img_path=image_path,
model_name='ArcFace', # Best accuracy model
detector_backend='retinaface', # Best detection
enforce_detection=False, # Don't fail if no faces
align=True # Face alignment for better accuracy
)
if not results:
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
# Convert to our format
faces = []
encodings = []
for i, result in enumerate(results):
# Extract face region info
region = result.get('region', {})
face_data = {
'image_path': image_path,
'face_id': f"df_{Path(image_path).stem}_{i}",
'location': (region.get('y', 0), region.get('x', 0) + region.get('w', 0),
region.get('y', 0) + region.get('h', 0), region.get('x', 0)),
'bbox': region,
'encoding': np.array(result['embedding'])
}
faces.append(face_data)
encodings.append(np.array(result['embedding']))
processing_time = time.time() - start_time
self.log(f"deepface: Found {len(faces)} faces in {processing_time:.2f}s")
return {
'faces': faces,
'encodings': encodings,
'processing_time': processing_time
}
except Exception as e:
self.log(f"deepface error on {image_path}: {e}", "ERROR")
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
def calculate_similarity_matrix(self, encodings: List[np.ndarray]) -> np.ndarray:
"""Calculate similarity matrix between all face encodings using cosine distance"""
n_faces = len(encodings)
if n_faces == 0:
return np.array([])
similarity_matrix = np.zeros((n_faces, n_faces))
for i in range(n_faces):
for j in range(n_faces):
if i == j:
similarity_matrix[i, j] = 0.0 # Same face
else:
# Use cosine distance for ArcFace embeddings
enc1_norm = encodings[i] / np.linalg.norm(encodings[i])
enc2_norm = encodings[j] / np.linalg.norm(encodings[j])
cosine_sim = np.dot(enc1_norm, enc2_norm)
cosine_distance = 1 - cosine_sim
similarity_matrix[i, j] = cosine_distance
return similarity_matrix
def find_top_matches(self, similarity_matrix: np.ndarray, faces: List[Dict],
top_k: int = 5) -> List[Dict]:
"""Find top matches for each face"""
top_matches = []
for i, face in enumerate(faces):
if i >= similarity_matrix.shape[0]:
continue
# Get distances to all other faces
distances = similarity_matrix[i, :]
# Find top matches (excluding self) - lower cosine distance = more similar
sorted_indices = np.argsort(distances)
matches = []
for idx in sorted_indices[1:top_k+1]: # Skip self (index 0)
if idx < len(faces):
other_face = faces[idx]
distance = distances[idx]
# Convert to confidence percentage for display
confidence = max(0, (1 - distance) * 100)
matches.append({
'face_id': other_face['face_id'],
'image_path': other_face['image_path'],
'distance': distance,
'confidence': confidence
})
top_matches.append({
'query_face': face,
'matches': matches
})
return top_matches
def save_face_crops(self, faces: List[Dict], output_dir: str):
"""Save face crops for manual inspection"""
crops_dir = Path(output_dir) / "face_crops" / "deepface"
crops_dir.mkdir(parents=True, exist_ok=True)
for face in faces:
try:
# Load original image
image = Image.open(face['image_path'])
# Extract face region
bbox = face['bbox']
left = bbox.get('x', 0)
top = bbox.get('y', 0)
right = left + bbox.get('w', 0)
bottom = top + bbox.get('h', 0)
# Add padding
padding = 20
left = max(0, left - padding)
top = max(0, top - padding)
right = min(image.width, right + padding)
bottom = min(image.height, bottom + padding)
# Crop and save
face_crop = image.crop((left, top, right, bottom))
crop_path = crops_dir / f"{face['face_id']}.jpg"
face_crop.save(crop_path, "JPEG", quality=95)
except Exception as e:
self.log(f"Error saving crop for {face['face_id']}: {e}", "ERROR")
def save_similarity_matrix(self, matrix: np.ndarray, faces: List[Dict], output_dir: str):
"""Save similarity matrix as CSV file"""
matrices_dir = Path(output_dir) / "similarity_matrices"
matrices_dir.mkdir(parents=True, exist_ok=True)
if matrix.size > 0:
df = pd.DataFrame(matrix,
index=[f['face_id'] for f in faces],
columns=[f['face_id'] for f in faces])
df.to_csv(matrices_dir / "deepface_similarity.csv")
def generate_report(self, results: Dict, matches: List[Dict],
output_dir: Optional[str] = None) -> str:
"""Generate analysis report"""
report_lines = []
report_lines.append("=" * 60)
report_lines.append("DEEPFACE FACE RECOGNITION ANALYSIS")
report_lines.append("=" * 60)
report_lines.append("")
# Summary statistics
total_faces = len(results['faces'])
total_time = sum(results['times'])
report_lines.append("SUMMARY STATISTICS:")
report_lines.append(f" Total faces detected: {total_faces}")
report_lines.append(f" Total processing time: {total_time:.2f}s")
if total_faces > 0:
report_lines.append(f" Average time per face: {total_time/total_faces:.2f}s")
report_lines.append("")
# High confidence matches analysis
def analyze_high_confidence_matches(matches: List[Dict], threshold: float = 70.0):
high_conf_matches = []
for match_data in matches:
for match in match_data['matches']:
if match['confidence'] >= threshold:
high_conf_matches.append({
'query': match_data['query_face']['face_id'],
'match': match['face_id'],
'confidence': match['confidence'],
'query_image': match_data['query_face']['image_path'],
'match_image': match['image_path']
})
return high_conf_matches
high_conf = analyze_high_confidence_matches(matches)
report_lines.append("HIGH CONFIDENCE MATCHES (≥70%):")
report_lines.append(f" Found: {len(high_conf)} matches")
report_lines.append("")
# Show top matches for manual inspection
report_lines.append("TOP MATCHES FOR MANUAL INSPECTION:")
report_lines.append("")
for i, match_data in enumerate(matches[:5]): # Show first 5 faces
query_face = match_data['query_face']
report_lines.append(f"Query: {query_face['face_id']} ({Path(query_face['image_path']).name})")
for match in match_data['matches'][:3]: # Top 3 matches
report_lines.append(f"{match['face_id']}: {match['confidence']:.1f}% ({Path(match['image_path']).name})")
report_lines.append("")
# Analysis
report_lines.append("ANALYSIS:")
if len(high_conf) > total_faces * 0.5:
report_lines.append(" ⚠️ Many high-confidence matches found")
report_lines.append(" This may indicate good face detection or potential false positives")
elif len(high_conf) == 0:
report_lines.append(" ✅ No high-confidence matches found")
report_lines.append(" This suggests good separation between different people")
else:
report_lines.append(" 📊 Moderate number of high-confidence matches")
report_lines.append(" Manual inspection recommended for verification")
report_lines.append("")
report_lines.append("=" * 60)
report_text = "\n".join(report_lines)
# Save report if output directory specified
if output_dir:
report_path = Path(output_dir) / "deepface_report.txt"
with open(report_path, 'w') as f:
f.write(report_text)
self.log(f"Report saved to: {report_path}")
return report_text
def run_test(self, folder_path: str, save_crops: bool = False,
save_matrices: bool = False) -> Dict:
"""Run the DeepFace face recognition test"""
self.log(f"Starting DeepFace test on: {folder_path}")
# Get image files
image_files = self.get_image_files(folder_path)
if not image_files:
raise ValueError("No image files found in the specified folder")
# Create output directory if needed
output_dir = None
if save_crops or save_matrices:
output_dir = Path(folder_path).parent / "test_results"
output_dir.mkdir(exist_ok=True)
# Process images with DeepFace
self.log("Processing images with DeepFace...")
for image_path in image_files:
result = self.process_with_deepface(image_path)
self.results['faces'].extend(result['faces'])
self.results['times'].append(result['processing_time'])
self.results['encodings'].extend(result['encodings'])
# Calculate similarity matrix
self.log("Calculating similarity matrix...")
matrix = self.calculate_similarity_matrix(self.results['encodings'])
# Find top matches
matches = self.find_top_matches(matrix, self.results['faces'])
# Save outputs if requested
if save_crops and output_dir:
self.log("Saving face crops...")
self.save_face_crops(self.results['faces'], str(output_dir))
if save_matrices and output_dir:
self.log("Saving similarity matrix...")
self.save_similarity_matrix(matrix, self.results['faces'], str(output_dir))
# Generate and display report
report = self.generate_report(
self.results, matches, str(output_dir) if output_dir else None
)
print(report)
return {
'faces': self.results['faces'],
'matches': matches,
'matrix': matrix
}
def main():
"""Main CLI entry point"""
parser = argparse.ArgumentParser(
description="Test DeepFace on a folder of photos",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python test_deepface_only.py demo_photos/
python test_deepface_only.py demo_photos/ --save-crops --verbose
python test_deepface_only.py demo_photos/ --save-matrices --save-crops
"""
)
parser.add_argument('folder', help='Path to folder containing photos to test')
parser.add_argument('--save-crops', action='store_true',
help='Save face crops for manual inspection')
parser.add_argument('--save-matrices', action='store_true',
help='Save similarity matrix as CSV file')
parser.add_argument('--verbose', '-v', action='store_true',
help='Enable verbose logging')
args = parser.parse_args()
# Validate folder path
if not os.path.exists(args.folder):
print(f"Error: Folder not found: {args.folder}")
sys.exit(1)
# Check dependencies
try:
from deepface import DeepFace
except ImportError as e:
print(f"Error: Missing required dependency: {e}")
print("Please install with: pip install deepface")
sys.exit(1)
# Run test
try:
tester = DeepFaceTester(verbose=args.verbose)
results = tester.run_test(
args.folder,
save_crops=args.save_crops,
save_matrices=args.save_matrices
)
print("\n✅ DeepFace test completed successfully!")
if args.save_crops or args.save_matrices:
print(f"📁 Results saved to: {Path(args.folder).parent / 'test_results'}")
except Exception as e:
print(f"❌ Test failed: {e}")
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
+523
View File
@@ -0,0 +1,523 @@
#!/usr/bin/env python3
"""
Face Recognition Comparison Test Script
Compares face_recognition vs deepface on a folder of photos.
Tests accuracy and performance without modifying existing database.
Usage:
python test_face_recognition.py /path/to/photos [--save-crops] [--save-matrices] [--verbose]
Example:
python test_face_recognition.py demo_photos/ --save-crops --verbose
"""
import os
import sys
import time
import argparse
import tempfile
from pathlib import Path
from typing import List, Dict, Tuple, Optional
import numpy as np
import pandas as pd
from PIL import Image
# Face recognition libraries
import face_recognition
from deepface import DeepFace
# Supported image formats
SUPPORTED_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
class FaceRecognitionTester:
"""Test and compare face recognition libraries"""
def __init__(self, verbose: bool = False):
self.verbose = verbose
self.results = {
'face_recognition': {'faces': [], 'times': [], 'encodings': []},
'deepface': {'faces': [], 'times': [], 'encodings': []}
}
def log(self, message: str, level: str = "INFO"):
"""Print log message with timestamp"""
if self.verbose or level == "ERROR":
timestamp = time.strftime("%H:%M:%S")
print(f"[{timestamp}] {level}: {message}")
def get_image_files(self, folder_path: str) -> List[str]:
"""Get all supported image files from folder"""
folder = Path(folder_path)
if not folder.exists():
raise FileNotFoundError(f"Folder not found: {folder_path}")
image_files = []
for file_path in folder.rglob("*"):
if file_path.is_file() and file_path.suffix.lower() in SUPPORTED_FORMATS:
image_files.append(str(file_path))
self.log(f"Found {len(image_files)} image files")
return sorted(image_files)
def process_with_face_recognition(self, image_path: str) -> Dict:
"""Process image with face_recognition library"""
start_time = time.time()
try:
# Load image
image = face_recognition.load_image_file(image_path)
# Detect faces using CNN model (more accurate than HOG)
face_locations = face_recognition.face_locations(image, model="cnn")
if not face_locations:
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
# Get face encodings
face_encodings = face_recognition.face_encodings(image, face_locations)
# Convert to our format
faces = []
encodings = []
for i, (location, encoding) in enumerate(zip(face_locations, face_encodings)):
top, right, bottom, left = location
face_data = {
'image_path': image_path,
'face_id': f"fr_{Path(image_path).stem}_{i}",
'location': location,
'bbox': {'top': top, 'right': right, 'bottom': bottom, 'left': left},
'encoding': encoding
}
faces.append(face_data)
encodings.append(encoding)
processing_time = time.time() - start_time
self.log(f"face_recognition: Found {len(faces)} faces in {processing_time:.2f}s")
return {
'faces': faces,
'encodings': encodings,
'processing_time': processing_time
}
except Exception as e:
self.log(f"face_recognition error on {image_path}: {e}", "ERROR")
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
def process_with_deepface(self, image_path: str) -> Dict:
"""Process image with deepface library"""
start_time = time.time()
try:
# Use DeepFace to detect and encode faces
results = DeepFace.represent(
img_path=image_path,
model_name='ArcFace', # Best accuracy model
detector_backend='retinaface', # Best detection
enforce_detection=False, # Don't fail if no faces
align=True # Face alignment for better accuracy
)
if not results:
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
# Convert to our format
faces = []
encodings = []
for i, result in enumerate(results):
# Extract face region info
region = result.get('region', {})
face_data = {
'image_path': image_path,
'face_id': f"df_{Path(image_path).stem}_{i}",
'location': (region.get('y', 0), region.get('x', 0) + region.get('w', 0),
region.get('y', 0) + region.get('h', 0), region.get('x', 0)),
'bbox': region,
'encoding': np.array(result['embedding'])
}
faces.append(face_data)
encodings.append(np.array(result['embedding']))
processing_time = time.time() - start_time
self.log(f"deepface: Found {len(faces)} faces in {processing_time:.2f}s")
return {
'faces': faces,
'encodings': encodings,
'processing_time': processing_time
}
except Exception as e:
self.log(f"deepface error on {image_path}: {e}", "ERROR")
return {'faces': [], 'encodings': [], 'processing_time': time.time() - start_time}
def calculate_similarity_matrix(self, encodings: List[np.ndarray], method: str) -> np.ndarray:
"""Calculate similarity matrix between all face encodings"""
n_faces = len(encodings)
if n_faces == 0:
return np.array([])
similarity_matrix = np.zeros((n_faces, n_faces))
for i in range(n_faces):
for j in range(n_faces):
if i == j:
similarity_matrix[i, j] = 0.0 # Same face
else:
if method == 'face_recognition':
# Use face_recognition distance (lower = more similar)
distance = face_recognition.face_distance([encodings[i]], encodings[j])[0]
similarity_matrix[i, j] = distance
else: # deepface
# Use cosine distance for ArcFace embeddings
enc1_norm = encodings[i] / np.linalg.norm(encodings[i])
enc2_norm = encodings[j] / np.linalg.norm(encodings[j])
cosine_sim = np.dot(enc1_norm, enc2_norm)
cosine_distance = 1 - cosine_sim
similarity_matrix[i, j] = cosine_distance
return similarity_matrix
def find_top_matches(self, similarity_matrix: np.ndarray, faces: List[Dict],
method: str, top_k: int = 5) -> List[Dict]:
"""Find top matches for each face"""
top_matches = []
for i, face in enumerate(faces):
if i >= similarity_matrix.shape[0]:
continue
# Get distances to all other faces
distances = similarity_matrix[i, :]
# Find top matches (excluding self)
if method == 'face_recognition':
# Lower distance = more similar
sorted_indices = np.argsort(distances)
else: # deepface
# Lower cosine distance = more similar
sorted_indices = np.argsort(distances)
matches = []
for idx in sorted_indices[1:top_k+1]: # Skip self (index 0)
if idx < len(faces):
other_face = faces[idx]
distance = distances[idx]
# Convert to confidence percentage for display
if method == 'face_recognition':
confidence = max(0, (1 - distance) * 100)
else: # deepface
confidence = max(0, (1 - distance) * 100)
matches.append({
'face_id': other_face['face_id'],
'image_path': other_face['image_path'],
'distance': distance,
'confidence': confidence
})
top_matches.append({
'query_face': face,
'matches': matches
})
return top_matches
def save_face_crops(self, faces: List[Dict], output_dir: str, method: str):
"""Save face crops for manual inspection"""
crops_dir = Path(output_dir) / "face_crops" / method
crops_dir.mkdir(parents=True, exist_ok=True)
for face in faces:
try:
# Load original image
image = Image.open(face['image_path'])
# Extract face region
if method == 'face_recognition':
top, right, bottom, left = face['location']
else: # deepface
bbox = face['bbox']
left = bbox.get('x', 0)
top = bbox.get('y', 0)
right = left + bbox.get('w', 0)
bottom = top + bbox.get('h', 0)
# Add padding
padding = 20
left = max(0, left - padding)
top = max(0, top - padding)
right = min(image.width, right + padding)
bottom = min(image.height, bottom + padding)
# Crop and save
face_crop = image.crop((left, top, right, bottom))
crop_path = crops_dir / f"{face['face_id']}.jpg"
face_crop.save(crop_path, "JPEG", quality=95)
except Exception as e:
self.log(f"Error saving crop for {face['face_id']}: {e}", "ERROR")
def save_similarity_matrices(self, fr_matrix: np.ndarray, df_matrix: np.ndarray,
fr_faces: List[Dict], df_faces: List[Dict], output_dir: str):
"""Save similarity matrices as CSV files"""
matrices_dir = Path(output_dir) / "similarity_matrices"
matrices_dir.mkdir(parents=True, exist_ok=True)
# Save face_recognition matrix
if fr_matrix.size > 0:
fr_df = pd.DataFrame(fr_matrix,
index=[f['face_id'] for f in fr_faces],
columns=[f['face_id'] for f in fr_faces])
fr_df.to_csv(matrices_dir / "face_recognition_similarity.csv")
# Save deepface matrix
if df_matrix.size > 0:
df_df = pd.DataFrame(df_matrix,
index=[f['face_id'] for f in df_faces],
columns=[f['face_id'] for f in df_faces])
df_df.to_csv(matrices_dir / "deepface_similarity.csv")
def generate_report(self, fr_results: Dict, df_results: Dict,
fr_matches: List[Dict], df_matches: List[Dict],
output_dir: Optional[str] = None) -> str:
"""Generate comparison report"""
report_lines = []
report_lines.append("=" * 60)
report_lines.append("FACE RECOGNITION COMPARISON REPORT")
report_lines.append("=" * 60)
report_lines.append("")
# Summary statistics
fr_total_faces = len(fr_results['faces'])
df_total_faces = len(df_results['faces'])
fr_total_time = sum(fr_results['times'])
df_total_time = sum(df_results['times'])
report_lines.append("SUMMARY STATISTICS:")
report_lines.append(f" face_recognition: {fr_total_faces} faces in {fr_total_time:.2f}s")
report_lines.append(f" deepface: {df_total_faces} faces in {df_total_time:.2f}s")
report_lines.append(f" Speed ratio: {df_total_time/fr_total_time:.1f}x slower (deepface)")
report_lines.append("")
# High confidence matches analysis
def analyze_high_confidence_matches(matches: List[Dict], method: str, threshold: float = 70.0):
high_conf_matches = []
for match_data in matches:
for match in match_data['matches']:
if match['confidence'] >= threshold:
high_conf_matches.append({
'query': match_data['query_face']['face_id'],
'match': match['face_id'],
'confidence': match['confidence'],
'query_image': match_data['query_face']['image_path'],
'match_image': match['image_path']
})
return high_conf_matches
fr_high_conf = analyze_high_confidence_matches(fr_matches, 'face_recognition')
df_high_conf = analyze_high_confidence_matches(df_matches, 'deepface')
report_lines.append("HIGH CONFIDENCE MATCHES (≥70%):")
report_lines.append(f" face_recognition: {len(fr_high_conf)} matches")
report_lines.append(f" deepface: {len(df_high_conf)} matches")
report_lines.append("")
# Show top matches for manual inspection
report_lines.append("TOP MATCHES FOR MANUAL INSPECTION:")
report_lines.append("")
# face_recognition top matches
report_lines.append("face_recognition top matches:")
for i, match_data in enumerate(fr_matches[:3]): # Show first 3 faces
query_face = match_data['query_face']
report_lines.append(f" Query: {query_face['face_id']} ({Path(query_face['image_path']).name})")
for match in match_data['matches'][:3]: # Top 3 matches
report_lines.append(f"{match['face_id']}: {match['confidence']:.1f}% ({Path(match['image_path']).name})")
report_lines.append("")
# deepface top matches
report_lines.append("deepface top matches:")
for i, match_data in enumerate(df_matches[:3]): # Show first 3 faces
query_face = match_data['query_face']
report_lines.append(f" Query: {query_face['face_id']} ({Path(query_face['image_path']).name})")
for match in match_data['matches'][:3]: # Top 3 matches
report_lines.append(f"{match['face_id']}: {match['confidence']:.1f}% ({Path(match['image_path']).name})")
report_lines.append("")
# Recommendations
report_lines.append("RECOMMENDATIONS:")
if len(fr_high_conf) > len(df_high_conf) * 1.5:
report_lines.append(" ⚠️ face_recognition shows significantly more high-confidence matches")
report_lines.append(" This may indicate more false positives")
if df_total_time > fr_total_time * 3:
report_lines.append(" ⚠️ deepface is significantly slower")
report_lines.append(" Consider GPU acceleration or faster models")
if df_total_faces > fr_total_faces:
report_lines.append(" ✅ deepface detected more faces")
report_lines.append(" Better face detection in difficult conditions")
report_lines.append("")
report_lines.append("=" * 60)
report_text = "\n".join(report_lines)
# Save report if output directory specified
if output_dir:
report_path = Path(output_dir) / "comparison_report.txt"
with open(report_path, 'w') as f:
f.write(report_text)
self.log(f"Report saved to: {report_path}")
return report_text
def run_test(self, folder_path: str, save_crops: bool = False,
save_matrices: bool = False) -> Dict:
"""Run the complete face recognition comparison test"""
self.log(f"Starting face recognition test on: {folder_path}")
# Get image files
image_files = self.get_image_files(folder_path)
if not image_files:
raise ValueError("No image files found in the specified folder")
# Create output directory if needed
output_dir = None
if save_crops or save_matrices:
output_dir = Path(folder_path).parent / "test_results"
output_dir.mkdir(exist_ok=True)
# Process images with both methods
self.log("Processing images with face_recognition...")
for image_path in image_files:
result = self.process_with_face_recognition(image_path)
self.results['face_recognition']['faces'].extend(result['faces'])
self.results['face_recognition']['times'].append(result['processing_time'])
self.results['face_recognition']['encodings'].extend(result['encodings'])
self.log("Processing images with deepface...")
for image_path in image_files:
result = self.process_with_deepface(image_path)
self.results['deepface']['faces'].extend(result['faces'])
self.results['deepface']['times'].append(result['processing_time'])
self.results['deepface']['encodings'].extend(result['encodings'])
# Calculate similarity matrices
self.log("Calculating similarity matrices...")
fr_matrix = self.calculate_similarity_matrix(
self.results['face_recognition']['encodings'], 'face_recognition'
)
df_matrix = self.calculate_similarity_matrix(
self.results['deepface']['encodings'], 'deepface'
)
# Find top matches
fr_matches = self.find_top_matches(
fr_matrix, self.results['face_recognition']['faces'], 'face_recognition'
)
df_matches = self.find_top_matches(
df_matrix, self.results['deepface']['faces'], 'deepface'
)
# Save outputs if requested
if save_crops and output_dir:
self.log("Saving face crops...")
self.save_face_crops(self.results['face_recognition']['faces'], str(output_dir), 'face_recognition')
self.save_face_crops(self.results['deepface']['faces'], str(output_dir), 'deepface')
if save_matrices and output_dir:
self.log("Saving similarity matrices...")
self.save_similarity_matrices(
fr_matrix, df_matrix,
self.results['face_recognition']['faces'],
self.results['deepface']['faces'],
str(output_dir)
)
# Generate and display report
report = self.generate_report(
self.results['face_recognition'], self.results['deepface'],
fr_matches, df_matches, str(output_dir) if output_dir else None
)
print(report)
return {
'face_recognition': {
'faces': self.results['face_recognition']['faces'],
'matches': fr_matches,
'matrix': fr_matrix
},
'deepface': {
'faces': self.results['deepface']['faces'],
'matches': df_matches,
'matrix': df_matrix
}
}
def main():
"""Main CLI entry point"""
parser = argparse.ArgumentParser(
description="Compare face_recognition vs deepface on a folder of photos",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python test_face_recognition.py demo_photos/
python test_face_recognition.py demo_photos/ --save-crops --verbose
python test_face_recognition.py demo_photos/ --save-matrices --save-crops
"""
)
parser.add_argument('folder', help='Path to folder containing photos to test')
parser.add_argument('--save-crops', action='store_true',
help='Save face crops for manual inspection')
parser.add_argument('--save-matrices', action='store_true',
help='Save similarity matrices as CSV files')
parser.add_argument('--verbose', '-v', action='store_true',
help='Enable verbose logging')
args = parser.parse_args()
# Validate folder path
if not os.path.exists(args.folder):
print(f"Error: Folder not found: {args.folder}")
sys.exit(1)
# Check dependencies
try:
import face_recognition
from deepface import DeepFace
except ImportError as e:
print(f"Error: Missing required dependency: {e}")
print("Please install with: pip install face_recognition deepface")
sys.exit(1)
# Run test
try:
tester = FaceRecognitionTester(verbose=args.verbose)
results = tester.run_test(
args.folder,
save_crops=args.save_crops,
save_matrices=args.save_matrices
)
print("\n✅ Test completed successfully!")
if args.save_crops or args.save_matrices:
print(f"📁 Results saved to: {Path(args.folder).parent / 'test_results'}")
except Exception as e:
print(f"❌ Test failed: {e}")
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Simple test to verify thumbnail sizes work
"""
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
def test_thumbnails():
root = tk.Tk()
root.title("Thumbnail Size Test")
root.geometry("1000x600")
frame = ttk.Frame(root, padding="20")
frame.pack(fill=tk.BOTH, expand=True)
# Create test images with different sizes
sizes = [
(100, 100, "Small (100x100)"),
(200, 200, "Medium (200x200)"),
(300, 300, "Large (300x300)"),
(400, 400, "HUGE (400x400)")
]
for i, (width, height, label) in enumerate(sizes):
# Create a colored rectangle
test_image = Image.new('RGB', (width, height), color='red')
photo = ImageTk.PhotoImage(test_image)
# Create label with image
img_label = ttk.Label(frame, image=photo)
img_label.image = photo # Keep a reference
img_label.grid(row=0, column=i, padx=10, pady=10)
# Create label with text
text_label = ttk.Label(frame, text=label, font=("Arial", 12, "bold"))
text_label.grid(row=1, column=i, padx=10)
# Add instruction
instruction = ttk.Label(frame, text="This shows the difference in thumbnail sizes. The GUI will use 400x400 and 350x350 pixels!",
font=("Arial", 14, "bold"), foreground="blue")
instruction.grid(row=2, column=0, columnspan=4, pady=20)
# Add button to test DeepFace GUI
def open_deepface_gui():
root.destroy()
import subprocess
subprocess.Popen(['python', 'test_deepface_gui.py'])
test_btn = ttk.Button(frame, text="Open DeepFace GUI", command=open_deepface_gui)
test_btn.grid(row=3, column=0, columnspan=4, pady=20)
root.mainloop()
if __name__ == "__main__":
test_thumbnails()
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Test script to show thumbnail size differences
"""
from PIL import Image, ImageTk
import tkinter as tk
from tkinter import ttk
def create_test_thumbnails():
"""Create test thumbnails to show size differences"""
root = tk.Tk()
root.title("Thumbnail Size Test")
root.geometry("800x600")
# Create a test image (colored rectangle)
test_image = Image.new('RGB', (100, 100), color='red')
# Create different sized thumbnails
sizes = [
(100, 100, "Original (100x100)"),
(200, 200, "Medium (200x200)"),
(300, 300, "Large (300x300)"),
(400, 400, "HUGE (400x400)")
]
frame = ttk.Frame(root, padding="20")
frame.pack(fill=tk.BOTH, expand=True)
for i, (width, height, label) in enumerate(sizes):
# Resize the test image
resized = test_image.resize((width, height), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(resized)
# Create label with image
img_label = ttk.Label(frame, image=photo)
img_label.image = photo # Keep a reference
img_label.grid(row=0, column=i, padx=10, pady=10)
# Create label with text
text_label = ttk.Label(frame, text=label, font=("Arial", 12, "bold"))
text_label.grid(row=1, column=i, padx=10)
# Add instruction
instruction = ttk.Label(frame, text="This shows the difference in thumbnail sizes. The GUI will use 400x400 and 350x350 pixels!",
font=("Arial", 14, "bold"), foreground="blue")
instruction.grid(row=2, column=0, columnspan=4, pady=20)
root.mainloop()
if __name__ == "__main__":
create_test_thumbnails()