Files
hair/image/compare_test/batch_test.py
T
xsl 5101bb5f6b 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)
2026-07-26 22:06:27 +08:00

163 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()