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>
62 lines
1.9 KiB
Python
62 lines
1.9 KiB
Python
"""MediaPipe Face Mesh 关键点检测封装(单例)。
|
||
|
||
封装经典 Solutions API(mp.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 array(OpenCV 格式)。
|
||
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)}")
|