feat(接口2): 测试页暴露重绘分辨率选项 + 分辨率对比测试套件

- test_interface2.html: 新增「重绘分辨率」下拉(默认896/1024/768/640/不缩图0),
  仅 female 生效,append redraw_max_side 字段;male 时自动隐藏
- 新增分辨率对比测试工具 (image/compare_test, image/res_test):
  - batch_test.py: 串行批量测试脚本,支持断点续跑
  - gen_report.py: 生成自包含 HTML 对比报告(速度色阶+缩略图+点击放大)
- 两轮测试结果:
  - v1: 4图×5发型×2档=40次 (compare_test)
  - v2: 5图×3发型×5档=75次 (res_test)
  - 结论: 大图(长边>1600)原图直送比896档慢~3倍; 中小图各档差异小
- .gitignore: 忽略测试结果图/原图副本/运行日志(out/, *.log, progress.json)
This commit is contained in:
xsl
2026-07-26 22:06:27 +08:00
parent ff4019c570
commit 5101bb5f6b
12 changed files with 1583 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""接口2 female 批量对比测试:4图 × 5发型 × 3分辨率档位 = 60次请求。
串行执行,记录耗时与成败,结果图按 图_发型_档位 命名保存。
用法: python3 batch_test.py
支持断点续跑(progress.json);新增档位时只会补跑未完成项。
"""
import base64, csv, json, os, sys, time, traceback
import requests
API = "http://127.0.0.1:8187/api/v1/hair/grow"
TOKEN = "dev-shared-secret-2026"
TIMEOUT = 600
OUT = "/home/ubuntu/hair/image/compare_test/out"
PROGRESS = "/home/ubuntu/hair/image/compare_test/progress.json"
IMG_DIR = "/home/ubuntu/hair/image"
IMAGES = [
("girl2", f"{IMG_DIR}/girl_img/girl2.jpg"),
("girl5", f"{IMG_DIR}/girl_img/girl5.jpg"),
("qwer", f"{IMG_DIR}/qwer.jpg"),
("asdf", f"{IMG_DIR}/asdf.jpg"),
]
# female: 1=ellipse 2=flower 3=heart 4=straight 5=wave
STYLES = [
(1, "ellipse"), (2, "flower"), (3, "heart"), (4, "straight"), (5, "wave"),
]
# 三档: 默认896 / 显式1024 / 原图直送0
SIDES = [
("default896", None), # 不传 → 后端默认896
("side1024", 1024), # 长边压到 1024
("origin0", 0), # 原图直送
]
SIDE_LABEL = {
"default896": "默认896",
"side1024": "1024",
"origin0": "原图0",
}
def load_progress():
if os.path.exists(PROGRESS):
try:
return json.load(open(PROGRESS))
except Exception:
pass
return {"done": [], "results": []}
def save_progress(prog):
json.dump(prog, open(PROGRESS, "w"), ensure_ascii=False, indent=1)
def run_one(img_name, img_path, style_idx, style_key, side_name, side_val):
"""跑单次请求,返回 dict 结果。"""
key = f"{img_name}_{style_key}_{side_name}"
with open(img_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
data = {
"image_base64": "data:image/jpeg;base64," + img_b64,
"gender": "female",
"hair_style": str(style_idx),
}
if side_val is not None:
data["redraw_max_side"] = str(side_val)
t0 = time.time()
rec = {"key": key, "img": img_name, "style": style_key, "style_idx": style_idx,
"side": side_name, "side_val": side_val, "ok": False,
"elapsed": 0.0, "err": "", "out_w": 0, "out_h": 0}
try:
r = requests.post(API, data=data, headers={"X-Internal-Token": TOKEN}, timeout=TIMEOUT)
rec["elapsed"] = round(time.time() - t0, 1)
d = r.json()
if d.get("code") != 0:
rec["err"] = f"code={d.get('code')} {d.get('message','')}"[:200]
return rec
results = (d.get("data") or {}).get("results") or []
if not results:
rec["err"] = "空结果"
return rec
it = results[0]
grown = it.get("grown_image_base64")
if not grown:
rec["err"] = "无生发图(grown_png=None, 重绘失败)"
return rec
raw = base64.b64decode(grown)
from PIL import Image
import io as _io
im = Image.open(_io.BytesIO(raw))
rec["out_w"], rec["out_h"] = im.size
out_path = f"{OUT}/{key}.jpg"
with open(out_path, "wb") as fo:
fo.write(raw)
rec["ok"] = True
rec["bytes"] = len(raw)
except requests.exceptions.Timeout:
rec["elapsed"] = round(time.time() - t0, 1)
rec["err"] = f"超时(>{TIMEOUT}s)"
except Exception as e:
rec["elapsed"] = round(time.time() - t0, 1)
rec["err"] = f"{type(e).__name__}: {str(e)[:180]}"
return rec
def main():
os.makedirs(OUT, exist_ok=True)
prog = load_progress()
done_keys = set(prog["done"])
total = len(IMAGES) * len(STYLES) * len(SIDES)
print(f"=== 接口2 female 批量对比测试 ===")
print(f"矩阵: {len(IMAGES)}× {len(STYLES)}发型 × {len(SIDES)}档 = {total}")
print(f"已跳过 {len(done_keys)} 个已完成项\n")
idx = 0
for img_name, img_path in IMAGES:
for style_idx, style_key in STYLES:
for side_name, side_val in SIDES:
idx += 1
key = f"{img_name}_{style_key}_{side_name}"
if key in done_keys:
print(f"[{idx}/{total}] ⏭ 跳过已完成 {key}")
continue
print(f"[{idx}/{total}] ▶ {key} (side={side_val})...", end=" ", flush=True)
rec = run_one(img_name, img_path, style_idx, style_key, side_name, side_val)
prog["results"].append(rec)
prog["done"].append(key)
save_progress(prog)
if rec["ok"]:
print(f"{rec['elapsed']}s 出图 {rec['out_w']}x{rec['out_h']} ({rec['bytes']}B)")
else:
print(f"{rec['elapsed']}s {rec['err']}")
time.sleep(2) # 串行间隔,避免队列堆积
# 汇总
print("\n" + "="*70)
print("汇总报告")
print("="*70)
write_report(prog["results"])
print(f"\n结果图: {OUT}/")
print(f"CSV报告: {OUT}/report.csv")
print(f"JSON明细: {OUT}/report.json")
def write_report(results):
# CSV
csv_path = f"{OUT}/report.csv"
with open(csv_path, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["img","style","side","ok","elapsed_s","out_w","out_h","err"])
for r in results:
w.writerow([r["img"],r["style"],r["side"],r["ok"],r["elapsed"],
r["out_w"],r["out_h"],r["err"]])
# JSON
json.dump(results, open(f"{OUT}/report.json","w"), ensure_ascii=False, indent=1)
# 控制台速度对比表
print("\n--- 速度对比(秒,✓=成功 ✗=失败)---")
headers = ["", "发型"] + [SIDE_LABEL[s] for s, _ in SIDES]
print(f"{headers[0]:<8}{headers[1]:<10}" + "".join(f"{h:>12}" for h in headers[2:]))
for img_name, _ in IMAGES:
for _, style_key in STYLES:
cells = []
for side_name, _ in SIDES:
r = next((x for x in results if x["img"]==img_name and x["style"]==style_key and x["side"]==side_name), None)
cells.append(f"{r['elapsed']}{'' if r['ok'] else ''}" if r else "-")
print(f"{img_name:<8}{style_key:<10}" + "".join(f"{c:>12}" for c in cells))
if __name__ == "__main__":
main()