san021/phi_ratio
0
1import cv2
2import dlib
3import numpy as np
4import os
5
6# --- Constants ---
7# Define the path to the shape predictor model file
8# We use os.path.join to make it work on any operating system (Windows, Mac, Linux)
9MODEL_PATH = os.path.join(os.path.dirname(__file__), "..", "models", "shape_predictor_68_face_landmarks.dat")
10
11# --- Error Classes ---
12class NoFaceFoundError(Exception):
13 """Custom exception raised when no face is detected in the image."""
14 pass
15
16class MultipleFacesFoundError(Exception):
17 """Custom exception raised when multiple faces are detected."""
18 pass
19
20# --- Initialization ---
21# Initialize dlib's face detector (HOG-based)
22detector = dlib.get_frontal_face_detector()
23
24# Load the facial landmark predictor
25try:
26 predictor = dlib.shape_predictor(MODEL_PATH)
27except RuntimeError as e:
28 print(f"Error loading model from {MODEL_PATH}")
29 print("Please make sure you have downloaded the file and placed it in the 'models' directory.")
30 print(f"Error details: {e}")
31 exit()
32
33def get_landmarks(image):
34 """
35 Detects faces in an image and returns the 68 facial landmarks
36 for the first face found.
37
38 Args:
39 image (numpy.ndarray): The input image (loaded via OpenCV).
40
41 Returns:
42 numpy.ndarray: A 68x2 NumPy array where each row is an (x, y)
43 coordinate of a facial landmark.
44
45 Raises:
46 NoFaceFoundError: If no faces are detected in the image.
47 MultipleFacesFoundError: If more than one face is detected.
48 """
49 # Convert the image to grayscale (dlib works on grayscale images)
50 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
51
52 # Detect faces in the grayscale image.
53 # The '1' indicates to upsample the image 1 time, which helps find smaller faces.
54 rects = detector(gray, 1)
55
56 # --- Handle detection results ---
57 if len(rects) == 0:
58 # If no faces are found, raise our custom error
59 raise NoFaceFoundError("No face was detected in the provided image.")
60
61 if len(rects) > 1:
62 # If multiple faces are found, we raise an error.
63 # For a Phi ratio, we should only analyze one face at a time.
64 raise MultipleFacesFoundError("Multiple faces were detected. Please provide an image with one face.")
65
66 # --- Get Landmarks ---
67 # Get the landmarks for the *first* face found
68 shape = predictor(gray, rects[0])
69
70 # Convert the shape object to a 68x2 NumPy array
71 # This makes it much easier to work with the coordinates
72 coords = np.zeros((68, 2), dtype="int")
73 for i in range(0, 68):
74 coords[i] = (shape.part(i).x, shape.part(i).y)
75
76 return coords