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:
@@ -0,0 +1,74 @@
|
||||
"""pytest 公共夹具与合成关键点工具。"""
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
|
||||
OUTPUT = os.path.join(os.path.dirname(__file__), "output")
|
||||
os.makedirs(OUTPUT, exist_ok=True)
|
||||
|
||||
|
||||
def fixture(name):
|
||||
return os.path.join(FIXTURES, name)
|
||||
|
||||
|
||||
class _LM:
|
||||
"""模拟 MediaPipe landmark.x/.y/.z(归一化坐标)。"""
|
||||
def __init__(self, x, y, z=0.0):
|
||||
self.x, self.y, self.z = x, y, z
|
||||
|
||||
|
||||
def build_synthetic_landmarks(px_per_cm=50.0, W=1000, H=1000):
|
||||
"""按已知 cm 几何摆放关键点,返回 (landmarks_list, ground_truth_dict)。
|
||||
|
||||
坐标与 px_per_cm 都由测试设定,故每段 cm/占比真值已知,测量数学应分毫不差。
|
||||
"""
|
||||
cx = W / 2
|
||||
|
||||
def Y(cm_from_top):
|
||||
return (cm_from_top * px_per_cm) / H
|
||||
|
||||
def X(px):
|
||||
return px / W
|
||||
|
||||
gt = {
|
||||
"top_court_cm": 4.0, "upper_court_cm": 5.0,
|
||||
"middle_court_cm": 6.0, "lower_court_cm": 5.0,
|
||||
"eye_width_cm": 3.0, "inter_eye_cm": 3.4, "face_width_cm": 14.0,
|
||||
"px_per_cm": px_per_cm,
|
||||
}
|
||||
y_hairtop = 2.0
|
||||
y_hairline = y_hairtop + gt["top_court_cm"]
|
||||
y_brow = y_hairline + gt["upper_court_cm"]
|
||||
y_nose = y_brow + gt["middle_court_cm"]
|
||||
y_chin = y_nose + gt["lower_court_cm"]
|
||||
|
||||
lm = {i: _LM(X(cx), 0.0) for i in range(478)}
|
||||
# 纵向中轴点
|
||||
lm[9] = _LM(X(cx), Y(y_brow)); lm[151] = _LM(X(cx), Y(y_brow))
|
||||
lm[94] = _LM(X(cx), Y(y_nose))
|
||||
lm[152] = _LM(X(cx), Y(y_chin))
|
||||
# 七眼横向点
|
||||
ew = gt["eye_width_cm"] * px_per_cm
|
||||
ie = gt["inter_eye_cm"] * px_per_cm
|
||||
fw = gt["face_width_cm"] * px_per_cm
|
||||
eye_y = Y(y_brow + 2.0)
|
||||
lm[133] = _LM(X(cx - ie / 2), eye_y); lm[33] = _LM(X(cx - ie / 2 - ew), eye_y)
|
||||
lm[362] = _LM(X(cx + ie / 2), eye_y); lm[263] = _LM(X(cx + ie / 2 + ew), eye_y)
|
||||
lm[234] = _LM(X(cx - fw / 2), eye_y); lm[454] = _LM(X(cx + fw / 2), eye_y)
|
||||
# 虹膜边缘点:直径 = 1.17cm * px_per_cm,使尺度可被精确反解
|
||||
d = 1.17 * px_per_cm
|
||||
lm[469] = _LM(X(cx - ie / 2 - ew / 2 - d / 2), eye_y)
|
||||
lm[471] = _LM(X(cx - ie / 2 - ew / 2 + d / 2), eye_y)
|
||||
lm[474] = _LM(X(cx + ie / 2 + ew / 2 - d / 2), eye_y)
|
||||
lm[476] = _LM(X(cx + ie / 2 + ew / 2 + d / 2), eye_y)
|
||||
return [lm[i] for i in range(478)], gt
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oversize_file(tmp_path):
|
||||
"""1006 用例:>1MB 的占位文件(无需合法图片,只看字节数)。"""
|
||||
p = tmp_path / "oversize.bin"
|
||||
p.write_bytes(b"\x00" * 1_100_000)
|
||||
return p
|
||||
Reference in New Issue
Block a user