convrec-face-recognition / face_deduplication.py
convaiinnovations's picture
Upload folder using huggingface_hub
28edaa2 verified
Raw
History Blame Contribute Delete
4.77 kB
"""
Face Deduplication Module
Find and manage duplicate faces in image collections
"""
import os
from pathlib import Path
import numpy as np
from collections import defaultdict
from face_recognition import FaceRecognition
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class FaceDeduplication:
"""Face deduplication using similarity matching"""
def __init__(self, model_path='best_model.pth'):
"""Initialize with face recognition model"""
self.face_rec = FaceRecognition(model_path)
def find_duplicates(self, folder_path, threshold=0.5):
"""
Find duplicate faces in a folder
Args:
folder_path: Path to folder containing images
threshold: Similarity threshold for considering duplicates
Returns:
List of duplicate groups
"""
# Get all image files
image_extensions = {'.jpg', '.jpeg', '.png', '.bmp'}
image_files = []
folder_path = Path(folder_path)
for ext in image_extensions:
image_files.extend(folder_path.glob(f'**/*{ext}'))
image_files.extend(folder_path.glob(f'**/*{ext.upper()}'))
image_files = list(set(image_files))
logger.info(f"Found {len(image_files)} images")
if len(image_files) < 2:
return []
# Extract embeddings
embeddings = []
valid_files = []
for img_path in image_files:
try:
emb = self.face_rec.extract_embedding(str(img_path))
if emb is not None:
embeddings.append(emb)
valid_files.append(str(img_path))
except Exception as e:
logger.warning(f"Error processing {img_path}: {e}")
if len(embeddings) < 2:
return []
# Compute similarity matrix
embeddings = np.array(embeddings)
similarity_matrix = np.dot(embeddings, embeddings.T)
# Find duplicates
duplicate_groups = []
processed = set()
for i in range(len(valid_files)):
if i in processed:
continue
# Find all images similar to image i
similar_indices = np.where(similarity_matrix[i] > threshold)[0]
if len(similar_indices) > 1:
group = [valid_files[idx] for idx in similar_indices]
duplicate_groups.append(group)
processed.update(similar_indices)
logger.info(f"Found {len(duplicate_groups)} duplicate groups")
return duplicate_groups
def remove_duplicates(self, folder_path, threshold=0.5, keep='first', dry_run=True):
"""
Remove duplicate faces from folder
Args:
folder_path: Path to folder containing images
threshold: Similarity threshold
keep: Strategy ('first', 'last', 'best')
dry_run: If True, only show what would be removed
Returns:
Dictionary with kept and removed files
"""
duplicate_groups = self.find_duplicates(folder_path, threshold)
kept_files = []
removed_files = []
for group in duplicate_groups:
if keep == 'first':
keep_file = group[0]
remove_files = group[1:]
elif keep == 'last':
keep_file = group[-1]
remove_files = group[:-1]
else: # 'best' - keep largest file
sizes = [(f, os.path.getsize(f)) for f in group]
sizes.sort(key=lambda x: x[1], reverse=True)
keep_file = sizes[0][0]
remove_files = [f for f in group if f != keep_file]
kept_files.append(keep_file)
removed_files.extend(remove_files)
if not dry_run:
for file_path in removed_files:
try:
os.remove(file_path)
logger.info(f"Removed: {file_path}")
except Exception as e:
logger.error(f"Could not remove {file_path}: {e}")
else:
logger.info(f"DRY RUN - Would remove {len(removed_files)} files")
return {
'kept': kept_files,
'removed': removed_files,
'dry_run': dry_run
}
# Example usage
if __name__ == '__main__':
dedup = FaceDeduplication('best_model.pth')
# Find duplicates
if os.path.exists('data'):
duplicates = dedup.find_duplicates('data', threshold=0.5)
for i, group in enumerate(duplicates, 1):
print(f"\nDuplicate group {i}:")
for img in group:
print(f" - {img}")
print("Face deduplication ready!")