This commit introduces a comprehensive analysis of pose modes and face width detection to enhance profile classification accuracy. New scripts have been added to analyze pose data in the database, check identified faces for pose information, and validate yaw angles. The PoseDetector class has been updated to calculate face width from landmarks, which serves as an additional indicator for profile detection. The frontend and API have been modified to include pose mode in responses, ensuring better integration with existing functionalities. Documentation has been updated to reflect these changes, improving user experience and accuracy in face processing.
193 lines
6.2 KiB
Python
193 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Analyze pose_mode values in the faces table
|
|
"""
|
|
|
|
import sqlite3
|
|
import sys
|
|
import os
|
|
from collections import Counter
|
|
from typing import Dict, List, Tuple
|
|
|
|
# Default database path
|
|
DEFAULT_DB_PATH = "data/photos.db"
|
|
|
|
|
|
def analyze_poses(db_path: str) -> None:
|
|
"""Analyze pose_mode values in faces table"""
|
|
|
|
if not os.path.exists(db_path):
|
|
print(f"❌ Database not found: {db_path}")
|
|
return
|
|
|
|
print(f"📊 Analyzing poses in database: {db_path}\n")
|
|
|
|
try:
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
# Get total number of faces
|
|
cursor.execute("SELECT COUNT(*) FROM faces")
|
|
total_faces = cursor.fetchone()[0]
|
|
print(f"Total faces in database: {total_faces}\n")
|
|
|
|
if total_faces == 0:
|
|
print("No faces found in database.")
|
|
conn.close()
|
|
return
|
|
|
|
# Get pose_mode distribution
|
|
cursor.execute("""
|
|
SELECT pose_mode, COUNT(*) as count
|
|
FROM faces
|
|
GROUP BY pose_mode
|
|
ORDER BY count DESC
|
|
""")
|
|
|
|
pose_modes = cursor.fetchall()
|
|
|
|
print("=" * 60)
|
|
print("POSE_MODE DISTRIBUTION")
|
|
print("=" * 60)
|
|
for row in pose_modes:
|
|
pose_mode = row['pose_mode'] or 'NULL'
|
|
count = row['count']
|
|
percentage = (count / total_faces) * 100
|
|
print(f" {pose_mode:30s} : {count:6d} ({percentage:5.1f}%)")
|
|
|
|
print("\n" + "=" * 60)
|
|
print("ANGLE STATISTICS")
|
|
print("=" * 60)
|
|
|
|
# Yaw angle statistics
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total,
|
|
COUNT(yaw_angle) as with_yaw,
|
|
MIN(yaw_angle) as min_yaw,
|
|
MAX(yaw_angle) as max_yaw,
|
|
AVG(yaw_angle) as avg_yaw
|
|
FROM faces
|
|
WHERE yaw_angle IS NOT NULL
|
|
""")
|
|
yaw_stats = cursor.fetchone()
|
|
|
|
# Pitch angle statistics
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total,
|
|
COUNT(pitch_angle) as with_pitch,
|
|
MIN(pitch_angle) as min_pitch,
|
|
MAX(pitch_angle) as max_pitch,
|
|
AVG(pitch_angle) as avg_pitch
|
|
FROM faces
|
|
WHERE pitch_angle IS NOT NULL
|
|
""")
|
|
pitch_stats = cursor.fetchone()
|
|
|
|
# Roll angle statistics
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total,
|
|
COUNT(roll_angle) as with_roll,
|
|
MIN(roll_angle) as min_roll,
|
|
MAX(roll_angle) as max_roll,
|
|
AVG(roll_angle) as avg_roll
|
|
FROM faces
|
|
WHERE roll_angle IS NOT NULL
|
|
""")
|
|
roll_stats = cursor.fetchone()
|
|
|
|
print(f"\nYaw Angle:")
|
|
print(f" Faces with yaw data: {yaw_stats['with_yaw']}")
|
|
if yaw_stats['with_yaw'] > 0:
|
|
print(f" Min: {yaw_stats['min_yaw']:.1f}°")
|
|
print(f" Max: {yaw_stats['max_yaw']:.1f}°")
|
|
print(f" Avg: {yaw_stats['avg_yaw']:.1f}°")
|
|
|
|
print(f"\nPitch Angle:")
|
|
print(f" Faces with pitch data: {pitch_stats['with_pitch']}")
|
|
if pitch_stats['with_pitch'] > 0:
|
|
print(f" Min: {pitch_stats['min_pitch']:.1f}°")
|
|
print(f" Max: {pitch_stats['max_pitch']:.1f}°")
|
|
print(f" Avg: {pitch_stats['avg_pitch']:.1f}°")
|
|
|
|
print(f"\nRoll Angle:")
|
|
print(f" Faces with roll data: {roll_stats['with_roll']}")
|
|
if roll_stats['with_roll'] > 0:
|
|
print(f" Min: {roll_stats['min_roll']:.1f}°")
|
|
print(f" Max: {roll_stats['max_roll']:.1f}°")
|
|
print(f" Avg: {roll_stats['avg_roll']:.1f}°")
|
|
|
|
# Sample faces with different poses
|
|
print("\n" + "=" * 60)
|
|
print("SAMPLE FACES BY POSE")
|
|
print("=" * 60)
|
|
|
|
for row in pose_modes[:10]: # Top 10 pose modes
|
|
pose_mode = row['pose_mode']
|
|
cursor.execute("""
|
|
SELECT id, photo_id, pose_mode, yaw_angle, pitch_angle, roll_angle
|
|
FROM faces
|
|
WHERE pose_mode = ?
|
|
LIMIT 3
|
|
""", (pose_mode,))
|
|
samples = cursor.fetchall()
|
|
|
|
print(f"\n{pose_mode}:")
|
|
for sample in samples:
|
|
yaw_str = f"{sample['yaw_angle']:.1f}°" if sample['yaw_angle'] is not None else "N/A"
|
|
pitch_str = f"{sample['pitch_angle']:.1f}°" if sample['pitch_angle'] is not None else "N/A"
|
|
roll_str = f"{sample['roll_angle']:.1f}°" if sample['roll_angle'] is not None else "N/A"
|
|
print(f" Face ID {sample['id']}: "
|
|
f"yaw={yaw_str} "
|
|
f"pitch={pitch_str} "
|
|
f"roll={roll_str}")
|
|
|
|
conn.close()
|
|
|
|
except sqlite3.Error as e:
|
|
print(f"❌ Database error: {e}")
|
|
except Exception as e:
|
|
print(f"❌ Error: {e}")
|
|
|
|
|
|
def check_web_database() -> None:
|
|
"""Check if web database exists and analyze it"""
|
|
# Common web database locations
|
|
web_db_paths = [
|
|
"data/punimtag.db", # Default web database
|
|
"data/web_photos.db",
|
|
"data/photos_web.db",
|
|
"web_photos.db",
|
|
]
|
|
|
|
for db_path in web_db_paths:
|
|
if os.path.exists(db_path):
|
|
print(f"\n{'='*60}")
|
|
print(f"WEB DATABASE: {db_path}")
|
|
print(f"{'='*60}\n")
|
|
analyze_poses(db_path)
|
|
break
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Check desktop database
|
|
desktop_db = DEFAULT_DB_PATH
|
|
if os.path.exists(desktop_db):
|
|
analyze_poses(desktop_db)
|
|
|
|
# Check web database
|
|
check_web_database()
|
|
|
|
# If no database found, list what we tried
|
|
if not os.path.exists(desktop_db):
|
|
print(f"❌ Desktop database not found: {desktop_db}")
|
|
print("\nTrying to find database files...")
|
|
for root, dirs, files in os.walk("data"):
|
|
for file in files:
|
|
if file.endswith(('.db', '.sqlite', '.sqlite3')):
|
|
print(f" Found: {os.path.join(root, file)}")
|
|
|