File size: 2,633 Bytes
2787dfd
 
 
cbf5ccd
2787dfd
cbf5ccd
2787dfd
cbf5ccd
2787dfd
cbf5ccd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2787dfd
cbf5ccd
 
 
2787dfd
cbf5ccd
 
 
 
 
2787dfd
 
cbf5ccd
 
 
 
 
2787dfd
cbf5ccd
 
2787dfd
cbf5ccd
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import cv2
import mediapipe as mp
import numpy as np
from typing import Tuple, Optional

# Initialize MediaPipe face detection
mp_face_detection = mp.solutions.face_detection
mp_face_mesh = mp.solutions.face_mesh  # For detailed landmarks

def classify_face_shape(landmarks) -> str:
    """Determine face shape using geometric ratios."""
    # Example: Calculate jaw-to-forehead ratio
    jaw_width = landmarks[234].x - landmarks[454].x  # Left/right jaw points
    forehead_height = landmarks[10].y - landmarks[152].y  # Forehead to chin
    
    ratio = jaw_width / forehead_height
    if ratio > 1.1: return "Square"
    elif ratio > 0.9: return "Round"
    else: return "Oval"

def estimate_skin_tone(image, face_roi) -> str:
    """Analyze skin tone in the cheek region."""
    x, y, w, h = face_roi
    cheek_region = image[y:y+h//2, x:x+w//2]  # Get left cheek area
    
    # Convert to LAB color space for skin tone analysis
    lab = cv2.cvtColor(cheek_region, cv2.COLOR_BGR2LAB)
    l, a, b = np.mean(lab, axis=(0,1))
    
    if l > 160: return "Fair"
    elif l > 120: return "Medium"
    else: return "Dark"

def extract_features(image_path: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
    """Extract face attributes from an image."""
    # Read and convert image
    image = cv2.imread(image_path)
    if image is None:
        return None, None, None
        
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    height, width = image.shape[:2]

    # Face detection
    with mp_face_detection.FaceDetection(min_detection_confidence=0.5) as detector:
        results = detector.process(image_rgb)
        if not results.detections:
            return None, None, None
            
        # Get face bounding box
        bbox = results.detections[0].location_data.relative_bounding_box
        x, y = int(bbox.xmin * width), int(bbox.ymin * height)
        w, h = int(bbox.width * width), int(bbox.height * height)
        
        # Face size classification
        face_size = "Large" if w > 300 else "Medium" if w > 200 else "Small"
        
        # Face mesh for detailed landmarks
        with mp_face_mesh.FaceMesh(static_image_mode=True) as mesh:
            mesh_results = mesh.process(image_rgb)
            if mesh_results.multi_face_landmarks:
                landmarks = mesh_results.multi_face_landmarks[0].landmark
                face_shape = classify_face_shape(landmarks)
                skin_tone = estimate_skin_tone(image, (x, y, w, h))
                return face_shape, skin_tone, face_size
                
    return None, None, None  # Fallback if mesh detection fails