feat: Implement face detection improvements and cleanup script

This commit introduces significant enhancements to the face detection system, addressing false positives by updating configuration settings and validation logic. Key changes include stricter confidence thresholds, increased minimum face size, and improved aspect ratio requirements. A new script for cleaning up existing false positives from the database has also been added, successfully removing 199 false positive faces. Documentation has been updated to reflect these changes and provide usage instructions for the cleanup process.
This commit is contained in:
tanyar09
2025-10-16 15:56:17 -04:00
parent d398b139f5
commit 68673ccdbe
8 changed files with 295 additions and 6 deletions
+5
View File
@@ -39,6 +39,11 @@ DEFAULT_PROCESSING_LIMIT = 50
MIN_FACE_QUALITY = 0.3
DEFAULT_CONFIDENCE_THRESHOLD = 0.5
# Face detection filtering settings
MIN_FACE_CONFIDENCE = 0.7 # Minimum confidence from detector to accept face (increased from 0.4 to reduce false positives)
MIN_FACE_SIZE = 60 # Minimum face size in pixels (width or height) - increased to filter out small decorative objects
MAX_FACE_SIZE = 1500 # Maximum face size in pixels (to avoid full-image false positives)
# GUI settings
FACE_CROP_SIZE = 100
ICON_SIZE = 20
+115 -2
View File
@@ -25,7 +25,10 @@ from src.core.config import (
DEEPFACE_DETECTOR_BACKEND,
DEEPFACE_MODEL_NAME,
DEEPFACE_ENFORCE_DETECTION,
DEEPFACE_ALIGN_FACES
DEEPFACE_ALIGN_FACES,
MIN_FACE_CONFIDENCE,
MIN_FACE_SIZE,
MAX_FACE_SIZE
)
from src.core.database import DatabaseManager
@@ -171,6 +174,12 @@ class FaceProcessor:
'h': facial_area.get('h', 0)
}
# Apply filtering to reduce false positives
if not self._is_valid_face_detection(face_confidence, location):
if self.verbose >= 2:
print(f" Face {i+1}: Filtered out (confidence: {face_confidence:.3f}, size: {location['w']}x{location['h']})")
continue
# Calculate face quality score
# Convert facial_area to (top, right, bottom, left) for quality calculation
face_location_tuple = (
@@ -214,6 +223,110 @@ class FaceProcessor:
print(f"✅ Processed {processed_count} photos")
return processed_count
def cleanup_false_positive_faces(self, verbose: bool = True) -> int:
"""Remove faces that are likely false positives based on improved filtering criteria
This method can be used to clean up existing false positives in the database
after improving the face detection filtering.
Returns:
Number of faces removed
"""
if verbose:
print("🧹 Cleaning up false positive faces...")
removed_count = 0
with self.db.get_db_connection() as conn:
cursor = conn.cursor()
# Get all faces with their metadata
cursor.execute('''
SELECT id, location, face_confidence, quality_score, detector_backend, model_name
FROM faces
WHERE person_id IS NULL
''')
faces_to_check = cursor.fetchall()
if verbose:
print(f" Checking {len(faces_to_check)} unidentified faces...")
for face_id, location_str, face_confidence, quality_score, detector_backend, model_name in faces_to_check:
try:
# Parse location string back to dict
import ast
location = ast.literal_eval(location_str) if isinstance(location_str, str) else location_str
# Apply the same validation logic
if not self._is_valid_face_detection(face_confidence or 0.0, location):
# This face would be filtered out by current criteria, remove it
cursor.execute('DELETE FROM faces WHERE id = ?', (face_id,))
removed_count += 1
if verbose and removed_count <= 10: # Show first 10 removals
print(f" Removed face {face_id}: confidence={face_confidence:.2f}, size={location.get('w', 0)}x{location.get('h', 0)}")
elif verbose and removed_count == 11:
print(" ... (showing first 10 removals)")
except Exception as e:
if verbose:
print(f" ⚠️ Error checking face {face_id}: {e}")
continue
conn.commit()
if verbose:
print(f"✅ Removed {removed_count} false positive faces")
return removed_count
def _is_valid_face_detection(self, face_confidence: float, location: dict) -> bool:
"""Validate if a face detection is likely to be a real face (not a false positive)"""
try:
# Check confidence threshold - be more strict
if face_confidence < MIN_FACE_CONFIDENCE:
return False
# Check face size
width = location.get('w', 0)
height = location.get('h', 0)
# Too small faces are likely false positives (balloons, decorations, etc.)
if width < MIN_FACE_SIZE or height < MIN_FACE_SIZE:
return False
# Too large faces might be full-image false positives
if width > MAX_FACE_SIZE or height > MAX_FACE_SIZE:
return False
# Check aspect ratio - faces should be roughly square (not too wide/tall)
aspect_ratio = width / height if height > 0 else 1.0
if aspect_ratio < 0.4 or aspect_ratio > 2.5: # More strict aspect ratio (was 0.3-3.0)
return False
# Additional filtering for very small faces with low confidence
# Small faces need higher confidence to be accepted
face_area = width * height
if face_area < 10000: # Less than 100x100 pixels
if face_confidence < 0.8: # Require 80% confidence for small faces
return False
# Filter out faces that are too close to image edges (often false positives)
x = location.get('x', 0)
y = location.get('y', 0)
# If face is very close to edges, require higher confidence
if x < 10 or y < 10: # Within 10 pixels of top/left edge
if face_confidence < 0.85: # Require 85% confidence for edge faces
return False
return True
except Exception as e:
if self.verbose >= 2:
print(f"⚠️ Error validating face detection: {e}")
return True # Default to accepting on error
def _calculate_face_quality_score(self, image: np.ndarray, face_location: tuple) -> float:
"""Calculate face quality score based on multiple factors"""
try:
@@ -628,7 +741,7 @@ class FaceProcessor:
avg_quality = (unid_quality + person_quality) / 2
adaptive_tolerance = self._calculate_adaptive_tolerance(tolerance, avg_quality)
distance = face_recognition.face_distance([unid_enc], person_enc)[0]
distance = self._calculate_cosine_similarity(unid_enc, person_enc)
if distance <= adaptive_tolerance and distance < best_distance:
best_distance = distance
+3
View File
@@ -1305,6 +1305,9 @@ class IdentifyPanel:
if self.components['compare_var'].get():
self._identify_selected_similar_faces(person_data)
# Clear the form after successful identification
self._clear_form()
# Move to next face
self._go_next()
+1 -1
View File
@@ -295,7 +295,7 @@ class PhotoTagger:
def main():
"""Main CLI interface"""
# Suppress pkg_resources deprecation warning from face_recognition library
# Suppress TensorFlow and other deprecation warnings from DeepFace dependencies
import warnings
warnings.filterwarnings("ignore", message="pkg_resources is deprecated", category=UserWarning)
+4 -3
View File
@@ -83,12 +83,13 @@ def create_directories():
def test_installation():
"""Test if face recognition works"""
print("🧪 Testing face recognition installation...")
"""Test if DeepFace face recognition works"""
print("🧪 Testing DeepFace face recognition installation...")
try:
import face_recognition
from deepface import DeepFace
import numpy as np
from PIL import Image
import tensorflow as tf
print("✅ All required modules imported successfully")
return True
except ImportError as e: