feat(worker): 接口1 四庭七眼测量真实实现(替换 Mock)

worker 侧从 Mock 替换为真实算法:
- face_analysis 包:detector(MediaPipe 478点) / pose(solvePnP 姿态) /
  calibration(虹膜直径法) / hair_segmenter+bisenet_model(方案B 头发分割) /
  measure(方案A兜底+B/A决策+七眼+换算) / annotation(numpy渐变线+中文标注)
- app.py:/api/v1/face/measure 接真实实现,返回 annotated_image_base64
  (不落盘不拼URL,落盘由网关做);加 X-Internal-Token 鉴权、/health 就绪态、
  可配置分辨率门槛、异常兜底
- 部署:start.sh/run_worker.sh/hair-worker.service 监听 8187;worker_config 示例
- 测试 tests/:Tier1合成真值<1e-6 + Tier2缩放不变 + Tier3叠加 + 错误码集成 +
  数值回归,pytest 24 项全绿
- 文档补实测基线表 + RTX5090/torch 说明

注:worker 为 RTX 5090(sm_120),pinned torch 2.2.2(cu121) 只到 sm_90,
BiSeNet 已自动回退 CPU(方案B 正常);要用 GPU 需换 torch cu128(≥2.7)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
xsl
2026-06-14 16:07:28 +08:00
co-authored by Claude Opus 4.8
parent 3b706ec0ef
commit 8d3b145111
25 changed files with 1806 additions and 35 deletions
+61
View File
@@ -0,0 +1,61 @@
"""MediaPipe Face Mesh 关键点检测封装(单例)。
封装经典 Solutions APImp.solutions.face_mesh),模型权重内置于 pip 包,
无需额外下载。开启 refine_landmarks=True 以获得虹膜点(尺度校准用),
static_image_mode=True 适配单张图片推理,max_num_faces=1 只取最大/首个人脸。
详见技术方案 §8.2。
"""
import cv2
import numpy as np
import mediapipe as mp
mp_face_mesh = mp.solutions.face_mesh
class FaceMeshDetector:
"""MediaPipe Face Mesh 封装,单例模式(模块底部 detector)。"""
def __init__(self):
self.face_mesh = mp_face_mesh.FaceMesh(
static_image_mode=True,
max_num_faces=1, # 仅检测单人(取最大脸)
refine_landmarks=True, # 启用虹膜 + 唇部精细关键点
min_detection_confidence=0.5,
)
def detect(self, image: np.ndarray):
"""检测人脸关键点。
Args:
image: BGR numpy arrayOpenCV 格式)。
Returns:
landmarks: NormalizedLandmarkList.landmark 列表),或检测失败时 None。
"""
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = self.face_mesh.process(rgb)
if results.multi_face_landmarks:
return results.multi_face_landmarks[0]
return None
def close(self):
self.face_mesh.close()
# 全局单例:模块加载时初始化一次,避免每请求重建(重建很慢)。
detector = FaceMeshDetector()
if __name__ == "__main__":
import sys
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)
lms = detector.detect(img)
if lms is None:
print("detected landmarks: None(未检出人脸)")
sys.exit(1)
print(f"detected landmarks: {len(lms.landmark)}")