punimtag/scripts/interactive_identifier.py
2025-08-15 00:57:39 -08:00

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()