| import os |
| import numpy as np |
| import cv2 |
| import torch |
| from PIL import Image |
|
|
| |
| from .exp_recognition_model import trnscm, load_model, classes |
|
|
| |
| |
| |
| |
|
|
| current_path = os.path.dirname(os.path.abspath(__file__)) |
|
|
| _MODEL = None |
| _DEVICE = None |
|
|
|
|
| def _to_bgr_np(img): |
| """ |
| Convert various inputs -> OpenCV BGR numpy array. |
| Supports: filepath str, PIL.Image, numpy RGB/gray/RGBA. |
| """ |
| if img is None: |
| return None |
|
|
| |
| if isinstance(img, str): |
| return cv2.imread(img) |
|
|
| |
| if isinstance(img, Image.Image): |
| rgb = np.array(img.convert("RGB")) |
| return cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) |
|
|
| |
| if isinstance(img, np.ndarray): |
| arr = img |
|
|
| |
| if arr.ndim == 2: |
| return cv2.cvtColor(arr, cv2.COLOR_GRAY2BGR) |
|
|
| |
| if arr.ndim == 3 and arr.shape[2] == 4: |
| arr = arr[:, :, :3] |
|
|
| |
| if arr.ndim == 3 and arr.shape[2] == 3: |
| return cv2.cvtColor(arr, cv2.COLOR_RGB2BGR) |
|
|
| return arr |
|
|
| return None |
|
|
|
|
| def detected_face(image_bgr): |
| """ |
| Returns PIL grayscale cropped face with maximum area. |
| Returns 0 if not detected or cascade not available. |
| """ |
| face_haar = os.path.join(current_path, "haarcascade_frontalface_default.xml") |
| face_cascade = cv2.CascadeClassifier(face_haar) |
|
|
| |
| if face_cascade.empty(): |
| return 0 |
|
|
| gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY) |
| faces = face_cascade.detectMultiScale(gray, 1.3, 5) |
|
|
| if faces is None or len(faces) == 0: |
| return 0 |
|
|
| |
| x, y, w, h = max(faces, key=lambda f: f[2] * f[3]) |
| crop = gray[y:y + h, x:x + w] |
| return Image.fromarray(crop) |
|
|
|
|
| def _load_cached_model(): |
| """ |
| Load the expression model once and cache it. |
| """ |
| global _MODEL, _DEVICE |
| if _MODEL is not None: |
| return _MODEL, _DEVICE |
|
|
| _DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") |
|
|
| ckpt_path = os.path.join(current_path, "best_resnet18_expression.pt") |
| if not os.path.exists(ckpt_path): |
| raise FileNotFoundError(f"Missing checkpoint: {ckpt_path}") |
|
|
| _MODEL = load_model(ckpt_path, device=str(_DEVICE), num_classes=len(classes)) |
| return _MODEL, _DEVICE |
|
|
|
|
| def get_expression(img): |
| """ |
| img: can be numpy array (RGB), PIL.Image, or filepath string |
| returns: expression string |
| """ |
| model, device = _load_cached_model() |
|
|
| img_bgr = _to_bgr_np(img) |
| if img_bgr is None: |
| return "UNKNOWN" |
|
|
| face = detected_face(img_bgr) |
| if face == 0: |
| |
| face = Image.fromarray(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)) |
|
|
| x = trnscm(face).unsqueeze(0).to(device) |
| with torch.no_grad(): |
| logits = model(x) |
| pred = int(torch.argmax(logits, dim=1).item()) |
|
|
| return classes.get(pred, "UNKNOWN").capitalize() |