"""Tier 2 缩放不变性 + Tier 3 落点可视化 + 数值回归(均走方案 A,torch 无关、确定性)。""" import os import cv2 import numpy as np from conftest import fixture, OUTPUT from face_analysis.detector import detector from face_analysis.measure import measure_face def _run(img): h, w = img.shape[:2] lms = detector.detect(img) assert lms is not None # 固定走方案 A(mask=None),保证确定性与 torch 无关 return measure_face(lms, None, w, h) def test_scale_invariance(): """等比放大 2×:占比几乎不变(±0.5%),cm 近似不变(±2%)。""" img = cv2.imread(fixture("frontal.jpg")) big = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC) r1, r2 = _run(img), _run(big) cm1 = {"top": r1.top_cm, "upper": r1.upper_cm, "middle": r1.middle_cm, "lower": r1.lower_cm} cm2 = {"top": r2.top_cm, "upper": r2.upper_cm, "middle": r2.middle_cm, "lower": r2.lower_cm} d1, d2 = r1.to_response(), r2.to_response() for k in ["top_court", "upper_court", "middle_court", "lower_court"]: a = d1["four_courts"]["ratios"][k] b = d2["four_courts"]["ratios"][k] assert abs(a - b) < 0.005, f"ratio {k} 漂移过大: {a} vs {b}" for k in ["top", "upper", "middle", "lower"]: assert abs(cm1[k] - cm2[k]) / cm1[k] < 0.02, f"cm {k} 漂移过大: {cm1[k]} vs {cm2[k]}" def test_landmark_overlay(): """生成 5 纵向点叠加图供人工核验,并断言坐标在图内且自上而下有序。""" img = cv2.imread(fixture("frontal.jpg")) h, w = img.shape[:2] r = _run(img) pts = r.to_response()["landmarks"] order = ["hair_top", "hairline", "brow_center", "nose_bottom", "chin_tip"] ys = [pts[n]["y"] for n in order] # 坐标在图像范围内 for n in order: assert 0 <= pts[n]["x"] <= w assert 0 <= pts[n]["y"] <= h # 自上而下严格递增 assert ys == sorted(ys), f"纵向点未自上而下有序: {ys}" # dump 叠加图 canvas = img.copy() for n in order: cv2.circle(canvas, (pts[n]["x"], pts[n]["y"]), 4, (0, 0, 255), -1) cv2.line(canvas, (0, pts[n]["y"]), (w, pts[n]["y"]), (0, 255, 0), 1) cv2.imwrite(os.path.join(OUTPUT, "frontal_landmarks.png"), canvas) # 数值回归基线:frontal.jpg 方案A 首次实测值,防重构回归(容差 1%)。 _BASELINE = { "face_total_height_cm": 24.01, "top_court_cm": 5.06, "upper_court_cm": 6.07, "middle_court_cm": 7.23, "lower_court_cm": 5.64, "eye_width_cm": 2.52, "face_width_cm": 12.49, "inter_eye_distance_cm": 3.24, } def test_numeric_regression(): img = cv2.imread(fixture("frontal.jpg")) d = _run(img).to_response() got = { "face_total_height_cm": d["face_total_height_cm"], **{k: d["four_courts"][k] for k in ["top_court_cm", "upper_court_cm", "middle_court_cm", "lower_court_cm"]}, **{k: d["seven_eyes"][k] for k in ["eye_width_cm", "face_width_cm", "inter_eye_distance_cm"]}, } for k, base in _BASELINE.items(): assert abs(got[k] - base) / base < 0.01, f"{k} 回归: 基线 {base}, 实测 {got[k]}"