Spaces:
Runtime error
Runtime error
| import numpy as np | |
| from facenet_pytorch import MTCNN | |
| import torch | |
| import cv2 | |
| class StructureAnalyzer: | |
| def __init__(self, device=None): | |
| if device is None: | |
| self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') | |
| else: | |
| self.device = device | |
| self.mtcnn = MTCNN(keep_all=False, device=self.device) | |
| def analyze(self, image_pil): | |
| """ | |
| Analyzes facial structure symmetry and alignment using MTCNN landmarks. | |
| :param image_pil: PIL Image. | |
| :return: dict with 'score' (0 to 1) and 'confidence'. | |
| """ | |
| try: | |
| boxes, probs, landmarks = self.mtcnn.detect(image_pil, landmarks=True) | |
| if landmarks is None or len(landmarks) == 0: | |
| return {"score": 0.0, "confidence": 0.0, "anomaly_detected": False, "error": "No face/landmarks detected"} | |
| # Take the highest probability face | |
| lm = landmarks[0] | |
| # MTCNN landmarks: [left_eye, right_eye, nose, left_mouth, right_mouth] | |
| left_eye = lm[0] | |
| right_eye = lm[1] | |
| nose = lm[2] | |
| left_mouth = lm[3] | |
| right_mouth = lm[4] | |
| # Structural Heuristics: | |
| # 1. Eye symmetry: Y-coordinate difference should be relatively small | |
| # (Though tilting the head changes this, we normalize by eye distance) | |
| eye_dist = np.linalg.norm(left_eye - right_eye) + 1e-7 | |
| eye_y_diff = abs(left_eye[1] - right_eye[1]) / eye_dist | |
| # 2. Nose centering: The horizontal distance from nose to left eye vs right eye | |
| dist_nose_left = np.linalg.norm(nose - left_eye) | |
| dist_nose_right = np.linalg.norm(nose - right_eye) | |
| nose_symmetry = abs(dist_nose_left - dist_nose_right) / (eye_dist + 1e-7) | |
| # 3. Mouth centering | |
| mouth_center = (left_mouth + right_mouth) / 2 | |
| mouth_nose_x_diff = abs(mouth_center[0] - nose[0]) / (eye_dist + 1e-7) | |
| # Compute anomaly score based on heuristics | |
| # If the face is highly asymmetrical, it could be an artifact of poor generation. | |
| # However, extreme poses also cause this, so we must be careful. | |
| score = 0.0 | |
| # If eye y-diff is very high but the head isn't tilted (which is hard to know without full 3d pose) | |
| # We'll just combine these weakly. | |
| anomaly_factor = eye_y_diff + nose_symmetry + mouth_nose_x_diff | |
| # Typical faces have anomaly_factor < 0.5 depending on pose | |
| if anomaly_factor > 0.8: | |
| score = min(1.0, (anomaly_factor - 0.8) * 2.0) | |
| return { | |
| "score": float(score), | |
| "confidence": 0.4, # Structural analysis alone from 5 points is weak | |
| "anomaly_detected": score > 0.5, | |
| "metrics": { | |
| "eye_y_diff_norm": float(eye_y_diff), | |
| "nose_symmetry_norm": float(nose_symmetry), | |
| "mouth_nose_x_diff_norm": float(mouth_nose_x_diff) | |
| } | |
| } | |
| except Exception as e: | |
| print(f"Structural Analysis Error: {e}") | |
| return {"score": 0.0, "confidence": 0.0, "anomaly_detected": False, "error": str(e)} | |
| if __name__ == "__main__": | |
| from PIL import Image | |
| import sys | |
| if len(sys.argv) > 1: | |
| img = Image.open(sys.argv[1]).convert("RGB") | |
| analyzer = StructureAnalyzer() | |
| res = analyzer.analyze(img) | |
| print(f"Structure Result: {res}") | |