| import numpy as np | |
| class FaceGeometry: | |
| def __init__(self, landmarks, hairline_y=None): | |
| self.lm = landmarks | |
| self.hairline_y = hairline_y | |
| def _p(self, idx): | |
| return self.lm[idx][:2] | |
| def chin(self): | |
| return self._p(152) | |
| def nose(self): | |
| return self._p(1) | |
| def left_cheek(self): | |
| return self._p(234) | |
| def right_cheek(self): | |
| return self._p(454) | |
| def left_eye(self): | |
| return self._p(33) | |
| def right_eye(self): | |
| return self._p(263) | |
| def forehead_top(self): | |
| if self.hairline_y is not None: | |
| mid_x = (self._p(234)[0] + self._p(454)[0]) / 2 | |
| return np.array([mid_x, self.hairline_y]) | |
| return self._p(10) | |
| def dist(self, a, b): | |
| return np.linalg.norm(a - b) | |
| def face_height(self): | |
| return self.dist(self.chin(), self.forehead_top()) | |
| def face_width(self): | |
| return self.dist(self.left_cheek(), self.right_cheek()) | |
| def jaw_width(self): | |
| return self.dist(self._p(172), self._p(397)) | |
| def eye_dist(self): | |
| return self.dist(self.left_eye(), self.right_eye()) | |
| def nose_position_ratio(self): | |
| return (self.nose()[1] - self.forehead_top()[1]) / self.face_height() | |
| def face_ratio(self): | |
| return self.face_height() / self.face_width() | |
| def jaw_ratio(self): | |
| return self.jaw_width() / self.face_width() | |
| def symmetry_score(self): | |
| mid = (self._p(33) + self._p(263)) / 2 | |
| left_dist = np.linalg.norm(self._p(234) - mid) | |
| right_dist = np.linalg.norm(self._p(454) - mid) | |
| return abs(left_dist - right_dist) / self.face_width() | |
| def jaw_to_height(self): | |
| return self.jaw_width() / self.face_height() | |
| def eye_ratio(self): | |
| return self.eye_dist() / self.face_width() | |
| def eye_height(self): | |
| eye_h = self.dist(self._p(159), self._p(145)) | |
| eye_w = self.dist(self._p(33), self._p(133)) | |
| return eye_h / eye_w | |
| def lip_ratio(self): | |
| return self.dist(self._p(61), self._p(291)) / self.face_width() | |
| def chin_ratio(self): | |
| return self.dist(self._p(152), self._p(175)) / self.face_height() | |
| def forehead_ratio(self): | |
| forehead_h = self.dist(self.forehead_top(), self._p(10)) | |
| return forehead_h / self.face_height() | |
| def lower_face_ratio(self): | |
| mouth = self._p(0) | |
| return self.dist(mouth, self.chin()) / self.face_height() | |
| def chin_prominence(self): | |
| jaw_mid = (self._p(172) + self._p(397)) / 2 | |
| return self.dist(jaw_mid, self.chin()) / self.face_height() | |
| def brow_line_y(self): | |
| left_brow = self._p(55) | |
| right_brow = self._p(285) | |
| return (left_brow[1] + right_brow[1]) / 2 | |
| def nose_base_y(self): | |
| return self._p(2)[1] | |
| def upper_third(self): | |
| top = self.forehead_top()[1] | |
| return abs(self.brow_line_y() - top) | |
| def middle_third(self): | |
| return abs(self.nose_base_y() - self.brow_line_y()) | |
| def lower_third(self): | |
| return abs(self.chin()[1] - self.nose_base_y()) | |
| def facial_thirds_ratio(self): | |
| u = self.upper_third() | |
| m = self.middle_third() | |
| l = self.lower_third() | |
| total = u + m + l | |
| if total == 0: | |
| return None | |
| return { | |
| "upper": round(u / total, 3), | |
| "middle": round(m / total, 3), | |
| "lower": round(l / total, 3), | |
| } | |
| def middle_lower_ratio(self): | |
| m = self.middle_third() | |
| l = self.lower_third() | |
| return m / l if l > 0 else 1.0 |