Spaces:
Sleeping
Sleeping
File size: 4,626 Bytes
a5a6a2e b1cf9e9 d03a2fa a5a6a2e d03a2fa b1cf9e9 b1e603d b1cf9e9 b1e603d b1cf9e9 b1e603d a5a6a2e 7f3db4a a5a6a2e b1cf9e9 a5a6a2e d03a2fa b1e603d b1cf9e9 a5a6a2e b1e603d a5a6a2e | 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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import logging
import math
import cv2
import numpy as np
logger = logging.getLogger(__name__)
def _expand_box(x, y, w, h, img_w, img_h, scale=1.2):
pad_w = int(w * (scale - 1) / 2)
pad_h = int(h * (scale - 1) / 2)
x1 = max(0, x - pad_w)
y1 = max(0, y - pad_h)
x2 = min(img_w, x + w + pad_w)
y2 = min(img_h, y + h + pad_h)
return x1, y1, x2, y2
def _crop_to_face(img):
cv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
face_cascade_path = cv2.data.haarcascades + "haarcascade_frontalface_alt2.xml"
face_cascade = cv2.CascadeClassifier(face_cascade_path)
if face_cascade.empty():
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
if len(faces) == 0:
logger.info("No face detected; using full image for classification.")
return img
x, y, w, h = max(faces, key=lambda f: f[2] * f[3])
img_h, img_w = gray.shape[:2]
x1, y1, x2, y2 = _expand_box(x, y, w, h, img_w, img_h, scale=1.25)
return img.crop((x1, y1, x2, y2))
def _gaussian_score(value, target, sigma):
return math.exp(-((value - target) ** 2) / (2 * sigma ** 2))
def _shape_probabilities_from_geometry(landmarks):
from geometry import extract_features
feats = extract_features(landmarks)
lw_ratio = feats.get("lw_ratio", 0)
jaw_ratio = feats.get("jaw_ratio", 0)
if lw_ratio <= 0 or jaw_ratio <= 0:
return None
targets = {
"Round": {"lw": 1.05, "jaw": 0.88},
"Square": {"lw": 1.12, "jaw": 0.95},
"Oval": {"lw": 1.25, "jaw": 0.86},
"Oblong": {"lw": 1.35, "jaw": 0.86},
"Heart": {"lw": 1.22, "jaw": 0.75},
"Diamond": {"lw": 1.20, "jaw": 0.70},
}
scores = {}
for shape, target in targets.items():
lw_score = _gaussian_score(lw_ratio, target["lw"], 0.1)
jaw_score = _gaussian_score(jaw_ratio, target["jaw"], 0.07)
scores[shape] = lw_score * jaw_score
total = sum(scores.values())
if total <= 0:
return None
probabilities = {k: round(v / total, 4) for k, v in scores.items()}
return dict(sorted(probabilities.items(), key=lambda item: item[1], reverse=True))
def _blend_probabilities(primary, secondary, alpha=0.55):
labels = set(primary.keys()) | set(secondary.keys())
blended = {}
for label in labels:
blended[label] = alpha * primary.get(label, 0) + (1 - alpha) * secondary.get(label, 0)
total = sum(blended.values())
if total <= 0:
return primary
blended = {k: round(v / total, 4) for k, v in blended.items()}
return dict(sorted(blended.items(), key=lambda item: item[1], reverse=True))
def detect_face_shape(image_path):
"""
Detects face shape using PIL and trained SVM model.
"""
from PIL import Image
from landmarks import get_landmarks
from classifier import classify_face_shape
try:
# Load image with PIL
if isinstance(image_path, str):
img = Image.open(image_path)
else:
# Assume it's already a PIL image or numpy array
img = image_path
if img is None:
raise ValueError("Could not load image")
# Crop to detected face region to improve accuracy
if isinstance(image_path, str):
img = img.convert("RGB")
img = _crop_to_face(img)
geom_probs = None
if isinstance(image_path, str):
try:
landmarks = get_landmarks(image_path)
geom_probs = _shape_probabilities_from_geometry(landmarks)
except Exception as e:
logger.info(f"Landmark-based detection failed, falling back: {e}")
# Classify directly (classifier handles resizing/grayscale)
shape_probs = classify_face_shape(img)
if geom_probs:
geom_values = list(geom_probs.values())
top = geom_values[0] if geom_values else 0
runner_up = geom_values[1] if len(geom_values) > 1 else 0
if top >= 0.45 and (top - runner_up) >= 0.08:
return geom_probs
return _blend_probabilities(geom_probs, shape_probs, alpha=0.55)
# Get the top shape
best_shape = list(shape_probs.keys())[0]
logger.info(f"Detected face shape: {best_shape} for {image_path}")
return shape_probs
except Exception as e:
logger.error(f"Detection failed: {e}")
return {"Error": 1.0}
|