193 lines
6.3 KiB
Python
193 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Interactive Face Identifier for PunimTag
|
|
Allows users to identify unknown faces in the database
|
|
"""
|
|
|
|
import os
|
|
import cv2
|
|
import numpy as np
|
|
from punimtag import PunimTag
|
|
from typing import Optional
|
|
import sys
|
|
|
|
|
|
class InteractiveFaceIdentifier:
|
|
def __init__(self, db_path: str = 'punimtag.db'):
|
|
self.tagger = PunimTag(db_path=db_path)
|
|
self.window_name = 'Face Identifier'
|
|
|
|
def display_face(self, image_path: str, location: tuple) -> np.ndarray:
|
|
"""Load and display image with face highlighted"""
|
|
img = cv2.imread(image_path)
|
|
if img is None:
|
|
print(f"Error: Could not load image {image_path}")
|
|
return None
|
|
|
|
# Get face coordinates
|
|
top, right, bottom, left = location
|
|
|
|
# Draw rectangle around face
|
|
cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0), 3)
|
|
|
|
# Calculate display size (max 800x600)
|
|
height, width = img.shape[:2]
|
|
max_height, max_width = 600, 800
|
|
|
|
if height > max_height or width > max_width:
|
|
scale = min(max_height/height, max_width/width)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
img = cv2.resize(img, (new_width, new_height))
|
|
|
|
return img
|
|
|
|
def get_user_input(self, face_info: dict) -> Optional[str]:
|
|
"""Get user input for face identification"""
|
|
print("\n" + "="*50)
|
|
print(f"Image: {face_info['image_path']}")
|
|
print(f"Face ID: {face_info['face_id']}")
|
|
print("\nOptions:")
|
|
print("1. Enter person's name")
|
|
print("2. Skip this face (press Enter)")
|
|
print("3. Quit (press 'q')")
|
|
print("="*50)
|
|
|
|
user_input = input("\nEnter person's name (or press Enter to skip): ").strip()
|
|
|
|
if user_input.lower() == 'q':
|
|
return 'QUIT'
|
|
elif user_input == '':
|
|
return None
|
|
else:
|
|
return user_input
|
|
|
|
def run(self):
|
|
"""Run the interactive identification process"""
|
|
print("PunimTag Interactive Face Identifier")
|
|
print("=" * 50)
|
|
|
|
# Get unidentified faces
|
|
unidentified = self.tagger.get_unidentified_faces()
|
|
|
|
if not unidentified:
|
|
print("No unidentified faces found!")
|
|
return
|
|
|
|
print(f"Found {len(unidentified)} unidentified faces")
|
|
|
|
# Create window
|
|
cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
|
|
|
|
identified_count = 0
|
|
|
|
for i, face_info in enumerate(unidentified):
|
|
print(f"\nProcessing face {i+1} of {len(unidentified)}")
|
|
|
|
# Display the face
|
|
img = self.display_face(face_info['image_path'], face_info['location'])
|
|
|
|
if img is None:
|
|
continue
|
|
|
|
cv2.imshow(self.window_name, img)
|
|
cv2.waitKey(1) # Allow window to update
|
|
|
|
# Get user input
|
|
name = self.get_user_input(face_info)
|
|
|
|
if name == 'QUIT':
|
|
print("\nQuitting...")
|
|
break
|
|
elif name:
|
|
# Add person and assign face
|
|
person_id = self.tagger.add_person(name)
|
|
self.tagger.assign_face_to_person(face_info['face_id'], person_id, is_confirmed=True)
|
|
identified_count += 1
|
|
print(f"✓ Identified as: {name}")
|
|
else:
|
|
print("⊘ Skipped")
|
|
|
|
cv2.destroyAllWindows()
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Identification complete!")
|
|
print(f"Identified {identified_count} faces")
|
|
print(f"Skipped {len(unidentified) - identified_count} faces")
|
|
|
|
self.tagger.close()
|
|
|
|
|
|
class CLIFaceIdentifier:
|
|
"""Command-line only face identifier (no OpenCV required)"""
|
|
|
|
def __init__(self, db_path: str = 'punimtag.db'):
|
|
self.tagger = PunimTag(db_path=db_path)
|
|
|
|
def run(self):
|
|
"""Run CLI-based identification"""
|
|
print("PunimTag CLI Face Identifier")
|
|
print("=" * 50)
|
|
|
|
# Get unidentified faces
|
|
unidentified = self.tagger.get_unidentified_faces()
|
|
|
|
if not unidentified:
|
|
print("No unidentified faces found!")
|
|
return
|
|
|
|
print(f"Found {len(unidentified)} unidentified faces\n")
|
|
|
|
identified_count = 0
|
|
|
|
for i, face_info in enumerate(unidentified):
|
|
print(f"\n{'='*50}")
|
|
print(f"Face {i+1} of {len(unidentified)}")
|
|
print(f"Image: {face_info['image_path']}")
|
|
print(f"Location in image: top={face_info['location'][0]}, right={face_info['location'][1]}, "
|
|
f"bottom={face_info['location'][2]}, left={face_info['location'][3]}")
|
|
print(f"Face ID: {face_info['face_id']}")
|
|
|
|
name = input("\nEnter person's name (or press Enter to skip, 'q' to quit): ").strip()
|
|
|
|
if name.lower() == 'q':
|
|
print("\nQuitting...")
|
|
break
|
|
elif name:
|
|
# Add person and assign face
|
|
person_id = self.tagger.add_person(name)
|
|
self.tagger.assign_face_to_person(face_info['face_id'], person_id, is_confirmed=True)
|
|
identified_count += 1
|
|
print(f"✓ Identified as: {name}")
|
|
else:
|
|
print("⊘ Skipped")
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Identification complete!")
|
|
print(f"Identified {identified_count} faces")
|
|
print(f"Skipped {len(unidentified) - identified_count} faces")
|
|
|
|
self.tagger.close()
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
# Check if OpenCV is available
|
|
try:
|
|
import cv2
|
|
print("OpenCV available - using visual identifier")
|
|
identifier = InteractiveFaceIdentifier()
|
|
except ImportError:
|
|
print("OpenCV not available - using CLI identifier")
|
|
print("Install opencv-python for visual identification: pip install opencv-python")
|
|
identifier = CLIFaceIdentifier()
|
|
|
|
try:
|
|
identifier.run()
|
|
except KeyboardInterrupt:
|
|
print("\n\nInterrupted by user")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |