teachable-machine-app / backend /ml /face_utils.py
hammasShani's picture
initial project setup without venv
a10e1b9
Raw
History Blame Contribute Delete
2.51 kB
"""
face_utils.py β€” Face detection and crop using OpenCV.
Detects the largest frontal face in an image and returns a padded crop.
Returns (crop, face_found) where face_found=False means no person in frame.
NO edge mask is applied β€” raw pixel data is preserved so MobileNetV2
sees the same features during training and prediction.
"""
import cv2
import numpy as np
# OpenCV's built-in Haar cascades β€” no download needed
_frontal_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
_alt_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_alt2.xml'
)
def detect_and_crop_face(img_rgb: np.ndarray, padding: float = 0.25):
"""
Detect the largest face in img_rgb (uint8 RGB HxWx3) and return a padded crop.
Args:
img_rgb: uint8 numpy array in RGB colour space, shape (H, W, 3)
padding: fractional padding added around the detected face box (0.25 = 25%)
Returns:
(cropped_rgb, face_found)
- cropped_rgb: the face crop, or the original image if no face detected
- face_found: bool β€” False means no person in frame β†’ caller returns Unknown
"""
if img_rgb is None or img_rgb.size == 0:
return img_rgb, False
gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
gray = cv2.equalizeHist(gray) # improves detection in poor lighting
faces = []
# Try primary frontal cascade
detected = _frontal_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=4, minSize=(40, 40)
)
if len(detected) > 0:
faces = detected
# If no face found, try the alt cascade (detects tilted / partially visible faces)
if len(faces) == 0:
detected2 = _alt_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=4, minSize=(40, 40)
)
if len(detected2) > 0:
faces = detected2
if len(faces) == 0:
# No face detected at all β†’ caller should return Unknown
return img_rgb, False
# Pick the largest detected face
x, y, w, h = max(faces, key=lambda f: f[2] * f[3])
# Apply padding around the face box
pad_x = int(w * padding)
pad_y = int(h * padding)
H, W = img_rgb.shape[:2]
x1 = max(0, x - pad_x)
y1 = max(0, y - pad_y)
x2 = min(W, x + w + pad_x)
y2 = min(H, y + h + pad_y)
face_crop = img_rgb[y1:y2, x1:x2]
if face_crop.size == 0:
return img_rgb, False
return face_crop, True