"""尺度校准:像素 → 厘米(虹膜直径法,眼宽降级)。 人类虹膜直径高度稳定(成人平均 11.7mm),作为天然标尺把像素距离换算成厘米。 虹膜点(索引 469/471、474/476)需 refine_landmarks=True 才输出;缺失时降级用 眼宽(外→内眼角,均值约 2.85cm)。详见技术方案 §3。 """ from face_analysis.face_mesh_landmarks import ( IRIS_LEFT_LEFT, IRIS_LEFT_RIGHT, IRIS_RIGHT_LEFT, IRIS_RIGHT_RIGHT, LEFT_EYE_OUTER, LEFT_EYE_INNER, RIGHT_EYE_INNER, RIGHT_EYE_OUTER, ) AVG_IRIS_DIAMETER_CM = 1.17 # 虹膜平均直径 11.7mm AVG_EYE_WIDTH_CM = 2.85 # 眼裂平均宽度约 28.5mm(降级标尺) def _lm_list(landmarks): """兼容 NormalizedLandmarkList(有 .landmark)与裸 list 两种入参。""" return landmarks.landmark if hasattr(landmarks, "landmark") else landmarks def normalized_to_pixel(landmark, image_width, image_height): """归一化坐标 → 像素坐标。""" return landmark.x * image_width, landmark.y * image_height def pixel_distance(p1, p2): """两点像素欧氏距离。""" return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5 def _iris_diameter_px(lm, w, h): """左右虹膜直径像素均值;任一边缘点缺失/为 0 返回 None。""" try: ll = normalized_to_pixel(lm[IRIS_LEFT_LEFT], w, h) lr = normalized_to_pixel(lm[IRIS_LEFT_RIGHT], w, h) rl = normalized_to_pixel(lm[IRIS_RIGHT_LEFT], w, h) rr = normalized_to_pixel(lm[IRIS_RIGHT_RIGHT], w, h) except (IndexError, KeyError): return None left_d = pixel_distance(ll, lr) right_d = pixel_distance(rl, rr) if left_d <= 0 or right_d <= 0: return None return (left_d + right_d) / 2 def _eye_width_px(lm, w, h): """左右眼宽(外→内眼角)像素均值,作为虹膜降级标尺。""" l = pixel_distance(normalized_to_pixel(lm[LEFT_EYE_OUTER], w, h), normalized_to_pixel(lm[LEFT_EYE_INNER], w, h)) r = pixel_distance(normalized_to_pixel(lm[RIGHT_EYE_OUTER], w, h), normalized_to_pixel(lm[RIGHT_EYE_INNER], w, h)) return (l + r) / 2 def estimate_scale_factor(landmarks, image_width, image_height): """估算 px_per_cm(每厘米对应像素数)。 优先用虹膜直径法;虹膜点不可用时降级用眼宽。返回正浮点数。 """ lm = _lm_list(landmarks) iris_px = _iris_diameter_px(lm, image_width, image_height) if iris_px is not None: return iris_px / AVG_IRIS_DIAMETER_CM # 降级:眼宽法 eye_px = _eye_width_px(lm, image_width, image_height) return eye_px / AVG_EYE_WIDTH_CM if __name__ == "__main__": import sys import cv2 from face_analysis.detector import detector path = sys.argv[1] if len(sys.argv) > 1 else "tests/fixtures/frontal.jpg" img = cv2.imread(path) if img is None: print(f"无法读取图片: {path}") sys.exit(1) h, w = img.shape[:2] lms = detector.detect(img) if lms is None: print("未检出人脸") sys.exit(1) print(f"px_per_cm: {estimate_scale_factor(lms, w, h):.4f}")